From 92b0e2b400cb41b1268318b51608e3cae9f1fdb8 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Fri, 22 Sep 2023 01:39:35 +0200 Subject: [PATCH 01/24] initial highscore support --- Mods/vcmi/config/vcmi/czech.json | 1 - Mods/vcmi/config/vcmi/english.json | 634 +++++++++++++-------------- Mods/vcmi/config/vcmi/french.json | 1 - Mods/vcmi/config/vcmi/german.json | 1 - Mods/vcmi/config/vcmi/polish.json | 1 - Mods/vcmi/config/vcmi/ukrainian.json | 1 - client/CMakeLists.txt | 2 + client/VCMI_client.cbp | 2 + client/VCMI_client.vcxproj | 2 + client/VCMI_client.vcxproj.filters | 2 + client/mainmenu/CHighScoreScreen.cpp | 161 +++++++ client/mainmenu/CHighScoreScreen.h | 37 ++ client/mainmenu/CMainMenu.cpp | 9 +- client/mainmenu/CMainMenu.h | 1 + config/highscoreCreatures.json | 122 ++++++ lib/constants/EntityIdentifiers.h | 32 +- 16 files changed, 652 insertions(+), 357 deletions(-) create mode 100644 client/mainmenu/CHighScoreScreen.cpp create mode 100644 client/mainmenu/CHighScoreScreen.h create mode 100644 config/highscoreCreatures.json diff --git a/Mods/vcmi/config/vcmi/czech.json b/Mods/vcmi/config/vcmi/czech.json index dcbb8ccaf..7784defac 100644 --- a/Mods/vcmi/config/vcmi/czech.json +++ b/Mods/vcmi/config/vcmi/czech.json @@ -37,7 +37,6 @@ "vcmi.radialWheel.moveUnit" : "Přesunout jednotky do jiného oddílu", "vcmi.radialWheel.splitUnit" : "Rozdělit jednotku do jiné pozice", - "vcmi.mainMenu.highscoresNotImplemented" : "Omlouvám se, menu nejvyšší skóre ještě není implementováno\n", "vcmi.mainMenu.serverConnecting" : "Připojování...", "vcmi.mainMenu.serverAddressEnter" : "Zadejte adresu:", "vcmi.mainMenu.serverClosing" : "Zavírání...", diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index cc95f3e30..83e81dee6 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -1,375 +1,339 @@ { - "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", - "vcmi.adventureMap.monsterThreat.levels.3" : "A bit weaker", - "vcmi.adventureMap.monsterThreat.levels.4" : "Equal", - "vcmi.adventureMap.monsterThreat.levels.5" : "A bit stronger", - "vcmi.adventureMap.monsterThreat.levels.6" : "Strong", - "vcmi.adventureMap.monsterThreat.levels.7" : "Very Strong", - "vcmi.adventureMap.monsterThreat.levels.8" : "Challenging", - "vcmi.adventureMap.monsterThreat.levels.9" : "Overpowering", - "vcmi.adventureMap.monsterThreat.levels.10" : "Deadly", + "vcmi.adventureMap.monsterThreat.title" : "\n\nDiscussion : ", + "vcmi.adventureMap.monsterThreat.levels.0" : "Sans effort", + "vcmi.adventureMap.monsterThreat.levels.1" : "Très faible", + "vcmi.adventureMap.monsterThreat.levels.2" : "Faible", + "vcmi.adventureMap.monsterThreat.levels.3" : "Un peu plus faible", + "vcmi.adventureMap.monsterThreat.levels.4" : "Égal", + "vcmi.adventureMap.monsterThreat.levels.5" : "Un peu plus fort", + "vcmi.adventureMap.monsterThreat.levels.6" : "Fort", + "vcmi.adventureMap.monsterThreat.levels.7" : "Très fort", + "vcmi.adventureMap.monsterThreat.levels.8" : "Difficile", + "vcmi.adventureMap.monsterThreat.levels.9" : "Surpuissant", + "vcmi.adventureMap.monsterThreat.levels.10" : "Mortel", "vcmi.adventureMap.monsterThreat.levels.11" : "Impossible", - "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.adventureMap.confirmRestartGame" : "Êtes-vous sûr de vouloir redémarrer le jeu ?", + "vcmi.adventureMap.noTownWithMarket" : "Il n'y a pas de marchés disponibles !", + "vcmi.adventureMap.noTownWithTavern" : "Il n'y a pas de villes disponibles avec des tavernes !", + "vcmi.adventureMap.spellUnknownProblem" : "Il y a un problème inconnu avec ce sort ! Pas plus d'informations sont disponibles.", + "vcmi.adventureMap.playerAttacked" : "Le joueur a été attaqué : %s", + "vcmi.adventureMap.moveCostDetails" : "Points de mouvement - Coût : %TURNS tours + %POINTS points, Points restants : %REMAINING", + "vcmi.adventureMap.moveCostDetailsNoTurns" : "Points de mouvement - Coût : %POINTS points, Points restants : %REMAINING", - "vcmi.capitalColors.0" : "Red", - "vcmi.capitalColors.1" : "Blue", - "vcmi.capitalColors.2" : "Tan", - "vcmi.capitalColors.3" : "Green", + "vcmi.capitalColors.0" : "Rouge", + "vcmi.capitalColors.1" : "Bleu", + "vcmi.capitalColors.2" : "Ocre", + "vcmi.capitalColors.3" : "Vert", "vcmi.capitalColors.4" : "Orange", - "vcmi.capitalColors.5" : "Purple", - "vcmi.capitalColors.6" : "Teal", - "vcmi.capitalColors.7" : "Pink", - - "vcmi.heroOverview.startingArmy" : "Starting Units", - "vcmi.heroOverview.warMachine" : "War Machines", - "vcmi.heroOverview.secondarySkills" : "Secondary Skills", - "vcmi.heroOverview.spells" : "Spells", - - "vcmi.radialWheel.mergeSameUnit" : "Merge same creatures", - "vcmi.radialWheel.fillSingleUnit" : "Fill with single creatures", - "vcmi.radialWheel.splitSingleUnit" : "Split off single creature", - "vcmi.radialWheel.splitUnitEqually" : "Split creatures equally", - "vcmi.radialWheel.moveUnit" : "Move creatures to another army", - "vcmi.radialWheel.splitUnit" : "Split creature to another slot", + "vcmi.capitalColors.5" : "Violet", + "vcmi.capitalColors.6" : "Turquoise", + "vcmi.capitalColors.7" : "Rose", - "vcmi.mainMenu.highscoresNotImplemented" : "Sorry, high scores menu is not implemented yet\n", - "vcmi.mainMenu.serverConnecting" : "Connecting...", - "vcmi.mainMenu.serverAddressEnter" : "Enter address:", - "vcmi.mainMenu.serverClosing" : "Closing...", - "vcmi.mainMenu.hostTCP" : "Host TCP/IP game", - "vcmi.mainMenu.joinTCP" : "Join TCP/IP game", - "vcmi.mainMenu.playerName" : "Player", - - "vcmi.lobby.filename" : "Filename", - "vcmi.lobby.creationDate" : "Creation date", + "vcmi.mainMenu.serverConnecting" : "Connexion...", + "vcmi.mainMenu.serverAddressEnter" : "Entrez l'adresse :", + "vcmi.mainMenu.serverClosing" : "Fermeture...", + "vcmi.mainMenu.hostTCP" : "Hôte TCP/IP jeu", + "vcmi.mainMenu.joinTCP" : "Rejoindre TCP/IP jeu", + "vcmi.mainMenu.playerName" : "Joueur", - "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.server.errors.existingProcess" : "Un autre processus de serveur VCMI est en cours d'exécution. Veuillez l'arrêter' avant de démarrer un nouveau jeu.", + "vcmi.server.errors.modsIncompatibility" : "Les mods suivants sont nécessaires pour charger le jeu :", + "vcmi.server.confirmReconnect" : "Voulez-vous vous reconnecter à la dernière 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 the section of the game where players can control the movements of their heroes).", + "vcmi.settingsMainWindow.generalTab.hover" : "Général", + "vcmi.settingsMainWindow.generalTab.help" : "Passe à l'onglet Options générales, qui contient des paramètres liés au comportement général du client de jeu", + "vcmi.settingsMainWindow.battleTab.hover" : "Bataille", + "vcmi.settingsMainWindow.battleTab.help" : "Passe à l'onglet Options de combat, ce qui permet de configurer le comportement du jeu pendant les batailles", + "vcmi.settingsMainWindow.adventureTab.hover" : "Carte d'aventure", + "vcmi.settingsMainWindow.adventureTab.help" : "Passe à l'onglet Options de carte d'aventure (la carte d'aventure est la section du jeu où les joueurs peuvent contrôler les mouvements de leurs héros)", - "vcmi.systemOptions.videoGroup" : "Video Settings", - "vcmi.systemOptions.audioGroup" : "Audio Settings", - "vcmi.systemOptions.otherGroup" : "Other Settings", // unused right now - "vcmi.systemOptions.townsGroup" : "Town Screen", + "vcmi.systemOptions.videoGroup" : "Paramètres Vidéo", + "vcmi.systemOptions.audioGroup" : "Paramètres Audio", + "vcmi.systemOptions.otherGroup" : "Autres Paramètres", // Unused right now + "vcmi.systemOptions.townsGroup" : "Écran de la Ville", - "vcmi.systemOptions.fullscreenBorderless.hover" : "Fullscreen (borderless)", - "vcmi.systemOptions.fullscreenBorderless.help" : "{Borderless Fullscreen}\n\nIf selected, VCMI will run in borderless fullscreen mode. In this mode, game will always use same resolution as desktop, ignoring selected resolution.", - "vcmi.systemOptions.fullscreenExclusive.hover" : "Fullscreen (exclusive)", - "vcmi.systemOptions.fullscreenExclusive.help" : "{Fullscreen}\n\nIf selected, VCMI will run in exclusive fullscreen mode. In this mode, game will change resolution of monitor to selected resolution.", - "vcmi.systemOptions.resolutionButton.hover" : "Resolution: %wx%h", - "vcmi.systemOptions.resolutionButton.help" : "{Select Resolution}\n\nChange in-game screen resolution.", - "vcmi.systemOptions.resolutionMenu.hover" : "Select Resolution", - "vcmi.systemOptions.resolutionMenu.help" : "Change in-game screen resolution.", - "vcmi.systemOptions.scalingButton.hover" : "Interface Scaling: %p%", - "vcmi.systemOptions.scalingButton.help" : "{Interface Scaling}\n\nChanges scaling of in-game interface.", - "vcmi.systemOptions.scalingMenu.hover" : "Select Interface Scaling", - "vcmi.systemOptions.scalingMenu.help" : "Change in-game interface scaling.", - "vcmi.systemOptions.longTouchButton.hover" : "Long Touch Interval: %d ms", // Translation note: "ms" = "milliseconds" - "vcmi.systemOptions.longTouchButton.help" : "{Long Touch Interval}\n\nWhen using touchscreen, popup windows will appear after touching screen for specified duration, in milliseconds.", - "vcmi.systemOptions.longTouchMenu.hover" : "Select Long Touch Interval", - "vcmi.systemOptions.longTouchMenu.help" : "Change duration of long touch interval.", - "vcmi.systemOptions.longTouchMenu.entry" : "%d milliseconds", - "vcmi.systemOptions.framerateButton.hover" : "Show FPS", - "vcmi.systemOptions.framerateButton.help" : "{Show FPS}\n\nToggle the visibility of the Frames Per Second counter in the corner of the game window.", - "vcmi.systemOptions.hapticFeedbackButton.hover" : "Haptic feedback", - "vcmi.systemOptions.hapticFeedbackButton.help" : "{Haptic feedback}\n\nToggle the haptic feedback on touch inputs.", + "vcmi.systemOptions.fullscreenBorderless.hover" : "Plein écran (sans bord)", + "vcmi.systemOptions.fullscreenBorderless.help" : "{Borderless Fullscreen}\n\nSi sélectionné, VCMI fonctionnera en mode plein écran sans bordure. Dans ce mode, le jeu utilisera toujours la même résolution de le bureau, ignorant la résolution sélectionnée.", + "vcmi.systemOptions.fullscreenExclusive.hover" : "Plein écran (exclusif)", + "vcmi.systemOptions.fullscreenExclusive.help" : "{Fullscreen}\n\nSi sélectionné, VCMI s'exécutera en mode plein écran exclusif. Dans ce mode, le jeu modifiera la résolution du moniteur en résolution sélectionnée.", + "vcmi.systemOptions.resolutionButton.hover" : "Résolution : %wx%h", + "vcmi.systemOptions.resolutionButton.help" : "{Select Resolution}\n\nChanger la résolution d'écran dans le jeu.", + "vcmi.systemOptions.resolutionMenu.hover" : "Sélectionner la résolution", + "vcmi.systemOptions.resolutionMenu.help" : "Changer la résolution d'écran dans le jeu.", + "vcmi.systemOptions.scalingButton.hover" : "Échelle d'interface : %p%", + "vcmi.systemOptions.scalingButton.help" : "{Interface Scaling}\n\nChanger l'échelle de l'interface dans le jeu", + "vcmi.systemOptions.scalingMenu.hover" : "Sélectionner la mise à l'échelle de l'interface", + "vcmi.systemOptions.scalingMenu.help" : "Changer la mise à l'échelle de l'interface dans le jeu.", + "vcmi.systemOptions.longTouchButton.hover" : "Intervalle de touche long : %d ms", // Translation note: "ms" = "milliseconds" + "vcmi.systemOptions.longTouchButton.help" : "{Long Touch Interval}\n\nAu moment d'utiliser l'écran tactile, les fenêtres contextuelles apparaîtront après avoir touché l'écran pour une durée spécifiée, en millisecondes", + "vcmi.systemOptions.longTouchMenu.hover" : "Sélectionner l'intervalle de touche long", + "vcmi.systemOptions.longTouchMenu.help" : "Changer la durée de l'intervalle de touche long.", + "vcmi.systemOptions.longTouchMenu.entry" : "%d millisecondes", + "vcmi.systemOptions.framerateButton.hover" : "Afficher les FPS", + "vcmi.systemOptions.framerateButton.help" : "{Show FPS}\n\nAfficher/masquer le compteur de Frames Par Seconde dans le coin de la fenêtre du jeu", - "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 the info panel, instead of popping up in a separate window.", - "vcmi.adventureOptions.numericQuantities.hover" : "Numeric Creature Quantities", - "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\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\nShow the grid overlay, highlighting the borders between adventure map tiles.", - "vcmi.adventureOptions.borderScroll.hover" : "Border Scrolling", - "vcmi.adventureOptions.borderScroll.help" : "{Border Scrolling}\n\nScroll adventure map when cursor is adjacent to window edge. Can be disabled by holding down CTRL key.", - "vcmi.adventureOptions.infoBarCreatureManagement.hover" : "Info Panel Creature Management", - "vcmi.adventureOptions.infoBarCreatureManagement.help" : "{Info Panel Creature Management}\n\nAllows rearranging creatures in info panel instead of cycling between default components.", - "vcmi.adventureOptions.leftButtonDrag.hover" : "Left Click Drag Map", - "vcmi.adventureOptions.leftButtonDrag.help" : "{Left Click Drag Map}\n\nWhen enabled, moving mouse with left button pressed will drag adventure map view.", + "vcmi.adventureOptions.infoBarPick.hover" : "Afficher les messages dans le panneau d'information", + "vcmi.adventureOptions.infoBarPick.help" : "{Show Messages in Info Panel}\n\nAutant que possible, les messages de jeu de la visite des objets de la carte seront affichés dans le panneau d'informations, au lieu de faire apparaître dans une fenêtre séparée.", + "vcmi.adventureOptions.numericQuantities.hover" : "Dénombrement de créatures", + "vcmi.adventureOptions.numericQuantities.help" : "{Numeric Creature Quantities}\n\nAfficher les estimation de nombre de créatures ennemies au format numérique A-B.", + "vcmi.adventureOptions.forceMovementInfo.hover" : "Toujours afficher le coût de mouvement", + "vcmi.adventureOptions.forceMovementInfo.help" : "{Always Show Movement Cost}\n\nToujours afficher les données des points de mouvement dans les informations sur la barre d'état (au lieu de les voir que pen maintenant la touche alt).", + "vcmi.adventureOptions.showGrid.hover" : "Afficher la grille", + "vcmi.adventureOptions.showGrid.help" : "{Show Grid}\n\nMontrer la superposition de la grille, mettant en évidence les frontières entre les carreaux de carte d'aventure.", + "vcmi.adventureOptions.borderScroll.hover" : "Défilement de bord", + "vcmi.adventureOptions.borderScroll.help" : "{Border Scrolling}\n\nFait défiler la carte aventure lorsque le curseur est sur le bord de la fenêtre. Peut être désactivé en maintenant la touche Ctrl.", "vcmi.adventureOptions.mapScrollSpeed1.hover": "", "vcmi.adventureOptions.mapScrollSpeed5.hover": "", "vcmi.adventureOptions.mapScrollSpeed6.hover": "", - "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.adventureOptions.mapScrollSpeed1.help": "Régler la vitesse de défilement de la carte sur très lent", + "vcmi.adventureOptions.mapScrollSpeed5.help": "Régler la vitesse de défilement de la carte très rapide", + "vcmi.adventureOptions.mapScrollSpeed6.help": "Régler la vitesse de défilement de la carte sur instantanément.", - "vcmi.battleOptions.queueSizeLabel.hover": "Show Turn Order Queue", - "vcmi.battleOptions.queueSizeNoneButton.hover": "OFF", + "vcmi.battleOptions.queueSizeLabel.hover": "Afficher l'ordre de fil de tour", + "vcmi.battleOptions.queueSizeNoneButton.hover": "Désactivé", "vcmi.battleOptions.queueSizeAutoButton.hover": "AUTO", - "vcmi.battleOptions.queueSizeSmallButton.hover": "SMALL", - "vcmi.battleOptions.queueSizeBigButton.hover": "BIG", - "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.queueSizeSmallButton.hover": "PETITE", + "vcmi.battleOptions.queueSizeBigButton.hover": "GRANDE", + "vcmi.battleOptions.queueSizeNoneButton.help": "Ne pas afficher l'ordre de fil de tour", + "vcmi.battleOptions.queueSizeAutoButton.help": "Ajuster automatiquement la taille de l'ordre de fil de tour en fonction de la résolution du jeu (la PETITE taille est utilisée lorsque vous jouez au jeu sur une hauteur de résolution inférieure à 700 pixels, la GRANDE taille est utilisée au sinon)", + "vcmi.battleOptions.queueSizeSmallButton.help": "Régler la taille de l'ordre de fil de tour sur PETITE", + "vcmi.battleOptions.queueSizeBigButton.help": "Régler la taille de l'ordre de fil de tour à GRANDE (non prise en charge si la hauteur de la résolution du jeu est inférieure à 700 pixels)", "vcmi.battleOptions.animationsSpeed1.hover": "", "vcmi.battleOptions.animationsSpeed5.hover": "", "vcmi.battleOptions.animationsSpeed6.hover": "", - "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.movementHighlightOnHover.hover": "Movement Highlight on Hover", - "vcmi.battleOptions.movementHighlightOnHover.help": "{Movement Highlight on Hover}\n\nHighlight unit's movement range when you hover over it.", - "vcmi.battleOptions.rangeLimitHighlightOnHover.hover": "Show range limits for shooters", - "vcmi.battleOptions.rangeLimitHighlightOnHover.help": "{Show range limits for shooters on Hover}\n\nShow shooter's range limits when you hover over it.", - "vcmi.battleOptions.showStickyHeroInfoWindows.hover": "Show heroes statistics windows", - "vcmi.battleOptions.showStickyHeroInfoWindows.help": "{Show heroes statistics windows}\n\nPermanently toggle on heroes statistics windows that show primary stats and spell points.", - "vcmi.battleOptions.skipBattleIntroMusic.hover": "Skip Intro Music", - "vcmi.battleOptions.skipBattleIntroMusic.help": "{Skip Intro Music}\n\nAllow actions during the intro music that plays at the beginning of each battle.", + "vcmi.battleOptions.animationsSpeed1.help": "Régler la vitesse d'animation très lente", + "vcmi.battleOptions.animationsSpeed5.help": "Régler la vitesse d'animation très rapide", + "vcmi.battleOptions.animationsSpeed6.help": "Régler la vitesse d'animation sur instantané", + "vcmi.battleOptions.movementHighlightOnHover.hover": "Mise en surbrillance du mouvement au survol", + "vcmi.battleOptions.movementHighlightOnHover.help": "{Movement Highlight on Hover}\n\nMettre en surbrillance la plage de mouvement de l'unité lorsque vous la survolez.", + "vcmi.battleOptions.rangeLimitHighlightOnHover.hover": "Afficher les limites de portée pour les tireurs", + "vcmi.battleOptions.rangeLimitHighlightOnHover.help": "{Show range limits for shooters on Hover}\n\nAfficher les limites de portée du tireur lorsque vous le survolez.", + "vcmi.battleOptions.skipBattleIntroMusic.hover": "Ignorer la musique d'introduction", + "vcmi.battleOptions.skipBattleIntroMusic.help": "{Skip Intro Music}\n\nAutoriser les actions pendant la musique d'intro qui joue au début de chaque bataille", + "vcmi.battleWindow.pressKeyToSkipIntro" : "Appuyez sur n'importe quelle touche pour commencer la bataille immédiatement", - "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).", - "vcmi.battleWindow.damageEstimation.ranged" : "Shoot %CREATURE (%SHOTS, %DAMAGE).", - "vcmi.battleWindow.damageEstimation.rangedKills" : "Shoot %CREATURE (%SHOTS, %DAMAGE, %KILLS).", - "vcmi.battleWindow.damageEstimation.shots" : "%d shots left", - "vcmi.battleWindow.damageEstimation.shots.1" : "%d shot left", - "vcmi.battleWindow.damageEstimation.damage" : "%d damage", - "vcmi.battleWindow.damageEstimation.damage.1" : "%d damage", - "vcmi.battleWindow.damageEstimation.kills" : "%d will perish", - "vcmi.battleWindow.damageEstimation.kills.1" : "%d will perish", + "vcmi.battleWindow.damageEstimation.melee" : "Attaque %CREATURE (%DAMAGE).", + "vcmi.battleWindow.damageEstimation.meleeKills" : "Attaque %CREATURE (%DAMAGE, %KILLS).", + "vcmi.battleWindow.damageEstimation.ranged" : "Tir sur %CREATURE (%SHOTS, %DAMAGE).", + "vcmi.battleWindow.damageEstimation.rangedKills" : "Tir sur %CREATURE (%SHOTS, %DAMAGE, %KILLS).", + "vcmi.battleWindow.damageEstimation.shots" : "%d tirs restants", + "vcmi.battleWindow.damageEstimation.shots.1" : "%d tir restant", + "vcmi.battleWindow.damageEstimation.damage" : "%d dégâts", + "vcmi.battleWindow.damageEstimation.damage.1" : "%d dégâts", + "vcmi.battleWindow.damageEstimation.kills" : "%d va mourir", + "vcmi.battleWindow.damageEstimation.kills.1" : "%d va mourir", - "vcmi.battleResultsWindow.applyResultsLabel" : "Apply battle result", + "vcmi.battleResultsWindow.applyResultsLabel" : "Appliquer le résultat de la bataille", - "vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover" : "Show Available Creatures", - "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\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\nShow smaller information for town creatures in town summary (bottom-left corner of town screen).", + "vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover" : "Afficher les créatures disponibles", + "vcmi.otherOptions.availableCreaturesAsDwellingLabel.help" : "{Show Available Creatures}\n\nAfficher le nombre de créatures disponibles au recrutement au lieu de leur croissance dans le résumé de la ville (coin inférieur gauche de l'écran de la ville).", + "vcmi.otherOptions.creatureGrowthAsDwellingLabel.hover" : "Afficher la croissance hebdomadaire des créatures", + "vcmi.otherOptions.creatureGrowthAsDwellingLabel.help" : "{Show Weekly Growth of Creatures}\n\nAfficher la croissance hebdomadaire des créatures au lieu de la quantité disponible dans le résumé de la ville (coin inférieur gauche de l'écran de la ville).", + "vcmi.otherOptions.compactTownCreatureInfo.hover": "Infos compactes sur la créature", + "vcmi.otherOptions.compactTownCreatureInfo.help": "{Compact Creature Info}\n\nAfficher des informations plus petites pour les créatures de la ville dans le résumé de la ville (coin inférieur gauche de l'écran de la ville).", - "vcmi.townHall.missingBase" : "Base building %s must be built first", - "vcmi.townHall.noCreaturesToRecruit" : "There are no creatures to recruit!", - "vcmi.townHall.greetingManaVortex" : "As you near the %s your body is filled with new energy. You have doubled your normal spell points.", - "vcmi.townHall.greetingKnowledge" : "You study the glyphs on the %s and gain insight into the workings of various magics (+1 Knowledge).", - "vcmi.townHall.greetingSpellPower" : "The %s teaches you new ways to focus your magical powers (+1 Power).", - "vcmi.townHall.greetingExperience" : "A visit to the %s teaches you many new skills (+1000 Experience).", - "vcmi.townHall.greetingAttack" : "Some time spent at the %s allows you to learn more effective combat skills (+1 Attack Skill).", - "vcmi.townHall.greetingDefence" : "Spending time in the %s, the experienced warriors therein teach you additional defensive skills (+1 Defense).", - "vcmi.townHall.hasNotProduced" : "The %s has not produced anything yet.", - "vcmi.townHall.hasProduced" : "The %s produced %d %s this week.", - "vcmi.townHall.greetingCustomBonus" : "%s gives you +%d %s%s", - "vcmi.townHall.greetingCustomUntil" : " until next battle.", - "vcmi.townHall.greetingInTownMagicWell" : "%s has restored your spell points to maximum.", + "vcmi.townHall.missingBase" : "Le bâtiment de base %s doit être construit avant", + "vcmi.townHall.noCreaturesToRecruit" : "Il n'y a aucune créature à recruter !", + "vcmi.townHall.greetingManaVortex" : "Alors que vous approchez du %s, votre corps est rempli d'une nouvelle énergie. Vous avez doublé vos points de sort normaux.", + "vcmi.townHall.greetingKnowledge" : "Vous étudiez les glyphes sur le %s et découvrez le fonctionnement de diverses magies (+1 Connaissance).", + "vcmi.townHall.greetingSpellPower" : "Le %s vous apprend de nouvelles façons de concentrer vos pouvoirs magiques (+1 Pouvoir).", + "vcmi.townHall.greetingExperience" : "Une visite au %s vous apprend de nombreuses nouvelles compétences (+1000 Expérience).", + "vcmi.townHall.greetingAttack" : "Un peu de temps passé au %s vous permet d'apprendre des compétences de combat plus efficaces (+1 compétence d'attaque).", + "vcmi.townHall.greetingDefence" : "En passant du temps dans le %s, les guerriers expérimentés qui s'y trouvent vous enseignent des compétences défensives supplémentaires (+1 Défense).", + "vcmi.townHall.hasNotProduced" : "Le %s n'a encore rien produit.", + "vcmi.townHall.hasProduced" : "Le %s a produit %d %s cette semaine.", + "vcmi.townHall.greetingCustomBonus" : "%s vous offre +%d %s%s", + "vcmi.townHall.greetingCustomUntil" : " jusqu'à la prochaine bataille.", + "vcmi.townHall.greetingInTownMagicWell" : "%s a restauré vos points de sort au maximum.", - "vcmi.logicalExpressions.anyOf" : "Any of the following:", - "vcmi.logicalExpressions.allOf" : "All of the following:", - "vcmi.logicalExpressions.noneOf" : "None of the following:", + "vcmi.logicalExpressions.anyOf" : "L'un des éléments suivants :", + "vcmi.logicalExpressions.allOf" : "Tous les éléments suivants :", + "vcmi.logicalExpressions.noneOf" : "Aucun des éléments suivants :", - "vcmi.heroWindow.openCommander.hover" : "Open commander info window", - "vcmi.heroWindow.openCommander.help" : "Shows details about the commander of this hero.", - "vcmi.heroWindow.openBackpack.hover" : "Open artifact backpack window", - "vcmi.heroWindow.openBackpack.help" : "Opens window that allows easier artifact backpack management.", + "vcmi.heroWindow.openCommander.hover" : "Ouvrir la fenêtre d'informations du commandant", + "vcmi.heroWindow.openCommander.help" : "Affiche des détails sur le commandant de ce héros", - "vcmi.commanderWindow.artifactMessage" : "Do you want to return this artifact to the hero?", + "vcmi.commanderWindow.artifactMessage" : "Voulez-vous rendre cet artefact au héros ?", - "vcmi.creatureWindow.showBonuses.hover" : "Switch to bonuses view", - "vcmi.creatureWindow.showBonuses.help" : "Display all active bonuses of the commander.", - "vcmi.creatureWindow.showSkills.hover" : "Switch to skills view", - "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.creatureWindow.showBonuses.hover" : "Passer en vue bonus", + "vcmi.creatureWindow.showBonuses.help" : "Afficher tous les bonus actifs du commandant", + "vcmi.creatureWindow.showSkills.hover" : "Passer à la vue des compétences", + "vcmi.creatureWindow.showSkills.help" : "Afficher toutes les compétences apprises du commandant", + "vcmi.creatureWindow.returnArtifact.hover" : "Remettre l'artefact", + "vcmi.creatureWindow.returnArtifact.help" : "Cliquez sur ce bouton pour remettre l'artefact dans le sac à dos du héros", - "vcmi.questLog.hideComplete.hover" : "Hide complete quests", - "vcmi.questLog.hideComplete.help" : "Hide all completed quests.", - - "vcmi.randomMapTab.widgets.randomTemplate" : "(Random)", - "vcmi.randomMapTab.widgets.templateLabel" : "Template", - "vcmi.randomMapTab.widgets.teamAlignmentsButton" : "Setup...", - "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.questLog.hideComplete.hover" : "Masquer les quêtes terminées", + "vcmi.questLog.hideComplete.help" : "Masquer toutes les quêtes terminées", + "vcmi.randomMapTab.widgets.templateLabel" : "Modèle", + "vcmi.randomMapTab.widgets.teamAlignmentsButton" : "Configuration...", + "vcmi.randomMapTab.widgets.teamAlignmentsLabel" : "Alignements d'équipe", + "vcmi.randomMapTab.widgets.roadTypesLabel" : "Types de routes", // 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!", - "vcmi.map.victoryCondition.daysPassed.toSelf" : "Congratulations! You have managed to survive. Victory is yours!", - "vcmi.map.victoryCondition.eliminateMonsters.toOthers" : "The enemy has defeated all of the monsters plaguing this land and claims victory!", - "vcmi.map.victoryCondition.eliminateMonsters.toSelf" : "Congratulations! You have defeated all of the monsters plaguing this land and can claim victory!", - "vcmi.map.victoryCondition.collectArtifacts.message" : "Acquire Three Artifacts", - "vcmi.map.victoryCondition.angelicAlliance.toSelf" : "Congratulations! All your enemies have been defeated and you have Angelic Alliance! Victory is yours!", - "vcmi.map.victoryCondition.angelicAlliance.message" : "Defeat All Enemies and create Angelic Alliance", + "vcmi.map.victoryCondition.daysPassed.toOthers" : "L'ennemi a réussi à survivre jusqu'à ce jour. La victoire est à eux !", + "vcmi.map.victoryCondition.daysPassed.toSelf" : "Félicitations ! Vous avez réussi à survivre. La victoire est à vous !", + "vcmi.map.victoryCondition.eliminateMonsters.toOthers" : "L'ennemi a vaincu tous les monstres qui sévissent sur cette terre et revendique la victoire !", + "vcmi.map.victoryCondition.eliminateMonsters.toSelf" : "Félicitations ! Vous avez vaincu tous les monstres qui affligent ce pays et vous pouvez revendiquer la victoire !", + "vcmi.map.victoryCondition.collectArtifacts.message" : "Acquérir trois artefacts", + "vcmi.map.victoryCondition.angelicAlliance.toSelf" : "Félicitations ! Tous vos ennemis ont été vaincus et vous avez l'alliance angélique ! La victoire est à vous !", + "vcmi.map.victoryCondition.angelicAlliance.message" : "Vaincre tous les ennemis et créer une alliance angélique", // 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.0" : "Basic", + "vcmi.stackExperience.description" : "» D é t a i l s d e P i l e d ' E x p é r i e n c e «\n\nType de créature ................ : %s\nRang d'expérience ............... : %s (%i)\nPoints d'expérience ............. : %i\nPoints d'XP au rang suivant ..... : %i\nExpérience maximum par bataille . : %i%% (%i)\nNb de créatures dans la pile .... : %i\nMaximum de nouvelles recrues\n sans perdre le rang actuel ..... : %i\nMultiplicateur d'expérience ..... : %.2f\nMultiplicateur d'amélioration ... : %.2f\nExpérience après le rang 10 ..... : %i\nMaximum de Nouvelles Recrues pour\n rester au Rang 10 si\n l'Expérience Maximum ........... : %i", + "vcmi.stackExperience.rank.0" : "Basique", "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.2" : "Formé", + "vcmi.stackExperience.rank.3" : "Qualifié", + "vcmi.stackExperience.rank.4" : "Éprouvé", + "vcmi.stackExperience.rank.5" : "Vétéran", + "vcmi.stackExperience.rank.6" : "Adepte", "vcmi.stackExperience.rank.7" : "Expert", - "vcmi.stackExperience.rank.8" : "Elite", - "vcmi.stackExperience.rank.9" : "Master", - "vcmi.stackExperience.rank.10" : "Ace", + "vcmi.stackExperience.rank.8" : "Élite", + "vcmi.stackExperience.rank.9" : "Maître", + "vcmi.stackExperience.rank.10" : "As", - "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.AIR_IMMUNITY.name": "Air immunity", - "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 using a ranged attack", - "core.bonus.CATAPULT.name": "Catapult", - "core.bonus.CATAPULT.description": "Attacks siege walls", - "core.bonus.CHANGES_SPELL_COST_FOR_ALLY.name": "Reduce Casting Cost (${val})", - "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 spellcasting cost of enemy spells by ${val}", - "core.bonus.CHARGE_IMMUNITY.name": "Immune to Charge", - "core.bonus.CHARGE_IMMUNITY.description": "Immune to Cavalier's and Champion's Charge", - "core.bonus.DARKNESS.name": "Darkness cover", - "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": "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.ADDITIONAL_ATTACK.name": "Double frappe", + "core.bonus.ADDITIONAL_ATTACK.description": "Attaque deux fois", + "core.bonus.ADDITIONAL_RETALIATION.name": "Représailles supplémentaires", + "core.bonus.ADDITIONAL_RETALIATION.description": "Peut riposter ${val} fois de plus", + "core.bonus.AIR_IMMUNITY.name": "Immunité aérienne", + "core.bonus.AIR_IMMUNITY.description": "Immunisé contre tous les sorts de l'école de magie de l'Air", + "core.bonus.ATTACKS_ALL_ADJACENT.name": "Attaque tout autour", + "core.bonus.ATTACKS_ALL_ADJACENT.description": "Attaque tous les ennemis adjacents", + "core.bonus.BLOCKS_RETALIATION.name": "Pas de représailles", + "core.bonus.BLOCKS_RETALIATION.description": "L'ennemi ne peut pas riposter", + "core.bonus.BLOCKS_RANGED_RETALIATION.name": "Pas de représailles à distance", + "core.bonus.BLOCKS_RANGED_RETALIATION.description": "L'ennemi ne peut pas riposter en utilisant une attaque à distance", + "core.bonus.CATAPULT.name": "Catapulte", + "core.bonus.CATAPULT.description": "Attaque les murs de siège", + "core.bonus.CHANGES_SPELL_COST_FOR_ALLY.name": "Réduire le coût de lancement (${val})", + "core.bonus.CHANGES_SPELL_COST_FOR_ALLY.description": "Réduit le coût d'incantation du héros de ${val}", + "core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.name": "Amortisseur magique (${val})", + "core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.description": "Augmente le coût d'incantation des sorts ennemis de ${val}", + "core.bonus.CHARGE_IMMUNITY.name": "Immunisé contre la charge", + "core.bonus.CHARGE_IMMUNITY.description": "Immunisé contre la charge du cavalier et du champion", + "core.bonus.DARKNESS.name": "Couverture de ténèbres", + "core.bonus.DARKNESS.description": "Crée un linceul de ténèbres avec un rayon de ${val}", + "core.bonus.DEATH_STARE.name": "Regard mortel (${val}%)", + "core.bonus.DEATH_STARE.description": "A ${val}% de chances de tuer une seule créature", + "core.bonus.DEFENSIVE_STANCE.name": "Bonus de défense", + "core.bonus.DEFENSIVE_STANCE.description": "+${val} Défense en défense", "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": "Has a ${val}% chance of dealing double base damage when attacking", + "core.bonus.DESTRUCTION.description": "A ${val} % de chances de tuer des unités supplémentaires après l'attaque", + "core.bonus.DOUBLE_DAMAGE_CHANCE.name": "Coup mortel", + "core.bonus.DOUBLE_DAMAGE_CHANCE.description": "A ${val}% de chances d'infliger des dégâts de base doublés en attaquant", "core.bonus.DRAGON_NATURE.name": "Dragon", - "core.bonus.DRAGON_NATURE.description": "Creature has a Dragon Nature", - "core.bonus.EARTH_IMMUNITY.name": "Earth immunity", - "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": "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 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 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": "Flies when moving (ignores obstacles)", - "core.bonus.FREE_SHOOTING.name": "Shoot Close", - "core.bonus.FREE_SHOOTING.description": "Can use ranged attacks at melee range", - "core.bonus.GARGOYLE.name": "Gargoyle", - "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 attacks", - "core.bonus.HATE.name": "Hates ${subtype.creature}", - "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 ${val} hit points every round", - "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" : "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 ${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": "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": "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", - "core.bonus.NO_MORALE.description": "Creature is immune to morale effects", - "core.bonus.NO_WALL_PENALTY.name": "No wall penalty", - "core.bonus.NO_WALL_PENALTY.description": "Full damage during siege", - "core.bonus.NON_LIVING.name": "Non living", - "core.bonus.NON_LIVING.description": "Immunity to many effects", - "core.bonus.RANDOM_SPELLCASTER.name": "Random spellcaster", - "core.bonus.RANDOM_SPELLCASTER.description": "Can cast random spell", - "core.bonus.RANGED_RETALIATION.name": "Ranged retaliation", - "core.bonus.RANGED_RETALIATION.description": "Can perform ranged counterattack", - "core.bonus.RECEPTIVE.name": "Receptive", - "core.bonus.RECEPTIVE.description": "No Immunity to Friendly Spells", - "core.bonus.REBIRTH.name": "Rebirth (${val}%)", - "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.SHOOTER.name": "Ranged", - "core.bonus.SHOOTER.description": "Creature can shoot", - "core.bonus.SHOOTS_ALL_ADJACENT.name": "Shoot all around", - "core.bonus.SHOOTS_ALL_ADJACENT.description": "This creature's ranged attacks strike all targets in a small area", - "core.bonus.SOUL_STEAL.name": "Soul Steal", - "core.bonus.SOUL_STEAL.description": "Gains ${val} new creatures for each enemy killed", - "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": "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": "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 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", - "core.bonus.SPELL_LIKE_ATTACK.description": "Attacks with ${subtype.spell}", - "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 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", - "core.bonus.TWO_HEX_ATTACK_BREATH.description": "Breath Attack (2-hex range)", - "core.bonus.THREE_HEADED_ATTACK.name": "Three-headed attack", - "core.bonus.THREE_HEADED_ATTACK.description": "Attacks three adjacent units", + "core.bonus.DRAGON_NATURE.description": "La créature a une nature de dragon", + "core.bonus.EARTH_IMMUNITY.name": "Immunité terrestre", + "core.bonus.EARTH_IMMUNITY.description": "Immunisé contre tous les sorts de l'école de magie de la Terre", + "core.bonus.ENCHANTER.name": "Enchanteur", + "core.bonus.ENCHANTER.description": "Peut lancer en masse ${subtype.spell} à chaque tour", + "core.bonus.ENCHANTED.name": "Enchanté", + "core.bonus.ENCHANTED.description": "Affecté par ${subtype.spell} permanent", + "core.bonus.ENEMY_DEFENCE_REDUCTION.name": "Ignorer la défense (${val}%)", + "core.bonus.ENEMY_DEFENCE_REDUCTION.description": "Lors de l'attaque, ${val}% de la défense du défenseur est ignorée", + "core.bonus.FIRE_IMMUNITY.name": "Immunité au feu", + "core.bonus.FIRE_IMMUNITY.description": "Immunisé contre tous les sorts de l'école de magie du Feu", + "core.bonus.FIRE_SHIELD.name": "Bouclier de feu (${val}%)", + "core.bonus.FIRE_SHIELD.description": "Reflète une partie des dégâts de mêlée", + "core.bonus.FIRST_STRIKE.name": "Premier coup", + "core.bonus.FIRST_STRIKE.description": "Cette créature riposte avant d'être attaquée", + "core.bonus.FEAR.name": "Peur", + "core.bonus.FEAR.description": "Provoque la peur sur une pile ennemie", + "core.bonus.FEARLESS.name": "Intrépide", + "core.bonus.FEARLESS.description": "Immunité à la peur", + "core.bonus.FLYING.name": "Vol", + "core.bonus.FLYING.description": "Vole en se déplaçant (ignore les obstacles)", + "core.bonus.FREE_SHOOTING.name": "Tirer de près", + "core.bonus.FREE_SHOOTING.description": "Peut utiliser des attaques à distance au corps à corps", + "core.bonus.GARGOYLE.name": "Gargouille", + "core.bonus.GARGOYLE.description": "Ne peut pas être réanimé ou soigné", + "core.bonus.GENERAL_DAMAGE_REDUCTION.name": "Réduit les dégâts (${val}%)", + "core.bonus.GENERAL_DAMAGE_REDUCTION.description": "Réduit les dégâts physiques des attaques à distance ou de mêlée", + "core.bonus.HATE.name": "Déteste ${subtype.creature}", + "core.bonus.HATE.description": "Inflige ${val} % de dégâts supplémentaires à ${subtype.creature}", + "core.bonus.HEALER.name": "Guérisseur", + "core.bonus.HEALER.description": "Soigne les unités alliées", + "core.bonus.HP_REGENERATION.name": "Régénération", + "core.bonus.HP_REGENERATION.description": "Soigne ${val} points de vie à chaque tour", + "core.bonus.JOUSTING.name": "Charge de champion", + "core.bonus.JOUSTING.description": "+${val}% de dégâts pour chaque hexagone parcouru", + "core.bonus.KING.name": "Roi", + "core.bonus.KING.description": "Vulnérable au niveau POURFENDEUR ${val} ou supérieur", + "core.bonus.LEVEL_SPELL_IMMUNITY.name": "Immunité aux sorts 1-${val}", + "core.bonus.LEVEL_SPELL_IMMUNITY.description": "Immunisé aux sorts de niveaux 1-${val}", + "core.bonus.LIMITED_SHOOTING_RANGE.name" : "Portée de tir limitée", + "core.bonus.LIMITED_SHOOTING_RANGE.description" : "Impossible de cibler des unités plus loin que ${val} hexagones", + "core.bonus.LIFE_DRAIN.name": "Durée de vie du drain (${val}%)", + "core.bonus.LIFE_DRAIN.description": "Draine ${val}% des dégâts infligés", + "core.bonus.MANA_CHANNELING.name": "Chaîne magique ${val}%", + "core.bonus.MANA_CHANNELING.description": "Donne à votre héros ${val}% du mana dépensé par l'ennemi", + "core.bonus.MANA_DRAIN.name": "Drain de mana", + "core.bonus.MANA_DRAIN.description": "Draine ${val} mana à chaque tour", + "core.bonus.MAGIC_MIRROR.name": "Miroir magique (${val}%)", + "core.bonus.MAGIC_MIRROR.description": "A ${val} % de chances de rediriger un sort offensif vers une unité ennemie", + "core.bonus.MAGIC_RESISTANCE.name": "Résistance magique (${val}%)", + "core.bonus.MAGIC_RESISTANCE.description": "A ${val}% de chances de résister à un sort ennemi", + "core.bonus.MIND_IMMUNITY.name": "Immunité contre les sorts de l'esprit", + "core.bonus.MIND_IMMUNITY.description": "Immunisé contre les sorts de type Mental", + "core.bonus.NO_DISTANCE_PENALTY.name": "Aucune pénalité de distance", + "core.bonus.NO_DISTANCE_PENALTY.description": "Inflige des dégâts complets à n'importe quelle distance", + "core.bonus.NO_MELEE_PENALTY.name": "Aucune pénalité de mêlée", + "core.bonus.NO_MELEE_PENALTY.description": "La créature n'a pas de pénalité de mêlée", + "core.bonus.NO_MORALE.name": "Moral neutre", + "core.bonus.NO_MORALE.description": "La créature est immunisée contre les effets de moral", + "core.bonus.NO_WALL_PENALTY.name": "Aucune pénalité de mur", + "core.bonus.NO_WALL_PENALTY.description": "Dégâts complets pendant le siège", + "core.bonus.NON_LIVING.name": "Non vivant", + "core.bonus.NON_LIVING.description": "Immunité à de nombreux effets", + "core.bonus.RANDOM_SPELLCASTER.name": "Lanceur de sorts aléatoire", + "core.bonus.RANDOM_SPELLCASTER.description": "Peut lancer un sort aléatoire", + "core.bonus.RANGED_RETALIATION.name": "Représailles à distance", + "core.bonus.RANGED_RETALIATION.description": "Peut effectuer une contre-attaque à distance", + "core.bonus.RECEPTIVE.name": "Réceptif", + "core.bonus.RECEPTIVE.description": "Pas d'immunité aux sorts amicaux", + "core.bonus.REBIRTH.name": "Renaissance (${val}%)", + "core.bonus.REBIRTH.description": "${val}% de la pile augmentera après la mort", + "core.bonus.RETURN_AFTER_STRIKE.name": "Attaque et retour", + "core.bonus.RETURN_AFTER_STRIKE.description": "Revient après une attaque au corps à corps", + "core.bonus.SHOOTER.name": "Distance", + "core.bonus.SHOOTER.description": "La créature peut tirer", + "core.bonus.SHOOTS_ALL_ADJACENT.name": "Tirer tout autour", + "core.bonus.SHOOTS_ALL_ADJACENT.description": "Les attaques à distance de cette créature touchent toutes les cibles dans une petite zone", + "core.bonus.SOUL_STEAL.name": "Vol d'âme", + "core.bonus.SOUL_STEAL.description": "Gagne ${val} nouvelles créatures pour chaque ennemi tué", + "core.bonus.SPELLCASTER.name": "Lanceur de sorts", + "core.bonus.SPELLCASTER.description": "Peut lancer ${subtype.spell}", + "core.bonus.SPELL_AFTER_ATTACK.name": "Lancer après l'attaque", + "core.bonus.SPELL_AFTER_ATTACK.description": "A ${val}% de chances de lancer ${subtype.spell} après avoir attaqué", + "core.bonus.SPELL_BEFORE_ATTACK.name": "Lancer avant l'attaque", + "core.bonus.SPELL_BEFORE_ATTACK.description": "A ${val}% de chances de lancer ${subtype.spell} avant qu'il n'attaque", + "core.bonus.SPELL_DAMAGE_REDUCTION.name": "Résistance aux sorts", + "core.bonus.SPELL_DAMAGE_REDUCTION.description": "Dégâts des sorts réduits de ${val}%.", + "core.bonus.SPELL_IMMUNITY.name": "Immunité aux sorts", + "core.bonus.SPELL_IMMUNITY.description": "Immunisé contre ${subtype.spell}", + "core.bonus.SPELL_LIKE_ATTACK.name": "Attaque semblable à un sort", + "core.bonus.SPELL_LIKE_ATTACK.description": "Attaque avec ${subtype.spell}", + "core.bonus.SPELL_RESISTANCE_AURA.name": "Aura de résistance", + "core.bonus.SPELL_RESISTANCE_AURA.description": "Les piles à proximité obtiennent ${val}% de résistance magique", + "core.bonus.SUMMON_GUARDIANS.name": "Invoquer des gardiens", + "core.bonus.SUMMON_GUARDIANS.description": "Au début de la bataille, invoque ${subtype.creature} (${val}%)", + "core.bonus.SYNERGY_TARGET.name": "Synergique", + "core.bonus.SYNERGY_TARGET.description": "Cette créature est vulnérable à l'effet de synergie", + "core.bonus.TWO_HEX_ATTACK_BREATH.name": "Souffle", + "core.bonus.TWO_HEX_ATTACK_BREATH.description": "Attaque de souffle (portée de 2 hexagones)", + "core.bonus.THREE_HEADED_ATTACK.name": "Attaque à trois têtes", + "core.bonus.THREE_HEADED_ATTACK.description": "Attaque trois unités adjacentes", "core.bonus.TRANSMUTATION.name": "Transmutation", - "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": "Can retaliate against an unlimited number of attacks", - "core.bonus.WATER_IMMUNITY.name": "Water immunity", - "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)" + "core.bonus.TRANSMUTATION.description": "${val}% de chances de transformer l'unité attaquée en un type différent", + "core.bonus.UNDEAD.name": "Morts-vivants", + "core.bonus.UNDEAD.description": "La créature est un mort-vivant", + "core.bonus.UNLIMITED_RETALIATIONS.name": "Représailles illimitées", + "core.bonus.UNLIMITED_RETALIATIONS.description": "Peut riposter contre un nombre illimité d'attaques", + "core.bonus.WATER_IMMUNITY.name": "Immunité à l'eau", + "core.bonus.WATER_IMMUNITY.description": "Immunisé contre tous les sorts de l'école de magie de l'Eau", + "core.bonus.WIDE_BREATH.name": "Large souffle", + "core.bonus.WIDE_BREATH.description": "Attaque à souffle large (plusieurs hexagones)" } diff --git a/Mods/vcmi/config/vcmi/french.json b/Mods/vcmi/config/vcmi/french.json index 5d3e0a532..83e81dee6 100644 --- a/Mods/vcmi/config/vcmi/french.json +++ b/Mods/vcmi/config/vcmi/french.json @@ -30,7 +30,6 @@ "vcmi.capitalColors.6" : "Turquoise", "vcmi.capitalColors.7" : "Rose", - "vcmi.mainMenu.highscoresNotImplemented" : "Désolé, le menu des meilleurs scores n'est pas encore implémenté\n", "vcmi.mainMenu.serverConnecting" : "Connexion...", "vcmi.mainMenu.serverAddressEnter" : "Entrez l'adresse :", "vcmi.mainMenu.serverClosing" : "Fermeture...", diff --git a/Mods/vcmi/config/vcmi/german.json b/Mods/vcmi/config/vcmi/german.json index 926501565..0198d30fd 100644 --- a/Mods/vcmi/config/vcmi/german.json +++ b/Mods/vcmi/config/vcmi/german.json @@ -41,7 +41,6 @@ "vcmi.radialWheel.moveUnit" : "Verschieben der Kreatur in andere Armee", "vcmi.radialWheel.splitUnit" : "Aufsplitten der Kreatur in anderen Slot", - "vcmi.mainMenu.highscoresNotImplemented" : "Die Highscores sind aktuell noch nicht implementiert\n", "vcmi.mainMenu.serverConnecting" : "Verbinde...", "vcmi.mainMenu.serverAddressEnter" : "Addresse eingeben:", "vcmi.mainMenu.serverClosing" : "Trenne...", diff --git a/Mods/vcmi/config/vcmi/polish.json b/Mods/vcmi/config/vcmi/polish.json index f382a9578..be96f40e2 100644 --- a/Mods/vcmi/config/vcmi/polish.json +++ b/Mods/vcmi/config/vcmi/polish.json @@ -36,7 +36,6 @@ "vcmi.radialWheel.moveUnit" : "Przenieś stworzenia do innej armii", "vcmi.radialWheel.splitUnit" : "Podziel jednostkę do wybranego miejsca", - "vcmi.mainMenu.highscoresNotImplemented" : "Przepraszamy, najlepsze wyniki nie zostały jeszcze zaimplementowane\n", "vcmi.mainMenu.serverConnecting" : "Łączenie...", "vcmi.mainMenu.serverAddressEnter" : "Wprowadź adres:", "vcmi.mainMenu.serverClosing" : "Zamykanie...", diff --git a/Mods/vcmi/config/vcmi/ukrainian.json b/Mods/vcmi/config/vcmi/ukrainian.json index 78f16b9d4..0ad0bd9d2 100644 --- a/Mods/vcmi/config/vcmi/ukrainian.json +++ b/Mods/vcmi/config/vcmi/ukrainian.json @@ -37,7 +37,6 @@ "vcmi.radialWheel.moveUnit" : "Перемістити істоту до іншої армії", "vcmi.radialWheel.splitUnit" : "Розділити істоту в інший слот", - "vcmi.mainMenu.highscoresNotImplemented" : "Вибачте, таблицю рекордів ще не реалізовано\n", "vcmi.mainMenu.serverConnecting" : "Підключення...", "vcmi.mainMenu.serverAddressEnter" : "Вкажіть адресу:", "vcmi.mainMenu.serverClosing" : "Завершення...", diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 7e0731cb2..9abc1e961 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -59,6 +59,7 @@ set(client_SRCS mainmenu/CMainMenu.cpp mainmenu/CPrologEpilogVideo.cpp mainmenu/CreditsScreen.cpp + mainmenu/CHighScoreScreen.cpp mapView/MapRenderer.cpp mapView/MapRendererContext.cpp @@ -213,6 +214,7 @@ set(client_HEADERS mainmenu/CMainMenu.h mainmenu/CPrologEpilogVideo.h mainmenu/CreditsScreen.h + mainmenu/CHighScoreScreen.h mapView/IMapRendererContext.h mapView/IMapRendererObserver.h diff --git a/client/VCMI_client.cbp b/client/VCMI_client.cbp index 5abaf79fd..57fc618b5 100644 --- a/client/VCMI_client.cbp +++ b/client/VCMI_client.cbp @@ -198,6 +198,8 @@ + + diff --git a/client/VCMI_client.vcxproj b/client/VCMI_client.vcxproj index fd89da223..3fe7331f9 100644 --- a/client/VCMI_client.vcxproj +++ b/client/VCMI_client.vcxproj @@ -217,6 +217,7 @@ + @@ -286,6 +287,7 @@ + diff --git a/client/VCMI_client.vcxproj.filters b/client/VCMI_client.vcxproj.filters index d5f5af0fa..8831d0480 100644 --- a/client/VCMI_client.vcxproj.filters +++ b/client/VCMI_client.vcxproj.filters @@ -132,6 +132,7 @@ + @@ -290,5 +291,6 @@ + \ No newline at end of file diff --git a/client/mainmenu/CHighScoreScreen.cpp b/client/mainmenu/CHighScoreScreen.cpp new file mode 100644 index 000000000..d2874aee3 --- /dev/null +++ b/client/mainmenu/CHighScoreScreen.cpp @@ -0,0 +1,161 @@ +/* + * CHighScoreScreen.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 "CHighScoreScreen.h" +#include "../gui/CGuiHandler.h" +#include "../widgets/TextControls.h" +#include "../widgets/Buttons.h" +#include "../widgets/Images.h" +#include "../windows/InfoWindows.h" +#include "../render/Canvas.h" + +#include "../CGameInfo.h" +#include "../../lib/CGeneralTextHandler.h" +#include "../../lib/CConfigHandler.h" +#include "../../lib/CCreatureHandler.h" +#include "../../lib/constants/EntityIdentifiers.h" + +CHighScoreScreen::CHighScoreScreen() + : CWindowObject(BORDERED) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; + pos = center(Rect(0, 0, 800, 600)); + updateShadow(); + + addHighScores(); + addButtons(); + + // TODO: remove; only for testing + for (int i = 0; i < 11; i++) + { + Settings entry = persistentStorage.write["highscore"]["campaign"][std::to_string(i)]["player"]; + entry->String() = "test"; + Settings entry1 = persistentStorage.write["highscore"]["campaign"][std::to_string(i)]["campaign"]; + entry1->String() = "test"; + Settings entry2 = persistentStorage.write["highscore"]["campaign"][std::to_string(i)]["points"]; + entry2->Integer() = 100; + + Settings entry3 = persistentStorage.write["highscore"]["standard"][std::to_string(i)]["player"]; + entry3->String() = "test"; + Settings entry4 = persistentStorage.write["highscore"]["standard"][std::to_string(i)]["land"]; + entry4->String() = "test"; + Settings entry5 = persistentStorage.write["highscore"]["standard"][std::to_string(i)]["days"]; + entry5->Integer() = 123; + Settings entry6 = persistentStorage.write["highscore"]["standard"][std::to_string(i)]["points"]; + entry6->Integer() = 100; + } +} + +void CHighScoreScreen::addButtons() +{ + OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; + + buttons.clear(); + + buttons.push_back(std::make_shared(Point(31, 113), AnimationPath::builtin("HISCCAM.DEF"), CButton::tooltip(), [&](){ buttonCampaginClick(); })); + buttons.push_back(std::make_shared(Point(31, 345), AnimationPath::builtin("HISCSTA.DEF"), CButton::tooltip(), [&](){ buttonStandardClick(); })); + buttons.push_back(std::make_shared(Point(726, 113), AnimationPath::builtin("HISCRES.DEF"), CButton::tooltip(), [&](){ buttonResetClick(); })); + buttons.push_back(std::make_shared(Point(726, 345), AnimationPath::builtin("HISCEXT.DEF"), CButton::tooltip(), [&](){ buttonExitClick(); })); +} + +void CHighScoreScreen::addHighScores() +{ + OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; + + background = std::make_shared(ImagePath::builtin(highscorepage == HighScorePage::STANDARD ? "HISCORE" : "HISCORE2")); + + texts.clear(); + + static const JsonNode configCreatures(JsonPath::builtin("CONFIG/highscoreCreatures.json")); + auto creatures = configCreatures["creatures"].Vector(); + for(auto & creature : creatures) { + images.push_back(std::make_shared(AnimationPath::builtin("CPRSMALL"), (*CGI->creh)[CreatureID::decode(creature["creature"].String())]->getIconIndex(), 0, 10, 10)); + } + + // Header + texts.push_back(std::make_shared(115, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.433"))); + texts.push_back(std::make_shared(220, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.434"))); + + if(highscorepage == HighScorePage::STANDARD) + { + texts.push_back(std::make_shared(400, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.435"))); + texts.push_back(std::make_shared(555, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.436"))); + texts.push_back(std::make_shared(625, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.75"))); + } + else + { + texts.push_back(std::make_shared(410, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.672"))); + texts.push_back(std::make_shared(590, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.75"))); + } + + // Content + int y = 65; + auto & data = persistentStorage["highscore"][highscorepage == HighScorePage::STANDARD ? "standard" : "campaign"]; + for (int i = 0; i < 11; i++) + { + auto & curData = data[std::to_string(i)]; + + texts.push_back(std::make_shared(115, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, std::to_string(i))); + texts.push_back(std::make_shared(220, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, curData["player"].String())); + + if(highscorepage == HighScorePage::STANDARD) + { + texts.push_back(std::make_shared(400, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, curData["land"].String())); + texts.push_back(std::make_shared(555, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, std::to_string(curData["days"].Integer()))); + texts.push_back(std::make_shared(625, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, std::to_string(curData["points"].Integer()))); + } + else + { + texts.push_back(std::make_shared(410, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, curData["campaign"].String())); + texts.push_back(std::make_shared(590, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, std::to_string(curData["points"].Integer()))); + } + } +} + +void CHighScoreScreen::buttonCampaginClick() +{ + highscorepage = HighScorePage::CAMPAIGN; + addHighScores(); + addButtons(); + redraw(); +} + +void CHighScoreScreen::buttonStandardClick() +{ + OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; + highscorepage = HighScorePage::STANDARD; + addHighScores(); + addButtons(); + redraw(); +} + +void CHighScoreScreen::buttonResetClick() +{ + CInfoWindow::showYesNoDialog( + CGI->generaltexth->allTexts[666], + {}, + [this]() + { + Settings entry = persistentStorage.write["highscore"]; + entry->clear(); + addHighScores(); + addButtons(); + redraw(); + }, + 0 + ); +} + +void CHighScoreScreen::buttonExitClick() +{ + close(); +} diff --git a/client/mainmenu/CHighScoreScreen.h b/client/mainmenu/CHighScoreScreen.h new file mode 100644 index 000000000..418b1a43c --- /dev/null +++ b/client/mainmenu/CHighScoreScreen.h @@ -0,0 +1,37 @@ +/* + * CHighScoreScreen.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 "../windows/CWindowObject.h" + +class CButton; +class CLabel; +class CAnimImage; + +class CHighScoreScreen : public CWindowObject +{ + enum HighScorePage { STANDARD, CAMPAIGN }; + + void addButtons(); + void addHighScores(); + + void buttonCampaginClick(); + void buttonStandardClick(); + void buttonResetClick(); + void buttonExitClick(); + + HighScorePage highscorepage = HighScorePage::STANDARD; + + std::shared_ptr background; + std::vector> buttons; + std::vector> texts; + std::vector> images; +public: + CHighScoreScreen(); +}; diff --git a/client/mainmenu/CMainMenu.cpp b/client/mainmenu/CMainMenu.cpp index 119763fed..d922eda25 100644 --- a/client/mainmenu/CMainMenu.cpp +++ b/client/mainmenu/CMainMenu.cpp @@ -12,6 +12,7 @@ #include "CCampaignScreen.h" #include "CreditsScreen.h" +#include "CHighScoreScreen.h" #include "../lobby/CBonusSelection.h" #include "../lobby/CSelectionBase.h" @@ -210,7 +211,7 @@ static std::function genCommand(CMenuScreen * menu, std::vectorgeneraltexth->translate("vcmi.mainMenu.highscoresNotImplemented"), std::vector>(), PlayerColor(1)); + return std::bind(CMainMenu::openHighScoreScreen); } } } @@ -389,6 +390,12 @@ void CMainMenu::startTutorial() CSH->startMapAfterConnection(mapInfo); } +void CMainMenu::openHighScoreScreen() +{ + GH.windows().createAndPushWindow(); + return; +} + std::shared_ptr CMainMenu::create() { if(!CMM) diff --git a/client/mainmenu/CMainMenu.h b/client/mainmenu/CMainMenu.h index f0e246c60..ea9010797 100644 --- a/client/mainmenu/CMainMenu.h +++ b/client/mainmenu/CMainMenu.h @@ -152,6 +152,7 @@ public: static void openCampaignLobby(const std::string & campaignFileName, std::string campaignSet = ""); static void openCampaignLobby(std::shared_ptr campaign); static void startTutorial(); + static void openHighScoreScreen(); void openCampaignScreen(std::string name); static std::shared_ptr create(); diff --git a/config/highscoreCreatures.json b/config/highscoreCreatures.json new file mode 100644 index 000000000..51392069b --- /dev/null +++ b/config/highscoreCreatures.json @@ -0,0 +1,122 @@ +{ + "creatures": [ + { "min" : 1, "max" : 4, "creature": "core:imp" }, + { "min" : 5, "max" : 8, "creature": "core:gremlin" }, + { "min" : 9, "max" : 12, "creature": "core:gnoll" }, + { "min" : 13, "max" : 16, "creature": "core:troglodyte" }, + { "min" : 17, "max" : 20, "creature": "core:familiar" }, + { "min" : 21, "max" : 24, "creature": "core:skeleton" }, + { "min" : 25, "max" : 28, "creature": "core:goblin" }, + { "min" : 29, "max" : 32, "creature": "core:masterGremlin" }, + { "min" : 33, "max" : 36, "creature": "core:hobgoblin" }, + { "min" : 37, "max" : 40, "creature": "core:pikeman" }, + { "min" : 41, "max" : 44, "creature": "core:infernoTroglodyte" }, + { "min" : 45, "max" : 48, "creature": "core:skeletonWarrior" }, + { "min" : 49, "max" : 52, "creature": "core:gnollMarauder" }, + { "min" : 53, "max" : 56, "creature": "core:walkingDead" }, + { "min" : 57, "max" : 60, "creature": "core:centaur" }, + { "min" : 61, "max" : 64, "creature": "core:herberdier" }, + { "min" : 65, "max" : 68, "creature": "core:archer" }, + { "min" : 69, "max" : 72, "creature": "core:lizardman" }, + { "min" : 73, "max" : 76, "creature": "core:zombie" }, + { "min" : 77, "max" : 80, "creature": "core:wolfRider" }, + { "min" : 81, "max" : 84, "creature": "core:centaurCaptian" }, + { "min" : 85, "max" : 88, "creature": "core:dwarf" }, + { "min" : 89, "max" : 92, "creature": "core:harpy" }, + { "min" : 93, "max" : 96, "creature": "core:lizardWarrior" }, + { "min" : 97, "max" : 100, "creature": "core:gog" }, + { "min" : 101, "max" : 104, "creature": "core:stoneGargoyle" }, + { "min" : 105, "max" : 108, "creature": "core:masksman" }, + { "min" : 109, "max" : 112, "creature": "core:orc" }, + { "min" : 113, "max" : 116, "creature": "core:obsidianGargoyle" }, + { "min" : 117, "max" : 120, "creature": "core:wofRaider" }, + { "min" : 121, "max" : 124, "creature": "core:battleDwarf" }, + { "min" : 125, "max" : 128, "creature": "core:elf" }, + { "min" : 129, "max" : 132, "creature": "core:harpyHag" }, + { "min" : 133, "max" : 136, "creature": "core:magog" }, + { "min" : 137, "max" : 140, "creature": "core:orcChieftain" }, + { "min" : 141, "max" : 144, "creature": "core:stoneGolem" }, + { "min" : 145, "max" : 148, "creature": "core:wight" }, + { "min" : 149, "max" : 152, "creature": "core:serpentFly" }, + { "min" : 153, "max" : 156, "creature": "core:dragonFly" }, + { "min" : 157, "max" : 160, "creature": "core:wraith" }, + { "min" : 161, "max" : 164, "creature": "core:waterElemental" }, + { "min" : 165, "max" : 168, "creature": "core:earthElemental" }, + { "min" : 169, "max" : 172, "creature": "core:grandElf" }, + { "min" : 173, "max" : 176, "creature": "core:beholder" }, + { "min" : 177, "max" : 180, "creature": "core:fireElemental" }, + { "min" : 181, "max" : 184, "creature": "core:griffin" }, + { "min" : 185, "max" : 187, "creature": "core:airElemental" }, + { "min" : 188, "max" : 190, "creature": "core:hellHound" }, + { "min" : 191, "max" : 193, "creature": "core:evilEye" }, + { "min" : 194, "max" : 196, "creature": "core:cerberus" }, + { "min" : 197, "max" : 199, "creature": "core:ironGolem" }, + { "min" : 200, "max" : 202, "creature": "core:orge" }, + { "min" : 203, "max" : 205, "creature": "core:swordman" }, + { "min" : 206, "max" : 208, "creature": "core:demon" }, + { "min" : 209, "max" : 211, "creature": "core:royalGriffin" }, + { "min" : 212, "max" : 214, "creature": "core:hornedDemon" }, + { "min" : 215, "max" : 217, "creature": "core:monk" }, + { "min" : 218, "max" : 220, "creature": "core:dendroidGuard" }, + { "min" : 221, "max" : 223, "creature": "core:medusa" }, + { "min" : 224, "max" : 226, "creature": "core:pegasus" }, + { "min" : 227, "max" : 229, "creature": "core:silverPegasus" }, + { "min" : 230, "max" : 232, "creature": "core:basilisk" }, + { "min" : 233, "max" : 235, "creature": "core:vampire" }, + { "min" : 236, "max" : 238, "creature": "core:mage" }, + { "min" : 239, "max" : 241, "creature": "core:medusaQueen" }, + { "min" : 242, "max" : 244, "creature": "core:crusider" }, + { "min" : 245, "max" : 247, "creature": "core:goldGolem" }, + { "min" : 248, "max" : 250, "creature": "core:orgeMage" }, + { "min" : 251, "max" : 253, "creature": "core:archMage" }, + { "min" : 254, "max" : 256, "creature": "core:greaterBasilisk" }, + { "min" : 257, "max" : 259, "creature": "core:zealot" }, + { "min" : 260, "max" : 262, "creature": "core:pitFiend" }, + { "min" : 263, "max" : 265, "creature": "core:diamondGolem" }, + { "min" : 266, "max" : 268, "creature": "core:vampireLord" }, + { "min" : 269, "max" : 271, "creature": "core:dendroidSolider" }, + { "min" : 272, "max" : 274, "creature": "core:minotaur" }, + { "min" : 275, "max" : 277, "creature": "core:lich" }, + { "min" : 278, "max" : 280, "creature": "core:genie" }, + { "min" : 281, "max" : 283, "creature": "core:gorgon" }, + { "min" : 284, "max" : 286, "creature": "core:masterGenie" }, + { "min" : 287, "max" : 289, "creature": "core:roc" }, + { "min" : 290, "max" : 292, "creature": "core:mightyGorgon" }, + { "min" : 293, "max" : 295, "creature": "core:minotaurKing" }, + { "min" : 296, "max" : 298, "creature": "core:powerLich" }, + { "min" : 299, "max" : 301, "creature": "core:thunderbird" }, + { "min" : 302, "max" : 304, "creature": "core:pitLord" }, + { "min" : 305, "max" : 307, "creature": "core:cyclops" }, + { "min" : 308, "max" : 310, "creature": "core:wyvern" }, + { "min" : 311, "max" : 313, "creature": "core:cyclopsKing" }, + { "min" : 314, "max" : 316, "creature": "core:wyvernMonarch" }, + { "min" : 317, "max" : 319, "creature": "core:manticore" }, + { "min" : 320, "max" : 322, "creature": "core:scorpicore" }, + { "min" : 323, "max" : 325, "creature": "core:efreeti" }, + { "min" : 326, "max" : 328, "creature": "core:unicorn" }, + { "min" : 329, "max" : 331, "creature": "core:efreetSultan" }, + { "min" : 332, "max" : 334, "creature": "core:cavalier" }, + { "min" : 335, "max" : 337, "creature": "core:naga" }, + { "min" : 338, "max" : 340, "creature": "core:warUnicorn" }, + { "min" : 341, "max" : 343, "creature": "core:blackKnight" }, + { "min" : 344, "max" : 346, "creature": "core:champion" }, + { "min" : 347, "max" : 349, "creature": "core:dreadKnight" }, + { "min" : 350, "max" : 352, "creature": "core:nagaQueen" }, + { "min" : 353, "max" : 355, "creature": "core:behemoth" }, + { "min" : 356, "max" : 358, "creature": "core:boneDragon" }, + { "min" : 359, "max" : 361, "creature": "core:giant" }, + { "min" : 362, "max" : 364, "creature": "core:hydra" }, + { "min" : 365, "max" : 367, "creature": "core:ghostDragon" }, + { "min" : 368, "max" : 370, "creature": "core:redDragon" }, + { "min" : 371, "max" : 373, "creature": "core:greenDragon" }, + { "min" : 374, "max" : 376, "creature": "core:angle" }, + { "min" : 377, "max" : 379, "creature": "core:devil" }, + { "min" : 380, "max" : 382, "creature": "core:chaosHydra" }, + { "min" : 383, "max" : 385, "creature": "core:ancientBehemoth" }, + { "min" : 386, "max" : 388, "creature": "core:archDevil" }, + { "min" : 389, "max" : 391, "creature": "core:titan" }, + { "min" : 392, "max" : 394, "creature": "core:goldDragon" }, + { "min" : 395, "max" : 397, "creature": "core:blackDragon" }, + { "min" : 398, "max" : 400, "creature": "core:archAngle " } + ] +} \ No newline at end of file diff --git a/lib/constants/EntityIdentifiers.h b/lib/constants/EntityIdentifiers.h index 80fbd48d9..1b5932626 100644 --- a/lib/constants/EntityIdentifiers.h +++ b/lib/constants/EntityIdentifiers.h @@ -204,8 +204,8 @@ class HeroTypeID : public Identifier public: using Identifier::Identifier; ///json serialization helpers - static si32 decode(const std::string & identifier); - static std::string encode(const si32 index); + DLL_LINKAGE static si32 decode(const std::string & identifier); + DLL_LINKAGE static std::string encode(const si32 index); static std::string entityType(); DLL_LINKAGE static const HeroTypeID NONE; @@ -248,8 +248,8 @@ public: std::string toString() const; - static si32 decode(const std::string& identifier); - static std::string encode(const si32 index); + DLL_LINKAGE static si32 decode(const std::string& identifier); + DLL_LINKAGE static std::string encode(const si32 index); static std::string entityType(); }; @@ -333,8 +333,8 @@ public: static const FactionID CONFLUX; static const FactionID NEUTRAL; - static si32 decode(const std::string& identifier); - static std::string encode(const si32 index); + DLL_LINKAGE static si32 decode(const std::string& identifier); + DLL_LINKAGE static std::string encode(const si32 index); static std::string entityType(); }; @@ -603,8 +603,8 @@ public: static_assert (AFTER_LAST == BACKPACK_START, "incorrect number of artifact slots"); - static si32 decode(const std::string & identifier); - static std::string encode(const si32 index); + DLL_LINKAGE static si32 decode(const std::string & identifier); + DLL_LINKAGE static std::string encode(const si32 index); }; class ArtifactPosition : public IdentifierWithEnum @@ -643,8 +643,8 @@ public: using IdentifierWithEnum::IdentifierWithEnum; ///json serialization helpers - static si32 decode(const std::string & identifier); - static std::string encode(const si32 index); + DLL_LINKAGE static si32 decode(const std::string & identifier); + DLL_LINKAGE static std::string encode(const si32 index); static std::string entityType(); }; @@ -683,8 +683,8 @@ public: using IdentifierWithEnum::IdentifierWithEnum; ///json serialization helpers - static si32 decode(const std::string & identifier); - static std::string encode(const si32 index); + DLL_LINKAGE static si32 decode(const std::string & identifier); + DLL_LINKAGE static std::string encode(const si32 index); static std::string entityType(); }; @@ -801,8 +801,8 @@ public: using IdentifierWithEnum::IdentifierWithEnum; ///json serialization helpers - static si32 decode(const std::string & identifier); - static std::string encode(const si32 index); + DLL_LINKAGE static si32 decode(const std::string & identifier); + DLL_LINKAGE static std::string encode(const si32 index); static std::string entityType(); }; @@ -855,8 +855,8 @@ class TerrainId : public IdentifierWithEnum public: using IdentifierWithEnum::IdentifierWithEnum; - static si32 decode(const std::string & identifier); - static std::string encode(const si32 index); + DLL_LINKAGE static si32 decode(const std::string & identifier); + DLL_LINKAGE static std::string encode(const si32 index); static std::string entityType(); }; From 909b06f7c15a98b5c5341f5cb0a388ff052facb7 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Fri, 22 Sep 2023 02:19:31 +0200 Subject: [PATCH 02/24] draw images --- client/mainmenu/CHighScoreScreen.cpp | 14 ++++++++---- config/highscoreCreatures.json | 34 ++++++++++++++-------------- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/client/mainmenu/CHighScoreScreen.cpp b/client/mainmenu/CHighScoreScreen.cpp index d2874aee3..e7950ed13 100644 --- a/client/mainmenu/CHighScoreScreen.cpp +++ b/client/mainmenu/CHighScoreScreen.cpp @@ -42,7 +42,7 @@ CHighScoreScreen::CHighScoreScreen() Settings entry1 = persistentStorage.write["highscore"]["campaign"][std::to_string(i)]["campaign"]; entry1->String() = "test"; Settings entry2 = persistentStorage.write["highscore"]["campaign"][std::to_string(i)]["points"]; - entry2->Integer() = 100; + entry2->Integer() = std::rand() % 400 * 5; Settings entry3 = persistentStorage.write["highscore"]["standard"][std::to_string(i)]["player"]; entry3->String() = "test"; @@ -51,7 +51,7 @@ CHighScoreScreen::CHighScoreScreen() Settings entry5 = persistentStorage.write["highscore"]["standard"][std::to_string(i)]["days"]; entry5->Integer() = 123; Settings entry6 = persistentStorage.write["highscore"]["standard"][std::to_string(i)]["points"]; - entry6->Integer() = 100; + entry6->Integer() = std::rand() % 400; } } @@ -74,12 +74,10 @@ void CHighScoreScreen::addHighScores() background = std::make_shared(ImagePath::builtin(highscorepage == HighScorePage::STANDARD ? "HISCORE" : "HISCORE2")); texts.clear(); + images.clear(); static const JsonNode configCreatures(JsonPath::builtin("CONFIG/highscoreCreatures.json")); auto creatures = configCreatures["creatures"].Vector(); - for(auto & creature : creatures) { - images.push_back(std::make_shared(AnimationPath::builtin("CPRSMALL"), (*CGI->creh)[CreatureID::decode(creature["creature"].String())]->getIconIndex(), 0, 10, 10)); - } // Header texts.push_back(std::make_shared(115, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.433"))); @@ -118,6 +116,12 @@ void CHighScoreScreen::addHighScores() texts.push_back(std::make_shared(410, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, curData["campaign"].String())); texts.push_back(std::make_shared(590, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, std::to_string(curData["points"].Integer()))); } + + int divide = (highscorepage == HighScorePage::STANDARD) ? 1 : 5; + for(auto & creature : creatures) { + if(curData["points"].Integer() / divide <= creature["max"].Integer() && curData["points"].Integer() / divide >= creature["min"].Integer()) + images.push_back(std::make_shared(AnimationPath::builtin("CPRSMALL"), (*CGI->creh)[CreatureID::decode(creature["creature"].String())]->getIconIndex(), 0, 670, y - 15 + i * 50)); + } } } diff --git a/config/highscoreCreatures.json b/config/highscoreCreatures.json index 51392069b..455c20378 100644 --- a/config/highscoreCreatures.json +++ b/config/highscoreCreatures.json @@ -10,28 +10,28 @@ { "min" : 29, "max" : 32, "creature": "core:masterGremlin" }, { "min" : 33, "max" : 36, "creature": "core:hobgoblin" }, { "min" : 37, "max" : 40, "creature": "core:pikeman" }, - { "min" : 41, "max" : 44, "creature": "core:infernoTroglodyte" }, + { "min" : 41, "max" : 44, "creature": "core:infernalTroglodyte" }, { "min" : 45, "max" : 48, "creature": "core:skeletonWarrior" }, { "min" : 49, "max" : 52, "creature": "core:gnollMarauder" }, { "min" : 53, "max" : 56, "creature": "core:walkingDead" }, { "min" : 57, "max" : 60, "creature": "core:centaur" }, - { "min" : 61, "max" : 64, "creature": "core:herberdier" }, + { "min" : 61, "max" : 64, "creature": "core:halberdier" }, { "min" : 65, "max" : 68, "creature": "core:archer" }, { "min" : 69, "max" : 72, "creature": "core:lizardman" }, { "min" : 73, "max" : 76, "creature": "core:zombie" }, - { "min" : 77, "max" : 80, "creature": "core:wolfRider" }, - { "min" : 81, "max" : 84, "creature": "core:centaurCaptian" }, + { "min" : 77, "max" : 80, "creature": "core:goblinWolfRider" }, + { "min" : 81, "max" : 84, "creature": "core:centaurCaptain" }, { "min" : 85, "max" : 88, "creature": "core:dwarf" }, { "min" : 89, "max" : 92, "creature": "core:harpy" }, { "min" : 93, "max" : 96, "creature": "core:lizardWarrior" }, { "min" : 97, "max" : 100, "creature": "core:gog" }, { "min" : 101, "max" : 104, "creature": "core:stoneGargoyle" }, - { "min" : 105, "max" : 108, "creature": "core:masksman" }, + { "min" : 105, "max" : 108, "creature": "core:sharpshooter" }, { "min" : 109, "max" : 112, "creature": "core:orc" }, { "min" : 113, "max" : 116, "creature": "core:obsidianGargoyle" }, - { "min" : 117, "max" : 120, "creature": "core:wofRaider" }, + { "min" : 117, "max" : 120, "creature": "core:hobgoblinWolfRider" }, { "min" : 121, "max" : 124, "creature": "core:battleDwarf" }, - { "min" : 125, "max" : 128, "creature": "core:elf" }, + { "min" : 125, "max" : 128, "creature": "core:woodElf" }, { "min" : 129, "max" : 132, "creature": "core:harpyHag" }, { "min" : 133, "max" : 136, "creature": "core:magog" }, { "min" : 137, "max" : 140, "creature": "core:orcChieftain" }, @@ -51,8 +51,8 @@ { "min" : 191, "max" : 193, "creature": "core:evilEye" }, { "min" : 194, "max" : 196, "creature": "core:cerberus" }, { "min" : 197, "max" : 199, "creature": "core:ironGolem" }, - { "min" : 200, "max" : 202, "creature": "core:orge" }, - { "min" : 203, "max" : 205, "creature": "core:swordman" }, + { "min" : 200, "max" : 202, "creature": "core:ogre" }, + { "min" : 203, "max" : 205, "creature": "core:swordsman" }, { "min" : 206, "max" : 208, "creature": "core:demon" }, { "min" : 209, "max" : 211, "creature": "core:royalGriffin" }, { "min" : 212, "max" : 214, "creature": "core:hornedDemon" }, @@ -65,16 +65,16 @@ { "min" : 233, "max" : 235, "creature": "core:vampire" }, { "min" : 236, "max" : 238, "creature": "core:mage" }, { "min" : 239, "max" : 241, "creature": "core:medusaQueen" }, - { "min" : 242, "max" : 244, "creature": "core:crusider" }, + { "min" : 242, "max" : 244, "creature": "core:crusader" }, { "min" : 245, "max" : 247, "creature": "core:goldGolem" }, - { "min" : 248, "max" : 250, "creature": "core:orgeMage" }, + { "min" : 248, "max" : 250, "creature": "core:ogreMage" }, { "min" : 251, "max" : 253, "creature": "core:archMage" }, { "min" : 254, "max" : 256, "creature": "core:greaterBasilisk" }, { "min" : 257, "max" : 259, "creature": "core:zealot" }, { "min" : 260, "max" : 262, "creature": "core:pitFiend" }, { "min" : 263, "max" : 265, "creature": "core:diamondGolem" }, { "min" : 266, "max" : 268, "creature": "core:vampireLord" }, - { "min" : 269, "max" : 271, "creature": "core:dendroidSolider" }, + { "min" : 269, "max" : 271, "creature": "core:dendroidSoldier" }, { "min" : 272, "max" : 274, "creature": "core:minotaur" }, { "min" : 275, "max" : 277, "creature": "core:lich" }, { "min" : 278, "max" : 280, "creature": "core:genie" }, @@ -86,13 +86,13 @@ { "min" : 296, "max" : 298, "creature": "core:powerLich" }, { "min" : 299, "max" : 301, "creature": "core:thunderbird" }, { "min" : 302, "max" : 304, "creature": "core:pitLord" }, - { "min" : 305, "max" : 307, "creature": "core:cyclops" }, + { "min" : 305, "max" : 307, "creature": "core:cyclop" }, { "min" : 308, "max" : 310, "creature": "core:wyvern" }, - { "min" : 311, "max" : 313, "creature": "core:cyclopsKing" }, + { "min" : 311, "max" : 313, "creature": "core:cyclopKing" }, { "min" : 314, "max" : 316, "creature": "core:wyvernMonarch" }, { "min" : 317, "max" : 319, "creature": "core:manticore" }, { "min" : 320, "max" : 322, "creature": "core:scorpicore" }, - { "min" : 323, "max" : 325, "creature": "core:efreeti" }, + { "min" : 323, "max" : 325, "creature": "core:efreet" }, { "min" : 326, "max" : 328, "creature": "core:unicorn" }, { "min" : 329, "max" : 331, "creature": "core:efreetSultan" }, { "min" : 332, "max" : 334, "creature": "core:cavalier" }, @@ -109,7 +109,7 @@ { "min" : 365, "max" : 367, "creature": "core:ghostDragon" }, { "min" : 368, "max" : 370, "creature": "core:redDragon" }, { "min" : 371, "max" : 373, "creature": "core:greenDragon" }, - { "min" : 374, "max" : 376, "creature": "core:angle" }, + { "min" : 374, "max" : 376, "creature": "core:angel" }, { "min" : 377, "max" : 379, "creature": "core:devil" }, { "min" : 380, "max" : 382, "creature": "core:chaosHydra" }, { "min" : 383, "max" : 385, "creature": "core:ancientBehemoth" }, @@ -117,6 +117,6 @@ { "min" : 389, "max" : 391, "creature": "core:titan" }, { "min" : 392, "max" : 394, "creature": "core:goldDragon" }, { "min" : 395, "max" : 397, "creature": "core:blackDragon" }, - { "min" : 398, "max" : 400, "creature": "core:archAngle " } + { "min" : 398, "max" : 400, "creature": "core:archangel" } ] } \ No newline at end of file From fe0adfa4bd6c6fd2fda63d466ba7eb5213622c90 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Fri, 22 Sep 2023 02:21:23 +0200 Subject: [PATCH 03/24] fix --- Mods/vcmi/config/vcmi/english.json | 633 +++++++++++++++-------------- 1 file changed, 334 insertions(+), 299 deletions(-) diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index 83e81dee6..5622649b3 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -1,339 +1,374 @@ { - "vcmi.adventureMap.monsterThreat.title" : "\n\nDiscussion : ", - "vcmi.adventureMap.monsterThreat.levels.0" : "Sans effort", - "vcmi.adventureMap.monsterThreat.levels.1" : "Très faible", - "vcmi.adventureMap.monsterThreat.levels.2" : "Faible", - "vcmi.adventureMap.monsterThreat.levels.3" : "Un peu plus faible", - "vcmi.adventureMap.monsterThreat.levels.4" : "Égal", - "vcmi.adventureMap.monsterThreat.levels.5" : "Un peu plus fort", - "vcmi.adventureMap.monsterThreat.levels.6" : "Fort", - "vcmi.adventureMap.monsterThreat.levels.7" : "Très fort", - "vcmi.adventureMap.monsterThreat.levels.8" : "Difficile", - "vcmi.adventureMap.monsterThreat.levels.9" : "Surpuissant", - "vcmi.adventureMap.monsterThreat.levels.10" : "Mortel", + "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", + "vcmi.adventureMap.monsterThreat.levels.3" : "A bit weaker", + "vcmi.adventureMap.monsterThreat.levels.4" : "Equal", + "vcmi.adventureMap.monsterThreat.levels.5" : "A bit stronger", + "vcmi.adventureMap.monsterThreat.levels.6" : "Strong", + "vcmi.adventureMap.monsterThreat.levels.7" : "Very Strong", + "vcmi.adventureMap.monsterThreat.levels.8" : "Challenging", + "vcmi.adventureMap.monsterThreat.levels.9" : "Overpowering", + "vcmi.adventureMap.monsterThreat.levels.10" : "Deadly", "vcmi.adventureMap.monsterThreat.levels.11" : "Impossible", - "vcmi.adventureMap.confirmRestartGame" : "Êtes-vous sûr de vouloir redémarrer le jeu ?", - "vcmi.adventureMap.noTownWithMarket" : "Il n'y a pas de marchés disponibles !", - "vcmi.adventureMap.noTownWithTavern" : "Il n'y a pas de villes disponibles avec des tavernes !", - "vcmi.adventureMap.spellUnknownProblem" : "Il y a un problème inconnu avec ce sort ! Pas plus d'informations sont disponibles.", - "vcmi.adventureMap.playerAttacked" : "Le joueur a été attaqué : %s", - "vcmi.adventureMap.moveCostDetails" : "Points de mouvement - Coût : %TURNS tours + %POINTS points, Points restants : %REMAINING", - "vcmi.adventureMap.moveCostDetailsNoTurns" : "Points de mouvement - Coût : %POINTS points, Points restants : %REMAINING", + "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.capitalColors.0" : "Rouge", - "vcmi.capitalColors.1" : "Bleu", - "vcmi.capitalColors.2" : "Ocre", - "vcmi.capitalColors.3" : "Vert", + "vcmi.capitalColors.0" : "Red", + "vcmi.capitalColors.1" : "Blue", + "vcmi.capitalColors.2" : "Tan", + "vcmi.capitalColors.3" : "Green", "vcmi.capitalColors.4" : "Orange", - "vcmi.capitalColors.5" : "Violet", - "vcmi.capitalColors.6" : "Turquoise", - "vcmi.capitalColors.7" : "Rose", + "vcmi.capitalColors.5" : "Purple", + "vcmi.capitalColors.6" : "Teal", + "vcmi.capitalColors.7" : "Pink", + + "vcmi.heroOverview.startingArmy" : "Starting Units", + "vcmi.heroOverview.warMachine" : "War Machines", + "vcmi.heroOverview.secondarySkills" : "Secondary Skills", + "vcmi.heroOverview.spells" : "Spells", + + "vcmi.radialWheel.mergeSameUnit" : "Merge same creatures", + "vcmi.radialWheel.fillSingleUnit" : "Fill with single creatures", + "vcmi.radialWheel.splitSingleUnit" : "Split off single creature", + "vcmi.radialWheel.splitUnitEqually" : "Split creatures equally", + "vcmi.radialWheel.moveUnit" : "Move creatures to another army", + "vcmi.radialWheel.splitUnit" : "Split creature to another slot", - "vcmi.mainMenu.serverConnecting" : "Connexion...", - "vcmi.mainMenu.serverAddressEnter" : "Entrez l'adresse :", - "vcmi.mainMenu.serverClosing" : "Fermeture...", - "vcmi.mainMenu.hostTCP" : "Hôte TCP/IP jeu", - "vcmi.mainMenu.joinTCP" : "Rejoindre TCP/IP jeu", - "vcmi.mainMenu.playerName" : "Joueur", + "vcmi.mainMenu.serverConnecting" : "Connecting...", + "vcmi.mainMenu.serverAddressEnter" : "Enter address:", + "vcmi.mainMenu.serverClosing" : "Closing...", + "vcmi.mainMenu.hostTCP" : "Host TCP/IP game", + "vcmi.mainMenu.joinTCP" : "Join TCP/IP game", + "vcmi.mainMenu.playerName" : "Player", + + "vcmi.lobby.filename" : "Filename", + "vcmi.lobby.creationDate" : "Creation date", - "vcmi.server.errors.existingProcess" : "Un autre processus de serveur VCMI est en cours d'exécution. Veuillez l'arrêter' avant de démarrer un nouveau jeu.", - "vcmi.server.errors.modsIncompatibility" : "Les mods suivants sont nécessaires pour charger le jeu :", - "vcmi.server.confirmReconnect" : "Voulez-vous vous reconnecter à la dernière 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" : "Général", - "vcmi.settingsMainWindow.generalTab.help" : "Passe à l'onglet Options générales, qui contient des paramètres liés au comportement général du client de jeu", - "vcmi.settingsMainWindow.battleTab.hover" : "Bataille", - "vcmi.settingsMainWindow.battleTab.help" : "Passe à l'onglet Options de combat, ce qui permet de configurer le comportement du jeu pendant les batailles", - "vcmi.settingsMainWindow.adventureTab.hover" : "Carte d'aventure", - "vcmi.settingsMainWindow.adventureTab.help" : "Passe à l'onglet Options de carte d'aventure (la carte d'aventure est la section du jeu où les joueurs peuvent contrôler les mouvements de leurs héros)", + "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 the section of the game where players can control the movements of their heroes).", - "vcmi.systemOptions.videoGroup" : "Paramètres Vidéo", - "vcmi.systemOptions.audioGroup" : "Paramètres Audio", - "vcmi.systemOptions.otherGroup" : "Autres Paramètres", // Unused right now - "vcmi.systemOptions.townsGroup" : "Écran de la Ville", + "vcmi.systemOptions.videoGroup" : "Video Settings", + "vcmi.systemOptions.audioGroup" : "Audio Settings", + "vcmi.systemOptions.otherGroup" : "Other Settings", // unused right now + "vcmi.systemOptions.townsGroup" : "Town Screen", - "vcmi.systemOptions.fullscreenBorderless.hover" : "Plein écran (sans bord)", - "vcmi.systemOptions.fullscreenBorderless.help" : "{Borderless Fullscreen}\n\nSi sélectionné, VCMI fonctionnera en mode plein écran sans bordure. Dans ce mode, le jeu utilisera toujours la même résolution de le bureau, ignorant la résolution sélectionnée.", - "vcmi.systemOptions.fullscreenExclusive.hover" : "Plein écran (exclusif)", - "vcmi.systemOptions.fullscreenExclusive.help" : "{Fullscreen}\n\nSi sélectionné, VCMI s'exécutera en mode plein écran exclusif. Dans ce mode, le jeu modifiera la résolution du moniteur en résolution sélectionnée.", - "vcmi.systemOptions.resolutionButton.hover" : "Résolution : %wx%h", - "vcmi.systemOptions.resolutionButton.help" : "{Select Resolution}\n\nChanger la résolution d'écran dans le jeu.", - "vcmi.systemOptions.resolutionMenu.hover" : "Sélectionner la résolution", - "vcmi.systemOptions.resolutionMenu.help" : "Changer la résolution d'écran dans le jeu.", - "vcmi.systemOptions.scalingButton.hover" : "Échelle d'interface : %p%", - "vcmi.systemOptions.scalingButton.help" : "{Interface Scaling}\n\nChanger l'échelle de l'interface dans le jeu", - "vcmi.systemOptions.scalingMenu.hover" : "Sélectionner la mise à l'échelle de l'interface", - "vcmi.systemOptions.scalingMenu.help" : "Changer la mise à l'échelle de l'interface dans le jeu.", - "vcmi.systemOptions.longTouchButton.hover" : "Intervalle de touche long : %d ms", // Translation note: "ms" = "milliseconds" - "vcmi.systemOptions.longTouchButton.help" : "{Long Touch Interval}\n\nAu moment d'utiliser l'écran tactile, les fenêtres contextuelles apparaîtront après avoir touché l'écran pour une durée spécifiée, en millisecondes", - "vcmi.systemOptions.longTouchMenu.hover" : "Sélectionner l'intervalle de touche long", - "vcmi.systemOptions.longTouchMenu.help" : "Changer la durée de l'intervalle de touche long.", - "vcmi.systemOptions.longTouchMenu.entry" : "%d millisecondes", - "vcmi.systemOptions.framerateButton.hover" : "Afficher les FPS", - "vcmi.systemOptions.framerateButton.help" : "{Show FPS}\n\nAfficher/masquer le compteur de Frames Par Seconde dans le coin de la fenêtre du jeu", + "vcmi.systemOptions.fullscreenBorderless.hover" : "Fullscreen (borderless)", + "vcmi.systemOptions.fullscreenBorderless.help" : "{Borderless Fullscreen}\n\nIf selected, VCMI will run in borderless fullscreen mode. In this mode, game will always use same resolution as desktop, ignoring selected resolution.", + "vcmi.systemOptions.fullscreenExclusive.hover" : "Fullscreen (exclusive)", + "vcmi.systemOptions.fullscreenExclusive.help" : "{Fullscreen}\n\nIf selected, VCMI will run in exclusive fullscreen mode. In this mode, game will change resolution of monitor to selected resolution.", + "vcmi.systemOptions.resolutionButton.hover" : "Resolution: %wx%h", + "vcmi.systemOptions.resolutionButton.help" : "{Select Resolution}\n\nChange in-game screen resolution.", + "vcmi.systemOptions.resolutionMenu.hover" : "Select Resolution", + "vcmi.systemOptions.resolutionMenu.help" : "Change in-game screen resolution.", + "vcmi.systemOptions.scalingButton.hover" : "Interface Scaling: %p%", + "vcmi.systemOptions.scalingButton.help" : "{Interface Scaling}\n\nChanges scaling of in-game interface.", + "vcmi.systemOptions.scalingMenu.hover" : "Select Interface Scaling", + "vcmi.systemOptions.scalingMenu.help" : "Change in-game interface scaling.", + "vcmi.systemOptions.longTouchButton.hover" : "Long Touch Interval: %d ms", // Translation note: "ms" = "milliseconds" + "vcmi.systemOptions.longTouchButton.help" : "{Long Touch Interval}\n\nWhen using touchscreen, popup windows will appear after touching screen for specified duration, in milliseconds.", + "vcmi.systemOptions.longTouchMenu.hover" : "Select Long Touch Interval", + "vcmi.systemOptions.longTouchMenu.help" : "Change duration of long touch interval.", + "vcmi.systemOptions.longTouchMenu.entry" : "%d milliseconds", + "vcmi.systemOptions.framerateButton.hover" : "Show FPS", + "vcmi.systemOptions.framerateButton.help" : "{Show FPS}\n\nToggle the visibility of the Frames Per Second counter in the corner of the game window.", + "vcmi.systemOptions.hapticFeedbackButton.hover" : "Haptic feedback", + "vcmi.systemOptions.hapticFeedbackButton.help" : "{Haptic feedback}\n\nToggle the haptic feedback on touch inputs.", - "vcmi.adventureOptions.infoBarPick.hover" : "Afficher les messages dans le panneau d'information", - "vcmi.adventureOptions.infoBarPick.help" : "{Show Messages in Info Panel}\n\nAutant que possible, les messages de jeu de la visite des objets de la carte seront affichés dans le panneau d'informations, au lieu de faire apparaître dans une fenêtre séparée.", - "vcmi.adventureOptions.numericQuantities.hover" : "Dénombrement de créatures", - "vcmi.adventureOptions.numericQuantities.help" : "{Numeric Creature Quantities}\n\nAfficher les estimation de nombre de créatures ennemies au format numérique A-B.", - "vcmi.adventureOptions.forceMovementInfo.hover" : "Toujours afficher le coût de mouvement", - "vcmi.adventureOptions.forceMovementInfo.help" : "{Always Show Movement Cost}\n\nToujours afficher les données des points de mouvement dans les informations sur la barre d'état (au lieu de les voir que pen maintenant la touche alt).", - "vcmi.adventureOptions.showGrid.hover" : "Afficher la grille", - "vcmi.adventureOptions.showGrid.help" : "{Show Grid}\n\nMontrer la superposition de la grille, mettant en évidence les frontières entre les carreaux de carte d'aventure.", - "vcmi.adventureOptions.borderScroll.hover" : "Défilement de bord", - "vcmi.adventureOptions.borderScroll.help" : "{Border Scrolling}\n\nFait défiler la carte aventure lorsque le curseur est sur le bord de la fenêtre. Peut être désactivé en maintenant la touche Ctrl.", + "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 the info panel, instead of popping up in a separate window.", + "vcmi.adventureOptions.numericQuantities.hover" : "Numeric Creature Quantities", + "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\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\nShow the grid overlay, highlighting the borders between adventure map tiles.", + "vcmi.adventureOptions.borderScroll.hover" : "Border Scrolling", + "vcmi.adventureOptions.borderScroll.help" : "{Border Scrolling}\n\nScroll adventure map when cursor is adjacent to window edge. Can be disabled by holding down CTRL key.", + "vcmi.adventureOptions.infoBarCreatureManagement.hover" : "Info Panel Creature Management", + "vcmi.adventureOptions.infoBarCreatureManagement.help" : "{Info Panel Creature Management}\n\nAllows rearranging creatures in info panel instead of cycling between default components.", + "vcmi.adventureOptions.leftButtonDrag.hover" : "Left Click Drag Map", + "vcmi.adventureOptions.leftButtonDrag.help" : "{Left Click Drag Map}\n\nWhen enabled, moving mouse with left button pressed will drag adventure map view.", "vcmi.adventureOptions.mapScrollSpeed1.hover": "", "vcmi.adventureOptions.mapScrollSpeed5.hover": "", "vcmi.adventureOptions.mapScrollSpeed6.hover": "", - "vcmi.adventureOptions.mapScrollSpeed1.help": "Régler la vitesse de défilement de la carte sur très lent", - "vcmi.adventureOptions.mapScrollSpeed5.help": "Régler la vitesse de défilement de la carte très rapide", - "vcmi.adventureOptions.mapScrollSpeed6.help": "Régler la vitesse de défilement de la carte sur instantanément.", + "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": "Afficher l'ordre de fil de tour", - "vcmi.battleOptions.queueSizeNoneButton.hover": "Désactivé", + "vcmi.battleOptions.queueSizeLabel.hover": "Show Turn Order Queue", + "vcmi.battleOptions.queueSizeNoneButton.hover": "OFF", "vcmi.battleOptions.queueSizeAutoButton.hover": "AUTO", - "vcmi.battleOptions.queueSizeSmallButton.hover": "PETITE", - "vcmi.battleOptions.queueSizeBigButton.hover": "GRANDE", - "vcmi.battleOptions.queueSizeNoneButton.help": "Ne pas afficher l'ordre de fil de tour", - "vcmi.battleOptions.queueSizeAutoButton.help": "Ajuster automatiquement la taille de l'ordre de fil de tour en fonction de la résolution du jeu (la PETITE taille est utilisée lorsque vous jouez au jeu sur une hauteur de résolution inférieure à 700 pixels, la GRANDE taille est utilisée au sinon)", - "vcmi.battleOptions.queueSizeSmallButton.help": "Régler la taille de l'ordre de fil de tour sur PETITE", - "vcmi.battleOptions.queueSizeBigButton.help": "Régler la taille de l'ordre de fil de tour à GRANDE (non prise en charge si la hauteur de la résolution du jeu est inférieure à 700 pixels)", + "vcmi.battleOptions.queueSizeSmallButton.hover": "SMALL", + "vcmi.battleOptions.queueSizeBigButton.hover": "BIG", + "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": "Régler la vitesse d'animation très lente", - "vcmi.battleOptions.animationsSpeed5.help": "Régler la vitesse d'animation très rapide", - "vcmi.battleOptions.animationsSpeed6.help": "Régler la vitesse d'animation sur instantané", - "vcmi.battleOptions.movementHighlightOnHover.hover": "Mise en surbrillance du mouvement au survol", - "vcmi.battleOptions.movementHighlightOnHover.help": "{Movement Highlight on Hover}\n\nMettre en surbrillance la plage de mouvement de l'unité lorsque vous la survolez.", - "vcmi.battleOptions.rangeLimitHighlightOnHover.hover": "Afficher les limites de portée pour les tireurs", - "vcmi.battleOptions.rangeLimitHighlightOnHover.help": "{Show range limits for shooters on Hover}\n\nAfficher les limites de portée du tireur lorsque vous le survolez.", - "vcmi.battleOptions.skipBattleIntroMusic.hover": "Ignorer la musique d'introduction", - "vcmi.battleOptions.skipBattleIntroMusic.help": "{Skip Intro Music}\n\nAutoriser les actions pendant la musique d'intro qui joue au début de chaque bataille", - "vcmi.battleWindow.pressKeyToSkipIntro" : "Appuyez sur n'importe quelle touche pour commencer la bataille immédiatement", + "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.movementHighlightOnHover.hover": "Movement Highlight on Hover", + "vcmi.battleOptions.movementHighlightOnHover.help": "{Movement Highlight on Hover}\n\nHighlight unit's movement range when you hover over it.", + "vcmi.battleOptions.rangeLimitHighlightOnHover.hover": "Show range limits for shooters", + "vcmi.battleOptions.rangeLimitHighlightOnHover.help": "{Show range limits for shooters on Hover}\n\nShow shooter's range limits when you hover over it.", + "vcmi.battleOptions.showStickyHeroInfoWindows.hover": "Show heroes statistics windows", + "vcmi.battleOptions.showStickyHeroInfoWindows.help": "{Show heroes statistics windows}\n\nPermanently toggle on heroes statistics windows that show primary stats and spell points.", + "vcmi.battleOptions.skipBattleIntroMusic.hover": "Skip Intro Music", + "vcmi.battleOptions.skipBattleIntroMusic.help": "{Skip Intro Music}\n\nAllow actions during the intro music that plays at the beginning of each battle.", - "vcmi.battleWindow.damageEstimation.melee" : "Attaque %CREATURE (%DAMAGE).", - "vcmi.battleWindow.damageEstimation.meleeKills" : "Attaque %CREATURE (%DAMAGE, %KILLS).", - "vcmi.battleWindow.damageEstimation.ranged" : "Tir sur %CREATURE (%SHOTS, %DAMAGE).", - "vcmi.battleWindow.damageEstimation.rangedKills" : "Tir sur %CREATURE (%SHOTS, %DAMAGE, %KILLS).", - "vcmi.battleWindow.damageEstimation.shots" : "%d tirs restants", - "vcmi.battleWindow.damageEstimation.shots.1" : "%d tir restant", - "vcmi.battleWindow.damageEstimation.damage" : "%d dégâts", - "vcmi.battleWindow.damageEstimation.damage.1" : "%d dégâts", - "vcmi.battleWindow.damageEstimation.kills" : "%d va mourir", - "vcmi.battleWindow.damageEstimation.kills.1" : "%d va mourir", + "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).", + "vcmi.battleWindow.damageEstimation.ranged" : "Shoot %CREATURE (%SHOTS, %DAMAGE).", + "vcmi.battleWindow.damageEstimation.rangedKills" : "Shoot %CREATURE (%SHOTS, %DAMAGE, %KILLS).", + "vcmi.battleWindow.damageEstimation.shots" : "%d shots left", + "vcmi.battleWindow.damageEstimation.shots.1" : "%d shot left", + "vcmi.battleWindow.damageEstimation.damage" : "%d damage", + "vcmi.battleWindow.damageEstimation.damage.1" : "%d damage", + "vcmi.battleWindow.damageEstimation.kills" : "%d will perish", + "vcmi.battleWindow.damageEstimation.kills.1" : "%d will perish", - "vcmi.battleResultsWindow.applyResultsLabel" : "Appliquer le résultat de la bataille", + "vcmi.battleResultsWindow.applyResultsLabel" : "Apply battle result", - "vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover" : "Afficher les créatures disponibles", - "vcmi.otherOptions.availableCreaturesAsDwellingLabel.help" : "{Show Available Creatures}\n\nAfficher le nombre de créatures disponibles au recrutement au lieu de leur croissance dans le résumé de la ville (coin inférieur gauche de l'écran de la ville).", - "vcmi.otherOptions.creatureGrowthAsDwellingLabel.hover" : "Afficher la croissance hebdomadaire des créatures", - "vcmi.otherOptions.creatureGrowthAsDwellingLabel.help" : "{Show Weekly Growth of Creatures}\n\nAfficher la croissance hebdomadaire des créatures au lieu de la quantité disponible dans le résumé de la ville (coin inférieur gauche de l'écran de la ville).", - "vcmi.otherOptions.compactTownCreatureInfo.hover": "Infos compactes sur la créature", - "vcmi.otherOptions.compactTownCreatureInfo.help": "{Compact Creature Info}\n\nAfficher des informations plus petites pour les créatures de la ville dans le résumé de la ville (coin inférieur gauche de l'écran de la ville).", + "vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover" : "Show Available Creatures", + "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\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\nShow smaller information for town creatures in town summary (bottom-left corner of town screen).", - "vcmi.townHall.missingBase" : "Le bâtiment de base %s doit être construit avant", - "vcmi.townHall.noCreaturesToRecruit" : "Il n'y a aucune créature à recruter !", - "vcmi.townHall.greetingManaVortex" : "Alors que vous approchez du %s, votre corps est rempli d'une nouvelle énergie. Vous avez doublé vos points de sort normaux.", - "vcmi.townHall.greetingKnowledge" : "Vous étudiez les glyphes sur le %s et découvrez le fonctionnement de diverses magies (+1 Connaissance).", - "vcmi.townHall.greetingSpellPower" : "Le %s vous apprend de nouvelles façons de concentrer vos pouvoirs magiques (+1 Pouvoir).", - "vcmi.townHall.greetingExperience" : "Une visite au %s vous apprend de nombreuses nouvelles compétences (+1000 Expérience).", - "vcmi.townHall.greetingAttack" : "Un peu de temps passé au %s vous permet d'apprendre des compétences de combat plus efficaces (+1 compétence d'attaque).", - "vcmi.townHall.greetingDefence" : "En passant du temps dans le %s, les guerriers expérimentés qui s'y trouvent vous enseignent des compétences défensives supplémentaires (+1 Défense).", - "vcmi.townHall.hasNotProduced" : "Le %s n'a encore rien produit.", - "vcmi.townHall.hasProduced" : "Le %s a produit %d %s cette semaine.", - "vcmi.townHall.greetingCustomBonus" : "%s vous offre +%d %s%s", - "vcmi.townHall.greetingCustomUntil" : " jusqu'à la prochaine bataille.", - "vcmi.townHall.greetingInTownMagicWell" : "%s a restauré vos points de sort au maximum.", + "vcmi.townHall.missingBase" : "Base building %s must be built first", + "vcmi.townHall.noCreaturesToRecruit" : "There are no creatures to recruit!", + "vcmi.townHall.greetingManaVortex" : "As you near the %s your body is filled with new energy. You have doubled your normal spell points.", + "vcmi.townHall.greetingKnowledge" : "You study the glyphs on the %s and gain insight into the workings of various magics (+1 Knowledge).", + "vcmi.townHall.greetingSpellPower" : "The %s teaches you new ways to focus your magical powers (+1 Power).", + "vcmi.townHall.greetingExperience" : "A visit to the %s teaches you many new skills (+1000 Experience).", + "vcmi.townHall.greetingAttack" : "Some time spent at the %s allows you to learn more effective combat skills (+1 Attack Skill).", + "vcmi.townHall.greetingDefence" : "Spending time in the %s, the experienced warriors therein teach you additional defensive skills (+1 Defense).", + "vcmi.townHall.hasNotProduced" : "The %s has not produced anything yet.", + "vcmi.townHall.hasProduced" : "The %s produced %d %s this week.", + "vcmi.townHall.greetingCustomBonus" : "%s gives you +%d %s%s", + "vcmi.townHall.greetingCustomUntil" : " until next battle.", + "vcmi.townHall.greetingInTownMagicWell" : "%s has restored your spell points to maximum.", - "vcmi.logicalExpressions.anyOf" : "L'un des éléments suivants :", - "vcmi.logicalExpressions.allOf" : "Tous les éléments suivants :", - "vcmi.logicalExpressions.noneOf" : "Aucun des éléments suivants :", + "vcmi.logicalExpressions.anyOf" : "Any of the following:", + "vcmi.logicalExpressions.allOf" : "All of the following:", + "vcmi.logicalExpressions.noneOf" : "None of the following:", - "vcmi.heroWindow.openCommander.hover" : "Ouvrir la fenêtre d'informations du commandant", - "vcmi.heroWindow.openCommander.help" : "Affiche des détails sur le commandant de ce héros", + "vcmi.heroWindow.openCommander.hover" : "Open commander info window", + "vcmi.heroWindow.openCommander.help" : "Shows details about the commander of this hero.", + "vcmi.heroWindow.openBackpack.hover" : "Open artifact backpack window", + "vcmi.heroWindow.openBackpack.help" : "Opens window that allows easier artifact backpack management.", - "vcmi.commanderWindow.artifactMessage" : "Voulez-vous rendre cet artefact au héros ?", + "vcmi.commanderWindow.artifactMessage" : "Do you want to return this artifact to the hero?", - "vcmi.creatureWindow.showBonuses.hover" : "Passer en vue bonus", - "vcmi.creatureWindow.showBonuses.help" : "Afficher tous les bonus actifs du commandant", - "vcmi.creatureWindow.showSkills.hover" : "Passer à la vue des compétences", - "vcmi.creatureWindow.showSkills.help" : "Afficher toutes les compétences apprises du commandant", - "vcmi.creatureWindow.returnArtifact.hover" : "Remettre l'artefact", - "vcmi.creatureWindow.returnArtifact.help" : "Cliquez sur ce bouton pour remettre l'artefact dans le sac à dos du héros", + "vcmi.creatureWindow.showBonuses.hover" : "Switch to bonuses view", + "vcmi.creatureWindow.showBonuses.help" : "Display all active bonuses of the commander.", + "vcmi.creatureWindow.showSkills.hover" : "Switch to skills view", + "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" : "Masquer les quêtes terminées", - "vcmi.questLog.hideComplete.help" : "Masquer toutes les quêtes terminées", + "vcmi.questLog.hideComplete.hover" : "Hide complete quests", + "vcmi.questLog.hideComplete.help" : "Hide all completed quests.", + + "vcmi.randomMapTab.widgets.randomTemplate" : "(Random)", + "vcmi.randomMapTab.widgets.templateLabel" : "Template", + "vcmi.randomMapTab.widgets.teamAlignmentsButton" : "Setup...", + "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.randomMapTab.widgets.templateLabel" : "Modèle", - "vcmi.randomMapTab.widgets.teamAlignmentsButton" : "Configuration...", - "vcmi.randomMapTab.widgets.teamAlignmentsLabel" : "Alignements d'équipe", - "vcmi.randomMapTab.widgets.roadTypesLabel" : "Types de routes", // Custom victory conditions for H3 campaigns and HotA maps - "vcmi.map.victoryCondition.daysPassed.toOthers" : "L'ennemi a réussi à survivre jusqu'à ce jour. La victoire est à eux !", - "vcmi.map.victoryCondition.daysPassed.toSelf" : "Félicitations ! Vous avez réussi à survivre. La victoire est à vous !", - "vcmi.map.victoryCondition.eliminateMonsters.toOthers" : "L'ennemi a vaincu tous les monstres qui sévissent sur cette terre et revendique la victoire !", - "vcmi.map.victoryCondition.eliminateMonsters.toSelf" : "Félicitations ! Vous avez vaincu tous les monstres qui affligent ce pays et vous pouvez revendiquer la victoire !", - "vcmi.map.victoryCondition.collectArtifacts.message" : "Acquérir trois artefacts", - "vcmi.map.victoryCondition.angelicAlliance.toSelf" : "Félicitations ! Tous vos ennemis ont été vaincus et vous avez l'alliance angélique ! La victoire est à vous !", - "vcmi.map.victoryCondition.angelicAlliance.message" : "Vaincre tous les ennemis et créer une alliance angélique", + "vcmi.map.victoryCondition.daysPassed.toOthers" : "The enemy has managed to survive till this day. Victory is theirs!", + "vcmi.map.victoryCondition.daysPassed.toSelf" : "Congratulations! You have managed to survive. Victory is yours!", + "vcmi.map.victoryCondition.eliminateMonsters.toOthers" : "The enemy has defeated all of the monsters plaguing this land and claims victory!", + "vcmi.map.victoryCondition.eliminateMonsters.toSelf" : "Congratulations! You have defeated all of the monsters plaguing this land and can claim victory!", + "vcmi.map.victoryCondition.collectArtifacts.message" : "Acquire Three Artifacts", + "vcmi.map.victoryCondition.angelicAlliance.toSelf" : "Congratulations! All your enemies have been defeated and you have Angelic Alliance! Victory is yours!", + "vcmi.map.victoryCondition.angelicAlliance.message" : "Defeat All Enemies and create Angelic Alliance", // few strings from WoG used by vcmi - "vcmi.stackExperience.description" : "» D é t a i l s d e P i l e d ' E x p é r i e n c e «\n\nType de créature ................ : %s\nRang d'expérience ............... : %s (%i)\nPoints d'expérience ............. : %i\nPoints d'XP au rang suivant ..... : %i\nExpérience maximum par bataille . : %i%% (%i)\nNb de créatures dans la pile .... : %i\nMaximum de nouvelles recrues\n sans perdre le rang actuel ..... : %i\nMultiplicateur d'expérience ..... : %.2f\nMultiplicateur d'amélioration ... : %.2f\nExpérience après le rang 10 ..... : %i\nMaximum de Nouvelles Recrues pour\n rester au Rang 10 si\n l'Expérience Maximum ........... : %i", - "vcmi.stackExperience.rank.0" : "Basique", + "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.0" : "Basic", "vcmi.stackExperience.rank.1" : "Novice", - "vcmi.stackExperience.rank.2" : "Formé", - "vcmi.stackExperience.rank.3" : "Qualifié", - "vcmi.stackExperience.rank.4" : "Éprouvé", - "vcmi.stackExperience.rank.5" : "Vétéran", - "vcmi.stackExperience.rank.6" : "Adepte", + "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" : "Élite", - "vcmi.stackExperience.rank.9" : "Maître", - "vcmi.stackExperience.rank.10" : "As", + "vcmi.stackExperience.rank.8" : "Elite", + "vcmi.stackExperience.rank.9" : "Master", + "vcmi.stackExperience.rank.10" : "Ace", - "core.bonus.ADDITIONAL_ATTACK.name": "Double frappe", - "core.bonus.ADDITIONAL_ATTACK.description": "Attaque deux fois", - "core.bonus.ADDITIONAL_RETALIATION.name": "Représailles supplémentaires", - "core.bonus.ADDITIONAL_RETALIATION.description": "Peut riposter ${val} fois de plus", - "core.bonus.AIR_IMMUNITY.name": "Immunité aérienne", - "core.bonus.AIR_IMMUNITY.description": "Immunisé contre tous les sorts de l'école de magie de l'Air", - "core.bonus.ATTACKS_ALL_ADJACENT.name": "Attaque tout autour", - "core.bonus.ATTACKS_ALL_ADJACENT.description": "Attaque tous les ennemis adjacents", - "core.bonus.BLOCKS_RETALIATION.name": "Pas de représailles", - "core.bonus.BLOCKS_RETALIATION.description": "L'ennemi ne peut pas riposter", - "core.bonus.BLOCKS_RANGED_RETALIATION.name": "Pas de représailles à distance", - "core.bonus.BLOCKS_RANGED_RETALIATION.description": "L'ennemi ne peut pas riposter en utilisant une attaque à distance", - "core.bonus.CATAPULT.name": "Catapulte", - "core.bonus.CATAPULT.description": "Attaque les murs de siège", - "core.bonus.CHANGES_SPELL_COST_FOR_ALLY.name": "Réduire le coût de lancement (${val})", - "core.bonus.CHANGES_SPELL_COST_FOR_ALLY.description": "Réduit le coût d'incantation du héros de ${val}", - "core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.name": "Amortisseur magique (${val})", - "core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.description": "Augmente le coût d'incantation des sorts ennemis de ${val}", - "core.bonus.CHARGE_IMMUNITY.name": "Immunisé contre la charge", - "core.bonus.CHARGE_IMMUNITY.description": "Immunisé contre la charge du cavalier et du champion", - "core.bonus.DARKNESS.name": "Couverture de ténèbres", - "core.bonus.DARKNESS.description": "Crée un linceul de ténèbres avec un rayon de ${val}", - "core.bonus.DEATH_STARE.name": "Regard mortel (${val}%)", - "core.bonus.DEATH_STARE.description": "A ${val}% de chances de tuer une seule créature", - "core.bonus.DEFENSIVE_STANCE.name": "Bonus de défense", - "core.bonus.DEFENSIVE_STANCE.description": "+${val} Défense en défense", + "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.AIR_IMMUNITY.name": "Air immunity", + "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 using a ranged attack", + "core.bonus.CATAPULT.name": "Catapult", + "core.bonus.CATAPULT.description": "Attacks siege walls", + "core.bonus.CHANGES_SPELL_COST_FOR_ALLY.name": "Reduce Casting Cost (${val})", + "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 spellcasting cost of enemy spells by ${val}", + "core.bonus.CHARGE_IMMUNITY.name": "Immune to Charge", + "core.bonus.CHARGE_IMMUNITY.description": "Immune to Cavalier's and Champion's Charge", + "core.bonus.DARKNESS.name": "Darkness cover", + "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": "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": "A ${val} % de chances de tuer des unités supplémentaires après l'attaque", - "core.bonus.DOUBLE_DAMAGE_CHANCE.name": "Coup mortel", - "core.bonus.DOUBLE_DAMAGE_CHANCE.description": "A ${val}% de chances d'infliger des dégâts de base doublés en attaquant", + "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": "Has a ${val}% chance of dealing double base damage when attacking", "core.bonus.DRAGON_NATURE.name": "Dragon", - "core.bonus.DRAGON_NATURE.description": "La créature a une nature de dragon", - "core.bonus.EARTH_IMMUNITY.name": "Immunité terrestre", - "core.bonus.EARTH_IMMUNITY.description": "Immunisé contre tous les sorts de l'école de magie de la Terre", - "core.bonus.ENCHANTER.name": "Enchanteur", - "core.bonus.ENCHANTER.description": "Peut lancer en masse ${subtype.spell} à chaque tour", - "core.bonus.ENCHANTED.name": "Enchanté", - "core.bonus.ENCHANTED.description": "Affecté par ${subtype.spell} permanent", - "core.bonus.ENEMY_DEFENCE_REDUCTION.name": "Ignorer la défense (${val}%)", - "core.bonus.ENEMY_DEFENCE_REDUCTION.description": "Lors de l'attaque, ${val}% de la défense du défenseur est ignorée", - "core.bonus.FIRE_IMMUNITY.name": "Immunité au feu", - "core.bonus.FIRE_IMMUNITY.description": "Immunisé contre tous les sorts de l'école de magie du Feu", - "core.bonus.FIRE_SHIELD.name": "Bouclier de feu (${val}%)", - "core.bonus.FIRE_SHIELD.description": "Reflète une partie des dégâts de mêlée", - "core.bonus.FIRST_STRIKE.name": "Premier coup", - "core.bonus.FIRST_STRIKE.description": "Cette créature riposte avant d'être attaquée", - "core.bonus.FEAR.name": "Peur", - "core.bonus.FEAR.description": "Provoque la peur sur une pile ennemie", - "core.bonus.FEARLESS.name": "Intrépide", - "core.bonus.FEARLESS.description": "Immunité à la peur", - "core.bonus.FLYING.name": "Vol", - "core.bonus.FLYING.description": "Vole en se déplaçant (ignore les obstacles)", - "core.bonus.FREE_SHOOTING.name": "Tirer de près", - "core.bonus.FREE_SHOOTING.description": "Peut utiliser des attaques à distance au corps à corps", - "core.bonus.GARGOYLE.name": "Gargouille", - "core.bonus.GARGOYLE.description": "Ne peut pas être réanimé ou soigné", - "core.bonus.GENERAL_DAMAGE_REDUCTION.name": "Réduit les dégâts (${val}%)", - "core.bonus.GENERAL_DAMAGE_REDUCTION.description": "Réduit les dégâts physiques des attaques à distance ou de mêlée", - "core.bonus.HATE.name": "Déteste ${subtype.creature}", - "core.bonus.HATE.description": "Inflige ${val} % de dégâts supplémentaires à ${subtype.creature}", - "core.bonus.HEALER.name": "Guérisseur", - "core.bonus.HEALER.description": "Soigne les unités alliées", - "core.bonus.HP_REGENERATION.name": "Régénération", - "core.bonus.HP_REGENERATION.description": "Soigne ${val} points de vie à chaque tour", - "core.bonus.JOUSTING.name": "Charge de champion", - "core.bonus.JOUSTING.description": "+${val}% de dégâts pour chaque hexagone parcouru", - "core.bonus.KING.name": "Roi", - "core.bonus.KING.description": "Vulnérable au niveau POURFENDEUR ${val} ou supérieur", - "core.bonus.LEVEL_SPELL_IMMUNITY.name": "Immunité aux sorts 1-${val}", - "core.bonus.LEVEL_SPELL_IMMUNITY.description": "Immunisé aux sorts de niveaux 1-${val}", - "core.bonus.LIMITED_SHOOTING_RANGE.name" : "Portée de tir limitée", - "core.bonus.LIMITED_SHOOTING_RANGE.description" : "Impossible de cibler des unités plus loin que ${val} hexagones", - "core.bonus.LIFE_DRAIN.name": "Durée de vie du drain (${val}%)", - "core.bonus.LIFE_DRAIN.description": "Draine ${val}% des dégâts infligés", - "core.bonus.MANA_CHANNELING.name": "Chaîne magique ${val}%", - "core.bonus.MANA_CHANNELING.description": "Donne à votre héros ${val}% du mana dépensé par l'ennemi", - "core.bonus.MANA_DRAIN.name": "Drain de mana", - "core.bonus.MANA_DRAIN.description": "Draine ${val} mana à chaque tour", - "core.bonus.MAGIC_MIRROR.name": "Miroir magique (${val}%)", - "core.bonus.MAGIC_MIRROR.description": "A ${val} % de chances de rediriger un sort offensif vers une unité ennemie", - "core.bonus.MAGIC_RESISTANCE.name": "Résistance magique (${val}%)", - "core.bonus.MAGIC_RESISTANCE.description": "A ${val}% de chances de résister à un sort ennemi", - "core.bonus.MIND_IMMUNITY.name": "Immunité contre les sorts de l'esprit", - "core.bonus.MIND_IMMUNITY.description": "Immunisé contre les sorts de type Mental", - "core.bonus.NO_DISTANCE_PENALTY.name": "Aucune pénalité de distance", - "core.bonus.NO_DISTANCE_PENALTY.description": "Inflige des dégâts complets à n'importe quelle distance", - "core.bonus.NO_MELEE_PENALTY.name": "Aucune pénalité de mêlée", - "core.bonus.NO_MELEE_PENALTY.description": "La créature n'a pas de pénalité de mêlée", - "core.bonus.NO_MORALE.name": "Moral neutre", - "core.bonus.NO_MORALE.description": "La créature est immunisée contre les effets de moral", - "core.bonus.NO_WALL_PENALTY.name": "Aucune pénalité de mur", - "core.bonus.NO_WALL_PENALTY.description": "Dégâts complets pendant le siège", - "core.bonus.NON_LIVING.name": "Non vivant", - "core.bonus.NON_LIVING.description": "Immunité à de nombreux effets", - "core.bonus.RANDOM_SPELLCASTER.name": "Lanceur de sorts aléatoire", - "core.bonus.RANDOM_SPELLCASTER.description": "Peut lancer un sort aléatoire", - "core.bonus.RANGED_RETALIATION.name": "Représailles à distance", - "core.bonus.RANGED_RETALIATION.description": "Peut effectuer une contre-attaque à distance", - "core.bonus.RECEPTIVE.name": "Réceptif", - "core.bonus.RECEPTIVE.description": "Pas d'immunité aux sorts amicaux", - "core.bonus.REBIRTH.name": "Renaissance (${val}%)", - "core.bonus.REBIRTH.description": "${val}% de la pile augmentera après la mort", - "core.bonus.RETURN_AFTER_STRIKE.name": "Attaque et retour", - "core.bonus.RETURN_AFTER_STRIKE.description": "Revient après une attaque au corps à corps", - "core.bonus.SHOOTER.name": "Distance", - "core.bonus.SHOOTER.description": "La créature peut tirer", - "core.bonus.SHOOTS_ALL_ADJACENT.name": "Tirer tout autour", - "core.bonus.SHOOTS_ALL_ADJACENT.description": "Les attaques à distance de cette créature touchent toutes les cibles dans une petite zone", - "core.bonus.SOUL_STEAL.name": "Vol d'âme", - "core.bonus.SOUL_STEAL.description": "Gagne ${val} nouvelles créatures pour chaque ennemi tué", - "core.bonus.SPELLCASTER.name": "Lanceur de sorts", - "core.bonus.SPELLCASTER.description": "Peut lancer ${subtype.spell}", - "core.bonus.SPELL_AFTER_ATTACK.name": "Lancer après l'attaque", - "core.bonus.SPELL_AFTER_ATTACK.description": "A ${val}% de chances de lancer ${subtype.spell} après avoir attaqué", - "core.bonus.SPELL_BEFORE_ATTACK.name": "Lancer avant l'attaque", - "core.bonus.SPELL_BEFORE_ATTACK.description": "A ${val}% de chances de lancer ${subtype.spell} avant qu'il n'attaque", - "core.bonus.SPELL_DAMAGE_REDUCTION.name": "Résistance aux sorts", - "core.bonus.SPELL_DAMAGE_REDUCTION.description": "Dégâts des sorts réduits de ${val}%.", - "core.bonus.SPELL_IMMUNITY.name": "Immunité aux sorts", - "core.bonus.SPELL_IMMUNITY.description": "Immunisé contre ${subtype.spell}", - "core.bonus.SPELL_LIKE_ATTACK.name": "Attaque semblable à un sort", - "core.bonus.SPELL_LIKE_ATTACK.description": "Attaque avec ${subtype.spell}", - "core.bonus.SPELL_RESISTANCE_AURA.name": "Aura de résistance", - "core.bonus.SPELL_RESISTANCE_AURA.description": "Les piles à proximité obtiennent ${val}% de résistance magique", - "core.bonus.SUMMON_GUARDIANS.name": "Invoquer des gardiens", - "core.bonus.SUMMON_GUARDIANS.description": "Au début de la bataille, invoque ${subtype.creature} (${val}%)", - "core.bonus.SYNERGY_TARGET.name": "Synergique", - "core.bonus.SYNERGY_TARGET.description": "Cette créature est vulnérable à l'effet de synergie", - "core.bonus.TWO_HEX_ATTACK_BREATH.name": "Souffle", - "core.bonus.TWO_HEX_ATTACK_BREATH.description": "Attaque de souffle (portée de 2 hexagones)", - "core.bonus.THREE_HEADED_ATTACK.name": "Attaque à trois têtes", - "core.bonus.THREE_HEADED_ATTACK.description": "Attaque trois unités adjacentes", + "core.bonus.DRAGON_NATURE.description": "Creature has a Dragon Nature", + "core.bonus.EARTH_IMMUNITY.name": "Earth immunity", + "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": "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 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 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": "Flies when moving (ignores obstacles)", + "core.bonus.FREE_SHOOTING.name": "Shoot Close", + "core.bonus.FREE_SHOOTING.description": "Can use ranged attacks at melee range", + "core.bonus.GARGOYLE.name": "Gargoyle", + "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 attacks", + "core.bonus.HATE.name": "Hates ${subtype.creature}", + "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 ${val} hit points every round", + "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" : "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 ${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": "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": "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", + "core.bonus.NO_MORALE.description": "Creature is immune to morale effects", + "core.bonus.NO_WALL_PENALTY.name": "No wall penalty", + "core.bonus.NO_WALL_PENALTY.description": "Full damage during siege", + "core.bonus.NON_LIVING.name": "Non living", + "core.bonus.NON_LIVING.description": "Immunity to many effects", + "core.bonus.RANDOM_SPELLCASTER.name": "Random spellcaster", + "core.bonus.RANDOM_SPELLCASTER.description": "Can cast random spell", + "core.bonus.RANGED_RETALIATION.name": "Ranged retaliation", + "core.bonus.RANGED_RETALIATION.description": "Can perform ranged counterattack", + "core.bonus.RECEPTIVE.name": "Receptive", + "core.bonus.RECEPTIVE.description": "No Immunity to Friendly Spells", + "core.bonus.REBIRTH.name": "Rebirth (${val}%)", + "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.SHOOTER.name": "Ranged", + "core.bonus.SHOOTER.description": "Creature can shoot", + "core.bonus.SHOOTS_ALL_ADJACENT.name": "Shoot all around", + "core.bonus.SHOOTS_ALL_ADJACENT.description": "This creature's ranged attacks strike all targets in a small area", + "core.bonus.SOUL_STEAL.name": "Soul Steal", + "core.bonus.SOUL_STEAL.description": "Gains ${val} new creatures for each enemy killed", + "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": "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": "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 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", + "core.bonus.SPELL_LIKE_ATTACK.description": "Attacks with ${subtype.spell}", + "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 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", + "core.bonus.TWO_HEX_ATTACK_BREATH.description": "Breath Attack (2-hex range)", + "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}% de chances de transformer l'unité attaquée en un type différent", - "core.bonus.UNDEAD.name": "Morts-vivants", - "core.bonus.UNDEAD.description": "La créature est un mort-vivant", - "core.bonus.UNLIMITED_RETALIATIONS.name": "Représailles illimitées", - "core.bonus.UNLIMITED_RETALIATIONS.description": "Peut riposter contre un nombre illimité d'attaques", - "core.bonus.WATER_IMMUNITY.name": "Immunité à l'eau", - "core.bonus.WATER_IMMUNITY.description": "Immunisé contre tous les sorts de l'école de magie de l'Eau", - "core.bonus.WIDE_BREATH.name": "Large souffle", - "core.bonus.WIDE_BREATH.description": "Attaque à souffle large (plusieurs hexagones)" + "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": "Can retaliate against an unlimited number of attacks", + "core.bonus.WATER_IMMUNITY.name": "Water immunity", + "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)" } From a4cb74f0dc0801524b10a70c96a7570cba5fdad8 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Fri, 22 Sep 2023 20:39:20 +0200 Subject: [PATCH 04/24] highscore input window --- client/CPlayerInterface.cpp | 5 +- client/CServerHandler.cpp | 4 + client/mainmenu/CHighScoreScreen.cpp | 111 +++++++++++-- client/mainmenu/CHighScoreScreen.h | 38 ++++- config/highscoreCreatures.json | 236 +++++++++++++-------------- 5 files changed, 262 insertions(+), 132 deletions(-) diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 135bd1f81..904ba2e14 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -37,6 +37,7 @@ #include "gui/WindowHandler.h" #include "mainmenu/CMainMenu.h" +#include "mainmenu/CHighScoreScreen.h" #include "mapView/mapHandler.h" @@ -1690,11 +1691,13 @@ void CPlayerInterface::requestReturningToMainMenu(bool won) else { GH.dispatchMainThread( - []() + [won]() { CSH->endGameplay(); GH.defActionsDef = 63; CMM->menu->switchToTab("main"); + if(won) + GH.windows().createAndPushWindow(); } ); } diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 291fe5b70..a627af56d 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -22,6 +22,7 @@ #include "mainmenu/CMainMenu.h" #include "mainmenu/CPrologEpilogVideo.h" +#include "mainmenu/CHighScoreScreen.h" #ifdef VCMI_ANDROID #include "../lib/CAndroidVMHelper.h" @@ -695,7 +696,10 @@ void CServerHandler::startCampaignScenario(std::shared_ptr cs) if(!ourCampaign->isCampaignFinished()) CMM->openCampaignLobby(ourCampaign); else + { CMM->openCampaignScreen(ourCampaign->campaignSet); + GH.windows().createAndPushWindow(); + } }; if(epilogue.hasPrologEpilog) { diff --git a/client/mainmenu/CHighScoreScreen.cpp b/client/mainmenu/CHighScoreScreen.cpp index e7950ed13..90b06f113 100644 --- a/client/mainmenu/CHighScoreScreen.cpp +++ b/client/mainmenu/CHighScoreScreen.cpp @@ -12,6 +12,8 @@ #include "CHighScoreScreen.h" #include "../gui/CGuiHandler.h" +#include "../gui/WindowHandler.h" +#include "../gui/Shortcut.h" #include "../widgets/TextControls.h" #include "../widgets/Buttons.h" #include "../widgets/Images.h" @@ -19,6 +21,7 @@ #include "../render/Canvas.h" #include "../CGameInfo.h" +#include "../CVideoHandler.h" #include "../../lib/CGeneralTextHandler.h" #include "../../lib/CConfigHandler.h" #include "../../lib/CCreatureHandler.h" @@ -44,13 +47,13 @@ CHighScoreScreen::CHighScoreScreen() Settings entry2 = persistentStorage.write["highscore"]["campaign"][std::to_string(i)]["points"]; entry2->Integer() = std::rand() % 400 * 5; - Settings entry3 = persistentStorage.write["highscore"]["standard"][std::to_string(i)]["player"]; + Settings entry3 = persistentStorage.write["highscore"]["scenario"][std::to_string(i)]["player"]; entry3->String() = "test"; - Settings entry4 = persistentStorage.write["highscore"]["standard"][std::to_string(i)]["land"]; + Settings entry4 = persistentStorage.write["highscore"]["scenario"][std::to_string(i)]["land"]; entry4->String() = "test"; - Settings entry5 = persistentStorage.write["highscore"]["standard"][std::to_string(i)]["days"]; + Settings entry5 = persistentStorage.write["highscore"]["scenario"][std::to_string(i)]["days"]; entry5->Integer() = 123; - Settings entry6 = persistentStorage.write["highscore"]["standard"][std::to_string(i)]["points"]; + Settings entry6 = persistentStorage.write["highscore"]["scenario"][std::to_string(i)]["points"]; entry6->Integer() = std::rand() % 400; } } @@ -71,7 +74,7 @@ void CHighScoreScreen::addHighScores() { OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; - background = std::make_shared(ImagePath::builtin(highscorepage == HighScorePage::STANDARD ? "HISCORE" : "HISCORE2")); + background = std::make_shared(ImagePath::builtin(highscorepage == HighScorePage::SCENARIO ? "HISCORE" : "HISCORE2")); texts.clear(); images.clear(); @@ -83,7 +86,7 @@ void CHighScoreScreen::addHighScores() texts.push_back(std::make_shared(115, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.433"))); texts.push_back(std::make_shared(220, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.434"))); - if(highscorepage == HighScorePage::STANDARD) + if(highscorepage == HighScorePage::SCENARIO) { texts.push_back(std::make_shared(400, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.435"))); texts.push_back(std::make_shared(555, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.436"))); @@ -97,15 +100,15 @@ void CHighScoreScreen::addHighScores() // Content int y = 65; - auto & data = persistentStorage["highscore"][highscorepage == HighScorePage::STANDARD ? "standard" : "campaign"]; + auto & data = persistentStorage["highscore"][highscorepage == HighScorePage::SCENARIO ? "scenario" : "campaign"]; for (int i = 0; i < 11; i++) { auto & curData = data[std::to_string(i)]; - texts.push_back(std::make_shared(115, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, std::to_string(i))); + texts.push_back(std::make_shared(115, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, std::to_string(i+1))); texts.push_back(std::make_shared(220, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, curData["player"].String())); - if(highscorepage == HighScorePage::STANDARD) + if(highscorepage == HighScorePage::SCENARIO) { texts.push_back(std::make_shared(400, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, curData["land"].String())); texts.push_back(std::make_shared(555, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, std::to_string(curData["days"].Integer()))); @@ -117,7 +120,7 @@ void CHighScoreScreen::addHighScores() texts.push_back(std::make_shared(590, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, std::to_string(curData["points"].Integer()))); } - int divide = (highscorepage == HighScorePage::STANDARD) ? 1 : 5; + int divide = (highscorepage == HighScorePage::SCENARIO) ? 1 : 5; for(auto & creature : creatures) { if(curData["points"].Integer() / divide <= creature["max"].Integer() && curData["points"].Integer() / divide >= creature["min"].Integer()) images.push_back(std::make_shared(AnimationPath::builtin("CPRSMALL"), (*CGI->creh)[CreatureID::decode(creature["creature"].String())]->getIconIndex(), 0, 670, y - 15 + i * 50)); @@ -136,7 +139,7 @@ void CHighScoreScreen::buttonCampaginClick() void CHighScoreScreen::buttonStandardClick() { OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; - highscorepage = HighScorePage::STANDARD; + highscorepage = HighScorePage::SCENARIO; addHighScores(); addButtons(); redraw(); @@ -163,3 +166,89 @@ void CHighScoreScreen::buttonExitClick() { close(); } + +CHighScoreInputScreen::CHighScoreInputScreen() + : CWindowObject(BORDERED) +{ + addUsedEvents(LCLICK); + + OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; + pos = center(Rect(0, 0, 800, 600)); + updateShadow(); + + int border = 100; + int textareaW = ((pos.w - 2 * border) / 4); + std::vector t = { "438", "439", "440", "441", "676" }; + for (int i = 0; i < 5; i++) + texts.push_back(std::make_shared(Rect(textareaW * i + border - (textareaW / 2), 450, textareaW, 100), FONT_HIGH_SCORE, ETextAlignment::TOPCENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt." + t[i]))); +} + +void CHighScoreInputScreen::addEntry(std::string text) { + +} + +void CHighScoreInputScreen::show(Canvas & to) +{ + CCS->videoh->update(pos.x, pos.y, to.getInternalSurface(), true, false); + redraw(); + + CIntObject::show(to); +} + +void CHighScoreInputScreen::activate() +{ + CCS->videoh->open(VideoPath::builtin("HSLOOP.SMK")); + CIntObject::activate(); +} + +void CHighScoreInputScreen::deactivate() +{ + CCS->videoh->close(); + CIntObject::deactivate(); +} + +void CHighScoreInputScreen::clickPressed(const Point & cursorPosition) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; + + if(!input) + { + input = std::make_shared( + [&] (std::string text) + { + if(!text.empty()) + { + addEntry(text); + } + close(); + }); + } +} + +CHighScoreInput::CHighScoreInput(std::function readyCB) + : CWindowObject(0), ready(readyCB) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; + + pos = center(Rect(0, 0, 232, 212)); + updateShadow(); + + background = std::make_shared(ImagePath::builtin("HIGHNAME")); + text = std::make_shared(Rect(15, 15, 202, 202), FONT_SMALL, ETextAlignment::TOPCENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.96")); + + buttonOk = std::make_shared(Point(26, 142), AnimationPath::builtin("MUBCHCK.DEF"), CGI->generaltexth->zelp[560], std::bind(&CHighScoreInput::okay, this), EShortcut::GLOBAL_ACCEPT); + buttonCancel = std::make_shared(Point(142, 142), AnimationPath::builtin("MUBCANC.DEF"), CGI->generaltexth->zelp[561], std::bind(&CHighScoreInput::abort, this), EShortcut::GLOBAL_CANCEL); + statusBar = CGStatusBar::create(std::make_shared(background->getSurface(), Rect(7, 186, 218, 18), 7, 186)); + textInput = std::make_shared(Rect(18, 104, 200, 25), FONT_SMALL, 0); + textInput->setText(settings["general"]["playerName"].String()); +} + +void CHighScoreInput::okay() +{ + ready(textInput->getText()); +} + +void CHighScoreInput::abort() +{ + ready(""); +} \ No newline at end of file diff --git a/client/mainmenu/CHighScoreScreen.h b/client/mainmenu/CHighScoreScreen.h index 418b1a43c..6c01c35e9 100644 --- a/client/mainmenu/CHighScoreScreen.h +++ b/client/mainmenu/CHighScoreScreen.h @@ -12,11 +12,13 @@ class CButton; class CLabel; +class CMultiLineLabel; class CAnimImage; +class CTextInput; class CHighScoreScreen : public CWindowObject { - enum HighScorePage { STANDARD, CAMPAIGN }; + enum HighScorePage { SCENARIO, CAMPAIGN }; void addButtons(); void addHighScores(); @@ -26,7 +28,7 @@ class CHighScoreScreen : public CWindowObject void buttonResetClick(); void buttonExitClick(); - HighScorePage highscorepage = HighScorePage::STANDARD; + HighScorePage highscorepage = HighScorePage::SCENARIO; std::shared_ptr background; std::vector> buttons; @@ -35,3 +37,35 @@ class CHighScoreScreen : public CWindowObject public: CHighScoreScreen(); }; + +class CHighScoreInput : public CWindowObject +{ + std::shared_ptr background; + std::shared_ptr text; + std::shared_ptr buttonOk; + std::shared_ptr buttonCancel; + std::shared_ptr statusBar; + std::shared_ptr textInput; + + std::function ready; + + void okay(); + void abort(); +public: + CHighScoreInput(std::function readyCB); +}; + +class CHighScoreInputScreen : public CWindowObject +{ + std::vector> texts; + std::shared_ptr input; +public: + CHighScoreInputScreen(); + + void addEntry(std::string text); + + void show(Canvas & to) override; + void activate() override; + void deactivate() override; + void clickPressed(const Point & cursorPosition) override; +}; \ No newline at end of file diff --git a/config/highscoreCreatures.json b/config/highscoreCreatures.json index 455c20378..9f6dcd2ad 100644 --- a/config/highscoreCreatures.json +++ b/config/highscoreCreatures.json @@ -1,122 +1,122 @@ { "creatures": [ - { "min" : 1, "max" : 4, "creature": "core:imp" }, - { "min" : 5, "max" : 8, "creature": "core:gremlin" }, - { "min" : 9, "max" : 12, "creature": "core:gnoll" }, - { "min" : 13, "max" : 16, "creature": "core:troglodyte" }, - { "min" : 17, "max" : 20, "creature": "core:familiar" }, - { "min" : 21, "max" : 24, "creature": "core:skeleton" }, - { "min" : 25, "max" : 28, "creature": "core:goblin" }, - { "min" : 29, "max" : 32, "creature": "core:masterGremlin" }, - { "min" : 33, "max" : 36, "creature": "core:hobgoblin" }, - { "min" : 37, "max" : 40, "creature": "core:pikeman" }, - { "min" : 41, "max" : 44, "creature": "core:infernalTroglodyte" }, - { "min" : 45, "max" : 48, "creature": "core:skeletonWarrior" }, - { "min" : 49, "max" : 52, "creature": "core:gnollMarauder" }, - { "min" : 53, "max" : 56, "creature": "core:walkingDead" }, - { "min" : 57, "max" : 60, "creature": "core:centaur" }, - { "min" : 61, "max" : 64, "creature": "core:halberdier" }, - { "min" : 65, "max" : 68, "creature": "core:archer" }, - { "min" : 69, "max" : 72, "creature": "core:lizardman" }, - { "min" : 73, "max" : 76, "creature": "core:zombie" }, - { "min" : 77, "max" : 80, "creature": "core:goblinWolfRider" }, - { "min" : 81, "max" : 84, "creature": "core:centaurCaptain" }, - { "min" : 85, "max" : 88, "creature": "core:dwarf" }, - { "min" : 89, "max" : 92, "creature": "core:harpy" }, - { "min" : 93, "max" : 96, "creature": "core:lizardWarrior" }, - { "min" : 97, "max" : 100, "creature": "core:gog" }, - { "min" : 101, "max" : 104, "creature": "core:stoneGargoyle" }, - { "min" : 105, "max" : 108, "creature": "core:sharpshooter" }, - { "min" : 109, "max" : 112, "creature": "core:orc" }, - { "min" : 113, "max" : 116, "creature": "core:obsidianGargoyle" }, - { "min" : 117, "max" : 120, "creature": "core:hobgoblinWolfRider" }, - { "min" : 121, "max" : 124, "creature": "core:battleDwarf" }, - { "min" : 125, "max" : 128, "creature": "core:woodElf" }, - { "min" : 129, "max" : 132, "creature": "core:harpyHag" }, - { "min" : 133, "max" : 136, "creature": "core:magog" }, - { "min" : 137, "max" : 140, "creature": "core:orcChieftain" }, - { "min" : 141, "max" : 144, "creature": "core:stoneGolem" }, - { "min" : 145, "max" : 148, "creature": "core:wight" }, - { "min" : 149, "max" : 152, "creature": "core:serpentFly" }, - { "min" : 153, "max" : 156, "creature": "core:dragonFly" }, - { "min" : 157, "max" : 160, "creature": "core:wraith" }, - { "min" : 161, "max" : 164, "creature": "core:waterElemental" }, - { "min" : 165, "max" : 168, "creature": "core:earthElemental" }, - { "min" : 169, "max" : 172, "creature": "core:grandElf" }, - { "min" : 173, "max" : 176, "creature": "core:beholder" }, - { "min" : 177, "max" : 180, "creature": "core:fireElemental" }, - { "min" : 181, "max" : 184, "creature": "core:griffin" }, - { "min" : 185, "max" : 187, "creature": "core:airElemental" }, - { "min" : 188, "max" : 190, "creature": "core:hellHound" }, - { "min" : 191, "max" : 193, "creature": "core:evilEye" }, - { "min" : 194, "max" : 196, "creature": "core:cerberus" }, - { "min" : 197, "max" : 199, "creature": "core:ironGolem" }, - { "min" : 200, "max" : 202, "creature": "core:ogre" }, - { "min" : 203, "max" : 205, "creature": "core:swordsman" }, - { "min" : 206, "max" : 208, "creature": "core:demon" }, - { "min" : 209, "max" : 211, "creature": "core:royalGriffin" }, - { "min" : 212, "max" : 214, "creature": "core:hornedDemon" }, - { "min" : 215, "max" : 217, "creature": "core:monk" }, - { "min" : 218, "max" : 220, "creature": "core:dendroidGuard" }, - { "min" : 221, "max" : 223, "creature": "core:medusa" }, - { "min" : 224, "max" : 226, "creature": "core:pegasus" }, - { "min" : 227, "max" : 229, "creature": "core:silverPegasus" }, - { "min" : 230, "max" : 232, "creature": "core:basilisk" }, - { "min" : 233, "max" : 235, "creature": "core:vampire" }, - { "min" : 236, "max" : 238, "creature": "core:mage" }, - { "min" : 239, "max" : 241, "creature": "core:medusaQueen" }, - { "min" : 242, "max" : 244, "creature": "core:crusader" }, - { "min" : 245, "max" : 247, "creature": "core:goldGolem" }, - { "min" : 248, "max" : 250, "creature": "core:ogreMage" }, - { "min" : 251, "max" : 253, "creature": "core:archMage" }, - { "min" : 254, "max" : 256, "creature": "core:greaterBasilisk" }, - { "min" : 257, "max" : 259, "creature": "core:zealot" }, - { "min" : 260, "max" : 262, "creature": "core:pitFiend" }, - { "min" : 263, "max" : 265, "creature": "core:diamondGolem" }, - { "min" : 266, "max" : 268, "creature": "core:vampireLord" }, - { "min" : 269, "max" : 271, "creature": "core:dendroidSoldier" }, - { "min" : 272, "max" : 274, "creature": "core:minotaur" }, - { "min" : 275, "max" : 277, "creature": "core:lich" }, - { "min" : 278, "max" : 280, "creature": "core:genie" }, - { "min" : 281, "max" : 283, "creature": "core:gorgon" }, - { "min" : 284, "max" : 286, "creature": "core:masterGenie" }, - { "min" : 287, "max" : 289, "creature": "core:roc" }, - { "min" : 290, "max" : 292, "creature": "core:mightyGorgon" }, - { "min" : 293, "max" : 295, "creature": "core:minotaurKing" }, - { "min" : 296, "max" : 298, "creature": "core:powerLich" }, - { "min" : 299, "max" : 301, "creature": "core:thunderbird" }, - { "min" : 302, "max" : 304, "creature": "core:pitLord" }, - { "min" : 305, "max" : 307, "creature": "core:cyclop" }, - { "min" : 308, "max" : 310, "creature": "core:wyvern" }, - { "min" : 311, "max" : 313, "creature": "core:cyclopKing" }, - { "min" : 314, "max" : 316, "creature": "core:wyvernMonarch" }, - { "min" : 317, "max" : 319, "creature": "core:manticore" }, - { "min" : 320, "max" : 322, "creature": "core:scorpicore" }, - { "min" : 323, "max" : 325, "creature": "core:efreet" }, - { "min" : 326, "max" : 328, "creature": "core:unicorn" }, - { "min" : 329, "max" : 331, "creature": "core:efreetSultan" }, - { "min" : 332, "max" : 334, "creature": "core:cavalier" }, - { "min" : 335, "max" : 337, "creature": "core:naga" }, - { "min" : 338, "max" : 340, "creature": "core:warUnicorn" }, - { "min" : 341, "max" : 343, "creature": "core:blackKnight" }, - { "min" : 344, "max" : 346, "creature": "core:champion" }, - { "min" : 347, "max" : 349, "creature": "core:dreadKnight" }, - { "min" : 350, "max" : 352, "creature": "core:nagaQueen" }, - { "min" : 353, "max" : 355, "creature": "core:behemoth" }, - { "min" : 356, "max" : 358, "creature": "core:boneDragon" }, - { "min" : 359, "max" : 361, "creature": "core:giant" }, - { "min" : 362, "max" : 364, "creature": "core:hydra" }, - { "min" : 365, "max" : 367, "creature": "core:ghostDragon" }, - { "min" : 368, "max" : 370, "creature": "core:redDragon" }, - { "min" : 371, "max" : 373, "creature": "core:greenDragon" }, - { "min" : 374, "max" : 376, "creature": "core:angel" }, - { "min" : 377, "max" : 379, "creature": "core:devil" }, - { "min" : 380, "max" : 382, "creature": "core:chaosHydra" }, - { "min" : 383, "max" : 385, "creature": "core:ancientBehemoth" }, - { "min" : 386, "max" : 388, "creature": "core:archDevil" }, - { "min" : 389, "max" : 391, "creature": "core:titan" }, - { "min" : 392, "max" : 394, "creature": "core:goldDragon" }, - { "min" : 395, "max" : 397, "creature": "core:blackDragon" }, - { "min" : 398, "max" : 400, "creature": "core:archangel" } + { "min" : 1, "max" : 4, "creature": "imp" }, + { "min" : 5, "max" : 8, "creature": "gremlin" }, + { "min" : 9, "max" : 12, "creature": "gnoll" }, + { "min" : 13, "max" : 16, "creature": "troglodyte" }, + { "min" : 17, "max" : 20, "creature": "familiar" }, + { "min" : 21, "max" : 24, "creature": "skeleton" }, + { "min" : 25, "max" : 28, "creature": "goblin" }, + { "min" : 29, "max" : 32, "creature": "masterGremlin" }, + { "min" : 33, "max" : 36, "creature": "hobgoblin" }, + { "min" : 37, "max" : 40, "creature": "pikeman" }, + { "min" : 41, "max" : 44, "creature": "infernalTroglodyte" }, + { "min" : 45, "max" : 48, "creature": "skeletonWarrior" }, + { "min" : 49, "max" : 52, "creature": "gnollMarauder" }, + { "min" : 53, "max" : 56, "creature": "walkingDead" }, + { "min" : 57, "max" : 60, "creature": "centaur" }, + { "min" : 61, "max" : 64, "creature": "halberdier" }, + { "min" : 65, "max" : 68, "creature": "archer" }, + { "min" : 69, "max" : 72, "creature": "lizardman" }, + { "min" : 73, "max" : 76, "creature": "zombie" }, + { "min" : 77, "max" : 80, "creature": "goblinWolfRider" }, + { "min" : 81, "max" : 84, "creature": "centaurCaptain" }, + { "min" : 85, "max" : 88, "creature": "dwarf" }, + { "min" : 89, "max" : 92, "creature": "harpy" }, + { "min" : 93, "max" : 96, "creature": "lizardWarrior" }, + { "min" : 97, "max" : 100, "creature": "gog" }, + { "min" : 101, "max" : 104, "creature": "stoneGargoyle" }, + { "min" : 105, "max" : 108, "creature": "sharpshooter" }, + { "min" : 109, "max" : 112, "creature": "orc" }, + { "min" : 113, "max" : 116, "creature": "obsidianGargoyle" }, + { "min" : 117, "max" : 120, "creature": "hobgoblinWolfRider" }, + { "min" : 121, "max" : 124, "creature": "battleDwarf" }, + { "min" : 125, "max" : 128, "creature": "woodElf" }, + { "min" : 129, "max" : 132, "creature": "harpyHag" }, + { "min" : 133, "max" : 136, "creature": "magog" }, + { "min" : 137, "max" : 140, "creature": "orcChieftain" }, + { "min" : 141, "max" : 144, "creature": "stoneGolem" }, + { "min" : 145, "max" : 148, "creature": "wight" }, + { "min" : 149, "max" : 152, "creature": "serpentFly" }, + { "min" : 153, "max" : 156, "creature": "dragonFly" }, + { "min" : 157, "max" : 160, "creature": "wraith" }, + { "min" : 161, "max" : 164, "creature": "waterElemental" }, + { "min" : 165, "max" : 168, "creature": "earthElemental" }, + { "min" : 169, "max" : 172, "creature": "grandElf" }, + { "min" : 173, "max" : 176, "creature": "beholder" }, + { "min" : 177, "max" : 180, "creature": "fireElemental" }, + { "min" : 181, "max" : 184, "creature": "griffin" }, + { "min" : 185, "max" : 187, "creature": "airElemental" }, + { "min" : 188, "max" : 190, "creature": "hellHound" }, + { "min" : 191, "max" : 193, "creature": "evilEye" }, + { "min" : 194, "max" : 196, "creature": "cerberus" }, + { "min" : 197, "max" : 199, "creature": "ironGolem" }, + { "min" : 200, "max" : 202, "creature": "ogre" }, + { "min" : 203, "max" : 205, "creature": "swordsman" }, + { "min" : 206, "max" : 208, "creature": "demon" }, + { "min" : 209, "max" : 211, "creature": "royalGriffin" }, + { "min" : 212, "max" : 214, "creature": "hornedDemon" }, + { "min" : 215, "max" : 217, "creature": "monk" }, + { "min" : 218, "max" : 220, "creature": "dendroidGuard" }, + { "min" : 221, "max" : 223, "creature": "medusa" }, + { "min" : 224, "max" : 226, "creature": "pegasus" }, + { "min" : 227, "max" : 229, "creature": "silverPegasus" }, + { "min" : 230, "max" : 232, "creature": "basilisk" }, + { "min" : 233, "max" : 235, "creature": "vampire" }, + { "min" : 236, "max" : 238, "creature": "mage" }, + { "min" : 239, "max" : 241, "creature": "medusaQueen" }, + { "min" : 242, "max" : 244, "creature": "crusader" }, + { "min" : 245, "max" : 247, "creature": "goldGolem" }, + { "min" : 248, "max" : 250, "creature": "ogreMage" }, + { "min" : 251, "max" : 253, "creature": "archMage" }, + { "min" : 254, "max" : 256, "creature": "greaterBasilisk" }, + { "min" : 257, "max" : 259, "creature": "zealot" }, + { "min" : 260, "max" : 262, "creature": "pitFiend" }, + { "min" : 263, "max" : 265, "creature": "diamondGolem" }, + { "min" : 266, "max" : 268, "creature": "vampireLord" }, + { "min" : 269, "max" : 271, "creature": "dendroidSoldier" }, + { "min" : 272, "max" : 274, "creature": "minotaur" }, + { "min" : 275, "max" : 277, "creature": "lich" }, + { "min" : 278, "max" : 280, "creature": "genie" }, + { "min" : 281, "max" : 283, "creature": "gorgon" }, + { "min" : 284, "max" : 286, "creature": "masterGenie" }, + { "min" : 287, "max" : 289, "creature": "roc" }, + { "min" : 290, "max" : 292, "creature": "mightyGorgon" }, + { "min" : 293, "max" : 295, "creature": "minotaurKing" }, + { "min" : 296, "max" : 298, "creature": "powerLich" }, + { "min" : 299, "max" : 301, "creature": "thunderbird" }, + { "min" : 302, "max" : 304, "creature": "pitLord" }, + { "min" : 305, "max" : 307, "creature": "cyclop" }, + { "min" : 308, "max" : 310, "creature": "wyvern" }, + { "min" : 311, "max" : 313, "creature": "cyclopKing" }, + { "min" : 314, "max" : 316, "creature": "wyvernMonarch" }, + { "min" : 317, "max" : 319, "creature": "manticore" }, + { "min" : 320, "max" : 322, "creature": "scorpicore" }, + { "min" : 323, "max" : 325, "creature": "efreet" }, + { "min" : 326, "max" : 328, "creature": "unicorn" }, + { "min" : 329, "max" : 331, "creature": "efreetSultan" }, + { "min" : 332, "max" : 334, "creature": "cavalier" }, + { "min" : 335, "max" : 337, "creature": "naga" }, + { "min" : 338, "max" : 340, "creature": "warUnicorn" }, + { "min" : 341, "max" : 343, "creature": "blackKnight" }, + { "min" : 344, "max" : 346, "creature": "champion" }, + { "min" : 347, "max" : 349, "creature": "dreadKnight" }, + { "min" : 350, "max" : 352, "creature": "nagaQueen" }, + { "min" : 353, "max" : 355, "creature": "behemoth" }, + { "min" : 356, "max" : 358, "creature": "boneDragon" }, + { "min" : 359, "max" : 361, "creature": "giant" }, + { "min" : 362, "max" : 364, "creature": "hydra" }, + { "min" : 365, "max" : 367, "creature": "ghostDragon" }, + { "min" : 368, "max" : 370, "creature": "redDragon" }, + { "min" : 371, "max" : 373, "creature": "greenDragon" }, + { "min" : 374, "max" : 376, "creature": "angel" }, + { "min" : 377, "max" : 379, "creature": "devil" }, + { "min" : 380, "max" : 382, "creature": "chaosHydra" }, + { "min" : 383, "max" : 385, "creature": "ancientBehemoth" }, + { "min" : 386, "max" : 388, "creature": "archDevil" }, + { "min" : 389, "max" : 391, "creature": "titan" }, + { "min" : 392, "max" : 394, "creature": "goldDragon" }, + { "min" : 395, "max" : 397, "creature": "blackDragon" }, + { "min" : 398, "max" : 400, "creature": "archangel" } ] } \ No newline at end of file From 30c6cf3b98bc85281edfb7eb26e25f5f37a7c4eb Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Fri, 22 Sep 2023 21:45:12 +0200 Subject: [PATCH 05/24] video & audio correction; win/lose --- client/CPlayerInterface.cpp | 3 +- client/CServerHandler.cpp | 2 +- client/CVideoHandler.cpp | 4 ++- client/CVideoHandler.h | 4 +-- client/mainmenu/CHighScoreScreen.cpp | 50 ++++++++++++++++++++++------ client/mainmenu/CHighScoreScreen.h | 5 ++- 6 files changed, 51 insertions(+), 17 deletions(-) diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 904ba2e14..7c3f53d35 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -1696,8 +1696,7 @@ void CPlayerInterface::requestReturningToMainMenu(bool won) CSH->endGameplay(); GH.defActionsDef = 63; CMM->menu->switchToTab("main"); - if(won) - GH.windows().createAndPushWindow(); + GH.windows().createAndPushWindow(won); } ); } diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index a627af56d..f134a5acb 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -698,7 +698,7 @@ void CServerHandler::startCampaignScenario(std::shared_ptr cs) else { CMM->openCampaignScreen(ourCampaign->campaignSet); - GH.windows().createAndPushWindow(); + GH.windows().createAndPushWindow(true); } }; if(epilogue.hasPrologEpilog) diff --git a/client/CVideoHandler.cpp b/client/CVideoHandler.cpp index ea1d905bc..954a05455 100644 --- a/client/CVideoHandler.cpp +++ b/client/CVideoHandler.cpp @@ -349,7 +349,7 @@ void CVideoPlayer::redraw( int x, int y, SDL_Surface *dst, bool update ) show(x, y, dst, update); } -void CVideoPlayer::update( int x, int y, SDL_Surface *dst, bool forceRedraw, bool update ) +void CVideoPlayer::update( int x, int y, SDL_Surface *dst, bool forceRedraw, bool update, std::function restart) { if (sws == nullptr) return; @@ -368,6 +368,8 @@ void CVideoPlayer::update( int x, int y, SDL_Surface *dst, bool forceRedraw, boo show(x,y,dst,update); else { + if(restart) + restart(); VideoPath filenameToReopen = fname; // create copy to backup this->fname open(filenameToReopen); nextFrame(); diff --git a/client/CVideoHandler.h b/client/CVideoHandler.h index ec9f60278..11317859b 100644 --- a/client/CVideoHandler.h +++ b/client/CVideoHandler.h @@ -31,7 +31,7 @@ public: class IMainVideoPlayer : public IVideoPlayer { public: - virtual void update(int x, int y, SDL_Surface *dst, bool forceRedraw, bool update = true){} + virtual void update(int x, int y, SDL_Surface *dst, bool forceRedraw, bool update = true, std::function restart = 0){} virtual bool openAndPlayVideo(const VideoPath & name, int x, int y, bool stopOnKey = false, bool scale = false) { return false; @@ -101,7 +101,7 @@ public: void show(int x, int y, SDL_Surface *dst, bool update = true) override; //blit current frame void redraw(int x, int y, SDL_Surface *dst, bool update = true) override; //reblits buffer - void update(int x, int y, SDL_Surface *dst, bool forceRedraw, bool update = true) override; //moves to next frame if appropriate, and blits it or blits only if redraw parameter is set true + void update(int x, int y, SDL_Surface *dst, bool forceRedraw, bool update = true, std::function restart = 0) override; //moves to next frame if appropriate, and blits it or blits only if redraw parameter is set true // Opens video, calls playVideo, closes video; returns playVideo result (if whole video has been played) bool openAndPlayVideo(const VideoPath & name, int x, int y, bool stopOnKey = false, bool scale = false) override; diff --git a/client/mainmenu/CHighScoreScreen.cpp b/client/mainmenu/CHighScoreScreen.cpp index 90b06f113..251d9c11a 100644 --- a/client/mainmenu/CHighScoreScreen.cpp +++ b/client/mainmenu/CHighScoreScreen.cpp @@ -22,6 +22,7 @@ #include "../CGameInfo.h" #include "../CVideoHandler.h" +#include "../CMusicHandler.h" #include "../../lib/CGeneralTextHandler.h" #include "../../lib/CConfigHandler.h" #include "../../lib/CCreatureHandler.h" @@ -167,8 +168,8 @@ void CHighScoreScreen::buttonExitClick() close(); } -CHighScoreInputScreen::CHighScoreInputScreen() - : CWindowObject(BORDERED) +CHighScoreInputScreen::CHighScoreInputScreen(bool won) + : CWindowObject(BORDERED), won(won) { addUsedEvents(LCLICK); @@ -176,11 +177,20 @@ CHighScoreInputScreen::CHighScoreInputScreen() pos = center(Rect(0, 0, 800, 600)); updateShadow(); - int border = 100; - int textareaW = ((pos.w - 2 * border) / 4); - std::vector t = { "438", "439", "440", "441", "676" }; - for (int i = 0; i < 5; i++) - texts.push_back(std::make_shared(Rect(textareaW * i + border - (textareaW / 2), 450, textareaW, 100), FONT_HIGH_SCORE, ETextAlignment::TOPCENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt." + t[i]))); + if(won) + { + int border = 100; + int textareaW = ((pos.w - 2 * border) / 4); + std::vector t = { "438", "439", "440", "441", "676" }; + for (int i = 0; i < 5; i++) + texts.push_back(std::make_shared(Rect(textareaW * i + border - (textareaW / 2), 450, textareaW, 100), FONT_HIGH_SCORE, ETextAlignment::TOPCENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt." + t[i]))); + + CCS->musich->playMusic(AudioPath::builtin("music/Win Scenario"), true, true); + } + else + CCS->musich->playMusic(AudioPath::builtin("music/UltimateLose"), false, true); + + video = won ? "HSANIM.SMK" : "LOSEGAME.SMK"; } void CHighScoreInputScreen::addEntry(std::string text) { @@ -189,7 +199,18 @@ void CHighScoreInputScreen::addEntry(std::string text) { void CHighScoreInputScreen::show(Canvas & to) { - CCS->videoh->update(pos.x, pos.y, to.getInternalSurface(), true, false); + CCS->videoh->update(pos.x, pos.y, to.getInternalSurface(), true, false, + [&]() + { + if(won) + { + CCS->videoh->close(); + video = "HSLOOP.SMK"; + CCS->videoh->open(VideoPath::builtin(video)); + } + else + close(); + }); redraw(); CIntObject::show(to); @@ -197,7 +218,7 @@ void CHighScoreInputScreen::show(Canvas & to) void CHighScoreInputScreen::activate() { - CCS->videoh->open(VideoPath::builtin("HSLOOP.SMK")); + CCS->videoh->open(VideoPath::builtin(video)); CIntObject::activate(); } @@ -211,6 +232,12 @@ void CHighScoreInputScreen::clickPressed(const Point & cursorPosition) { OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; + if(!won) + { + close(); + return; + } + if(!input) { input = std::make_shared( @@ -219,8 +246,11 @@ void CHighScoreInputScreen::clickPressed(const Point & cursorPosition) if(!text.empty()) { addEntry(text); + close(); + GH.windows().createAndPushWindow(); } - close(); + else + close(); }); } } diff --git a/client/mainmenu/CHighScoreScreen.h b/client/mainmenu/CHighScoreScreen.h index 6c01c35e9..bfed549ba 100644 --- a/client/mainmenu/CHighScoreScreen.h +++ b/client/mainmenu/CHighScoreScreen.h @@ -59,8 +59,11 @@ class CHighScoreInputScreen : public CWindowObject { std::vector> texts; std::shared_ptr input; + + std::string video; + bool won; public: - CHighScoreInputScreen(); + CHighScoreInputScreen(bool won); void addEntry(std::string text); From b0e5a11e62e9eb78175924e8d3344455c78609a1 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Fri, 22 Sep 2023 22:09:01 +0200 Subject: [PATCH 06/24] fix win build --- lib/constants/EntityIdentifiers.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/constants/EntityIdentifiers.h b/lib/constants/EntityIdentifiers.h index 1b5932626..8e03b635c 100644 --- a/lib/constants/EntityIdentifiers.h +++ b/lib/constants/EntityIdentifiers.h @@ -248,8 +248,8 @@ public: std::string toString() const; - DLL_LINKAGE static si32 decode(const std::string& identifier); - DLL_LINKAGE static std::string encode(const si32 index); + static si32 decode(const std::string& identifier); + static std::string encode(const si32 index); static std::string entityType(); }; @@ -333,8 +333,8 @@ public: static const FactionID CONFLUX; static const FactionID NEUTRAL; - DLL_LINKAGE static si32 decode(const std::string& identifier); - DLL_LINKAGE static std::string encode(const si32 index); + static si32 decode(const std::string& identifier); + static std::string encode(const si32 index); static std::string entityType(); }; From 1a0f5cf159393e6e72fc441470948248325a8251 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 23 Sep 2023 00:21:36 +0200 Subject: [PATCH 07/24] calc preperation --- client/CPlayerInterface.cpp | 27 ++++++++++++++++++++++++--- client/CServerHandler.cpp | 10 +++++++--- client/CServerHandler.h | 6 +++++- client/mainmenu/CHighScoreScreen.cpp | 7 ++++++- client/mainmenu/CHighScoreScreen.h | 20 +++++++++++++++++++- config/highscoreCreatures.json | 2 +- 6 files changed, 62 insertions(+), 10 deletions(-) diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 7c3f53d35..28f525432 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -66,6 +66,7 @@ #include "../lib/CGeneralTextHandler.h" #include "../lib/CHeroHandler.h" #include "../lib/CPlayerState.h" +#include "../lib/gameState/CGameState.h" #include "../lib/CStack.h" #include "../lib/CStopWatch.h" #include "../lib/CThreadHelper.h" @@ -1686,17 +1687,37 @@ void CPlayerInterface::showShipyardDialogOrProblemPopup(const IShipyard *obj) void CPlayerInterface::requestReturningToMainMenu(bool won) { + HighScoreParameter param; + param.difficulty = cb->getStartInfo()->difficulty; + param.day = cb->getDate(); + param.townAmount = cb->howManyTowns(); + param.usedCheat = cb->getPlayerState(*cb->getPlayerID())->enteredWinningCheatCode; + param.hasGrail = false; + for(const CGHeroInstance * h : cb->getHeroesInfo()) + if(h->hasArt(ArtifactID::GRAIL)) + param.hasGrail = true; + param.allDefeated = true; + for (PlayerColor player(0); player < PlayerColor::PLAYER_LIMIT; ++player) + { + auto ps = cb->getPlayerState(player); + if(ps && player != *cb->getPlayerID()) + if(!ps->checkVanquished()) + param.allDefeated = false; + } + HighScoreCalculation calc; + calc.parameters.push_back(param); + if(won && cb->getStartInfo()->campState) - CSH->startCampaignScenario(cb->getStartInfo()->campState); + CSH->startCampaignScenario(param, cb->getStartInfo()->campState); else { GH.dispatchMainThread( - [won]() + [won, calc]() { CSH->endGameplay(); GH.defActionsDef = 63; CMM->menu->switchToTab("main"); - GH.windows().createAndPushWindow(won); + GH.windows().createAndPushWindow(won, calc); } ); } diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index f134a5acb..597af86ea 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -669,14 +669,18 @@ void CServerHandler::endGameplay(bool closeConnection, bool restart) saveSession->Bool() = false; } -void CServerHandler::startCampaignScenario(std::shared_ptr cs) +void CServerHandler::startCampaignScenario(HighScoreParameter param, std::shared_ptr cs) { std::shared_ptr ourCampaign = cs; if (!cs) + { ourCampaign = si->campState; + calc.parameters.clear(); + } + calc.parameters.push_back(param); - GH.dispatchMainThread([ourCampaign]() + GH.dispatchMainThread([ourCampaign, this]() { CSH->campaignServerRestartLock.set(true); CSH->endGameplay(); @@ -698,7 +702,7 @@ void CServerHandler::startCampaignScenario(std::shared_ptr cs) else { CMM->openCampaignScreen(ourCampaign->campaignSet); - GH.windows().createAndPushWindow(true); + GH.windows().createAndPushWindow(true, calc); } }; if(epilogue.hasPrologEpilog) diff --git a/client/CServerHandler.h b/client/CServerHandler.h index 12a130f83..467092e9f 100644 --- a/client/CServerHandler.h +++ b/client/CServerHandler.h @@ -14,6 +14,8 @@ #include "../lib/StartInfo.h" #include "../lib/CondSh.h" +#include "mainmenu/CHighScoreScreen.h" + VCMI_LIB_NAMESPACE_BEGIN class CConnection; @@ -86,6 +88,8 @@ class CServerHandler : public IServerAPI, public LobbyInfo std::vector myNames; + HighScoreCalculation calc; + void threadHandleConnection(); void threadRunServer(); void onServerFinished(); @@ -159,7 +163,7 @@ public: void startGameplay(VCMI_LIB_WRAP_NAMESPACE(CGameState) * gameState = nullptr); void endGameplay(bool closeConnection = true, bool restart = false); - void startCampaignScenario(std::shared_ptr cs = {}); + void startCampaignScenario(HighScoreParameter param, std::shared_ptr cs = {}); void showServerError(const std::string & txt) const; // TODO: LobbyState must be updated within game so we should always know how many player interfaces our client handle diff --git a/client/mainmenu/CHighScoreScreen.cpp b/client/mainmenu/CHighScoreScreen.cpp index 251d9c11a..e47ed1a41 100644 --- a/client/mainmenu/CHighScoreScreen.cpp +++ b/client/mainmenu/CHighScoreScreen.cpp @@ -28,6 +28,11 @@ #include "../../lib/CCreatureHandler.h" #include "../../lib/constants/EntityIdentifiers.h" +int HighScoreCalculation::calculate() +{ + return 0; +} + CHighScoreScreen::CHighScoreScreen() : CWindowObject(BORDERED) { @@ -168,7 +173,7 @@ void CHighScoreScreen::buttonExitClick() close(); } -CHighScoreInputScreen::CHighScoreInputScreen(bool won) +CHighScoreInputScreen::CHighScoreInputScreen(bool won, HighScoreCalculation calc) : CWindowObject(BORDERED), won(won) { addUsedEvents(LCLICK); diff --git a/client/mainmenu/CHighScoreScreen.h b/client/mainmenu/CHighScoreScreen.h index bfed549ba..d7222af07 100644 --- a/client/mainmenu/CHighScoreScreen.h +++ b/client/mainmenu/CHighScoreScreen.h @@ -16,6 +16,24 @@ class CMultiLineLabel; class CAnimImage; class CTextInput; +class HighScoreParameter +{ +public: + int difficulty; + int day; + int townAmount; + bool usedCheat; + bool hasGrail; + bool allDefeated; +}; + +class HighScoreCalculation +{ +public: + std::vector parameters = std::vector(); + int calculate(); +}; + class CHighScoreScreen : public CWindowObject { enum HighScorePage { SCENARIO, CAMPAIGN }; @@ -63,7 +81,7 @@ class CHighScoreInputScreen : public CWindowObject std::string video; bool won; public: - CHighScoreInputScreen(bool won); + CHighScoreInputScreen(bool won, HighScoreCalculation calc); void addEntry(std::string text); diff --git a/config/highscoreCreatures.json b/config/highscoreCreatures.json index 9f6dcd2ad..b0feda288 100644 --- a/config/highscoreCreatures.json +++ b/config/highscoreCreatures.json @@ -117,6 +117,6 @@ { "min" : 389, "max" : 391, "creature": "titan" }, { "min" : 392, "max" : 394, "creature": "goldDragon" }, { "min" : 395, "max" : 397, "creature": "blackDragon" }, - { "min" : 398, "max" : 400, "creature": "archangel" } + { "min" : 398, "max" : 500, "creature": "archangel" } ] } \ No newline at end of file From 901a33bf1dde73ae6399339a46589664e8975623 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 23 Sep 2023 01:03:15 +0200 Subject: [PATCH 08/24] calculation --- client/mainmenu/CHighScoreScreen.cpp | 45 ++++++++++++++++++++++++++-- client/mainmenu/CHighScoreScreen.h | 3 +- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/client/mainmenu/CHighScoreScreen.cpp b/client/mainmenu/CHighScoreScreen.cpp index e47ed1a41..f9651b6b2 100644 --- a/client/mainmenu/CHighScoreScreen.cpp +++ b/client/mainmenu/CHighScoreScreen.cpp @@ -28,9 +28,44 @@ #include "../../lib/CCreatureHandler.h" #include "../../lib/constants/EntityIdentifiers.h" -int HighScoreCalculation::calculate() +auto HighScoreCalculation::calculate() { - return 0; + struct result + { + int basic; + int total; + int sumDays; + }; + + std::vector scoresBasic; + std::vector scoresTotal; + double sumBasic = 0; + double sumTotal = 0; + int sumDays = 0; + for(auto & param : parameters) + { + double tmp = 200 - (param.day + 10) / (param.townAmount + 5) + (param.allDefeated ? 25 : 0) + (param.hasGrail ? 25 : 0); + scoresBasic.push_back(static_cast(tmp)); + sumBasic += tmp; + if(param.difficulty == 0) + tmp *= 0.8; + if(param.difficulty == 1) + tmp *= 1.0; + if(param.difficulty == 2) + tmp *= 1.3; + if(param.difficulty == 3) + tmp *= 1.6; + if(param.difficulty == 4) + tmp *= 2.0; + scoresTotal.push_back(static_cast(tmp)); + sumTotal += tmp; + sumDays += param.day; + } + + if(scoresBasic.size() == 1) + return result { scoresBasic[0], scoresTotal[0], sumDays }; + + return result { static_cast((sumBasic / parameters.size()) * 5.0), static_cast((sumTotal / parameters.size()) * 5.0), sumDays }; } CHighScoreScreen::CHighScoreScreen() @@ -174,7 +209,7 @@ void CHighScoreScreen::buttonExitClick() } CHighScoreInputScreen::CHighScoreInputScreen(bool won, HighScoreCalculation calc) - : CWindowObject(BORDERED), won(won) + : CWindowObject(BORDERED), won(won), calc(calc) { addUsedEvents(LCLICK); @@ -189,6 +224,10 @@ CHighScoreInputScreen::CHighScoreInputScreen(bool won, HighScoreCalculation calc std::vector t = { "438", "439", "440", "441", "676" }; for (int i = 0; i < 5; i++) texts.push_back(std::make_shared(Rect(textareaW * i + border - (textareaW / 2), 450, textareaW, 100), FONT_HIGH_SCORE, ETextAlignment::TOPCENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt." + t[i]))); + + t = { std::to_string(calc.calculate().sumDays), std::to_string(calc.calculate().basic), CGI->generaltexth->translate("core.arraytxt." + std::to_string((142 + calc.parameters[0].difficulty))), std::to_string(calc.calculate().total), "TODO" }; + for (int i = 0; i < 5; i++) + texts.push_back(std::make_shared(Rect(textareaW * i + border - (textareaW / 2), 530, textareaW, 100), FONT_HIGH_SCORE, ETextAlignment::TOPCENTER, Colors::WHITE, t[i])); CCS->musich->playMusic(AudioPath::builtin("music/Win Scenario"), true, true); } diff --git a/client/mainmenu/CHighScoreScreen.h b/client/mainmenu/CHighScoreScreen.h index d7222af07..c5ca6fe57 100644 --- a/client/mainmenu/CHighScoreScreen.h +++ b/client/mainmenu/CHighScoreScreen.h @@ -31,7 +31,7 @@ class HighScoreCalculation { public: std::vector parameters = std::vector(); - int calculate(); + auto calculate(); }; class CHighScoreScreen : public CWindowObject @@ -80,6 +80,7 @@ class CHighScoreInputScreen : public CWindowObject std::string video; bool won; + HighScoreCalculation calc; public: CHighScoreInputScreen(bool won, HighScoreCalculation calc); From d3f007453d32bf143c89134966d6bc1d10f006e4 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 23 Sep 2023 01:36:01 +0200 Subject: [PATCH 09/24] win screen ready --- client/CPlayerInterface.cpp | 2 +- client/CServerHandler.cpp | 1 + client/mainmenu/CHighScoreScreen.cpp | 33 ++++++++++++++++++---------- client/mainmenu/CHighScoreScreen.h | 3 +++ 4 files changed, 27 insertions(+), 12 deletions(-) diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 28f525432..cc85e6144 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -66,7 +66,6 @@ #include "../lib/CGeneralTextHandler.h" #include "../lib/CHeroHandler.h" #include "../lib/CPlayerState.h" -#include "../lib/gameState/CGameState.h" #include "../lib/CStack.h" #include "../lib/CStopWatch.h" #include "../lib/CThreadHelper.h" @@ -1706,6 +1705,7 @@ void CPlayerInterface::requestReturningToMainMenu(bool won) } HighScoreCalculation calc; calc.parameters.push_back(param); + calc.isCampaign = false; if(won && cb->getStartInfo()->campState) CSH->startCampaignScenario(param, cb->getStartInfo()->campState); diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 597af86ea..9c105a9ed 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -676,6 +676,7 @@ void CServerHandler::startCampaignScenario(HighScoreParameter param, std::shared if (!cs) { ourCampaign = si->campState; + calc.isCampaign = true; calc.parameters.clear(); } calc.parameters.push_back(param); diff --git a/client/mainmenu/CHighScoreScreen.cpp b/client/mainmenu/CHighScoreScreen.cpp index f9651b6b2..86925bdf8 100644 --- a/client/mainmenu/CHighScoreScreen.cpp +++ b/client/mainmenu/CHighScoreScreen.cpp @@ -35,6 +35,7 @@ auto HighScoreCalculation::calculate() int basic; int total; int sumDays; + bool cheater; }; std::vector scoresBasic; @@ -42,6 +43,7 @@ auto HighScoreCalculation::calculate() double sumBasic = 0; double sumTotal = 0; int sumDays = 0; + bool cheater = false; for(auto & param : parameters) { double tmp = 200 - (param.day + 10) / (param.townAmount + 5) + (param.allDefeated ? 25 : 0) + (param.hasGrail ? 25 : 0); @@ -60,12 +62,27 @@ auto HighScoreCalculation::calculate() scoresTotal.push_back(static_cast(tmp)); sumTotal += tmp; sumDays += param.day; + if(param.usedCheat) + cheater = true; } if(scoresBasic.size() == 1) - return result { scoresBasic[0], scoresTotal[0], sumDays }; + return result { scoresBasic[0], scoresTotal[0], sumDays , cheater}; - return result { static_cast((sumBasic / parameters.size()) * 5.0), static_cast((sumTotal / parameters.size()) * 5.0), sumDays }; + return result { static_cast((sumBasic / parameters.size()) * 5.0), static_cast((sumTotal / parameters.size()) * 5.0), sumDays, cheater }; +} + +CreatureID HighScoreCalculation::getCreatureForPoints(int points, bool campaign) +{ + static const JsonNode configCreatures(JsonPath::builtin("CONFIG/highscoreCreatures.json")); + auto creatures = configCreatures["creatures"].Vector(); + int divide = campaign ? 5 : 1; + + for(auto & creature : creatures) + if(points / divide <= creature["max"].Integer() && points / divide >= creature["min"].Integer()) + return CreatureID::decode(creature["creature"].String()); + + return -1; } CHighScoreScreen::CHighScoreScreen() @@ -120,9 +137,6 @@ void CHighScoreScreen::addHighScores() texts.clear(); images.clear(); - static const JsonNode configCreatures(JsonPath::builtin("CONFIG/highscoreCreatures.json")); - auto creatures = configCreatures["creatures"].Vector(); - // Header texts.push_back(std::make_shared(115, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.433"))); texts.push_back(std::make_shared(220, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.434"))); @@ -161,11 +175,7 @@ void CHighScoreScreen::addHighScores() texts.push_back(std::make_shared(590, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, std::to_string(curData["points"].Integer()))); } - int divide = (highscorepage == HighScorePage::SCENARIO) ? 1 : 5; - for(auto & creature : creatures) { - if(curData["points"].Integer() / divide <= creature["max"].Integer() && curData["points"].Integer() / divide >= creature["min"].Integer()) - images.push_back(std::make_shared(AnimationPath::builtin("CPRSMALL"), (*CGI->creh)[CreatureID::decode(creature["creature"].String())]->getIconIndex(), 0, 670, y - 15 + i * 50)); - } + images.push_back(std::make_shared(AnimationPath::builtin("CPRSMALL"), (*CGI->creh)[HighScoreCalculation::getCreatureForPoints(curData["points"].Integer(), highscorepage == HighScorePage::CAMPAIGN)]->getIconIndex(), 0, 670, y - 15 + i * 50)); } } @@ -225,7 +235,8 @@ CHighScoreInputScreen::CHighScoreInputScreen(bool won, HighScoreCalculation calc for (int i = 0; i < 5; i++) texts.push_back(std::make_shared(Rect(textareaW * i + border - (textareaW / 2), 450, textareaW, 100), FONT_HIGH_SCORE, ETextAlignment::TOPCENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt." + t[i]))); - t = { std::to_string(calc.calculate().sumDays), std::to_string(calc.calculate().basic), CGI->generaltexth->translate("core.arraytxt." + std::to_string((142 + calc.parameters[0].difficulty))), std::to_string(calc.calculate().total), "TODO" }; + std::string creatureName = (calc.calculate().cheater) ? CGI->generaltexth->translate("core.genrltxt.260") : (*CGI->creh)[HighScoreCalculation::getCreatureForPoints(calc.calculate().total, calc.isCampaign)]->getNameSingularTranslated(); + t = { std::to_string(calc.calculate().sumDays), std::to_string(calc.calculate().basic), CGI->generaltexth->translate("core.arraytxt." + std::to_string((142 + calc.parameters[0].difficulty))), std::to_string(calc.calculate().total), creatureName }; for (int i = 0; i < 5; i++) texts.push_back(std::make_shared(Rect(textareaW * i + border - (textareaW / 2), 530, textareaW, 100), FONT_HIGH_SCORE, ETextAlignment::TOPCENTER, Colors::WHITE, t[i])); diff --git a/client/mainmenu/CHighScoreScreen.h b/client/mainmenu/CHighScoreScreen.h index c5ca6fe57..6be872569 100644 --- a/client/mainmenu/CHighScoreScreen.h +++ b/client/mainmenu/CHighScoreScreen.h @@ -31,7 +31,10 @@ class HighScoreCalculation { public: std::vector parameters = std::vector(); + bool isCampaign = false; + auto calculate(); + static CreatureID getCreatureForPoints(int points, bool campaign); }; class CHighScoreScreen : public CWindowObject From 049f90159d7e238ab06f6d8f6695565d9d946117 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 23 Sep 2023 01:53:45 +0200 Subject: [PATCH 10/24] highlight preparation --- client/mainmenu/CHighScoreScreen.cpp | 21 ++++++++++++--------- client/mainmenu/CHighScoreScreen.h | 4 +++- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/client/mainmenu/CHighScoreScreen.cpp b/client/mainmenu/CHighScoreScreen.cpp index 86925bdf8..5ca8f612c 100644 --- a/client/mainmenu/CHighScoreScreen.cpp +++ b/client/mainmenu/CHighScoreScreen.cpp @@ -85,8 +85,8 @@ CreatureID HighScoreCalculation::getCreatureForPoints(int points, bool campaign) return -1; } -CHighScoreScreen::CHighScoreScreen() - : CWindowObject(BORDERED) +CHighScoreScreen::CHighScoreScreen(int highlighted) + : CWindowObject(BORDERED), highlighted(highlighted) { OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; pos = center(Rect(0, 0, 800, 600)); @@ -95,6 +95,8 @@ CHighScoreScreen::CHighScoreScreen() addHighScores(); addButtons(); + // TODO write also datetime for RMB menu + // TODO: remove; only for testing for (int i = 0; i < 11; i++) { @@ -159,20 +161,21 @@ void CHighScoreScreen::addHighScores() for (int i = 0; i < 11; i++) { auto & curData = data[std::to_string(i)]; + ColorRGBA color = (i == highlighted) ? Colors::YELLOW : Colors::WHITE; - texts.push_back(std::make_shared(115, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, std::to_string(i+1))); - texts.push_back(std::make_shared(220, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, curData["player"].String())); + texts.push_back(std::make_shared(115, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, std::to_string(i+1))); + texts.push_back(std::make_shared(220, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, curData["player"].String())); if(highscorepage == HighScorePage::SCENARIO) { - texts.push_back(std::make_shared(400, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, curData["land"].String())); - texts.push_back(std::make_shared(555, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, std::to_string(curData["days"].Integer()))); - texts.push_back(std::make_shared(625, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, std::to_string(curData["points"].Integer()))); + texts.push_back(std::make_shared(400, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, curData["land"].String())); + texts.push_back(std::make_shared(555, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, std::to_string(curData["days"].Integer()))); + texts.push_back(std::make_shared(625, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, std::to_string(curData["points"].Integer()))); } else { - texts.push_back(std::make_shared(410, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, curData["campaign"].String())); - texts.push_back(std::make_shared(590, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, std::to_string(curData["points"].Integer()))); + texts.push_back(std::make_shared(410, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, curData["campaign"].String())); + texts.push_back(std::make_shared(590, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, std::to_string(curData["points"].Integer()))); } images.push_back(std::make_shared(AnimationPath::builtin("CPRSMALL"), (*CGI->creh)[HighScoreCalculation::getCreatureForPoints(curData["points"].Integer(), highscorepage == HighScorePage::CAMPAIGN)]->getIconIndex(), 0, 670, y - 15 + i * 50)); diff --git a/client/mainmenu/CHighScoreScreen.h b/client/mainmenu/CHighScoreScreen.h index 6be872569..2062647f6 100644 --- a/client/mainmenu/CHighScoreScreen.h +++ b/client/mainmenu/CHighScoreScreen.h @@ -55,8 +55,10 @@ class CHighScoreScreen : public CWindowObject std::vector> buttons; std::vector> texts; std::vector> images; + + int highlighted; public: - CHighScoreScreen(); + CHighScoreScreen(int highlighted = -1); }; class CHighScoreInput : public CWindowObject From e18a4a09a986ba97e3148dddfdd9b2f8acfe7782 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 23 Sep 2023 02:14:45 +0200 Subject: [PATCH 11/24] forward declaration --- client/CServerHandler.cpp | 8 ++++---- client/CServerHandler.h | 7 ++++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 9c105a9ed..6e18ebcb6 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -676,10 +676,10 @@ void CServerHandler::startCampaignScenario(HighScoreParameter param, std::shared if (!cs) { ourCampaign = si->campState; - calc.isCampaign = true; - calc.parameters.clear(); + calc->isCampaign = true; + calc->parameters.clear(); } - calc.parameters.push_back(param); + calc->parameters.push_back(param); GH.dispatchMainThread([ourCampaign, this]() { @@ -703,7 +703,7 @@ void CServerHandler::startCampaignScenario(HighScoreParameter param, std::shared else { CMM->openCampaignScreen(ourCampaign->campaignSet); - GH.windows().createAndPushWindow(true, calc); + GH.windows().createAndPushWindow(true, *calc); } }; if(epilogue.hasPrologEpilog) diff --git a/client/CServerHandler.h b/client/CServerHandler.h index 467092e9f..0b8b3f146 100644 --- a/client/CServerHandler.h +++ b/client/CServerHandler.h @@ -14,8 +14,6 @@ #include "../lib/StartInfo.h" #include "../lib/CondSh.h" -#include "mainmenu/CHighScoreScreen.h" - VCMI_LIB_NAMESPACE_BEGIN class CConnection; @@ -37,6 +35,9 @@ VCMI_LIB_NAMESPACE_END class CClient; class CBaseForLobbyApply; +class HighScoreCalculation; +class HighScoreParameter; + // TODO: Add mutex so we can't set CONNECTION_CANCELLED if client already connected, but thread not setup yet enum class EClientState : ui8 { @@ -88,7 +89,7 @@ class CServerHandler : public IServerAPI, public LobbyInfo std::vector myNames; - HighScoreCalculation calc; + std::shared_ptr calc; void threadHandleConnection(); void threadRunServer(); From d2398b804ad98534a7d535d36404136c4f401cb9 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 23 Sep 2023 13:14:45 +0200 Subject: [PATCH 12/24] implement adding --- client/CPlayerInterface.cpp | 1 + client/CServerHandler.cpp | 1 + client/mainmenu/CHighScoreScreen.cpp | 79 ++++++++++++++++++---------- client/mainmenu/CHighScoreScreen.h | 8 ++- 4 files changed, 59 insertions(+), 30 deletions(-) diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index cc85e6144..80dc8475a 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -1703,6 +1703,7 @@ void CPlayerInterface::requestReturningToMainMenu(bool won) if(!ps->checkVanquished()) param.allDefeated = false; } + param.land = cb->getMapHeader()->name; HighScoreCalculation calc; calc.parameters.push_back(param); calc.isCampaign = false; diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 6e18ebcb6..0b0133dcb 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -679,6 +679,7 @@ void CServerHandler::startCampaignScenario(HighScoreParameter param, std::shared calc->isCampaign = true; calc->parameters.clear(); } + param.campaign = cs->getName(); calc->parameters.push_back(param); GH.dispatchMainThread([ourCampaign, this]() diff --git a/client/mainmenu/CHighScoreScreen.cpp b/client/mainmenu/CHighScoreScreen.cpp index 5ca8f612c..9540bbad7 100644 --- a/client/mainmenu/CHighScoreScreen.cpp +++ b/client/mainmenu/CHighScoreScreen.cpp @@ -27,6 +27,9 @@ #include "../../lib/CConfigHandler.h" #include "../../lib/CCreatureHandler.h" #include "../../lib/constants/EntityIdentifiers.h" +#include "../../lib/TextOperations.h" + +#include "vstd/DateUtils.h" auto HighScoreCalculation::calculate() { @@ -85,8 +88,8 @@ CreatureID HighScoreCalculation::getCreatureForPoints(int points, bool campaign) return -1; } -CHighScoreScreen::CHighScoreScreen(int highlighted) - : CWindowObject(BORDERED), highlighted(highlighted) +CHighScoreScreen::CHighScoreScreen(HighScorePage highscorepage, int highlighted) + : CWindowObject(BORDERED), highscorepage(highscorepage), highlighted(highlighted) { OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; pos = center(Rect(0, 0, 800, 600)); @@ -94,28 +97,6 @@ CHighScoreScreen::CHighScoreScreen(int highlighted) addHighScores(); addButtons(); - - // TODO write also datetime for RMB menu - - // TODO: remove; only for testing - for (int i = 0; i < 11; i++) - { - Settings entry = persistentStorage.write["highscore"]["campaign"][std::to_string(i)]["player"]; - entry->String() = "test"; - Settings entry1 = persistentStorage.write["highscore"]["campaign"][std::to_string(i)]["campaign"]; - entry1->String() = "test"; - Settings entry2 = persistentStorage.write["highscore"]["campaign"][std::to_string(i)]["points"]; - entry2->Integer() = std::rand() % 400 * 5; - - Settings entry3 = persistentStorage.write["highscore"]["scenario"][std::to_string(i)]["player"]; - entry3->String() = "test"; - Settings entry4 = persistentStorage.write["highscore"]["scenario"][std::to_string(i)]["land"]; - entry4->String() = "test"; - Settings entry5 = persistentStorage.write["highscore"]["scenario"][std::to_string(i)]["days"]; - entry5->Integer() = 123; - Settings entry6 = persistentStorage.write["highscore"]["scenario"][std::to_string(i)]["points"]; - entry6->Integer() = std::rand() % 400; - } } void CHighScoreScreen::addButtons() @@ -164,17 +145,23 @@ void CHighScoreScreen::addHighScores() ColorRGBA color = (i == highlighted) ? Colors::YELLOW : Colors::WHITE; texts.push_back(std::make_shared(115, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, std::to_string(i+1))); - texts.push_back(std::make_shared(220, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, curData["player"].String())); + std::string tmp = curData["player"].String(); + TextOperations::trimRightUnicode(tmp, std::max(0, (int)TextOperations::getUnicodeCharactersCount(tmp) - 13)); + texts.push_back(std::make_shared(220, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, tmp)); if(highscorepage == HighScorePage::SCENARIO) { - texts.push_back(std::make_shared(400, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, curData["land"].String())); + std::string tmp = curData["land"].String(); + TextOperations::trimRightUnicode(tmp, std::max(0, (int)TextOperations::getUnicodeCharactersCount(tmp) - 25)); + texts.push_back(std::make_shared(400, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, tmp)); texts.push_back(std::make_shared(555, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, std::to_string(curData["days"].Integer()))); texts.push_back(std::make_shared(625, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, std::to_string(curData["points"].Integer()))); } else { - texts.push_back(std::make_shared(410, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, curData["campaign"].String())); + std::string tmp = curData["campaign"].String(); + TextOperations::trimRightUnicode(tmp, std::max(0, (int)TextOperations::getUnicodeCharactersCount(tmp) - 25)); + texts.push_back(std::make_shared(410, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, tmp)); texts.push_back(std::make_shared(590, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, std::to_string(curData["points"].Integer()))); } @@ -252,7 +239,43 @@ CHighScoreInputScreen::CHighScoreInputScreen(bool won, HighScoreCalculation calc } void CHighScoreInputScreen::addEntry(std::string text) { + for (int i = 0; i < 11; i++) + { + JsonNode node = persistentStorage["highscore"][calc.isCampaign ? "campaign" : "scenario"][std::to_string(i)]; + + if(node["points"].isNull() || node["points"].Integer() <= calc.calculate().total) + { + // move following entries down + for (int j = 10; j > i; j--) + { + JsonNode node = persistentStorage["highscore"][calc.isCampaign ? "campaign" : "scenario"][std::to_string(j)]; + Settings entry = persistentStorage.write["highscore"][calc.isCampaign ? "campaign" : "scenario"][std::to_string(j + 1)]; + entry->Struct() = node.Struct(); + } + Settings entry = persistentStorage.write["highscore"][calc.isCampaign ? "campaign" : "scenario"][std::to_string(i)]["player"]; + entry->String() = text; + if(calc.isCampaign) + { + Settings entry2 = persistentStorage.write["highscore"]["campaign"][std::to_string(i)]["campaign"]; + entry2->String() = calc.parameters[0].campaign; + } + else + { + Settings entry3 = persistentStorage.write["highscore"]["scenario"][std::to_string(i)]["land"]; + entry3->String() = calc.parameters[0].land; + } + Settings entry4 = persistentStorage.write["highscore"][calc.isCampaign ? "campaign" : "scenario"][std::to_string(i)]["days"]; + entry4->Integer() = calc.calculate().sumDays; + Settings entry5 = persistentStorage.write["highscore"][calc.isCampaign ? "campaign" : "scenario"][std::to_string(i)]["points"]; + entry5->Integer() = calc.calculate().total; + + Settings entry6 = persistentStorage.write["highscore"][calc.isCampaign ? "campaign" : "scenario"][std::to_string(i)]["datetime"]; + entry6->String() = vstd::getFormattedDateTime(std::time(0)); + + return; + } + } } void CHighScoreInputScreen::show(Canvas & to) @@ -305,7 +328,7 @@ void CHighScoreInputScreen::clickPressed(const Point & cursorPosition) { addEntry(text); close(); - GH.windows().createAndPushWindow(); + GH.windows().createAndPushWindow(calc.isCampaign ? CHighScoreScreen::HighScorePage::CAMPAIGN : CHighScoreScreen::HighScorePage::SCENARIO); } else close(); diff --git a/client/mainmenu/CHighScoreScreen.h b/client/mainmenu/CHighScoreScreen.h index 2062647f6..3fa402000 100644 --- a/client/mainmenu/CHighScoreScreen.h +++ b/client/mainmenu/CHighScoreScreen.h @@ -25,6 +25,8 @@ public: bool usedCheat; bool hasGrail; bool allDefeated; + std::string campaign; + std::string land; }; class HighScoreCalculation @@ -39,8 +41,10 @@ public: class CHighScoreScreen : public CWindowObject { +public: enum HighScorePage { SCENARIO, CAMPAIGN }; +private: void addButtons(); void addHighScores(); @@ -49,7 +53,7 @@ class CHighScoreScreen : public CWindowObject void buttonResetClick(); void buttonExitClick(); - HighScorePage highscorepage = HighScorePage::SCENARIO; + HighScorePage highscorepage; std::shared_ptr background; std::vector> buttons; @@ -58,7 +62,7 @@ class CHighScoreScreen : public CWindowObject int highlighted; public: - CHighScoreScreen(int highlighted = -1); + CHighScoreScreen(HighScorePage highscorepage = HighScorePage::SCENARIO, int highlighted = -1); }; class CHighScoreInput : public CWindowObject From e3edcb6cd852161efa495b58c52da49a297e19c8 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 23 Sep 2023 14:51:39 +0200 Subject: [PATCH 13/24] rmb; bugfix; tabs --- client/CServerHandler.cpp | 9 +- client/mainmenu/CHighScoreScreen.cpp | 458 ++++++++++++++------------- client/mainmenu/CHighScoreScreen.h | 82 ++--- 3 files changed, 289 insertions(+), 260 deletions(-) diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 0b0133dcb..f8758cc00 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -600,6 +600,8 @@ void CServerHandler::startGameplay(VCMI_LIB_WRAP_NAMESPACE(CGameState) * gameSta CMM->disable(); client = new CClient(); + calc = nullptr; + switch(si->mode) { case StartInfo::NEW_GAME: @@ -674,8 +676,11 @@ void CServerHandler::startCampaignScenario(HighScoreParameter param, std::shared std::shared_ptr ourCampaign = cs; if (!cs) - { ourCampaign = si->campState; + + if(calc == nullptr) + { + calc = std::make_shared(); calc->isCampaign = true; calc->parameters.clear(); } @@ -950,7 +955,7 @@ void CServerHandler::threadRunServer() } comm += " > \"" + logName + '\"'; - logGlobal->info("Server command line: %s", comm); + logGlobal->info("Server command line: %s", comm); #ifdef VCMI_WINDOWS int result = -1; diff --git a/client/mainmenu/CHighScoreScreen.cpp b/client/mainmenu/CHighScoreScreen.cpp index 9540bbad7..f24a5ea50 100644 --- a/client/mainmenu/CHighScoreScreen.cpp +++ b/client/mainmenu/CHighScoreScreen.cpp @@ -33,333 +33,355 @@ auto HighScoreCalculation::calculate() { - struct result - { - int basic; - int total; - int sumDays; - bool cheater; - }; - - std::vector scoresBasic; - std::vector scoresTotal; - double sumBasic = 0; - double sumTotal = 0; - int sumDays = 0; - bool cheater = false; - for(auto & param : parameters) - { - double tmp = 200 - (param.day + 10) / (param.townAmount + 5) + (param.allDefeated ? 25 : 0) + (param.hasGrail ? 25 : 0); - scoresBasic.push_back(static_cast(tmp)); - sumBasic += tmp; - if(param.difficulty == 0) - tmp *= 0.8; - if(param.difficulty == 1) - tmp *= 1.0; - if(param.difficulty == 2) - tmp *= 1.3; - if(param.difficulty == 3) - tmp *= 1.6; - if(param.difficulty == 4) - tmp *= 2.0; - scoresTotal.push_back(static_cast(tmp)); - sumTotal += tmp; - sumDays += param.day; - if(param.usedCheat) - cheater = true; - } + struct result + { + int basic; + int total; + int sumDays; + bool cheater; + }; + + std::vector scoresBasic; + std::vector scoresTotal; + double sumBasic = 0; + double sumTotal = 0; + int sumDays = 0; + bool cheater = false; + for(auto & param : parameters) + { + double tmp = 200 - (param.day + 10) / (param.townAmount + 5) + (param.allDefeated ? 25 : 0) + (param.hasGrail ? 25 : 0); + scoresBasic.push_back(static_cast(tmp)); + sumBasic += tmp; + if(param.difficulty == 0) + tmp *= 0.8; + if(param.difficulty == 1) + tmp *= 1.0; + if(param.difficulty == 2) + tmp *= 1.3; + if(param.difficulty == 3) + tmp *= 1.6; + if(param.difficulty == 4) + tmp *= 2.0; + scoresTotal.push_back(static_cast(tmp)); + sumTotal += tmp; + sumDays += param.day; + if(param.usedCheat) + cheater = true; + } - if(scoresBasic.size() == 1) - return result { scoresBasic[0], scoresTotal[0], sumDays , cheater}; + if(scoresBasic.size() == 1) + return result { scoresBasic[0], scoresTotal[0], sumDays , cheater}; - return result { static_cast((sumBasic / parameters.size()) * 5.0), static_cast((sumTotal / parameters.size()) * 5.0), sumDays, cheater }; + return result { static_cast((sumBasic / parameters.size()) * 5.0), static_cast((sumTotal / parameters.size()) * 5.0), sumDays, cheater }; } CreatureID HighScoreCalculation::getCreatureForPoints(int points, bool campaign) { - static const JsonNode configCreatures(JsonPath::builtin("CONFIG/highscoreCreatures.json")); - auto creatures = configCreatures["creatures"].Vector(); - int divide = campaign ? 5 : 1; + static const JsonNode configCreatures(JsonPath::builtin("CONFIG/highscoreCreatures.json")); + auto creatures = configCreatures["creatures"].Vector(); + int divide = campaign ? 5 : 1; - for(auto & creature : creatures) - if(points / divide <= creature["max"].Integer() && points / divide >= creature["min"].Integer()) - return CreatureID::decode(creature["creature"].String()); + for(auto & creature : creatures) + if(points / divide <= creature["max"].Integer() && points / divide >= creature["min"].Integer()) + return CreatureID::decode(creature["creature"].String()); - return -1; + return -1; } CHighScoreScreen::CHighScoreScreen(HighScorePage highscorepage, int highlighted) : CWindowObject(BORDERED), highscorepage(highscorepage), highlighted(highlighted) { + addUsedEvents(SHOW_POPUP); + OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; pos = center(Rect(0, 0, 800, 600)); updateShadow(); - addHighScores(); - addButtons(); + addHighScores(); + addButtons(); +} + +void CHighScoreScreen::showPopupWindow(const Point & cursorPosition) +{ + for (int i = 0; i < 11; i++) + { + Rect r = Rect(80, 40 + i * 50, 635, 50); + if(r.isInside(cursorPosition - pos)) + { + std::string tmp = persistentStorage["highscore"][highscorepage == HighScorePage::SCENARIO ? "scenario" : "campaign"][std::to_string(i)]["datetime"].String(); + if(!tmp.empty()) + CRClickPopup::createAndPush(tmp); + } + } } void CHighScoreScreen::addButtons() { OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; - - buttons.clear(); + + buttons.clear(); - buttons.push_back(std::make_shared(Point(31, 113), AnimationPath::builtin("HISCCAM.DEF"), CButton::tooltip(), [&](){ buttonCampaginClick(); })); - buttons.push_back(std::make_shared(Point(31, 345), AnimationPath::builtin("HISCSTA.DEF"), CButton::tooltip(), [&](){ buttonStandardClick(); })); - buttons.push_back(std::make_shared(Point(726, 113), AnimationPath::builtin("HISCRES.DEF"), CButton::tooltip(), [&](){ buttonResetClick(); })); - buttons.push_back(std::make_shared(Point(726, 345), AnimationPath::builtin("HISCEXT.DEF"), CButton::tooltip(), [&](){ buttonExitClick(); })); + buttons.push_back(std::make_shared(Point(31, 113), AnimationPath::builtin("HISCCAM.DEF"), CButton::tooltip(), [&](){ buttonCampaginClick(); })); + buttons.push_back(std::make_shared(Point(31, 345), AnimationPath::builtin("HISCSTA.DEF"), CButton::tooltip(), [&](){ buttonStandardClick(); })); + buttons.push_back(std::make_shared(Point(726, 113), AnimationPath::builtin("HISCRES.DEF"), CButton::tooltip(), [&](){ buttonResetClick(); })); + buttons.push_back(std::make_shared(Point(726, 345), AnimationPath::builtin("HISCEXT.DEF"), CButton::tooltip(), [&](){ buttonExitClick(); })); } void CHighScoreScreen::addHighScores() { OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; - background = std::make_shared(ImagePath::builtin(highscorepage == HighScorePage::SCENARIO ? "HISCORE" : "HISCORE2")); + background = std::make_shared(ImagePath::builtin(highscorepage == HighScorePage::SCENARIO ? "HISCORE" : "HISCORE2")); - texts.clear(); - images.clear(); + texts.clear(); + images.clear(); - // Header - texts.push_back(std::make_shared(115, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.433"))); - texts.push_back(std::make_shared(220, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.434"))); + // Header + texts.push_back(std::make_shared(115, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.433"))); + texts.push_back(std::make_shared(220, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.434"))); - if(highscorepage == HighScorePage::SCENARIO) - { - texts.push_back(std::make_shared(400, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.435"))); - texts.push_back(std::make_shared(555, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.436"))); - texts.push_back(std::make_shared(625, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.75"))); - } - else - { - texts.push_back(std::make_shared(410, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.672"))); - texts.push_back(std::make_shared(590, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.75"))); - } + if(highscorepage == HighScorePage::SCENARIO) + { + texts.push_back(std::make_shared(400, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.435"))); + texts.push_back(std::make_shared(555, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.436"))); + texts.push_back(std::make_shared(625, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.75"))); + } + else + { + texts.push_back(std::make_shared(410, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.672"))); + texts.push_back(std::make_shared(590, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.75"))); + } - // Content - int y = 65; - auto & data = persistentStorage["highscore"][highscorepage == HighScorePage::SCENARIO ? "scenario" : "campaign"]; - for (int i = 0; i < 11; i++) - { - auto & curData = data[std::to_string(i)]; - ColorRGBA color = (i == highlighted) ? Colors::YELLOW : Colors::WHITE; + // Content + int y = 65; + auto & data = persistentStorage["highscore"][highscorepage == HighScorePage::SCENARIO ? "scenario" : "campaign"]; + for (int i = 0; i < 11; i++) + { + auto & curData = data[std::to_string(i)]; + ColorRGBA color = (i == highlighted) ? Colors::YELLOW : Colors::WHITE; - texts.push_back(std::make_shared(115, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, std::to_string(i+1))); - std::string tmp = curData["player"].String(); - TextOperations::trimRightUnicode(tmp, std::max(0, (int)TextOperations::getUnicodeCharactersCount(tmp) - 13)); - texts.push_back(std::make_shared(220, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, tmp)); - - if(highscorepage == HighScorePage::SCENARIO) - { - std::string tmp = curData["land"].String(); - TextOperations::trimRightUnicode(tmp, std::max(0, (int)TextOperations::getUnicodeCharactersCount(tmp) - 25)); - texts.push_back(std::make_shared(400, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, tmp)); - texts.push_back(std::make_shared(555, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, std::to_string(curData["days"].Integer()))); - texts.push_back(std::make_shared(625, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, std::to_string(curData["points"].Integer()))); - } - else - { - std::string tmp = curData["campaign"].String(); - TextOperations::trimRightUnicode(tmp, std::max(0, (int)TextOperations::getUnicodeCharactersCount(tmp) - 25)); - texts.push_back(std::make_shared(410, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, tmp)); - texts.push_back(std::make_shared(590, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, std::to_string(curData["points"].Integer()))); - } + texts.push_back(std::make_shared(115, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, std::to_string(i+1))); + std::string tmp = curData["player"].String(); + TextOperations::trimRightUnicode(tmp, std::max(0, (int)TextOperations::getUnicodeCharactersCount(tmp) - 13)); + texts.push_back(std::make_shared(220, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, tmp)); - images.push_back(std::make_shared(AnimationPath::builtin("CPRSMALL"), (*CGI->creh)[HighScoreCalculation::getCreatureForPoints(curData["points"].Integer(), highscorepage == HighScorePage::CAMPAIGN)]->getIconIndex(), 0, 670, y - 15 + i * 50)); - } + if(highscorepage == HighScorePage::SCENARIO) + { + std::string tmp = curData["land"].String(); + TextOperations::trimRightUnicode(tmp, std::max(0, (int)TextOperations::getUnicodeCharactersCount(tmp) - 25)); + texts.push_back(std::make_shared(400, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, tmp)); + texts.push_back(std::make_shared(555, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, std::to_string(curData["days"].Integer()))); + texts.push_back(std::make_shared(625, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, std::to_string(curData["points"].Integer()))); + } + else + { + std::string tmp = curData["campaign"].String(); + TextOperations::trimRightUnicode(tmp, std::max(0, (int)TextOperations::getUnicodeCharactersCount(tmp) - 25)); + texts.push_back(std::make_shared(410, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, tmp)); + texts.push_back(std::make_shared(590, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, std::to_string(curData["points"].Integer()))); + } + + if(curData["points"].Integer() > 0) + images.push_back(std::make_shared(AnimationPath::builtin("CPRSMALL"), (*CGI->creh)[HighScoreCalculation::getCreatureForPoints(curData["points"].Integer(), highscorepage == HighScorePage::CAMPAIGN)]->getIconIndex(), 0, 670, y - 15 + i * 50)); + } } void CHighScoreScreen::buttonCampaginClick() { - highscorepage = HighScorePage::CAMPAIGN; - addHighScores(); - addButtons(); - redraw(); + highscorepage = HighScorePage::CAMPAIGN; + addHighScores(); + addButtons(); + redraw(); } void CHighScoreScreen::buttonStandardClick() { OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; - highscorepage = HighScorePage::SCENARIO; - addHighScores(); - addButtons(); - redraw(); + highscorepage = HighScorePage::SCENARIO; + addHighScores(); + addButtons(); + redraw(); } void CHighScoreScreen::buttonResetClick() { - CInfoWindow::showYesNoDialog( - CGI->generaltexth->allTexts[666], - {}, - [this]() - { - Settings entry = persistentStorage.write["highscore"]; - entry->clear(); - addHighScores(); - addButtons(); - redraw(); - }, - 0 - ); + CInfoWindow::showYesNoDialog( + CGI->generaltexth->allTexts[666], + {}, + [this]() + { + Settings entry = persistentStorage.write["highscore"]; + entry->clear(); + addHighScores(); + addButtons(); + redraw(); + }, + 0 + ); } void CHighScoreScreen::buttonExitClick() { - close(); + close(); } CHighScoreInputScreen::CHighScoreInputScreen(bool won, HighScoreCalculation calc) : CWindowObject(BORDERED), won(won), calc(calc) { - addUsedEvents(LCLICK); + addUsedEvents(LCLICK); - OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; + OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; pos = center(Rect(0, 0, 800, 600)); updateShadow(); - if(won) - { - int border = 100; - int textareaW = ((pos.w - 2 * border) / 4); - std::vector t = { "438", "439", "440", "441", "676" }; - for (int i = 0; i < 5; i++) - texts.push_back(std::make_shared(Rect(textareaW * i + border - (textareaW / 2), 450, textareaW, 100), FONT_HIGH_SCORE, ETextAlignment::TOPCENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt." + t[i]))); + if(won) + { + int border = 100; + int textareaW = ((pos.w - 2 * border) / 4); + std::vector t = { "438", "439", "440", "441", "676" }; + for (int i = 0; i < 5; i++) + texts.push_back(std::make_shared(Rect(textareaW * i + border - (textareaW / 2), 450, textareaW, 100), FONT_HIGH_SCORE, ETextAlignment::TOPCENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt." + t[i]))); - std::string creatureName = (calc.calculate().cheater) ? CGI->generaltexth->translate("core.genrltxt.260") : (*CGI->creh)[HighScoreCalculation::getCreatureForPoints(calc.calculate().total, calc.isCampaign)]->getNameSingularTranslated(); - t = { std::to_string(calc.calculate().sumDays), std::to_string(calc.calculate().basic), CGI->generaltexth->translate("core.arraytxt." + std::to_string((142 + calc.parameters[0].difficulty))), std::to_string(calc.calculate().total), creatureName }; - for (int i = 0; i < 5; i++) - texts.push_back(std::make_shared(Rect(textareaW * i + border - (textareaW / 2), 530, textareaW, 100), FONT_HIGH_SCORE, ETextAlignment::TOPCENTER, Colors::WHITE, t[i])); + std::string creatureName = (calc.calculate().cheater) ? CGI->generaltexth->translate("core.genrltxt.260") : (*CGI->creh)[HighScoreCalculation::getCreatureForPoints(calc.calculate().total, calc.isCampaign)]->getNameSingularTranslated(); + t = { std::to_string(calc.calculate().sumDays), std::to_string(calc.calculate().basic), CGI->generaltexth->translate("core.arraytxt." + std::to_string((142 + calc.parameters[0].difficulty))), std::to_string(calc.calculate().total), creatureName }; + for (int i = 0; i < 5; i++) + texts.push_back(std::make_shared(Rect(textareaW * i + border - (textareaW / 2), 530, textareaW, 100), FONT_HIGH_SCORE, ETextAlignment::TOPCENTER, Colors::WHITE, t[i])); - CCS->musich->playMusic(AudioPath::builtin("music/Win Scenario"), true, true); - } - else - CCS->musich->playMusic(AudioPath::builtin("music/UltimateLose"), false, true); + CCS->musich->playMusic(AudioPath::builtin("music/Win Scenario"), true, true); + } + else + CCS->musich->playMusic(AudioPath::builtin("music/UltimateLose"), false, true); - video = won ? "HSANIM.SMK" : "LOSEGAME.SMK"; + video = won ? "HSANIM.SMK" : "LOSEGAME.SMK"; } -void CHighScoreInputScreen::addEntry(std::string text) { - for (int i = 0; i < 11; i++) - { - JsonNode node = persistentStorage["highscore"][calc.isCampaign ? "campaign" : "scenario"][std::to_string(i)]; - - if(node["points"].isNull() || node["points"].Integer() <= calc.calculate().total) - { - // move following entries down - for (int j = 10; j > i; j--) - { - JsonNode node = persistentStorage["highscore"][calc.isCampaign ? "campaign" : "scenario"][std::to_string(j)]; - Settings entry = persistentStorage.write["highscore"][calc.isCampaign ? "campaign" : "scenario"][std::to_string(j + 1)]; - entry->Struct() = node.Struct(); - } +int CHighScoreInputScreen::addEntry(std::string text) { + for (int i = 0; i < 11; i++) + { + if(calc.calculate().cheater) + i = 10; - Settings entry = persistentStorage.write["highscore"][calc.isCampaign ? "campaign" : "scenario"][std::to_string(i)]["player"]; - entry->String() = text; - if(calc.isCampaign) - { - Settings entry2 = persistentStorage.write["highscore"]["campaign"][std::to_string(i)]["campaign"]; - entry2->String() = calc.parameters[0].campaign; - } - else - { - Settings entry3 = persistentStorage.write["highscore"]["scenario"][std::to_string(i)]["land"]; - entry3->String() = calc.parameters[0].land; - } - Settings entry4 = persistentStorage.write["highscore"][calc.isCampaign ? "campaign" : "scenario"][std::to_string(i)]["days"]; - entry4->Integer() = calc.calculate().sumDays; - Settings entry5 = persistentStorage.write["highscore"][calc.isCampaign ? "campaign" : "scenario"][std::to_string(i)]["points"]; - entry5->Integer() = calc.calculate().total; + JsonNode node = persistentStorage["highscore"][calc.isCampaign ? "campaign" : "scenario"][std::to_string(i)]; + + if(node["points"].isNull() || node["points"].Integer() <= calc.calculate().total) + { + // move following entries down + for (int j = 10; j + 1 >= i; j--) + { + JsonNode node = persistentStorage["highscore"][calc.isCampaign ? "campaign" : "scenario"][std::to_string(j - 1)]; + Settings entry = persistentStorage.write["highscore"][calc.isCampaign ? "campaign" : "scenario"][std::to_string(j)]; + entry->Struct() = node.Struct(); + } - Settings entry6 = persistentStorage.write["highscore"][calc.isCampaign ? "campaign" : "scenario"][std::to_string(i)]["datetime"]; - entry6->String() = vstd::getFormattedDateTime(std::time(0)); + Settings entry = persistentStorage.write["highscore"][calc.isCampaign ? "campaign" : "scenario"][std::to_string(i)]["player"]; + entry->String() = text; + if(calc.isCampaign) + { + Settings entry2 = persistentStorage.write["highscore"]["campaign"][std::to_string(i)]["campaign"]; + entry2->String() = calc.calculate().cheater ? CGI->generaltexth->translate("core.genrltxt.260") : calc.parameters[0].campaign; + } + else + { + Settings entry3 = persistentStorage.write["highscore"]["scenario"][std::to_string(i)]["land"]; + entry3->String() = calc.calculate().cheater ? CGI->generaltexth->translate("core.genrltxt.260") : calc.parameters[0].land; + } + Settings entry4 = persistentStorage.write["highscore"][calc.isCampaign ? "campaign" : "scenario"][std::to_string(i)]["days"]; + entry4->Integer() = calc.calculate().sumDays; + Settings entry5 = persistentStorage.write["highscore"][calc.isCampaign ? "campaign" : "scenario"][std::to_string(i)]["points"]; + entry5->Integer() = calc.calculate().cheater ? 0 : calc.calculate().total; - return; - } - } + Settings entry6 = persistentStorage.write["highscore"][calc.isCampaign ? "campaign" : "scenario"][std::to_string(i)]["datetime"]; + entry6->String() = vstd::getFormattedDateTime(std::time(0)); + + return i; + } + } + + return -1; } void CHighScoreInputScreen::show(Canvas & to) { CCS->videoh->update(pos.x, pos.y, to.getInternalSurface(), true, false, - [&]() - { - if(won) - { - CCS->videoh->close(); - video = "HSLOOP.SMK"; - CCS->videoh->open(VideoPath::builtin(video)); - } - else - close(); - }); - redraw(); + [&]() + { + if(won) + { + CCS->videoh->close(); + video = "HSLOOP.SMK"; + CCS->videoh->open(VideoPath::builtin(video)); + } + else + close(); + }); + redraw(); CIntObject::show(to); } void CHighScoreInputScreen::activate() { - CCS->videoh->open(VideoPath::builtin(video)); + CCS->videoh->open(VideoPath::builtin(video)); CIntObject::activate(); } void CHighScoreInputScreen::deactivate() { - CCS->videoh->close(); + CCS->videoh->close(); CIntObject::deactivate(); } void CHighScoreInputScreen::clickPressed(const Point & cursorPosition) { - OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; + OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; - if(!won) - { - close(); - return; - } + if(!won) + { + close(); + return; + } - if(!input) - { - input = std::make_shared( - [&] (std::string text) - { - if(!text.empty()) - { - addEntry(text); - close(); - GH.windows().createAndPushWindow(calc.isCampaign ? CHighScoreScreen::HighScorePage::CAMPAIGN : CHighScoreScreen::HighScorePage::SCENARIO); - } - else - close(); - }); - } + if(!input) + { + input = std::make_shared( + [&] (std::string text) + { + if(!text.empty()) + { + int pos = addEntry(text); + close(); + GH.windows().createAndPushWindow(calc.isCampaign ? CHighScoreScreen::HighScorePage::CAMPAIGN : CHighScoreScreen::HighScorePage::SCENARIO, pos); + } + else + close(); + }); + } } CHighScoreInput::CHighScoreInput(std::function readyCB) : CWindowObject(0), ready(readyCB) { - OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; + OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; pos = center(Rect(0, 0, 232, 212)); updateShadow(); - background = std::make_shared(ImagePath::builtin("HIGHNAME")); - text = std::make_shared(Rect(15, 15, 202, 202), FONT_SMALL, ETextAlignment::TOPCENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.96")); + background = std::make_shared(ImagePath::builtin("HIGHNAME")); + text = std::make_shared(Rect(15, 15, 202, 202), FONT_SMALL, ETextAlignment::TOPCENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.96")); - buttonOk = std::make_shared(Point(26, 142), AnimationPath::builtin("MUBCHCK.DEF"), CGI->generaltexth->zelp[560], std::bind(&CHighScoreInput::okay, this), EShortcut::GLOBAL_ACCEPT); - buttonCancel = std::make_shared(Point(142, 142), AnimationPath::builtin("MUBCANC.DEF"), CGI->generaltexth->zelp[561], std::bind(&CHighScoreInput::abort, this), EShortcut::GLOBAL_CANCEL); + buttonOk = std::make_shared(Point(26, 142), AnimationPath::builtin("MUBCHCK.DEF"), CGI->generaltexth->zelp[560], std::bind(&CHighScoreInput::okay, this), EShortcut::GLOBAL_ACCEPT); + buttonCancel = std::make_shared(Point(142, 142), AnimationPath::builtin("MUBCANC.DEF"), CGI->generaltexth->zelp[561], std::bind(&CHighScoreInput::abort, this), EShortcut::GLOBAL_CANCEL); statusBar = CGStatusBar::create(std::make_shared(background->getSurface(), Rect(7, 186, 218, 18), 7, 186)); - textInput = std::make_shared(Rect(18, 104, 200, 25), FONT_SMALL, 0); - textInput->setText(settings["general"]["playerName"].String()); + textInput = std::make_shared(Rect(18, 104, 200, 25), FONT_SMALL, 0); + textInput->setText(settings["general"]["playerName"].String()); } void CHighScoreInput::okay() { - ready(textInput->getText()); + ready(textInput->getText()); } void CHighScoreInput::abort() { - ready(""); + ready(""); } \ No newline at end of file diff --git a/client/mainmenu/CHighScoreScreen.h b/client/mainmenu/CHighScoreScreen.h index 3fa402000..d622df7a8 100644 --- a/client/mainmenu/CHighScoreScreen.h +++ b/client/mainmenu/CHighScoreScreen.h @@ -19,81 +19,83 @@ class CTextInput; class HighScoreParameter { public: - int difficulty; - int day; - int townAmount; - bool usedCheat; - bool hasGrail; - bool allDefeated; - std::string campaign; - std::string land; + int difficulty; + int day; + int townAmount; + bool usedCheat; + bool hasGrail; + bool allDefeated; + std::string campaign; + std::string land; }; class HighScoreCalculation { public: - std::vector parameters = std::vector(); - bool isCampaign = false; + std::vector parameters = std::vector(); + bool isCampaign = false; - auto calculate(); - static CreatureID getCreatureForPoints(int points, bool campaign); + auto calculate(); + static CreatureID getCreatureForPoints(int points, bool campaign); }; class CHighScoreScreen : public CWindowObject { public: - enum HighScorePage { SCENARIO, CAMPAIGN }; + enum HighScorePage { SCENARIO, CAMPAIGN }; private: - void addButtons(); - void addHighScores(); - - void buttonCampaginClick(); - void buttonStandardClick(); - void buttonResetClick(); - void buttonExitClick(); + void addButtons(); + void addHighScores(); + + void buttonCampaginClick(); + void buttonStandardClick(); + void buttonResetClick(); + void buttonExitClick(); - HighScorePage highscorepage; + void showPopupWindow(const Point & cursorPosition) override; - std::shared_ptr background; - std::vector> buttons; - std::vector> texts; - std::vector> images; + HighScorePage highscorepage; - int highlighted; + std::shared_ptr background; + std::vector> buttons; + std::vector> texts; + std::vector> images; + + int highlighted; public: CHighScoreScreen(HighScorePage highscorepage = HighScorePage::SCENARIO, int highlighted = -1); }; class CHighScoreInput : public CWindowObject { - std::shared_ptr background; - std::shared_ptr text; - std::shared_ptr buttonOk; + std::shared_ptr background; + std::shared_ptr text; + std::shared_ptr buttonOk; std::shared_ptr buttonCancel; std::shared_ptr statusBar; - std::shared_ptr textInput; + std::shared_ptr textInput; - std::function ready; - - void okay(); - void abort(); + std::function ready; + + void okay(); + void abort(); public: CHighScoreInput(std::function readyCB); }; class CHighScoreInputScreen : public CWindowObject { - std::vector> texts; - std::shared_ptr input; + std::vector> texts; + std::shared_ptr input; - std::string video; - bool won; - HighScoreCalculation calc; + std::string video; + bool won; + HighScoreCalculation calc; public: CHighScoreInputScreen(bool won, HighScoreCalculation calc); - void addEntry(std::string text); + int addEntry(std::string text); void show(Canvas & to) override; void activate() override; From 06895e6733f2b8d9508702dea6432fbae35b0c53 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 23 Sep 2023 15:11:48 +0200 Subject: [PATCH 14/24] fine tuning --- client/mainmenu/CHighScoreScreen.cpp | 33 ++++++++++++++++------------ client/mainmenu/CHighScoreScreen.h | 1 + 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/client/mainmenu/CHighScoreScreen.cpp b/client/mainmenu/CHighScoreScreen.cpp index f24a5ea50..b2f2ad834 100644 --- a/client/mainmenu/CHighScoreScreen.cpp +++ b/client/mainmenu/CHighScoreScreen.cpp @@ -138,22 +138,22 @@ void CHighScoreScreen::addHighScores() // Header texts.push_back(std::make_shared(115, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.433"))); - texts.push_back(std::make_shared(220, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.434"))); + texts.push_back(std::make_shared(225, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.434"))); if(highscorepage == HighScorePage::SCENARIO) { - texts.push_back(std::make_shared(400, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.435"))); - texts.push_back(std::make_shared(555, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.436"))); - texts.push_back(std::make_shared(625, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.75"))); + texts.push_back(std::make_shared(405, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.435"))); + texts.push_back(std::make_shared(557, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.436"))); + texts.push_back(std::make_shared(627, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.75"))); } else { - texts.push_back(std::make_shared(410, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.672"))); - texts.push_back(std::make_shared(590, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.75"))); + texts.push_back(std::make_shared(405, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.672"))); + texts.push_back(std::make_shared(592, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.75"))); } // Content - int y = 65; + int y = 66; auto & data = persistentStorage["highscore"][highscorepage == HighScorePage::SCENARIO ? "scenario" : "campaign"]; for (int i = 0; i < 11; i++) { @@ -163,22 +163,22 @@ void CHighScoreScreen::addHighScores() texts.push_back(std::make_shared(115, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, std::to_string(i+1))); std::string tmp = curData["player"].String(); TextOperations::trimRightUnicode(tmp, std::max(0, (int)TextOperations::getUnicodeCharactersCount(tmp) - 13)); - texts.push_back(std::make_shared(220, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, tmp)); + texts.push_back(std::make_shared(225, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, tmp)); if(highscorepage == HighScorePage::SCENARIO) { std::string tmp = curData["land"].String(); TextOperations::trimRightUnicode(tmp, std::max(0, (int)TextOperations::getUnicodeCharactersCount(tmp) - 25)); - texts.push_back(std::make_shared(400, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, tmp)); - texts.push_back(std::make_shared(555, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, std::to_string(curData["days"].Integer()))); - texts.push_back(std::make_shared(625, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, std::to_string(curData["points"].Integer()))); + texts.push_back(std::make_shared(405, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, tmp)); + texts.push_back(std::make_shared(557, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, std::to_string(curData["days"].Integer()))); + texts.push_back(std::make_shared(627, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, std::to_string(curData["points"].Integer()))); } else { std::string tmp = curData["campaign"].String(); TextOperations::trimRightUnicode(tmp, std::max(0, (int)TextOperations::getUnicodeCharactersCount(tmp) - 25)); - texts.push_back(std::make_shared(410, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, tmp)); - texts.push_back(std::make_shared(590, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, std::to_string(curData["points"].Integer()))); + texts.push_back(std::make_shared(405, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, tmp)); + texts.push_back(std::make_shared(592, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, std::to_string(curData["points"].Integer()))); } if(curData["points"].Integer() > 0) @@ -228,7 +228,7 @@ void CHighScoreScreen::buttonExitClick() CHighScoreInputScreen::CHighScoreInputScreen(bool won, HighScoreCalculation calc) : CWindowObject(BORDERED), won(won), calc(calc) { - addUsedEvents(LCLICK); + addUsedEvents(LCLICK | KEYBOARD); OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; pos = center(Rect(0, 0, 800, 600)); @@ -358,6 +358,11 @@ void CHighScoreInputScreen::clickPressed(const Point & cursorPosition) } } +void CHighScoreInputScreen::keyPressed(EShortcut key) +{ + clickPressed(Point()); +} + CHighScoreInput::CHighScoreInput(std::function readyCB) : CWindowObject(0), ready(readyCB) { diff --git a/client/mainmenu/CHighScoreScreen.h b/client/mainmenu/CHighScoreScreen.h index d622df7a8..b3e1ac449 100644 --- a/client/mainmenu/CHighScoreScreen.h +++ b/client/mainmenu/CHighScoreScreen.h @@ -101,4 +101,5 @@ public: void activate() override; void deactivate() override; void clickPressed(const Point & cursorPosition) override; + void keyPressed(EShortcut key) override; }; \ No newline at end of file From ca96749c45a0329a95d96777324acdac70ae648f Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 23 Sep 2023 16:28:28 +0200 Subject: [PATCH 15/24] workaround for missing video --- client/mainmenu/CHighScoreScreen.cpp | 11 ++++++++++- client/mainmenu/CHighScoreScreen.h | 3 +++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/client/mainmenu/CHighScoreScreen.cpp b/client/mainmenu/CHighScoreScreen.cpp index b2f2ad834..8d6a0e624 100644 --- a/client/mainmenu/CHighScoreScreen.cpp +++ b/client/mainmenu/CHighScoreScreen.cpp @@ -17,6 +17,7 @@ #include "../widgets/TextControls.h" #include "../widgets/Buttons.h" #include "../widgets/Images.h" +#include "../widgets/MiscWidgets.h" #include "../windows/InfoWindows.h" #include "../render/Canvas.h" @@ -234,6 +235,8 @@ CHighScoreInputScreen::CHighScoreInputScreen(bool won, HighScoreCalculation calc pos = center(Rect(0, 0, 800, 600)); updateShadow(); + background = std::make_shared(Rect(0, 0, pos.w, pos.h), Colors::BLACK); + if(won) { int border = 100; @@ -321,7 +324,13 @@ void CHighScoreInputScreen::show(Canvas & to) void CHighScoreInputScreen::activate() { - CCS->videoh->open(VideoPath::builtin(video)); + if(!CCS->videoh->open(VideoPath::builtin(video))) + { + if(!won) + close(); + } + else + background = nullptr; CIntObject::activate(); } diff --git a/client/mainmenu/CHighScoreScreen.h b/client/mainmenu/CHighScoreScreen.h index b3e1ac449..e995e7b58 100644 --- a/client/mainmenu/CHighScoreScreen.h +++ b/client/mainmenu/CHighScoreScreen.h @@ -16,6 +16,8 @@ class CMultiLineLabel; class CAnimImage; class CTextInput; +class TransparentFilledRectangle; + class HighScoreParameter { public: @@ -88,6 +90,7 @@ class CHighScoreInputScreen : public CWindowObject { std::vector> texts; std::shared_ptr input; + std::shared_ptr background; std::string video; bool won; From 6f8b62d77dded438cc63d1b9f0e8126b6b44feb2 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 23 Sep 2023 20:41:30 +0200 Subject: [PATCH 16/24] codereview --- client/CPlayerInterface.cpp | 14 +++---- client/CServerHandler.cpp | 14 +++---- client/CServerHandler.h | 2 +- client/CVideoHandler.cpp | 6 +-- client/CVideoHandler.h | 2 +- client/mainmenu/CHighScoreScreen.cpp | 59 +++++++++++----------------- client/mainmenu/CHighScoreScreen.h | 5 +-- lib/CPlayerState.cpp | 3 +- lib/CPlayerState.h | 2 + lib/NetPacksLib.cpp | 1 + 10 files changed, 50 insertions(+), 58 deletions(-) diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 80dc8475a..5bd29f8e4 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -1690,7 +1690,7 @@ void CPlayerInterface::requestReturningToMainMenu(bool won) param.difficulty = cb->getStartInfo()->difficulty; param.day = cb->getDate(); param.townAmount = cb->howManyTowns(); - param.usedCheat = cb->getPlayerState(*cb->getPlayerID())->enteredWinningCheatCode; + param.usedCheat = cb->getPlayerState(*cb->getPlayerID())->cheated; param.hasGrail = false; for(const CGHeroInstance * h : cb->getHeroesInfo()) if(h->hasArt(ArtifactID::GRAIL)) @@ -1698,27 +1698,27 @@ void CPlayerInterface::requestReturningToMainMenu(bool won) param.allDefeated = true; for (PlayerColor player(0); player < PlayerColor::PLAYER_LIMIT; ++player) { - auto ps = cb->getPlayerState(player); + auto ps = cb->getPlayerState(player, false); if(ps && player != *cb->getPlayerID()) if(!ps->checkVanquished()) param.allDefeated = false; } param.land = cb->getMapHeader()->name; - HighScoreCalculation calc; - calc.parameters.push_back(param); - calc.isCampaign = false; + HighScoreCalculation highScoreCalc; + highScoreCalc.parameters.push_back(param); + highScoreCalc.isCampaign = false; if(won && cb->getStartInfo()->campState) CSH->startCampaignScenario(param, cb->getStartInfo()->campState); else { GH.dispatchMainThread( - [won, calc]() + [won, highScoreCalc]() { CSH->endGameplay(); GH.defActionsDef = 63; CMM->menu->switchToTab("main"); - GH.windows().createAndPushWindow(won, calc); + GH.windows().createAndPushWindow(won, highScoreCalc); } ); } diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index f8758cc00..96ec74205 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -600,7 +600,7 @@ void CServerHandler::startGameplay(VCMI_LIB_WRAP_NAMESPACE(CGameState) * gameSta CMM->disable(); client = new CClient(); - calc = nullptr; + highScoreCalc = nullptr; switch(si->mode) { @@ -678,14 +678,14 @@ void CServerHandler::startCampaignScenario(HighScoreParameter param, std::shared if (!cs) ourCampaign = si->campState; - if(calc == nullptr) + if(highScoreCalc == nullptr) { - calc = std::make_shared(); - calc->isCampaign = true; - calc->parameters.clear(); + highScoreCalc = std::make_shared(); + highScoreCalc->isCampaign = true; + highScoreCalc->parameters.clear(); } param.campaign = cs->getName(); - calc->parameters.push_back(param); + highScoreCalc->parameters.push_back(param); GH.dispatchMainThread([ourCampaign, this]() { @@ -709,7 +709,7 @@ void CServerHandler::startCampaignScenario(HighScoreParameter param, std::shared else { CMM->openCampaignScreen(ourCampaign->campaignSet); - GH.windows().createAndPushWindow(true, *calc); + GH.windows().createAndPushWindow(true, *highScoreCalc); } }; if(epilogue.hasPrologEpilog) diff --git a/client/CServerHandler.h b/client/CServerHandler.h index 0b8b3f146..9fcccc20c 100644 --- a/client/CServerHandler.h +++ b/client/CServerHandler.h @@ -89,7 +89,7 @@ class CServerHandler : public IServerAPI, public LobbyInfo std::vector myNames; - std::shared_ptr calc; + std::shared_ptr highScoreCalc; void threadHandleConnection(); void threadRunServer(); diff --git a/client/CVideoHandler.cpp b/client/CVideoHandler.cpp index 954a05455..10bd54bf0 100644 --- a/client/CVideoHandler.cpp +++ b/client/CVideoHandler.cpp @@ -349,7 +349,7 @@ void CVideoPlayer::redraw( int x, int y, SDL_Surface *dst, bool update ) show(x, y, dst, update); } -void CVideoPlayer::update( int x, int y, SDL_Surface *dst, bool forceRedraw, bool update, std::function restart) +void CVideoPlayer::update( int x, int y, SDL_Surface *dst, bool forceRedraw, bool update, std::function onVideoRestart) { if (sws == nullptr) return; @@ -368,8 +368,8 @@ void CVideoPlayer::update( int x, int y, SDL_Surface *dst, bool forceRedraw, boo show(x,y,dst,update); else { - if(restart) - restart(); + if(onVideoRestart) + onVideoRestart(); VideoPath filenameToReopen = fname; // create copy to backup this->fname open(filenameToReopen); nextFrame(); diff --git a/client/CVideoHandler.h b/client/CVideoHandler.h index 11317859b..d447d64e6 100644 --- a/client/CVideoHandler.h +++ b/client/CVideoHandler.h @@ -101,7 +101,7 @@ public: void show(int x, int y, SDL_Surface *dst, bool update = true) override; //blit current frame void redraw(int x, int y, SDL_Surface *dst, bool update = true) override; //reblits buffer - void update(int x, int y, SDL_Surface *dst, bool forceRedraw, bool update = true, std::function restart = 0) override; //moves to next frame if appropriate, and blits it or blits only if redraw parameter is set true + void update(int x, int y, SDL_Surface *dst, bool forceRedraw, bool update = true, std::function onVideoRestart = nullptr) override; //moves to next frame if appropriate, and blits it or blits only if redraw parameter is set true // Opens video, calls playVideo, closes video; returns playVideo result (if whole video has been played) bool openAndPlayVideo(const VideoPath & name, int x, int y, bool stopOnKey = false, bool scale = false) override; diff --git a/client/mainmenu/CHighScoreScreen.cpp b/client/mainmenu/CHighScoreScreen.cpp index 8d6a0e624..d16715f57 100644 --- a/client/mainmenu/CHighScoreScreen.cpp +++ b/client/mainmenu/CHighScoreScreen.cpp @@ -122,8 +122,8 @@ void CHighScoreScreen::addButtons() buttons.clear(); - buttons.push_back(std::make_shared(Point(31, 113), AnimationPath::builtin("HISCCAM.DEF"), CButton::tooltip(), [&](){ buttonCampaginClick(); })); - buttons.push_back(std::make_shared(Point(31, 345), AnimationPath::builtin("HISCSTA.DEF"), CButton::tooltip(), [&](){ buttonStandardClick(); })); + buttons.push_back(std::make_shared(Point(31, 113), AnimationPath::builtin("HISCCAM.DEF"), CButton::tooltip(), [&](){ buttonCampaignClick(); })); + buttons.push_back(std::make_shared(Point(31, 345), AnimationPath::builtin("HISCSTA.DEF"), CButton::tooltip(), [&](){ buttonScenarioClick(); })); buttons.push_back(std::make_shared(Point(726, 113), AnimationPath::builtin("HISCRES.DEF"), CButton::tooltip(), [&](){ buttonResetClick(); })); buttons.push_back(std::make_shared(Point(726, 345), AnimationPath::builtin("HISCEXT.DEF"), CButton::tooltip(), [&](){ buttonExitClick(); })); } @@ -138,19 +138,19 @@ void CHighScoreScreen::addHighScores() images.clear(); // Header - texts.push_back(std::make_shared(115, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.433"))); - texts.push_back(std::make_shared(225, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.434"))); + texts.push_back(std::make_shared(115, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.433"))); // rank + texts.push_back(std::make_shared(225, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.434"))); // player if(highscorepage == HighScorePage::SCENARIO) { - texts.push_back(std::make_shared(405, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.435"))); - texts.push_back(std::make_shared(557, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.436"))); - texts.push_back(std::make_shared(627, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.75"))); + texts.push_back(std::make_shared(405, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.435"))); // land + texts.push_back(std::make_shared(557, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.436"))); // days + texts.push_back(std::make_shared(627, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.75"))); // score } else { - texts.push_back(std::make_shared(405, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.672"))); - texts.push_back(std::make_shared(592, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.75"))); + texts.push_back(std::make_shared(405, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.672"))); // campaign + texts.push_back(std::make_shared(592, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.75"))); // score } // Content @@ -161,7 +161,7 @@ void CHighScoreScreen::addHighScores() auto & curData = data[std::to_string(i)]; ColorRGBA color = (i == highlighted) ? Colors::YELLOW : Colors::WHITE; - texts.push_back(std::make_shared(115, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, std::to_string(i+1))); + texts.push_back(std::make_shared(115, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, std::to_string(i + 1))); std::string tmp = curData["player"].String(); TextOperations::trimRightUnicode(tmp, std::max(0, (int)TextOperations::getUnicodeCharactersCount(tmp) - 13)); texts.push_back(std::make_shared(225, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, tmp)); @@ -187,7 +187,7 @@ void CHighScoreScreen::addHighScores() } } -void CHighScoreScreen::buttonCampaginClick() +void CHighScoreScreen::buttonCampaignClick() { highscorepage = HighScorePage::CAMPAIGN; addHighScores(); @@ -195,7 +195,7 @@ void CHighScoreScreen::buttonCampaginClick() redraw(); } -void CHighScoreScreen::buttonStandardClick() +void CHighScoreScreen::buttonScenarioClick() { OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; highscorepage = HighScorePage::SCENARIO; @@ -241,7 +241,7 @@ CHighScoreInputScreen::CHighScoreInputScreen(bool won, HighScoreCalculation calc { int border = 100; int textareaW = ((pos.w - 2 * border) / 4); - std::vector t = { "438", "439", "440", "441", "676" }; + std::vector t = { "438", "439", "440", "441", "676" }; // time, score, difficulty, final score, rank for (int i = 0; i < 5; i++) texts.push_back(std::make_shared(Rect(textareaW * i + border - (textareaW / 2), 450, textareaW, 100), FONT_HIGH_SCORE, ETextAlignment::TOPCENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt." + t[i]))); @@ -264,37 +264,27 @@ int CHighScoreInputScreen::addEntry(std::string text) { if(calc.calculate().cheater) i = 10; - JsonNode node = persistentStorage["highscore"][calc.isCampaign ? "campaign" : "scenario"][std::to_string(i)]; + JsonNode baseNode = persistentStorage["highscore"][calc.isCampaign ? "campaign" : "scenario"]; - if(node["points"].isNull() || node["points"].Integer() <= calc.calculate().total) + if(baseNode[std::to_string(i)]["points"].isNull() || baseNode[std::to_string(i)]["points"].Integer() <= calc.calculate().total) { // move following entries down for (int j = 10; j + 1 >= i; j--) { - JsonNode node = persistentStorage["highscore"][calc.isCampaign ? "campaign" : "scenario"][std::to_string(j - 1)]; + JsonNode node = baseNode[std::to_string(j - 1)]; Settings entry = persistentStorage.write["highscore"][calc.isCampaign ? "campaign" : "scenario"][std::to_string(j)]; entry->Struct() = node.Struct(); } - Settings entry = persistentStorage.write["highscore"][calc.isCampaign ? "campaign" : "scenario"][std::to_string(i)]["player"]; - entry->String() = text; + Settings currentEntry = persistentStorage.write["highscore"][calc.isCampaign ? "campaign" : "scenario"][std::to_string(i)]; + currentEntry["player"].String() = text; if(calc.isCampaign) - { - Settings entry2 = persistentStorage.write["highscore"]["campaign"][std::to_string(i)]["campaign"]; - entry2->String() = calc.calculate().cheater ? CGI->generaltexth->translate("core.genrltxt.260") : calc.parameters[0].campaign; - } + currentEntry["campaign"].String() = calc.calculate().cheater ? CGI->generaltexth->translate("core.genrltxt.260") : calc.parameters[0].campaign; else - { - Settings entry3 = persistentStorage.write["highscore"]["scenario"][std::to_string(i)]["land"]; - entry3->String() = calc.calculate().cheater ? CGI->generaltexth->translate("core.genrltxt.260") : calc.parameters[0].land; - } - Settings entry4 = persistentStorage.write["highscore"][calc.isCampaign ? "campaign" : "scenario"][std::to_string(i)]["days"]; - entry4->Integer() = calc.calculate().sumDays; - Settings entry5 = persistentStorage.write["highscore"][calc.isCampaign ? "campaign" : "scenario"][std::to_string(i)]["points"]; - entry5->Integer() = calc.calculate().cheater ? 0 : calc.calculate().total; - - Settings entry6 = persistentStorage.write["highscore"][calc.isCampaign ? "campaign" : "scenario"][std::to_string(i)]["datetime"]; - entry6->String() = vstd::getFormattedDateTime(std::time(0)); + currentEntry["land"].String() = calc.calculate().cheater ? CGI->generaltexth->translate("core.genrltxt.260") : calc.parameters[0].land; + currentEntry["days"].Integer() = calc.calculate().sumDays; + currentEntry["points"].Integer() = calc.calculate().cheater ? 0 : calc.calculate().total; + currentEntry["datetime"].String() = vstd::getFormattedDateTime(std::time(0)); return i; } @@ -373,14 +363,13 @@ void CHighScoreInputScreen::keyPressed(EShortcut key) } CHighScoreInput::CHighScoreInput(std::function readyCB) - : CWindowObject(0), ready(readyCB) + : CWindowObject(0, ImagePath::builtin("HIGHNAME")), ready(readyCB) { OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; pos = center(Rect(0, 0, 232, 212)); updateShadow(); - background = std::make_shared(ImagePath::builtin("HIGHNAME")); text = std::make_shared(Rect(15, 15, 202, 202), FONT_SMALL, ETextAlignment::TOPCENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.96")); buttonOk = std::make_shared(Point(26, 142), AnimationPath::builtin("MUBCHCK.DEF"), CGI->generaltexth->zelp[560], std::bind(&CHighScoreInput::okay, this), EShortcut::GLOBAL_ACCEPT); diff --git a/client/mainmenu/CHighScoreScreen.h b/client/mainmenu/CHighScoreScreen.h index e995e7b58..00698d4e1 100644 --- a/client/mainmenu/CHighScoreScreen.h +++ b/client/mainmenu/CHighScoreScreen.h @@ -50,8 +50,8 @@ private: void addButtons(); void addHighScores(); - void buttonCampaginClick(); - void buttonStandardClick(); + void buttonCampaignClick(); + void buttonScenarioClick(); void buttonResetClick(); void buttonExitClick(); @@ -71,7 +71,6 @@ public: class CHighScoreInput : public CWindowObject { - std::shared_ptr background; std::shared_ptr text; std::shared_ptr buttonOk; std::shared_ptr buttonCancel; diff --git a/lib/CPlayerState.cpp b/lib/CPlayerState.cpp index faa021bbe..8061901fa 100644 --- a/lib/CPlayerState.cpp +++ b/lib/CPlayerState.cpp @@ -17,7 +17,7 @@ VCMI_LIB_NAMESPACE_BEGIN PlayerState::PlayerState() - : color(-1), human(false), enteredWinningCheatCode(false), + : color(-1), human(false), cheated(false), enteredWinningCheatCode(false), enteredLosingCheatCode(false), status(EPlayerStatus::INGAME) { setNodeType(PLAYER); @@ -29,6 +29,7 @@ PlayerState::PlayerState(PlayerState && other) noexcept: human(other.human), team(other.team), resources(other.resources), + cheated(other.cheated), enteredWinningCheatCode(other.enteredWinningCheatCode), enteredLosingCheatCode(other.enteredLosingCheatCode), status(other.status), diff --git a/lib/CPlayerState.h b/lib/CPlayerState.h index ac8c1d4c6..560c3d636 100644 --- a/lib/CPlayerState.h +++ b/lib/CPlayerState.h @@ -38,6 +38,7 @@ public: std::vector > dwellings; //used for town growth std::vector quests; //store info about all received quests + bool cheated; bool enteredWinningCheatCode, enteredLosingCheatCode; //if true, this player has entered cheat codes for loss / victory EPlayerStatus status; std::optional daysWithoutCastle; @@ -82,6 +83,7 @@ public: h & visitedObjects; h & status; h & daysWithoutCastle; + h & cheated; h & enteredLosingCheatCode; h & enteredWinningCheatCode; h & static_cast(*this); diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index 5fd1116ad..7a36f2abc 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -2509,6 +2509,7 @@ void PlayerCheated::applyGs(CGameState * gs) const gs->getPlayerState(player)->enteredLosingCheatCode = losingCheatCode; gs->getPlayerState(player)->enteredWinningCheatCode = winningCheatCode; + gs->getPlayerState(player)->cheated = true; } void PlayerStartsTurn::applyGs(CGameState * gs) const From c49d38b855ef8e374d9f9477b23081575a185710 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 23 Sep 2023 21:37:40 +0200 Subject: [PATCH 17/24] cheats + grail in town --- client/CPlayerInterface.cpp | 3 +++ server/processors/PlayerMessageProcessor.cpp | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 5bd29f8e4..58b8414c7 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -1695,6 +1695,9 @@ void CPlayerInterface::requestReturningToMainMenu(bool won) for(const CGHeroInstance * h : cb->getHeroesInfo()) if(h->hasArt(ArtifactID::GRAIL)) param.hasGrail = true; + for(const CGTownInstance * t : cb->getTownInfo()) + if(t->builtBuildings.find(BuildingID::GRAIL)) + param.hasGrail = true; param.allDefeated = true; for (PlayerColor player(0); player < PlayerColor::PLAYER_LIMIT; ++player) { diff --git a/server/processors/PlayerMessageProcessor.cpp b/server/processors/PlayerMessageProcessor.cpp index 6f5358c9c..7c5e1e21d 100644 --- a/server/processors/PlayerMessageProcessor.cpp +++ b/server/processors/PlayerMessageProcessor.cpp @@ -513,7 +513,13 @@ void PlayerMessageProcessor::executeCheatCode(const std::string & cheatName, Pla assert(callbacks.count(cheatName)); if (callbacks.count(cheatName)) + { + PlayerCheated pc; + pc.player = player; + gameHandler->sendAndApply(&pc); + callbacks.at(cheatName)(); + } } void PlayerMessageProcessor::sendSystemMessage(std::shared_ptr connection, const std::string & message) From 8a0565eb9b3b8ec4478af0f7252c30dfb267257d Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 23 Sep 2023 22:15:05 +0200 Subject: [PATCH 18/24] use vector --- client/CPlayerInterface.cpp | 4 +- client/mainmenu/CHighScoreScreen.cpp | 59 +++++++++++++++------------- 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 58b8414c7..ec1830637 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -1695,8 +1695,8 @@ void CPlayerInterface::requestReturningToMainMenu(bool won) for(const CGHeroInstance * h : cb->getHeroesInfo()) if(h->hasArt(ArtifactID::GRAIL)) param.hasGrail = true; - for(const CGTownInstance * t : cb->getTownInfo()) - if(t->builtBuildings.find(BuildingID::GRAIL)) + for(const CGTownInstance * t : cb->getTownsInfo()) + if(t->builtBuildings.count(BuildingID::GRAIL)) param.hasGrail = true; param.allDefeated = true; for (PlayerColor player(0); player < PlayerColor::PLAYER_LIMIT; ++player) diff --git a/client/mainmenu/CHighScoreScreen.cpp b/client/mainmenu/CHighScoreScreen.cpp index d16715f57..c9775bf60 100644 --- a/client/mainmenu/CHighScoreScreen.cpp +++ b/client/mainmenu/CHighScoreScreen.cpp @@ -109,7 +109,7 @@ void CHighScoreScreen::showPopupWindow(const Point & cursorPosition) Rect r = Rect(80, 40 + i * 50, 635, 50); if(r.isInside(cursorPosition - pos)) { - std::string tmp = persistentStorage["highscore"][highscorepage == HighScorePage::SCENARIO ? "scenario" : "campaign"][std::to_string(i)]["datetime"].String(); + std::string tmp = persistentStorage["highscore"][highscorepage == HighScorePage::SCENARIO ? "scenario" : "campaign"][i]["datetime"].String(); if(!tmp.empty()) CRClickPopup::createAndPush(tmp); } @@ -158,7 +158,7 @@ void CHighScoreScreen::addHighScores() auto & data = persistentStorage["highscore"][highscorepage == HighScorePage::SCENARIO ? "scenario" : "campaign"]; for (int i = 0; i < 11; i++) { - auto & curData = data[std::to_string(i)]; + auto & curData = data[i]; ColorRGBA color = (i == highlighted) ? Colors::YELLOW : Colors::WHITE; texts.push_back(std::make_shared(115, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, std::to_string(i + 1))); @@ -259,38 +259,41 @@ CHighScoreInputScreen::CHighScoreInputScreen(bool won, HighScoreCalculation calc } int CHighScoreInputScreen::addEntry(std::string text) { - for (int i = 0; i < 11; i++) + std::vector baseNode = persistentStorage["highscore"][calc.isCampaign ? "campaign" : "scenario"].Vector(); + + auto sortFunctor = [](const JsonNode & left, const JsonNode & right) { - if(calc.calculate().cheater) - i = 10; + return left["points"].Integer() > right["points"].Integer(); + }; - JsonNode baseNode = persistentStorage["highscore"][calc.isCampaign ? "campaign" : "scenario"]; - - if(baseNode[std::to_string(i)]["points"].isNull() || baseNode[std::to_string(i)]["points"].Integer() <= calc.calculate().total) + JsonNode newNode = JsonNode(); + newNode["player"].String() = text; + if(calc.isCampaign) + newNode["campaign"].String() = calc.calculate().cheater ? CGI->generaltexth->translate("core.genrltxt.260") : calc.parameters[0].campaign; + else + newNode["land"].String() = calc.calculate().cheater ? CGI->generaltexth->translate("core.genrltxt.260") : calc.parameters[0].land; + newNode["days"].Integer() = calc.calculate().sumDays; + newNode["points"].Integer() = calc.calculate().cheater ? 0 : calc.calculate().total; + newNode["datetime"].String() = vstd::getFormattedDateTime(std::time(0)); + newNode["posFlag"].Bool() = true; + + baseNode.push_back(newNode); + boost::range::sort(baseNode, sortFunctor); + + Settings s = persistentStorage.write["highscore"][calc.isCampaign ? "campaign" : "scenario"]; + int pos = -1; + for (int i = 0; i < baseNode.size(); i++) + { + if(!baseNode[i]["posFlag"].isNull()) { - // move following entries down - for (int j = 10; j + 1 >= i; j--) - { - JsonNode node = baseNode[std::to_string(j - 1)]; - Settings entry = persistentStorage.write["highscore"][calc.isCampaign ? "campaign" : "scenario"][std::to_string(j)]; - entry->Struct() = node.Struct(); - } - - Settings currentEntry = persistentStorage.write["highscore"][calc.isCampaign ? "campaign" : "scenario"][std::to_string(i)]; - currentEntry["player"].String() = text; - if(calc.isCampaign) - currentEntry["campaign"].String() = calc.calculate().cheater ? CGI->generaltexth->translate("core.genrltxt.260") : calc.parameters[0].campaign; - else - currentEntry["land"].String() = calc.calculate().cheater ? CGI->generaltexth->translate("core.genrltxt.260") : calc.parameters[0].land; - currentEntry["days"].Integer() = calc.calculate().sumDays; - currentEntry["points"].Integer() = calc.calculate().cheater ? 0 : calc.calculate().total; - currentEntry["datetime"].String() = vstd::getFormattedDateTime(std::time(0)); - - return i; + baseNode[i]["posFlag"].clear(); + pos = i; } } + + s->Vector() = baseNode; - return -1; + return pos; } void CHighScoreInputScreen::show(Canvas & to) From 96df11a6f2ccc6e40cb8ac5fbb55587829657056 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 23 Sep 2023 22:45:38 +0200 Subject: [PATCH 19/24] always show actual entry --- client/mainmenu/CHighScoreScreen.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/client/mainmenu/CHighScoreScreen.cpp b/client/mainmenu/CHighScoreScreen.cpp index c9775bf60..01ca631a5 100644 --- a/client/mainmenu/CHighScoreScreen.cpp +++ b/client/mainmenu/CHighScoreScreen.cpp @@ -158,10 +158,12 @@ void CHighScoreScreen::addHighScores() auto & data = persistentStorage["highscore"][highscorepage == HighScorePage::SCENARIO ? "scenario" : "campaign"]; for (int i = 0; i < 11; i++) { - auto & curData = data[i]; - ColorRGBA color = (i == highlighted) ? Colors::YELLOW : Colors::WHITE; + bool currentGameNotInListEntry = (i == 10 && highlighted > 10); + auto & curData = data[currentGameNotInListEntry ? highlighted : i]; - texts.push_back(std::make_shared(115, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, std::to_string(i + 1))); + ColorRGBA color = (i == highlighted || currentGameNotInListEntry) ? Colors::YELLOW : Colors::WHITE; + + texts.push_back(std::make_shared(115, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, std::to_string((currentGameNotInListEntry ? highlighted : i) + 1))); std::string tmp = curData["player"].String(); TextOperations::trimRightUnicode(tmp, std::max(0, (int)TextOperations::getUnicodeCharactersCount(tmp) - 13)); texts.push_back(std::make_shared(225, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, tmp)); From 41b03e7c5bfa354989f3fbeca16862a68ac56243 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 23 Sep 2023 23:11:17 +0200 Subject: [PATCH 20/24] fixed some edge cases --- client/mainmenu/CHighScoreScreen.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/client/mainmenu/CHighScoreScreen.cpp b/client/mainmenu/CHighScoreScreen.cpp index 01ca631a5..9521c00d3 100644 --- a/client/mainmenu/CHighScoreScreen.cpp +++ b/client/mainmenu/CHighScoreScreen.cpp @@ -106,10 +106,12 @@ void CHighScoreScreen::showPopupWindow(const Point & cursorPosition) { for (int i = 0; i < 11; i++) { + bool currentGameNotInListEntry = (i == 10 && highlighted > 10); + Rect r = Rect(80, 40 + i * 50, 635, 50); if(r.isInside(cursorPosition - pos)) { - std::string tmp = persistentStorage["highscore"][highscorepage == HighScorePage::SCENARIO ? "scenario" : "campaign"][i]["datetime"].String(); + std::string tmp = persistentStorage["highscore"][highscorepage == HighScorePage::SCENARIO ? "scenario" : "campaign"][currentGameNotInListEntry ? highlighted : i]["datetime"].String(); if(!tmp.empty()) CRClickPopup::createAndPush(tmp); } @@ -184,7 +186,7 @@ void CHighScoreScreen::addHighScores() texts.push_back(std::make_shared(592, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, std::to_string(curData["points"].Integer()))); } - if(curData["points"].Integer() > 0) + if(curData["points"].Integer() > 0 && curData["points"].Integer() <= ((highscorepage == HighScorePage::CAMPAIGN) ? 2500 : 500)) images.push_back(std::make_shared(AnimationPath::builtin("CPRSMALL"), (*CGI->creh)[HighScoreCalculation::getCreatureForPoints(curData["points"].Integer(), highscorepage == HighScorePage::CAMPAIGN)]->getIconIndex(), 0, 670, y - 15 + i * 50)); } } @@ -265,6 +267,8 @@ int CHighScoreInputScreen::addEntry(std::string text) { auto sortFunctor = [](const JsonNode & left, const JsonNode & right) { + if(left["points"].Integer() == right["points"].Integer()) + return left["posFlag"].Integer() > right["posFlag"].Integer(); return left["points"].Integer() > right["points"].Integer(); }; @@ -282,7 +286,6 @@ int CHighScoreInputScreen::addEntry(std::string text) { baseNode.push_back(newNode); boost::range::sort(baseNode, sortFunctor); - Settings s = persistentStorage.write["highscore"][calc.isCampaign ? "campaign" : "scenario"]; int pos = -1; for (int i = 0; i < baseNode.size(); i++) { @@ -292,7 +295,8 @@ int CHighScoreInputScreen::addEntry(std::string text) { pos = i; } } - + + Settings s = persistentStorage.write["highscore"][calc.isCampaign ? "campaign" : "scenario"]; s->Vector() = baseNode; return pos; From 68e536c290c970d5176cbf8f64433f25d0b3fc9f Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sun, 24 Sep 2023 02:00:42 +0200 Subject: [PATCH 21/24] code review --- client/mainmenu/CHighScoreScreen.cpp | 54 ++++++++++------------------ client/mainmenu/CHighScoreScreen.h | 2 ++ 2 files changed, 21 insertions(+), 35 deletions(-) diff --git a/client/mainmenu/CHighScoreScreen.cpp b/client/mainmenu/CHighScoreScreen.cpp index 9521c00d3..3fa3b65fa 100644 --- a/client/mainmenu/CHighScoreScreen.cpp +++ b/client/mainmenu/CHighScoreScreen.cpp @@ -34,46 +34,30 @@ auto HighScoreCalculation::calculate() { - struct result + struct Result { - int basic; - int total; - int sumDays; - bool cheater; + int basic = 0; + int total = 0; + int sumDays = 0; + bool cheater = false; }; - std::vector scoresBasic; - std::vector scoresTotal; - double sumBasic = 0; - double sumTotal = 0; - int sumDays = 0; - bool cheater = false; + Result firstResult, summary; + const std::array difficultyMultipliers{0.8, 1.0, 1.3, 1.6, 2.0}; for(auto & param : parameters) { double tmp = 200 - (param.day + 10) / (param.townAmount + 5) + (param.allDefeated ? 25 : 0) + (param.hasGrail ? 25 : 0); - scoresBasic.push_back(static_cast(tmp)); - sumBasic += tmp; - if(param.difficulty == 0) - tmp *= 0.8; - if(param.difficulty == 1) - tmp *= 1.0; - if(param.difficulty == 2) - tmp *= 1.3; - if(param.difficulty == 3) - tmp *= 1.6; - if(param.difficulty == 4) - tmp *= 2.0; - scoresTotal.push_back(static_cast(tmp)); - sumTotal += tmp; - sumDays += param.day; - if(param.usedCheat) - cheater = true; + firstResult = Result{static_cast(tmp), static_cast(tmp * difficultyMultipliers.at(param.difficulty)), param.day, param.usedCheat}; + summary.basic += firstResult.basic * 5.0 / parameters.size(); + summary.total += firstResult.total * 5.0 / parameters.size(); + summary.sumDays += firstResult.sumDays; + summary.cheater |= firstResult.cheater; } - if(scoresBasic.size() == 1) - return result { scoresBasic[0], scoresTotal[0], sumDays , cheater}; + if(parameters.size() == 1) + return firstResult; - return result { static_cast((sumBasic / parameters.size()) * 5.0), static_cast((sumTotal / parameters.size()) * 5.0), sumDays, cheater }; + return summary; } CreatureID HighScoreCalculation::getCreatureForPoints(int points, bool campaign) @@ -104,9 +88,9 @@ CHighScoreScreen::CHighScoreScreen(HighScorePage highscorepage, int highlighted) void CHighScoreScreen::showPopupWindow(const Point & cursorPosition) { - for (int i = 0; i < 11; i++) + for (int i = 0; i < screenRows; i++) { - bool currentGameNotInListEntry = (i == 10 && highlighted > 10); + bool currentGameNotInListEntry = i == (screenRows - 1) && highlighted > (screenRows - 1); Rect r = Rect(80, 40 + i * 50, 635, 50); if(r.isInside(cursorPosition - pos)) @@ -158,9 +142,9 @@ void CHighScoreScreen::addHighScores() // Content int y = 66; auto & data = persistentStorage["highscore"][highscorepage == HighScorePage::SCENARIO ? "scenario" : "campaign"]; - for (int i = 0; i < 11; i++) + for (int i = 0; i < screenRows; i++) { - bool currentGameNotInListEntry = (i == 10 && highlighted > 10); + bool currentGameNotInListEntry = (i == (screenRows - 1) && highlighted > (screenRows - 1)); auto & curData = data[currentGameNotInListEntry ? highlighted : i]; ColorRGBA color = (i == highlighted || currentGameNotInListEntry) ? Colors::YELLOW : Colors::WHITE; diff --git a/client/mainmenu/CHighScoreScreen.h b/client/mainmenu/CHighScoreScreen.h index 00698d4e1..6c9ae2513 100644 --- a/client/mainmenu/CHighScoreScreen.h +++ b/client/mainmenu/CHighScoreScreen.h @@ -64,6 +64,8 @@ private: std::vector> texts; std::vector> images; + const int screenRows = 11; + int highlighted; public: CHighScoreScreen(HighScorePage highscorepage = HighScorePage::SCENARIO, int highlighted = -1); From c3373ea34c816fe52eff4f7f48781913f3ed8906 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Mon, 25 Sep 2023 18:06:40 +0200 Subject: [PATCH 22/24] code review --- client/CPlayerInterface.cpp | 2 +- client/CServerHandler.cpp | 2 +- client/mainmenu/CHighScoreScreen.cpp | 8 ++--- client/mainmenu/CHighScoreScreen.h | 6 ++-- client/mainmenu/CMainMenu.cpp | 2 +- server/processors/PlayerMessageProcessor.cpp | 31 ++++++++++++-------- server/processors/PlayerMessageProcessor.h | 3 -- 7 files changed, 29 insertions(+), 25 deletions(-) diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index ec1830637..cb7cb5122 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -1706,7 +1706,7 @@ void CPlayerInterface::requestReturningToMainMenu(bool won) if(!ps->checkVanquished()) param.allDefeated = false; } - param.land = cb->getMapHeader()->name; + param.scenarioName = cb->getMapHeader()->name; HighScoreCalculation highScoreCalc; highScoreCalc.parameters.push_back(param); highScoreCalc.isCampaign = false; diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 96ec74205..f0fdeff30 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -684,7 +684,7 @@ void CServerHandler::startCampaignScenario(HighScoreParameter param, std::shared highScoreCalc->isCampaign = true; highScoreCalc->parameters.clear(); } - param.campaign = cs->getName(); + param.campaignName = cs->getName(); highScoreCalc->parameters.push_back(param); GH.dispatchMainThread([ourCampaign, this]() diff --git a/client/mainmenu/CHighScoreScreen.cpp b/client/mainmenu/CHighScoreScreen.cpp index 3fa3b65fa..64da9d598 100644 --- a/client/mainmenu/CHighScoreScreen.cpp +++ b/client/mainmenu/CHighScoreScreen.cpp @@ -156,7 +156,7 @@ void CHighScoreScreen::addHighScores() if(highscorepage == HighScorePage::SCENARIO) { - std::string tmp = curData["land"].String(); + std::string tmp = curData["scenarioName"].String(); TextOperations::trimRightUnicode(tmp, std::max(0, (int)TextOperations::getUnicodeCharactersCount(tmp) - 25)); texts.push_back(std::make_shared(405, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, tmp)); texts.push_back(std::make_shared(557, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, std::to_string(curData["days"].Integer()))); @@ -164,7 +164,7 @@ void CHighScoreScreen::addHighScores() } else { - std::string tmp = curData["campaign"].String(); + std::string tmp = curData["campaignName"].String(); TextOperations::trimRightUnicode(tmp, std::max(0, (int)TextOperations::getUnicodeCharactersCount(tmp) - 25)); texts.push_back(std::make_shared(405, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, tmp)); texts.push_back(std::make_shared(592, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, std::to_string(curData["points"].Integer()))); @@ -259,9 +259,9 @@ int CHighScoreInputScreen::addEntry(std::string text) { JsonNode newNode = JsonNode(); newNode["player"].String() = text; if(calc.isCampaign) - newNode["campaign"].String() = calc.calculate().cheater ? CGI->generaltexth->translate("core.genrltxt.260") : calc.parameters[0].campaign; + newNode["campaignName"].String() = calc.calculate().cheater ? CGI->generaltexth->translate("core.genrltxt.260") : calc.parameters[0].campaignName; else - newNode["land"].String() = calc.calculate().cheater ? CGI->generaltexth->translate("core.genrltxt.260") : calc.parameters[0].land; + newNode["scenarioName"].String() = calc.calculate().cheater ? CGI->generaltexth->translate("core.genrltxt.260") : calc.parameters[0].scenarioName; newNode["days"].Integer() = calc.calculate().sumDays; newNode["points"].Integer() = calc.calculate().cheater ? 0 : calc.calculate().total; newNode["datetime"].String() = vstd::getFormattedDateTime(std::time(0)); diff --git a/client/mainmenu/CHighScoreScreen.h b/client/mainmenu/CHighScoreScreen.h index 6c9ae2513..2fb31a949 100644 --- a/client/mainmenu/CHighScoreScreen.h +++ b/client/mainmenu/CHighScoreScreen.h @@ -27,8 +27,8 @@ public: bool usedCheat; bool hasGrail; bool allDefeated; - std::string campaign; - std::string land; + std::string campaignName; + std::string scenarioName; }; class HighScoreCalculation @@ -68,7 +68,7 @@ private: int highlighted; public: - CHighScoreScreen(HighScorePage highscorepage = HighScorePage::SCENARIO, int highlighted = -1); + CHighScoreScreen(HighScorePage highscorepage, int highlighted = -1); }; class CHighScoreInput : public CWindowObject diff --git a/client/mainmenu/CMainMenu.cpp b/client/mainmenu/CMainMenu.cpp index d922eda25..b5732c621 100644 --- a/client/mainmenu/CMainMenu.cpp +++ b/client/mainmenu/CMainMenu.cpp @@ -392,7 +392,7 @@ void CMainMenu::startTutorial() void CMainMenu::openHighScoreScreen() { - GH.windows().createAndPushWindow(); + GH.windows().createAndPushWindow(CHighScoreScreen::HighScorePage::SCENARIO); return; } diff --git a/server/processors/PlayerMessageProcessor.cpp b/server/processors/PlayerMessageProcessor.cpp index 7c5e1e21d..1e987df1d 100644 --- a/server/processors/PlayerMessageProcessor.cpp +++ b/server/processors/PlayerMessageProcessor.cpp @@ -109,11 +109,18 @@ bool PlayerMessageProcessor::handleHostCommand(PlayerColor player, const std::st } if(words.size() == 2 && words[1] == "cheaters") { - if (cheaters.empty()) - broadcastSystemMessage("No cheaters registered!"); + int playersCheated = 0; + for (const auto & player : gameHandler->gameState()->players) + { + if(player.second.cheated) + { + broadcastSystemMessage("Player " + player.first.toString() + " is cheater!"); + playersCheated++; + } + } - for (auto const & entry : cheaters) - broadcastSystemMessage("Player " + entry.toString() + " is cheater!"); + if (!playersCheated) + broadcastSystemMessage("No cheaters registered!"); return true; } @@ -411,7 +418,10 @@ bool PlayerMessageProcessor::handleCheatCode(const std::string & cheat, PlayerCo std::vector parameters = words; - cheaters.insert(i.first); + PlayerCheated pc; + pc.player = i.first; + gameHandler->sendAndApply(&pc); + playerTargetedCheat = true; parameters.erase(parameters.begin()); @@ -430,7 +440,10 @@ bool PlayerMessageProcessor::handleCheatCode(const std::string & cheat, PlayerCo if (!playerTargetedCheat) executeCheatCode(cheatName, player, currObj, words); - cheaters.insert(player); + PlayerCheated pc; + pc.player = player; + gameHandler->sendAndApply(&pc); + return true; } @@ -513,13 +526,7 @@ void PlayerMessageProcessor::executeCheatCode(const std::string & cheatName, Pla assert(callbacks.count(cheatName)); if (callbacks.count(cheatName)) - { - PlayerCheated pc; - pc.player = player; - gameHandler->sendAndApply(&pc); - callbacks.at(cheatName)(); - } } void PlayerMessageProcessor::sendSystemMessage(std::shared_ptr connection, const std::string & message) diff --git a/server/processors/PlayerMessageProcessor.h b/server/processors/PlayerMessageProcessor.h index d8f9e9878..47d2a8a4a 100644 --- a/server/processors/PlayerMessageProcessor.h +++ b/server/processors/PlayerMessageProcessor.h @@ -21,8 +21,6 @@ class CGameHandler; class PlayerMessageProcessor { - std::set cheaters; - void executeCheatCode(const std::string & cheatName, PlayerColor player, ObjectInstanceID currObj, const std::vector & arguments ); bool handleCheatCode(const std::string & cheatFullCommand, PlayerColor player, ObjectInstanceID currObj); bool handleHostCommand(PlayerColor player, const std::string & message); @@ -60,6 +58,5 @@ public: template void serialize(Handler &h, const int version) { - h & cheaters; } }; From 02c82cb35bdfc905644a9400f4fb2d0dfc4330c6 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Tue, 26 Sep 2023 12:01:16 +0200 Subject: [PATCH 23/24] order --- server/processors/PlayerMessageProcessor.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/processors/PlayerMessageProcessor.cpp b/server/processors/PlayerMessageProcessor.cpp index 1e987df1d..7cadfda29 100644 --- a/server/processors/PlayerMessageProcessor.cpp +++ b/server/processors/PlayerMessageProcessor.cpp @@ -437,13 +437,13 @@ bool PlayerMessageProcessor::handleCheatCode(const std::string & cheat, PlayerCo executeCheatCode(cheatName, i.first, h->id, parameters); } - if (!playerTargetedCheat) - executeCheatCode(cheatName, player, currObj, words); - PlayerCheated pc; pc.player = player; gameHandler->sendAndApply(&pc); + if (!playerTargetedCheat) + executeCheatCode(cheatName, player, currObj, words); + return true; } From 242e0ffa4ac34d6aec80c6256ab6e30174419ba6 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Tue, 26 Sep 2023 22:06:04 +0200 Subject: [PATCH 24/24] fix playername --- client/CPlayerInterface.cpp | 1 + client/mainmenu/CHighScoreScreen.cpp | 6 +++--- client/mainmenu/CHighScoreScreen.h | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index cb7cb5122..2d4110413 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -1707,6 +1707,7 @@ void CPlayerInterface::requestReturningToMainMenu(bool won) param.allDefeated = false; } param.scenarioName = cb->getMapHeader()->name; + param.playerName = cb->getStartInfo()->playerInfos.find(*cb->getPlayerID())->second.name; HighScoreCalculation highScoreCalc; highScoreCalc.parameters.push_back(param); highScoreCalc.isCampaign = false; diff --git a/client/mainmenu/CHighScoreScreen.cpp b/client/mainmenu/CHighScoreScreen.cpp index 64da9d598..9d39343a5 100644 --- a/client/mainmenu/CHighScoreScreen.cpp +++ b/client/mainmenu/CHighScoreScreen.cpp @@ -335,7 +335,7 @@ void CHighScoreInputScreen::clickPressed(const Point & cursorPosition) if(!input) { - input = std::make_shared( + input = std::make_shared(calc.parameters[0].playerName, [&] (std::string text) { if(!text.empty()) @@ -355,7 +355,7 @@ void CHighScoreInputScreen::keyPressed(EShortcut key) clickPressed(Point()); } -CHighScoreInput::CHighScoreInput(std::function readyCB) +CHighScoreInput::CHighScoreInput(std::string playerName, std::function readyCB) : CWindowObject(0, ImagePath::builtin("HIGHNAME")), ready(readyCB) { OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; @@ -369,7 +369,7 @@ CHighScoreInput::CHighScoreInput(std::function readyCB) buttonCancel = std::make_shared(Point(142, 142), AnimationPath::builtin("MUBCANC.DEF"), CGI->generaltexth->zelp[561], std::bind(&CHighScoreInput::abort, this), EShortcut::GLOBAL_CANCEL); statusBar = CGStatusBar::create(std::make_shared(background->getSurface(), Rect(7, 186, 218, 18), 7, 186)); textInput = std::make_shared(Rect(18, 104, 200, 25), FONT_SMALL, 0); - textInput->setText(settings["general"]["playerName"].String()); + textInput->setText(playerName); } void CHighScoreInput::okay() diff --git a/client/mainmenu/CHighScoreScreen.h b/client/mainmenu/CHighScoreScreen.h index 2fb31a949..89e4bdf21 100644 --- a/client/mainmenu/CHighScoreScreen.h +++ b/client/mainmenu/CHighScoreScreen.h @@ -29,6 +29,7 @@ public: bool allDefeated; std::string campaignName; std::string scenarioName; + std::string playerName; }; class HighScoreCalculation @@ -84,7 +85,7 @@ class CHighScoreInput : public CWindowObject void okay(); void abort(); public: - CHighScoreInput(std::function readyCB); + CHighScoreInput(std::string playerName, std::function readyCB); }; class CHighScoreInputScreen : public CWindowObject