Browse Source

Merge remote-tracking branch 'origin/develop' into settings-rework

Dydzio 2 years ago
parent
commit
fc7d2b9778
100 changed files with 1920 additions and 1493 deletions
  1. 12 0
      CMakeLists.txt
  2. 157 0
      ChangeLog.md
  3. 21 1
      Global.h
  4. 4 4
      Mods/vcmi/config/vcmi/english.json
  5. 166 7
      Mods/vcmi/config/vcmi/polish.json
  6. 4 4
      Mods/vcmi/mod.json
  7. 1 0
      README.md
  8. 4 4
      client/CMT.cpp
  9. 3 0
      client/CMakeLists.txt
  10. 7 28
      client/CPlayerInterface.cpp
  11. 0 4
      client/CPlayerInterface.h
  12. 3 9
      client/CServerHandler.cpp
  13. 1 0
      client/CVideoHandler.cpp
  14. 94 67
      client/adventureMap/CAdvMapInt.cpp
  15. 5 2
      client/adventureMap/CAdvMapInt.h
  16. 8 11
      client/adventureMap/CInGameConsole.cpp
  17. 3 3
      client/adventureMap/CInGameConsole.h
  18. 3 2
      client/adventureMap/CMinimap.cpp
  19. 1 1
      client/adventureMap/CMinimap.h
  20. 16 12
      client/adventureMap/CResDataBar.cpp
  21. 2 1
      client/adventureMap/CResDataBar.h
  22. 23 23
      client/adventureMap/CTerrainRect.cpp
  23. 4 4
      client/adventureMap/CTerrainRect.h
  24. 1 0
      client/adventureMap/mapHandler.cpp
  25. 537 468
      client/battle/BattleActionsController.cpp
  26. 59 33
      client/battle/BattleActionsController.h
  27. 65 30
      client/battle/BattleFieldController.cpp
  28. 4 5
      client/battle/BattleFieldController.h
  29. 6 4
      client/battle/BattleInterface.cpp
  30. 2 2
      client/battle/BattleInterface.h
  31. 52 99
      client/battle/BattleInterfaceClasses.cpp
  32. 12 22
      client/battle/BattleInterfaceClasses.h
  33. 24 49
      client/battle/BattleStacksController.cpp
  34. 2 8
      client/battle/BattleStacksController.h
  35. 14 11
      client/battle/BattleWindow.cpp
  36. 4 1
      client/battle/BattleWindow.h
  37. 2 5
      client/battle/CreatureAnimation.cpp
  38. 157 31
      client/gui/CGuiHandler.cpp
  39. 29 5
      client/gui/CGuiHandler.h
  40. 27 16
      client/gui/CIntObject.cpp
  41. 16 18
      client/gui/CIntObject.h
  42. 0 20
      client/gui/CursorHandler.cpp
  43. 0 3
      client/gui/CursorHandler.h
  44. 19 23
      client/gui/MouseButton.h
  45. 1 1
      client/lobby/CBonusSelection.cpp
  46. 2 4
      client/lobby/CSelectionBase.cpp
  47. 1 2
      client/lobby/CSelectionBase.h
  48. 2 7
      client/lobby/SelectionTab.cpp
  49. 2 1
      client/lobby/SelectionTab.h
  50. 23 25
      client/mainmenu/CMainMenu.cpp
  51. 1 0
      client/mainmenu/CPrologEpilogVideo.cpp
  52. 1 1
      client/render/CBitmapHandler.cpp
  53. 26 0
      client/render/Colors.cpp
  54. 50 0
      client/render/Colors.h
  55. 0 2
      client/render/Graphics.cpp
  56. 2 1
      client/renderSDL/CTrueTypeFont.cpp
  57. 2 1
      client/renderSDL/CursorHardware.cpp
  58. 3 2
      client/renderSDL/CursorSoftware.cpp
  59. 28 131
      client/renderSDL/SDL_Extensions.cpp
  60. 10 80
      client/renderSDL/SDL_Extensions.h
  61. 1 1
      client/renderSDL/SDL_PixelAccess.h
  62. 9 12
      client/widgets/Buttons.cpp
  63. 3 3
      client/widgets/Buttons.h
  64. 1 0
      client/widgets/CArtifactHolder.cpp
  65. 8 15
      client/widgets/CGarrisonInt.cpp
  66. 14 11
      client/widgets/MiscWidgets.cpp
  67. 2 0
      client/widgets/MiscWidgets.h
  68. 16 17
      client/widgets/TextControls.cpp
  69. 6 5
      client/widgets/TextControls.h
  70. 2 2
      client/windows/BattleOptionsWindow.cpp
  71. 7 10
      client/windows/CCastleInterface.cpp
  72. 3 2
      client/windows/CCastleInterface.h
  73. 4 5
      client/windows/CCreatureWindow.cpp
  74. 0 1
      client/windows/CHeroWindow.cpp
  75. 0 1
      client/windows/CKingdomInterface.cpp
  76. 5 3
      client/windows/CMessage.cpp
  77. 1 0
      client/windows/CQuestLog.cpp
  78. 1 1
      client/windows/CQuestLog.h
  79. 21 37
      client/windows/CSpellWindow.cpp
  80. 2 1
      client/windows/CSpellWindow.h
  81. 7 3
      client/windows/CTradeWindow.cpp
  82. 1 1
      client/windows/CWindowObject.cpp
  83. 22 29
      client/windows/GUIClasses.cpp
  84. 3 1
      client/windows/GUIClasses.h
  85. 4 4
      client/windows/InfoWindows.cpp
  86. 2 2
      client/windows/SystemOptionsWindow.cpp
  87. 0 3
      cmake_modules/VCMI_lib.cmake
  88. 1 1
      cmake_modules/VersionDefinition.cmake
  89. 2 1
      config/heroes/tower.json
  90. 7 1
      debian/changelog
  91. 2 2
      include/vstd/StringUtils.h
  92. 2 1
      launcher/eu.vcmi.VCMI.metainfo.xml
  93. 2 1
      lib/CHeroHandler.cpp
  94. 1 1
      lib/HeroBonus.h
  95. 0 1
      lib/VCMI_lib.cbp
  96. 4 0
      lib/battle/BattleHex.h
  97. 22 7
      lib/battle/CBattleInfoCallback.h
  98. 1 3
      lib/filesystem/CBinaryReader.cpp
  99. 1 4
      lib/mapObjects/CGHeroInstance.cpp
  100. 2 2
      lib/mapObjects/CRewardableConstructor.cpp

+ 12 - 0
CMakeLists.txt

@@ -173,6 +173,18 @@ set(CMAKE_XCODE_ATTRIBUTE_MARKETING_VERSION ${APP_SHORT_VERSION})
 set(CMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH NO)
 set(CMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH[variant=Debug] YES)
 
+#Check for endian
+if(${CMAKE_VERSION} VERSION_LESS "3.20.0") 
+	include(TestBigEndian)
+	test_big_endian(VCMI_ENDIAN_BIG)
+	if(VCMI_ENDIAN_BIG)
+		add_definitions(-DVCMI_ENDIAN_BIG)
+	endif()
+elseif(${CMAKE_CXX_BYTE_ORDER} EQUAL "BIG_ENDIAN")
+	add_definitions(-DVCMI_ENDIAN_BIG)
+endif()
+
+
 if(ENABLE_LAUNCHER)
 	add_definitions(-DENABLE_LAUNCHER)
 endif()

+ 157 - 0
ChangeLog.md

@@ -1,3 +1,160 @@
+# 1.1.1 -> 1.2.0
+(unreleased, changes for 1.1.1..9c59025)
+
+### GENERAL:
+* Implemented hardware cursor support
+* Heroes III language can now be detected automatically
+* Increased targeted framerate from 48 to 60
+* Fixed bonus values of heroes who specialize in secondary skills
+* Fixed bonus values of heroes who specialize in creatures
+* Fixed damage increase from Adela's Bless specialty
+* Fixed missing obstacles in battles on subterranean terrain 
+* Added vcmifaerie, vcmiazure, vcmiarmy, vcmiexp cheats
+* Video files now play at correct speed
+
+### MAP EDITOR:
+* Added translations to German, Polish, Russian, Ukrainian
+* Implemented cut/copy/paste operations
+* Implemented lasso brush for terrain editing
+
+### LAUNCHER:
+* Icons now have higher resolution, to prevent upscaling artifacts
+* Added translations to German, Polish, Russian, Ukrainian
+* Mods tab layout has been adjusted based on feedback from players
+* Settings tab layout has been redesigned to support longer texts
+* Added button to start map editor directly from Launcher
+
+### ADVENTURE MAP:
+* Implemented different hero movement sounds for offroad movement
+* Cartographers now reveal terrain in the same way as in H3
+* Holding ALT will now show movement points information in status bar
+* It is now not possible to receive rewards from School of War without required gold amount
+* Owned objects, like Mines and Dwellings will always show their owner in status bar
+* It is now possible to interact with on-map Shipyard when no hero is selected
+
+## INTERFACE
+* Fixed white status bar on server connection screen
+* Buttons in battle window now correctly show tooltip in status bar
+* Fixed cursor image during enemy turn in combat
+* Fixed cases of incorrect artifact slot highlighting
+* Game will no longer promt to assemble artifacts if they fall into backpack
+* It is now possible to use in-game console for vcmi commands
+* Stacks sized 1000-9999 units will not be displayed as "1k"
+* It is now possible to select destination town for Town Portal via double-click
+
+### TOWN SCREEN
+* Fixed gradual fade-in of a newly built building
+* Fixed duration of building fade-in to match H3
+* Fixed rendering of Shipyard in Castle
+
+### BATTLES:
+* All effects will now wait for battle opening sound before playing
+* Hex highlighting will now be disabled during enemy turn
+* Fixed incorrect log message when casting spell that kills zero units
+* Implemented animated cursor for spellcasting
+* Fixed multiple issues related to ordering of creature animations
+* Fixed missing flags from hero animations when opening menus
+* Fixed rendering order of moat and grid shadow
+* Jousting bonus from Champions will now be correctly accounted for in damage estimation
+* Building Castle building will now provide walls with additional health point
+* Speed of all battle animations should now match H3
+* Fixed missing obstacles on subterranean terrain
+* Ballistics mechanics now matches H3 logic
+* Arrow Tower base damage should now match H3
+* Destruction of wall segments will now remove ranged attack penalty
+* Force Field cast in front of drawbridge will now block it as in H3
+* Fixed computations for Behemoth defense reduction ability 
+* Bad luck (if enabled) will now multiple all damage by 50%, in line with other damage reducing mechanics
+* Fixed highlighting of movement range for creatures standing on a corpse
+* All battle animations now have same duration/speed as in H3
+* Added missing combat log message on resurrecting creatures
+* Fixed visibility of blue border around targeted creature when spellcaster is making turn
+* Fixed selection highlight when in targeted creature spellcasting mode
+* Hovering over hero now correctly shows hero cursor
+* Creature currently making turn is now highlighted in the Battle Queue 
+* Hovering over creature icon in Battle Queue will highlight this creature in the battlefield
+
+### SPELLS:
+* Hero casting animation will play before spell effect
+* Fire Shield: added sound effect
+* Fire Shield: effect now correctly plays on defending creature
+* Earthquake: added sound effect
+* Earthquake: spell will not select sections that were already destroyed before cast
+* Remove Obstacles: fixed error message when casting on maps without obstacles
+* All area-effect spells (e.g. Fireball) will play their effect animation on top
+* Summoning spells: added fade-in effect for summoned creatures
+* Fixed timing of hit animation for damage-dealing spells
+* Obstacle-creating spells: UI is now locked during effect animation
+* Obstacle-creating spells: added sound effect
+* Added reverse death animation for spells that bring stack back to life
+* Bloodlust: implemented visual effect
+* Teleport: implemented visual fade-out and fade-in effect for teleporting
+* Berserk: Fixed duration of effect
+* Frost Ring: Fixed spell effect range
+* Fixed several cases where multiple different effects could play at the same time
+* All spells that can affecte multiple targets will now highlight affected stacks
+* Bless and Curse now provide +1 or -1 to base damage on Advanced & Expert levels
+
+### ABILITIES:
+* Rebirth (Phoenix): Sound will now play in the same time as animation effect
+* Master Genie spellcasting: Sound will now play in the same time as animation effect
+* Power Lich, Magogs: Sound will now play in the same time as attack animation effect
+* Dragon Breath attack now correctly uses different attack animation if multiple targets are hit
+* Petrification: implemented visual effect
+* Paralyze: added visual effect
+* Blind: Stacks will no longer retailate on attack that blinds them
+* Demon Summon: Added animation effect for summoning
+* Fire shield will no longer trigger on non-adjacent attacks, e.g. from Dragon Breath
+* Weakness now has correct visual effect 
+* Added damage bonus for opposite elements for Elementals
+* Added damage reduction for Magic Elemental attacks against creatures immune to magic
+* Added incoming damage reduction to Petrify
+* Added counter-attack damage reduction for Paralyze
+
+### MODDING:
+* All configurable objects from H3 now have their configuration in json
+* Improvements to functionality of configurable objects
+* It is now possible to define new hero movement sounds in terrains
+* Implemented translation support for mods
+* Files with new Terrains, Roads and Rivers are now validated by game
+* Parameters controlling effect of attack and defences stats on damage are now configurable in defaultMods.json
+* New bonus: LIMITED_SHOOTING_RANGE. Creatures with this bonus can only use ranged attack within specified range
+* Battle window and Random Map Tab now have their layout defined in json file
+* Implemented code support for alternative actions mod
+* Implemented code support for improved random map dialog
+
+# 1.1.0 -> 1.1.1
+
+### GENERAL:
+* Fixed missing sound in Polish version from gog.com 
+* Fixed positioning of main menu buttons in localized versions of H3
+* Fixed crash on transferring artifact to commander
+* Fixed game freeze on receiving multiple artifact assembly dialogs after combat
+* Fixed potential game freeze on end of music playback
+* macOS/iOS: fixed sound glitches
+* Android: upgraded version of SDL library
+* Android: reworked right click gesture and relative pointer mode
+* Improved map loading speed
+* Ubuntu PPA: game will no longer crash on assertion failure
+
+### ADVENTURE MAP:
+* Fixed hero movement lag in single-player games
+* Fixed number of drowned troops on visiting Sirens to match H3
+* iOS: pinch gesture visits current object (Spacebar behavior) instead of activating in-game console
+
+### TOWNS:
+* Fixed displaying growth bonus from Statue of Legion
+* Growth bonus tooltip ordering now matches H3
+* Buy All Units dialog will now buy units starting from the highest level
+
+### LAUNCHER:
+* Local mods can be disabled or uninstalled
+* Fixed styling of Launcher interface
+
+### MAP EDITOR:
+* Fixed saving of roads and rivers
+* Fixed placement of heroes on map
+
 # 1.0.0 -> 1.1.0
 
 ### GENERAL:

+ 21 - 1
Global.h

@@ -725,7 +725,27 @@ namespace vstd
 		return a + (b - a) * f;
 	}
 
-	using boost::math::round;
+	/// converts number into string using metric system prefixes, e.g. 'k' or 'M' to keep resulting strings within specified size
+	/// Note that resulting string may have more symbols than digits: minus sign and prefix symbol
+	template<typename Arithmetic>
+	std::string formatMetric(Arithmetic number, int maxDigits)
+	{
+		Arithmetic max = std::pow(10, maxDigits);
+		if (std::abs(number) < max)
+			return std::to_string(number);
+
+		std::string symbols = " kMGTPE";
+		auto iter = symbols.begin();
+
+		while (std::abs(number) >= max)
+		{
+			number /= 1000;
+			iter++;
+
+			assert(iter != symbols.end());//should be enough even for int64
+		}
+		return std::to_string(number) + *iter;
+	}
 }
 using vstd::operator-=;
 

+ 4 - 4
Mods/vcmi/config/vcmi/english.json

@@ -93,7 +93,7 @@
 	"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_RETALIATION.description": "Enemy cannot retaliate",
 	"core.bonus.BLOCKS_RANGED_RETALIATION.name": "No ranged retaliation",
 	"core.bonus.BLOCKS_RANGED_RETALIATION.description": "Enemy cannot Retaliate by shooting",
 	"core.bonus.CATAPULT.name": "Catapult",
@@ -143,7 +143,7 @@
 	"core.bonus.FREE_SHOOTING.name": "Shoot Close",
 	"core.bonus.FREE_SHOOTING.description": "Can shoot in Close Combat",
 	"core.bonus.FULL_HP_REGENERATION.name": "Regeneration",
-	"core.bonus.FULL_HP_REGENERATION.description": "May Regenerate to full Health",
+	"core.bonus.FULL_HP_REGENERATION.description": "May regenerate to full health",
 	"core.bonus.GARGOYLE.name": "Gargoyle",
 	"core.bonus.GARGOYLE.description": "Cannot be rised or healed",
 	"core.bonus.GENERAL_DAMAGE_REDUCTION.name": "Reduce Damage (${val}%)",
@@ -165,7 +165,7 @@
 	"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" : "",
+	"core.bonus.LIMITED_SHOOTING_RANGE.description" : "Cannot shoot targets beyond ${val} hexes away",
 	"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}%",
@@ -221,7 +221,7 @@
 	"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}% resistance",
+	"core.bonus.SPELL_RESISTANCE_AURA.description": "Nearby stacks get ${val}% magic resistance",
 	"core.bonus.SUMMON_GUARDIANS.name": "Summon guardians",
 	"core.bonus.SUMMON_GUARDIANS.description": "At battle start summons ${subtype.creature} (${val}%)",
 	"core.bonus.SYNERGY_TARGET.name": "Synergizable",

+ 166 - 7
Mods/vcmi/config/vcmi/polish.json

@@ -1,5 +1,5 @@
 {
-	"vcmi.adventureMap.monsterThreat.title"     : "\n\n Poziom zagrożenia: ",
+	"vcmi.adventureMap.monsterThreat.title"     : "\n\n Zagrożenie: ",
 	"vcmi.adventureMap.monsterThreat.levels.0"  : "Zerowy",
 	"vcmi.adventureMap.monsterThreat.levels.1"  : "Bardzo słaby",
 	"vcmi.adventureMap.monsterThreat.levels.2"  : "Słaby",
@@ -13,11 +13,13 @@
 	"vcmi.adventureMap.monsterThreat.levels.10" : "Śmiertelny",
 	"vcmi.adventureMap.monsterThreat.levels.11" : "Nie do pokonania",
 
-	"vcmi.adventureMap.confirmRestartGame"  : "Czy na pewno chcesz zrestartować grę?",
-	"vcmi.adventureMap.noTownWithMarket"    : "Brak dostępnego targowiska!",
-	"vcmi.adventureMap.noTownWithTavern"    : "Brak dostępnego miasta z karczmą!",
-	"vcmi.adventureMap.spellUnknownProblem" : "Nieznany problem z zaklęciem, brak dodatkowych informacji.",
-	"vcmi.adventureMap.playerAttacked"      : "Gracz został zaatakowany: %s",
+	"vcmi.adventureMap.confirmRestartGame"     : "Czy na pewno chcesz zrestartować grę?",
+	"vcmi.adventureMap.noTownWithMarket"       : "Brak dostępnego targowiska!",
+	"vcmi.adventureMap.noTownWithTavern"       : "Brak dostępnego miasta z karczmą!",
+	"vcmi.adventureMap.spellUnknownProblem"    : "Nieznany problem z zaklęciem, brak dodatkowych informacji.",
+	"vcmi.adventureMap.playerAttacked"         : "Gracz został zaatakowany: %s",
+	"vcmi.adventureMap.moveCostDetails"        : "Punkty ruchu - Koszt: %TURNS tury + %POINTS punkty, Pozostałe punkty: %REMAINING",
+	"vcmi.adventureMap.moveCostDetailsNoTurns" : "Punkty ruchu - Koszt: %POINTS punkty, Pozostałe punkty: %REMAINING",
 
 	"vcmi.server.errors.existingProcess"     : "Inny proces vcmiserver został już uruchomiony, zakończ go nim przejdziesz dalej",
 	"vcmi.server.errors.modsIncompatibility" : "Mody wymagane do wczytania gry:",
@@ -66,5 +68,162 @@
 	"vcmi.randomMapTab.widgets.defaultTemplate"      : "domyślny",
 	"vcmi.randomMapTab.widgets.templateLabel"        : "Szablon",
 	"vcmi.randomMapTab.widgets.teamAlignmentsButton" : "Ustaw...",
-	"vcmi.randomMapTab.widgets.teamAlignmentsLabel"  : "Sojusze"
+	"vcmi.randomMapTab.widgets.teamAlignmentsLabel"  : "Sojusze",
+
+	"core.bonus.ADDITIONAL_ATTACK.name": "Podwójne Uderzenie",
+	"core.bonus.ADDITIONAL_ATTACK.description": "Atakuje podwójnie",
+	"core.bonus.ADDITIONAL_RETALIATION.name": "Dodatkowe kontrataki",
+	"core.bonus.ADDITIONAL_RETALIATION.description": "Może kontratakować ${val} dodatkowych razy",
+	"core.bonus.AIR_IMMUNITY.name": "Odporność na powietrze",
+	"core.bonus.AIR_IMMUNITY.description": "Odporny na wszystkie czary szkoły powietrza",
+	"core.bonus.ATTACKS_ALL_ADJACENT.name": "Atakuje wszystko dookoła",
+	"core.bonus.ATTACKS_ALL_ADJACENT.description": "Atakuje wszystkich sąsiadujących wrogów",
+	"core.bonus.BLOCKS_RETALIATION.name": "Bez kontrataku",
+	"core.bonus.BLOCKS_RETALIATION.description": "Wróg nie może kontratakować",
+	"core.bonus.BLOCKS_RANGED_RETALIATION.name": "Brak kontrataku dystansowego",
+	"core.bonus.BLOCKS_RANGED_RETALIATION.description": "Wróg nie może kontratakować poprzez strzelanie",
+	"core.bonus.CATAPULT.name": "Katapulta",
+	"core.bonus.CATAPULT.description": "Atakuje mury obronne",
+	"core.bonus.CATAPULT_EXTRA_SHOTS.name": "Dodatkowe ataki oblężnicze",
+	"core.bonus.CATAPULT_EXTRA_SHOTS.description": "Może uderzyć mury obronne ${val} dodatkowych razy na atak",
+	"core.bonus.CHANGES_SPELL_COST_FOR_ALLY.name": "Zmniejsz koszt czarów (${val})",
+	"core.bonus.CHANGES_SPELL_COST_FOR_ALLY.description": "Zmniejsza koszt czaru bohatera",
+	"core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.name": "Tłumienie magii (${val})",
+	"core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.description": "Zwiększa koszt wrogich czarów",
+	"core.bonus.CHARGE_IMMUNITY.name": "Odporność na szarżę",
+	"core.bonus.CHARGE_IMMUNITY.description": "Odporny na szarżę czempionów",
+	"core.bonus.DARKNESS.name": "Całun ciemności",
+	"core.bonus.DARKNESS.description": "Generuje ${val} wartości promienia mgły wojny",
+	"core.bonus.DEATH_STARE.name": "Spojrzenie Śmierci (${val}%)",
+	"core.bonus.DEATH_STARE.description": "${val}% szans na zabicie jednego stworzenia",
+	"core.bonus.DEFENSIVE_STANCE.name": "Bonus do obrony",
+	"core.bonus.DEFENSIVE_STANCE.description": "+${val} Obrony kiedy broni",
+	"core.bonus.DESTRUCTION.name": "Destrukcja",
+	"core.bonus.DESTRUCTION.description": "Ma ${val}% szans na zabicie dodatkowych jednostek po ataku",
+	"core.bonus.DOUBLE_DAMAGE_CHANCE.name": "Uderzenie Śmierci",
+	"core.bonus.DOUBLE_DAMAGE_CHANCE.description": "${val}% szans na podwójne obrażenia",
+	"core.bonus.DRAGON_NATURE.name": "Smok",
+	"core.bonus.DRAGON_NATURE.description": "Stworzenie posiada smoczą naturę",
+	"core.bonus.DIRECT_DAMAGE_IMMUNITY.name": "Odporność na bezpośrednie obrażenia",
+	"core.bonus.DIRECT_DAMAGE_IMMUNITY.description": "Odporny na czary zadające bezpośrednie obrażenia",
+	"core.bonus.EARTH_IMMUNITY.name": "Odporność na ziemię",
+	"core.bonus.EARTH_IMMUNITY.description": "Odporny na wszystkie czary szkoły ziemi",
+	"core.bonus.ENCHANTER.name": "Czarodziej",
+	"core.bonus.ENCHANTER.description": "Może rzucać masowy czar ${subtype.spell} każdej tury",
+	"core.bonus.ENCHANTED.name": "Zaczarowany",
+	"core.bonus.ENCHANTED.description": "Pod wpływem trwałego ${subtype.spell}",
+	"core.bonus.ENEMY_DEFENCE_REDUCTION.name": "Ignoruje Obronę (${val}%)",
+	"core.bonus.ENEMY_DEFENCE_REDUCTION.description": "Ignoruje część obrony podczas ataku",
+	"core.bonus.FIRE_IMMUNITY.name": "Odporność na ogień",
+	"core.bonus.FIRE_IMMUNITY.description": "Odporny na wszystkie czary szkoły ognia",
+	"core.bonus.FIRE_SHIELD.name": "Ognista tarcza (${val}%)",
+	"core.bonus.FIRE_SHIELD.description": "Odbija część obrażeń z walki wręcz",
+	"core.bonus.FIRST_STRIKE.name": "Pierwsze Uderzenie",
+	"core.bonus.FIRST_STRIKE.description": "To stworzenie atakuje pierwsze w ramach kontrataku",
+	"core.bonus.FEAR.name": "Strach",
+	"core.bonus.FEAR.description": "Wzbudza strach na wrogim stworzeniu",
+	"core.bonus.FEARLESS.name": "Nieustraszony",
+	"core.bonus.FEARLESS.description": "Odporny na strach",
+	"core.bonus.FLYING.name": "Lot",
+	"core.bonus.FLYING.description": "Może latać (ignoruje przeszkody)",
+	"core.bonus.FREE_SHOOTING.name": "Bliski Strzał",
+	"core.bonus.FREE_SHOOTING.description": "Może strzelać w zasięgu walki wręcz",
+	"core.bonus.FULL_HP_REGENERATION.name": "Regeneracja",
+	"core.bonus.FULL_HP_REGENERATION.description": "Może zregenerować się do pełni zdrowia",
+	"core.bonus.GARGOYLE.name": "Gargulec",
+	"core.bonus.GARGOYLE.description": "Nie może być wskrzeszony lub uleczony",
+	"core.bonus.GENERAL_DAMAGE_REDUCTION.name": "Zmniejsz obrażenia (${val}%)",
+	"core.bonus.GENERAL_DAMAGE_REDUCTION.description": "Zmiejsza obrażenia fizyczne z dystansu lub walki wręcz",
+	"core.bonus.HATE.name": "Nienawidzi ${subtype.creature}",
+	"core.bonus.HATE.description": "Zadaje ${val}% więcej obrażeń",
+	"core.bonus.HEALER.name": "Uzdrowiciel",
+	"core.bonus.HEALER.description": "Leczy sprzymierzone jednostki",
+	"core.bonus.HP_REGENERATION.name": "Regeneracja",
+	"core.bonus.HP_REGENERATION.description": "Leczy ${val} punktów zdrowia każdej rundy",
+	"core.bonus.JOUSTING.name": "Szarża Czempiona",
+	"core.bonus.JOUSTING.description": "+5% obrażeń na przebytego heksa",
+	"core.bonus.KING1.name": "Król 1",
+	"core.bonus.KING1.description": "Wrażliwy na podstawowy czar POGROMCA",
+	"core.bonus.KING2.name": "Król 2",
+	"core.bonus.KING2.description": "Wrażliwy na zaawansowany czar POGROMCA",
+	"core.bonus.KING3.name": "Król 3",
+	"core.bonus.KING3.description":"Wrażliwy na ekspercki czar POGROMCA",
+	"core.bonus.LEVEL_SPELL_IMMUNITY.name": "Odporność na czary 1-${val}",
+	"core.bonus.LEVEL_SPELL_IMMUNITY.description": "Odporny na czary 1-${val} poziomu",
+	"core.bonus.LIMITED_SHOOTING_RANGE.name" : "Ograniczony zasięg strzelania",
+	"core.bonus.LIMITED_SHOOTING_RANGE.description" : "Nie może strzelać do celów będących dalej niż ${val} heksów",
+	"core.bonus.LIFE_DRAIN.name": "Wysysa życie (${val}%)",
+	"core.bonus.LIFE_DRAIN.description": "Wysysa ${val}% zadanych obrażeń",
+	"core.bonus.MANA_CHANNELING.name": "Transfer many ${val}%",
+	"core.bonus.MANA_CHANNELING.description": "Daje twojemu bohaterowi manę zużytą przez przeciwnika",
+	"core.bonus.MANA_DRAIN.name": "Wyssanie many",
+	"core.bonus.MANA_DRAIN.description": "Wysysa ${val} many każdej tury",
+	"core.bonus.MAGIC_MIRROR.name": "Magiczne Zwierciadło (${val}%)",
+	"core.bonus.MAGIC_MIRROR.description": "${val}% szans na odbicie ofensywnego czaru do wroga",
+	"core.bonus.MAGIC_RESISTANCE.name": "Odporność na Magię(${MR}%)",
+	"core.bonus.MAGIC_RESISTANCE.description": "${MR}% szans na przeciwstawienie się wrogiemu czarowi",
+	"core.bonus.MIND_IMMUNITY.name": "Odporność na czasy umysłu",
+	"core.bonus.MIND_IMMUNITY.description": "Odporny na czary typu umysłu",
+	"core.bonus.NO_DISTANCE_PENALTY.name": "Brak ograniczeń za odległość",
+	"core.bonus.NO_DISTANCE_PENALTY.description": "Pełne obrażenia z każdego zasięgu",
+	"core.bonus.NO_MELEE_PENALTY.name": "Brak ograniczeń za walkę wręcz",
+	"core.bonus.NO_MELEE_PENALTY.description": "Stworzenie nie ma kar w walce wręcz",
+	"core.bonus.NO_MORALE.name": "Neutralne Morale",
+	"core.bonus.NO_MORALE.description": "Stworzenie jest odporne na efekty morale",
+	"core.bonus.NO_WALL_PENALTY.name": "Brak kar za strzelanie przez przeszkody",
+	"core.bonus.NO_WALL_PENALTY.description": "Pełne obrażenia podczas oblężenia",
+	"core.bonus.NON_LIVING.name": "Nie żyjący",
+	"core.bonus.NON_LIVING.description": "Niewrażliwość na wiele efektów",
+	"core.bonus.RANDOM_SPELLCASTER.name": "Losowy czarodziej",
+	"core.bonus.RANDOM_SPELLCASTER.description": "Może rzucić losowy czar",
+	"core.bonus.RANGED_RETALIATION.name": "Dystansowy kontratak",
+	"core.bonus.RANGED_RETALIATION.description": "Może wykonać kontratak dystansowy",
+	"core.bonus.RECEPTIVE.name": "Absorpcyjny",
+	"core.bonus.RECEPTIVE.description": "Brak odporności na przyjazne zaklęcia",
+	"core.bonus.REBIRTH.name": "Odrodzenie (${val}%)",
+	"core.bonus.REBIRTH.description": "${val}% stworzeń powstanie po śmierci",
+	"core.bonus.RETURN_AFTER_STRIKE.name": "Atak i Powrót",
+	"core.bonus.RETURN_AFTER_STRIKE.description": "Wraca po ataku wręcz",
+	"core.bonus.SELF_LUCK.name": "Pozytywne szczęście",
+	"core.bonus.SELF_LUCK.description": "Zawsze posiada pozytywne szczęście",
+	"core.bonus.SELF_MORALE.name": "Pozytywne Morale",
+	"core.bonus.SELF_MORALE.description": "Zawsze posiada pozytywne morale",
+	"core.bonus.SHOOTER.name": "Dystansowy",
+	"core.bonus.SHOOTER.description": "Stworzenie może strzelać",
+	"core.bonus.SHOOTS_ALL_ADJACENT.name": "Ostrzeliwuje wszystko dookoła",
+	"core.bonus.SHOOTS_ALL_ADJACENT.description": "Ataki dystansowe tego stworzenia uderzają we wszystkie cele na małym obszarze",
+	"core.bonus.SOUL_STEAL.name": "Kradzież dusz",
+	"core.bonus.SOUL_STEAL.description": "Zdobywa ${val} nowych stworzeń za każdego zabitego wroga",
+	"core.bonus.SPELLCASTER.name": "Czarodziej",
+	"core.bonus.SPELLCASTER.description": "Może rzucić ${subtype.spell}",
+	"core.bonus.SPELL_AFTER_ATTACK.name": "Rzuca czar po ataku",
+	"core.bonus.SPELL_AFTER_ATTACK.description": "${val}% szans aby rzucić ${subtype.spell} po ataku",
+	"core.bonus.SPELL_BEFORE_ATTACK.name": "Rzuca czar przed atakiem",
+	"core.bonus.SPELL_BEFORE_ATTACK.description": "${val}% szans aby rzucić ${subtype.spell} przed atakiem",
+	"core.bonus.SPELL_DAMAGE_REDUCTION.name": "Odporność na czary",
+	"core.bonus.SPELL_DAMAGE_REDUCTION.description": "Obrażenia od czarów są zmniejszone o ${val}%.",
+	"core.bonus.SPELL_IMMUNITY.name": "Odporność na czar",
+	"core.bonus.SPELL_IMMUNITY.description": "Odporny na ${subtype.spell}",
+	"core.bonus.SPELL_LIKE_ATTACK.name": "Atak czaropodobny",
+	"core.bonus.SPELL_LIKE_ATTACK.description": "Atakuje z użyciem ${subtype.spell}",
+	"core.bonus.SPELL_RESISTANCE_AURA.name": "Aura Odporności",
+	"core.bonus.SPELL_RESISTANCE_AURA.description": "Pobliskie stworzenia otrzymują ${val}% magicznej odporności",
+	"core.bonus.SUMMON_GUARDIANS.name": "Wezwij strażników",
+	"core.bonus.SUMMON_GUARDIANS.description": "Na początku walki wzywa ${subtype.creature} (${val}%)",
+	"core.bonus.SYNERGY_TARGET.name": "Synergiczny",
+	"core.bonus.SYNERGY_TARGET.description": "To stworzenie jest podatne na efekt synergii",
+	"core.bonus.TWO_HEX_ATTACK_BREATH.name": "Zionięcie",
+	"core.bonus.TWO_HEX_ATTACK_BREATH.description": "Atak zionący (zasięg 2 heksów)",
+	"core.bonus.THREE_HEADED_ATTACK.name": "Atak trzema głowami",
+	"core.bonus.THREE_HEADED_ATTACK.description": "Atakuje trzy sąsiadujące jednostki",
+	"core.bonus.TRANSMUTATION.name": "Transmutacja",
+	"core.bonus.TRANSMUTATION.description": "${val}% szans aby przetransformować atakowaną jednostkę na inny typ",
+	"core.bonus.UNDEAD.name": "Nieumarły",
+	"core.bonus.UNDEAD.description": "Stworzenie jest nieumarłe",
+	"core.bonus.UNLIMITED_RETALIATIONS.name": "Nieskończone kontrataki",
+	"core.bonus.UNLIMITED_RETALIATIONS.description": "Kontratakuje nieskończoną ilość razy",
+	"core.bonus.WATER_IMMUNITY.name": "Odporność na wodę",
+	"core.bonus.WATER_IMMUNITY.description": "Odporny na wszystkie czary szkoły wody",
+	"core.bonus.WIDE_BREATH.name": "Szerokie zionięcie",
+	"core.bonus.WIDE_BREATH.description": "Szeroki atak zionięciem (wiele heksów)"
 }

+ 4 - 4
Mods/vcmi/mod.json

@@ -14,10 +14,10 @@
 	},
 	
 	"polish" : {
-		"name" : "VCMI essential files",
-		"description" : "Essential files required for VCMI to run correctly",
-		"author" : "VCMI Team",
-		"modType" : "Graphical",
+		"name" : "Podstawowe pliki VCMI",
+		"description" : "Dodatkowe pliki wymagane do prawidłowego działania VCMI",
+		"author" : "Zespół VCMI",
+		"modType" : "Graficzny",
 		
 		"translations" : [
 			"config/vcmi/polish.json"

+ 1 - 0
README.md

@@ -1,6 +1,7 @@
 [![GitHub](https://github.com/vcmi/vcmi/actions/workflows/github.yml/badge.svg)](https://github.com/vcmi/vcmi/actions/workflows/github.yml)
 [![AppVeyor Build status](https://ci.appveyor.com/api/projects/status/github/vcmi/vcmi?branch=develop&svg=true)](https://ci.appveyor.com/project/vcmi/vcmi)
 [![Coverity Scan Build Status](https://scan.coverity.com/projects/vcmi/badge.svg)](https://scan.coverity.com/projects/vcmi)
+[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.1.1/total)](https://github.com/vcmi/vcmi/releases/tag/1.1.1)
 [![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.1.0/total)](https://github.com/vcmi/vcmi/releases/tag/1.1.0)
 [![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/total)](https://github.com/vcmi/vcmi/releases)
 # VCMI Project

+ 4 - 4
client/CMT.cpp

@@ -54,7 +54,7 @@
 #include <SDL.h>
 
 #ifdef VCMI_WINDOWS
-#include "SDL_syswm.h"
+#include <SDL_syswm.h>
 #endif
 #ifdef VCMI_ANDROID
 #include "lib/CAndroidVMHelper.h"
@@ -839,7 +839,7 @@ static bool recreateWindow(int w, int h, int bpp, bool fullscreen, int displayIn
 	}
 
 
-	#if (SDL_BYTEORDER == SDL_BIG_ENDIAN)
+	#ifdef VCMI_ENDIAN_BIG
 		int bmask = 0xff000000;
 		int gmask = 0x00ff0000;
 		int rmask = 0x0000ff00;
@@ -949,7 +949,7 @@ static void handleEvent(SDL_Event & ev)
 	}
 	else if(ev.type == SDL_USEREVENT)
 	{
-		switch(ev.user.code)
+		switch(static_cast<EUserEvent>(ev.user.code))
 		{
 		case EUserEvent::FORCE_QUIT:
 			{
@@ -1050,7 +1050,7 @@ static void handleEvent(SDL_Event & ev)
 static void mainLoop()
 {
 	SettingsListener resChanged = settings.listen["video"]["fullscreen"];
-	resChanged([](const JsonNode &newState){  CGuiHandler::pushSDLEvent(SDL_USEREVENT, EUserEvent::FULLSCREEN_TOGGLED); });
+	resChanged([](const JsonNode &newState){  CGuiHandler::pushUserEvent(EUserEvent::FULLSCREEN_TOGGLED); });
 
 	inGuiThread.reset(new bool(true));
 	GH.mainFPSmng->init();

+ 3 - 0
client/CMakeLists.txt

@@ -54,6 +54,7 @@ set(client_SRCS
 	render/CFadeAnimation.cpp
 	render/Canvas.cpp
 	render/ColorFilter.cpp
+	render/Colors.cpp
 	render/Graphics.cpp
 	render/IFont.cpp
 
@@ -140,6 +141,7 @@ set(client_HEADERS
 	gui/CIntObject.h
 	gui/CursorHandler.h
 	gui/InterfaceObjectConfigurable.h
+	gui/MouseButton.h
 	gui/NotificationHandler.h
 	gui/TextAlignment.h
 
@@ -164,6 +166,7 @@ set(client_HEADERS
 	render/CFadeAnimation.h
 	render/Canvas.h
 	render/ColorFilter.h
+	render/Colors.h
 	render/Graphics.h
 	render/ICursor.h
 	render/IFont.h

+ 7 - 28
client/CPlayerInterface.cpp

@@ -23,6 +23,7 @@
 #include "gui/CursorHandler.h"
 #include "windows/CKingdomInterface.h"
 #include "CGameInfo.h"
+#include "CMT.h"
 #include "windows/CHeroWindow.h"
 #include "windows/CCreatureWindow.h"
 #include "windows/CQuestLog.h"
@@ -87,8 +88,6 @@
 		return;						\
 	RETURN_IF_QUICK_COMBAT
 
-using namespace CSDL_Ext;
-
 extern std::queue<SDL_Event> SDLEventsQueue;
 extern boost::mutex eventsM;
 boost::recursive_mutex * CPlayerInterface::pim = new boost::recursive_mutex;
@@ -1043,7 +1042,7 @@ void CPlayerInterface::showComp(const Component &comp, std::string message)
 void CPlayerInterface::showInfoDialog(const std::string &text, const std::vector<Component> & components, int soundID)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
-	if (settings["session"]["autoSkip"].Bool() && !LOCPLINT->shiftPressed())
+	if (settings["session"]["autoSkip"].Bool() && !GH.isKeyboardShiftDown())
 	{
 		return;
 	}
@@ -1067,7 +1066,7 @@ void CPlayerInterface::showInfoDialog(const std::string &text, const std::vector
 	LOG_TRACE_PARAMS(logGlobal, "player=%s, text=%s, is LOCPLINT=%d", playerID % text % (this==LOCPLINT));
 	waitWhileDialog();
 
-	if (settings["session"]["autoSkip"].Bool() && !LOCPLINT->shiftPressed())
+	if (settings["session"]["autoSkip"].Bool() && !GH.isKeyboardShiftDown())
 	{
 		return;
 	}
@@ -1328,16 +1327,6 @@ void CPlayerInterface::moveHero( const CGHeroInstance *h, CGPath path )
 	boost::thread moveHeroTask(std::bind(&CPlayerInterface::doMoveHero,this,h,path));
 }
 
-bool CPlayerInterface::shiftPressed() const
-{
-	return isShiftKeyDown();
-}
-
-bool CPlayerInterface::altPressed() const
-{
-	return isAltKeyDown();
-}
-
 void CPlayerInterface::showGarrisonDialog( const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits, QueryID queryID)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
@@ -1558,11 +1547,6 @@ void CPlayerInterface::playerBlocked(int reason, bool start)
 	}
 }
 
-bool CPlayerInterface::ctrlPressed() const
-{
-	return isCtrlKeyDown();
-}
-
 const CArmedInstance * CPlayerInterface::getSelection()
 {
 	return currentSelection;
@@ -1604,7 +1588,7 @@ void CPlayerInterface::update()
 
 	if (!adventureInt || adventureInt->isActive())
 		GH.simpleRedraw();
-	else if((adventureInt->swipeEnabled && adventureInt->swipeMovementRequested) || adventureInt->scrollingDir)
+	else if((adventureInt->swipeEnabled && adventureInt->swipeMovementRequested) || (adventureInt->scrollingDir && GH.isKeyboardCtrlDown()))
 		GH.totalRedraw(); //player forces map scrolling though interface is disabled
 	else
 		GH.simpleRedraw();
@@ -1990,7 +1974,7 @@ void CPlayerInterface::acceptTurn()
 	adventureInt->updateNextHero(nullptr);
 	adventureInt->showAll(screen);
 
-	if(settings["session"]["autoSkip"].Bool() && !LOCPLINT->shiftPressed())
+	if(settings["session"]["autoSkip"].Bool() && !GH.isKeyboardShiftDown())
 	{
 		if(CInfoWindow *iw = dynamic_cast<CInfoWindow *>(GH.topInt().get()))
 			iw->close();
@@ -2154,12 +2138,7 @@ void CPlayerInterface::requestReturningToMainMenu(bool won)
 	if(won && cb->getStartInfo()->campState)
 		CSH->startCampaignScenario(cb->getStartInfo()->campState);
 	else
-		sendCustomEvent(EUserEvent::RETURN_TO_MAIN_MENU);
-}
-
-void CPlayerInterface::sendCustomEvent( int code )
-{
-	CGuiHandler::pushSDLEvent(SDL_USEREVENT, code);
+		GH.pushUserEvent(EUserEvent::RETURN_TO_MAIN_MENU);
 }
 
 void CPlayerInterface::askToAssembleArtifact(const ArtifactLocation &al)
@@ -2283,7 +2262,7 @@ void CPlayerInterface::waitForAllDialogs(bool unlockPim)
 
 void CPlayerInterface::proposeLoadingGame()
 {
-	showYesNoDialog(CGI->generaltexth->allTexts[68], [this](){ sendCustomEvent(EUserEvent::RETURN_TO_MENU_LOAD); }, nullptr);
+	showYesNoDialog(CGI->generaltexth->allTexts[68], [](){ GH.pushUserEvent(EUserEvent::RETURN_TO_MENU_LOAD); }, nullptr);
 }
 
 CPlayerInterface::SpellbookLastSetting::SpellbookLastSetting()

+ 0 - 4
client/CPlayerInterface.h

@@ -213,9 +213,6 @@ public:
 	void heroKilled(const CGHeroInstance* hero);
 	void waitWhileDialog(bool unlockPim = true);
 	void waitForAllDialogs(bool unlockPim = true);
-	bool shiftPressed() const; //determines if shift key is pressed (left or right or both)
-	bool ctrlPressed() const; //determines if ctrl key is pressed (left or right or both)
-	bool altPressed() const; //determines if alt key is pressed (left or right or both)
 	void redrawHeroWin(const CGHeroInstance * hero);
 	void openTownWindow(const CGTownInstance * town); //shows townscreen
 	void openHeroWindow(const CGHeroInstance * hero); //shows hero window with given hero
@@ -243,7 +240,6 @@ public:
 	void tryDiggging(const CGHeroInstance *h);
 	void showShipyardDialogOrProblemPopup(const IShipyard *obj); //obj may be town or shipyard;
 	void requestReturningToMainMenu(bool won);
-	void sendCustomEvent(int code);
 	void proposeLoadingGame();
 
 	// Ambient sounds

+ 3 - 9
client/CServerHandler.cpp

@@ -60,8 +60,6 @@
 #include <windows.h>
 #endif
 
-#include <SDL_events.h>
-
 template<typename T> class CApplyOnLobby;
 
 const std::string CServerHandler::localhostAddress{"127.0.0.1"};
@@ -655,14 +653,10 @@ void CServerHandler::endGameplay(bool closeConnection, bool restart)
 
 void CServerHandler::startCampaignScenario(std::shared_ptr<CCampaignState> cs)
 {
-	SDL_Event event;
-	event.type = SDL_USEREVENT;
-	event.user.code = EUserEvent::CAMPAIGN_START_SCENARIO;
 	if(cs)
-		event.user.data1 = CMemorySerializer::deepCopy(*cs.get()).release();
+		GH.pushUserEvent(EUserEvent::CAMPAIGN_START_SCENARIO, CMemorySerializer::deepCopy(*cs.get()).release());
 	else
-		event.user.data1 = CMemorySerializer::deepCopy(*si->campState.get()).release();
-	SDL_PushEvent(&event);
+		GH.pushUserEvent(EUserEvent::CAMPAIGN_START_SCENARIO, CMemorySerializer::deepCopy(*si->campState.get()).release());
 }
 
 void CServerHandler::showServerError(std::string txt)
@@ -824,7 +818,7 @@ void CServerHandler::threadHandleConnection()
 			if(client)
 			{
 				state = EClientState::DISCONNECTING;
-				CGuiHandler::pushSDLEvent(SDL_USEREVENT, EUserEvent::RETURN_TO_MAIN_MENU);
+				CGuiHandler::pushUserEvent(EUserEvent::RETURN_TO_MAIN_MENU);
 			}
 			else
 			{

+ 1 - 0
client/CVideoHandler.cpp

@@ -10,6 +10,7 @@
 #include "StdInc.h"
 #include "CVideoHandler.h"
 
+#include "CMT.h"
 #include "gui/CGuiHandler.h"
 #include "renderSDL/SDL_Extensions.h"
 #include "CPlayerInterface.h"

+ 94 - 67
client/adventureMap/CAdvMapInt.cpp

@@ -31,6 +31,7 @@
 #include "../widgets/TextControls.h"
 #include "../widgets/Buttons.h"
 #include "../windows/SettingsMainContainer.h"
+#include "../CMT.h"
 
 #include "../../CCallback.h"
 #include "../../lib/CConfigHandler.h"
@@ -43,9 +44,6 @@
 #include "../../lib/UnlockGuard.h"
 #include "../../lib/TerrainHandler.h"
 
-#include <SDL_surface.h>
-#include <SDL_events.h>
-
 #define ADVOPT (conf.go()->ac)
 
 std::shared_ptr<CAdvMapInt> adventureInt;
@@ -91,8 +89,8 @@ CAdvMapInt::CAdvMapInt():
 	swipeTargetPosition(int3(-1, -1, -1))
 {
 	pos.x = pos.y = 0;
-	pos.w = screen->w;
-	pos.h = screen->h;
+	pos.w = GH.screenDimensions().x;
+	pos.h = GH.screenDimensions().y;
 	strongInterest = true; // handle all mouse move events to prevent dead mouse move space in fullscreen mode
 	townList.onSelect = std::bind(&CAdvMapInt::selectionChanged,this);
 	bg = IImage::createFromFile(ADVOPT.mainGraphic);
@@ -138,7 +136,7 @@ CAdvMapInt::CAdvMapInt():
 	nextHero     = makeButton(301, std::bind(&CAdvMapInt::fnextHero,this),         ADVOPT.nextHero,     SDLK_h);
 	endTurn      = makeButton(302, std::bind(&CAdvMapInt::fendTurn,this),          ADVOPT.endTurn,      SDLK_e);
 
-	int panelSpaceBottom = screen->h - resdatabar.pos.h - 4;
+	int panelSpaceBottom = GH.screenDimensions().y - resdatabar.pos.h - 4;
 
 	panelMain = std::make_shared<CAdvMapPanel>(nullptr, Point(0, 0));
 	// TODO correct drawing position
@@ -159,7 +157,7 @@ CAdvMapInt::CAdvMapInt():
 	// TODO move configs to resolutions.json, similarly to previous buttons
 	config::ButtonInfo worldViewBackConfig = config::ButtonInfo();
 	worldViewBackConfig.defName = "IOK6432.DEF";
-	worldViewBackConfig.x = screen->w - 73;
+	worldViewBackConfig.x = GH.screenDimensions().x - 73;
 	worldViewBackConfig.y = 343 + 195;
 	worldViewBackConfig.playerColoured = false;
 	panelWorldView->addChildToPanel(
@@ -167,7 +165,7 @@ CAdvMapInt::CAdvMapInt():
 
 	config::ButtonInfo worldViewPuzzleConfig = config::ButtonInfo();
 	worldViewPuzzleConfig.defName = "VWPUZ.DEF";
-	worldViewPuzzleConfig.x = screen->w - 188;
+	worldViewPuzzleConfig.x = GH.screenDimensions().x - 188;
 	worldViewPuzzleConfig.y = 343 + 195;
 	worldViewPuzzleConfig.playerColoured = false;
 	panelWorldView->addChildToPanel( // no help text for this one
@@ -176,7 +174,7 @@ CAdvMapInt::CAdvMapInt():
 
 	config::ButtonInfo worldViewScale1xConfig = config::ButtonInfo();
 	worldViewScale1xConfig.defName = "VWMAG1.DEF";
-	worldViewScale1xConfig.x = screen->w - 191;
+	worldViewScale1xConfig.x = GH.screenDimensions().x - 191;
 	worldViewScale1xConfig.y = 23 + 195;
 	worldViewScale1xConfig.playerColoured = false;
 	panelWorldView->addChildToPanel( // help text is wrong for this button
@@ -184,7 +182,7 @@ CAdvMapInt::CAdvMapInt():
 
 	config::ButtonInfo worldViewScale2xConfig = config::ButtonInfo();
 	worldViewScale2xConfig.defName = "VWMAG2.DEF";
-	worldViewScale2xConfig.x = screen->w - 191 + 63;
+	worldViewScale2xConfig.x = GH.screenDimensions().x- 191 + 63;
 	worldViewScale2xConfig.y = 23 + 195;
 	worldViewScale2xConfig.playerColoured = false;
 	panelWorldView->addChildToPanel( // help text is wrong for this button
@@ -192,7 +190,7 @@ CAdvMapInt::CAdvMapInt():
 
 	config::ButtonInfo worldViewScale4xConfig = config::ButtonInfo();
 	worldViewScale4xConfig.defName = "VWMAG4.DEF";
-	worldViewScale4xConfig.x = screen->w - 191 + 126;
+	worldViewScale4xConfig.x = GH.screenDimensions().x- 191 + 126;
 	worldViewScale4xConfig.y = 23 + 195;
 	worldViewScale4xConfig.playerColoured = false;
 	panelWorldView->addChildToPanel( // help text is wrong for this button
@@ -201,7 +199,7 @@ CAdvMapInt::CAdvMapInt():
 	config::ButtonInfo worldViewUndergroundConfig = config::ButtonInfo();
 	worldViewUndergroundConfig.defName = "IAM010.DEF";
 	worldViewUndergroundConfig.additionalDefs.push_back("IAM003.DEF");
-	worldViewUndergroundConfig.x = screen->w - 115;
+	worldViewUndergroundConfig.x = GH.screenDimensions().x - 115;
 	worldViewUndergroundConfig.y = 343 + 195;
 	worldViewUndergroundConfig.playerColoured = true;
 	worldViewUnderground = makeButton(294, std::bind(&CAdvMapInt::fswitchLevel,this), worldViewUndergroundConfig, SDLK_u);
@@ -617,7 +615,7 @@ void CAdvMapInt::handleMapScrollingUpdate()
 	int scrollSpeed = static_cast<int>(settings["adventure"]["scrollSpeed"].Float());
 	//if advmap needs updating AND (no dialog is shown OR ctrl is pressed)
 	if((animValHitCount % (4 / scrollSpeed)) == 0
-	   && ((GH.topInt().get() == this) || CSDL_Ext::isCtrlKeyDown()))
+	   && GH.isKeyboardCtrlDown())
 	{
 		if((scrollingDir & LEFT) && (position.x > -CGI->mh->frameW))
 			position.x--;
@@ -713,21 +711,46 @@ void CAdvMapInt::centerOn(const CGObjectInstance * obj, bool fade)
 	centerOn(obj->getSightCenter(), fade);
 }
 
-void CAdvMapInt::keyPressed(const SDL_KeyboardEvent & key)
+void CAdvMapInt::keyReleased(const SDL_Keycode &key)
 {
+	if (mode == EAdvMapMode::WORLD_VIEW)
+		return;
 
+	switch (key)
+	{
+		case SDLK_s:
+			if(isActive())
+				GH.pushIntT<CSavingScreen>();
+			return;
+		default:
+		{
+			auto direction = keyToMoveDirection(key);
+
+			if (!direction)
+				return;
+
+			ui8 Dir = (direction->x<0 ? LEFT  : 0) |
+				  (direction->x>0 ? RIGHT : 0) |
+				  (direction->y<0 ? UP    : 0) |
+				  (direction->y>0 ? DOWN  : 0) ;
+
+			scrollingDir &= ~Dir;
+		}
+	}
+}
+
+void CAdvMapInt::keyPressed(const SDL_Keycode & key)
+{
 	if (mode == EAdvMapMode::WORLD_VIEW)
 		return;
 
-	ui8 Dir = 0;
-	SDL_Keycode k = key.keysym.sym;
 	const CGHeroInstance *h = curHero(); //selected hero
 	const CGTownInstance *t = curTown(); //selected town
 
-	switch(k)
+	switch(key)
 	{
 	case SDLK_g:
-		if(key.state != SDL_PRESSED || GH.topInt()->type & BLOCK_ADV_HOTKEYS)
+		if(GH.topInt()->type & BLOCK_ADV_HOTKEYS)
 			return;
 
 		{
@@ -751,13 +774,9 @@ void CAdvMapInt::keyPressed(const SDL_KeyboardEvent & key)
 		if(isActive())
 			LOCPLINT->proposeLoadingGame();
 		return;
-	case SDLK_s:
-		if(isActive() && key.type == SDL_KEYUP)
-			GH.pushIntT<CSavingScreen>();
-		return;
 	case SDLK_d:
 		{
-			if(h && isActive() && LOCPLINT->makingTurn && key.state == SDL_PRESSED)
+			if(h && isActive() && LOCPLINT->makingTurn)
 				LOCPLINT->tryDiggging(h);
 			return;
 		}
@@ -770,17 +789,17 @@ void CAdvMapInt::keyPressed(const SDL_KeyboardEvent & key)
 			LOCPLINT->viewWorldMap();
 		return;
 	case SDLK_r:
-		if(isActive() && LOCPLINT->ctrlPressed())
+		if(isActive() && GH.isKeyboardCtrlDown())
 		{
 			LOCPLINT->showYesNoDialog(CGI->generaltexth->translate("vcmi.adventureMap.confirmRestartGame"),
-				[](){ LOCPLINT->sendCustomEvent(EUserEvent::RESTART_GAME); }, nullptr);
+				[](){ GH.pushUserEvent(EUserEvent::RESTART_GAME); }, nullptr);
 		}
 		return;
 	case SDLK_SPACE: //space - try to revisit current object with selected hero
 		{
 			if(!isActive())
 				return;
-			if(h && key.state == SDL_PRESSED)
+			if(h)
 			{
 				auto unlockPim = vstd::makeUnlockGuard(*CPlayerInterface::pim);
 				//TODO!!!!!!! possible freeze, when GS mutex is locked and network thread can't apply package
@@ -793,7 +812,7 @@ void CAdvMapInt::keyPressed(const SDL_KeyboardEvent & key)
 		return;
 	case SDLK_RETURN:
 		{
-			if(!isActive() || !selection || key.state != SDL_PRESSED)
+			if(!isActive() || !selection)
 				return;
 			if(h)
 				LOCPLINT->openHeroWindow(h);
@@ -803,7 +822,7 @@ void CAdvMapInt::keyPressed(const SDL_KeyboardEvent & key)
 		}
 	case SDLK_ESCAPE:
 		{
-			if(isActive() || GH.topInt().get() != this || !spellBeingCasted || key.state != SDL_PRESSED)
+			if(isActive() || GH.topInt().get() != this || !spellBeingCasted)
 				return;
 
 			leaveCastingMode();
@@ -812,10 +831,10 @@ void CAdvMapInt::keyPressed(const SDL_KeyboardEvent & key)
 	case SDLK_t:
 		{
 			//act on key down if marketplace windows is not already opened
-			if(key.state != SDL_PRESSED || GH.topInt()->type & BLOCK_ADV_HOTKEYS)
+			if(GH.topInt()->type & BLOCK_ADV_HOTKEYS)
 				return;
 
-			if(LOCPLINT->ctrlPressed()) //CTRL + T => open marketplace
+			if(GH.isKeyboardCtrlDown()) //CTRL + T => open marketplace
 			{
 				//check if we have any marketplace
 				const CGTownInstance *townWithMarket = nullptr;
@@ -841,37 +860,29 @@ void CAdvMapInt::keyPressed(const SDL_KeyboardEvent & key)
 		}
 	default:
 		{
-			static const int3 directions[] = {  int3(-1, +1, 0), int3(0, +1, 0), int3(+1, +1, 0),
-												int3(-1, 0, 0),  int3(0, 0, 0),  int3(+1, 0, 0),
-												int3(-1, -1, 0), int3(0, -1, 0), int3(+1, -1, 0) };
+			auto direction = keyToMoveDirection(key);
 
-			//numpad arrow
-			if(CGuiHandler::isArrowKey(k))
-				k = CGuiHandler::arrowToNum(k);
+			if (!direction)
+				return;
 
-			k -= SDLK_KP_1;
+			ui8 Dir = (direction->x<0 ? LEFT  : 0) |
+				  (direction->x>0 ? RIGHT : 0) |
+				  (direction->y<0 ? UP    : 0) |
+				  (direction->y>0 ? DOWN  : 0) ;
 
-			if(k < 0 || k > 8)
-				return;
+			scrollingDir |= Dir;
 
-			if (!CGI->mh->canStartHeroMovement())
+			//ctrl makes arrow move screen, not hero
+			if(GH.isKeyboardCtrlDown())
 				return;
 
-			int3 dir = directions[k];
-
-			if(!isActive() || LOCPLINT->ctrlPressed())//ctrl makes arrow move screen, not hero
-			{
-				Dir = (dir.x<0 ? LEFT  : 0) |
-					  (dir.x>0 ? RIGHT : 0) |
-					  (dir.y<0 ? UP    : 0) |
-					  (dir.y>0 ? DOWN  : 0) ;
-				break;
-			}
+			if(!h || !isActive())
+				return;
 
-			if(!h || key.state != SDL_PRESSED)
-				break;
+			if (!CGI->mh->canStartHeroMovement())
+				return;
 
-			if(k == 4)
+			if(*direction == Point(0,0))
 			{
 				centerOn(h);
 				return;
@@ -879,7 +890,7 @@ void CAdvMapInt::keyPressed(const SDL_KeyboardEvent & key)
 
 			CGPath &path = LOCPLINT->paths[h];
 			terrain.currentPath = &path;
-			int3 dst = h->visitablePos() + dir;
+			int3 dst = h->visitablePos() + int3(direction->x, direction->y, 0);
 			if(dst != verifyPos(dst) || !LOCPLINT->cb->getPathsInfo(h)->getPath(path, dst))
 			{
 				terrain.currentPath = nullptr;
@@ -895,13 +906,29 @@ void CAdvMapInt::keyPressed(const SDL_KeyboardEvent & key)
 
 		return;
 	}
-	if(Dir && key.state == SDL_PRESSED //arrow is pressed
-		&& LOCPLINT->ctrlPressed()
-	)
-		scrollingDir |= Dir;
-	else
-		scrollingDir &= ~Dir;
 }
+
+boost::optional<Point> CAdvMapInt::keyToMoveDirection(const SDL_Keycode & key)
+{
+	switch (key) {
+		case SDLK_DOWN:  return Point( 0, +1);
+		case SDLK_LEFT:  return Point(-1,  0);
+		case SDLK_RIGHT: return Point(+1,  0);
+		case SDLK_UP:    return Point( 0, -1);
+
+		case SDLK_KP_1: return Point(-1, +1);
+		case SDLK_KP_2: return Point( 0, +1);
+		case SDLK_KP_3: return Point(+1, +1);
+		case SDLK_KP_4: return Point(-1,  0);
+		case SDLK_KP_5: return Point( 0,  0);
+		case SDLK_KP_6: return Point(+1,  0);
+		case SDLK_KP_7: return Point(-1, -1);
+		case SDLK_KP_8: return Point( 0, -1);
+		case SDLK_KP_9: return Point(+1, -1);
+	}
+	return boost::none;
+}
+
 void CAdvMapInt::handleRightClick(std::string text, tribool down)
 {
 	if(down)
@@ -972,7 +999,7 @@ void CAdvMapInt::select(const CArmedInstance *sel, bool centerView)
 	heroList.redraw();
 }
 
-void CAdvMapInt::mouseMoved( const SDL_MouseMotionEvent & sEvent )
+void CAdvMapInt::mouseMoved( const Point & cursorPosition )
 {
 #if defined(VCMI_ANDROID) || defined(VCMI_IOS)
 	if(swipeEnabled)
@@ -981,9 +1008,9 @@ void CAdvMapInt::mouseMoved( const SDL_MouseMotionEvent & sEvent )
 	// adventure map scrolling with mouse
 	// currently disabled in world view mode (as it is in OH3), but should work correctly if mode check is removed
 	// don't scroll if there is no window in focus - these events don't seem to correspond to the actual mouse movement
-	if(!CSDL_Ext::isCtrlKeyDown() && isActive() && sEvent.windowID != 0 && mode == EAdvMapMode::NORMAL)
+	if(!GH.isKeyboardCtrlDown() && isActive() && mode == EAdvMapMode::NORMAL)
 	{
-		if(sEvent.x<15)
+		if(cursorPosition.x<15)
 		{
 			scrollingDir |= LEFT;
 		}
@@ -991,7 +1018,7 @@ void CAdvMapInt::mouseMoved( const SDL_MouseMotionEvent & sEvent )
 		{
 			scrollingDir &= ~LEFT;
 		}
-		if(sEvent.x>screen->w-15)
+		if(cursorPosition.x > GH.screenDimensions().x - 15)
 		{
 			scrollingDir |= RIGHT;
 		}
@@ -999,7 +1026,7 @@ void CAdvMapInt::mouseMoved( const SDL_MouseMotionEvent & sEvent )
 		{
 			scrollingDir &= ~RIGHT;
 		}
-		if(sEvent.y<15)
+		if(cursorPosition.y<15)
 		{
 			scrollingDir |= UP;
 		}
@@ -1007,7 +1034,7 @@ void CAdvMapInt::mouseMoved( const SDL_MouseMotionEvent & sEvent )
 		{
 			scrollingDir &= ~UP;
 		}
-		if(sEvent.y>screen->h-15)
+		if(cursorPosition.y > GH.screenDimensions().y - 15)
 		{
 			scrollingDir |= DOWN;
 		}
@@ -1246,7 +1273,7 @@ void CAdvMapInt::tileHovered(const int3 &mapPos)
 		const CGPathNode * pathNode = LOCPLINT->cb->getPathsInfo(hero)->getPathInfo(mapPos);
 		assert(pathNode);
 
-		if(LOCPLINT->altPressed() && pathNode->reachable()) //overwrite status bar text with movement info
+		if(GH.isKeyboardAltDown() && pathNode->reachable()) //overwrite status bar text with movement info
 		{
 			ShowMoveDetailsInStatusbar(*hero, *pathNode);
 		}

+ 5 - 2
client/adventureMap/CAdvMapInt.h

@@ -56,6 +56,8 @@ class CAdvMapInt : public CIntObject
 	//Return object that must be active at this tile (=clickable)
 	const CGObjectInstance *getActiveObject(const int3 &tile);
 
+	boost::optional<Point> keyToMoveDirection(const SDL_Keycode & key);
+
 public:
 	CAdvMapInt();
 
@@ -158,8 +160,9 @@ public:
 	void centerOn(const CGObjectInstance *obj, bool fade = false);
 	int3 verifyPos(int3 ver);
 	void handleRightClick(std::string text, tribool down);
-	void keyPressed(const SDL_KeyboardEvent & key) override;
-	void mouseMoved (const SDL_MouseMotionEvent & sEvent) override;
+	void keyPressed(const SDL_Keycode & key) override;
+	void keyReleased(const SDL_Keycode & key) override;
+	void mouseMoved (const Point & cursorPosition) override;
 	bool isActive();
 
 	bool isHeroSleeping(const CGHeroInstance *hero);

+ 8 - 11
client/adventureMap/CInGameConsole.cpp

@@ -11,7 +11,7 @@
 #include "StdInc.h"
 #include "CInGameConsole.h"
 
-#include "../renderSDL/SDL_Extensions.h"
+#include "../render/Colors.h"
 #include "../CGameInfo.h"
 #include "../CMusicHandler.h"
 #include "../CPlayerInterface.h"
@@ -24,7 +24,6 @@
 #include "../../lib/mapObjects/CArmedInstance.h"
 
 #include <SDL_timer.h>
-#include <SDL_events.h>
 
 CInGameConsole::CInGameConsole()
 	: CIntObject(KEYBOARD | TEXTINPUT),
@@ -91,13 +90,11 @@ void CInGameConsole::print(const std::string &txt)
 	}
 }
 
-void CInGameConsole::keyPressed (const SDL_KeyboardEvent & key)
+void CInGameConsole::keyPressed (const SDL_Keycode & key)
 {
-	if(key.type != SDL_KEYDOWN) return;
+	if(!captureAllKeys && key != SDLK_TAB) return; //because user is not entering any text
 
-	if(!captureAllKeys && key.keysym.sym != SDLK_TAB) return; //because user is not entering any text
-
-	switch(key.keysym.sym)
+	switch(key)
 	{
 	case SDLK_TAB:
 	case SDLK_ESCAPE:
@@ -106,7 +103,7 @@ void CInGameConsole::keyPressed (const SDL_KeyboardEvent & key)
 			{
 				endEnteringText(false);
 			}
-			else if(SDLK_TAB == key.keysym.sym)
+			else if(SDLK_TAB == key)
 			{
 				startEnteringText();
 			}
@@ -178,19 +175,19 @@ void CInGameConsole::keyPressed (const SDL_KeyboardEvent & key)
 	}
 }
 
-void CInGameConsole::textInputed(const SDL_TextInputEvent & event)
+void CInGameConsole::textInputed(const std::string & inputtedText)
 {
 	if(!captureAllKeys || enteredText.size() == 0)
 		return;
 	enteredText.resize(enteredText.size()-1);
 
-	enteredText += event.text;
+	enteredText += inputtedText;
 	enteredText += "_";
 
 	refreshEnteredText();
 }
 
-void CInGameConsole::textEdited(const SDL_TextEditingEvent & event)
+void CInGameConsole::textEdited(const std::string & inputtedText)
 {
  //do nothing here
 }

+ 3 - 3
client/adventureMap/CInGameConsole.h

@@ -26,10 +26,10 @@ public:
 	std::string enteredText;
 	void show(SDL_Surface * to) override;
 	void print(const std::string &txt);
-	void keyPressed (const SDL_KeyboardEvent & key) override; //call-in
+	void keyPressed(const SDL_Keycode & key) override;
 
-	void textInputed(const SDL_TextInputEvent & event) override;
-	void textEdited(const SDL_TextEditingEvent & event) override;
+	void textInputed(const std::string & enteredText) override;
+	void textEdited(const std::string & enteredText) override;
 
 	void startEnteringText();
 	void endEnteringText(bool processEnteredText);

+ 3 - 2
client/adventureMap/CMinimap.cpp

@@ -17,6 +17,7 @@
 #include "../CGameInfo.h"
 #include "../CPlayerInterface.h"
 #include "../gui/CGuiHandler.h"
+#include "../render/Colors.h"
 #include "../renderSDL/SDL_PixelAccess.h"
 
 #include "../../CCallback.h"
@@ -229,9 +230,9 @@ void CMinimap::hover(bool on)
 		GH.statusbar->clear();
 }
 
-void CMinimap::mouseMoved(const SDL_MouseMotionEvent & sEvent)
+void CMinimap::mouseMoved(const Point & cursorPosition)
 {
-	if(mouseState(EIntObjMouseBtnType::LEFT))
+	if(mouseState(MouseButton::LEFT))
 		moveAdvMapSelection();
 }
 

+ 1 - 1
client/adventureMap/CMinimap.h

@@ -53,7 +53,7 @@ protected:
 	void clickLeft(tribool down, bool previousState) override;
 	void clickRight(tribool down, bool previousState) override;
 	void hover (bool on) override;
-	void mouseMoved (const SDL_MouseMotionEvent & sEvent) override;
+	void mouseMoved (const Point & cursorPosition) override;
 
 	void moveAdvMapSelection();
 

+ 16 - 12
client/adventureMap/CResDataBar.cpp

@@ -12,6 +12,7 @@
 
 #include "../CGameInfo.h"
 #include "../CPlayerInterface.h"
+#include "../render/Colors.h"
 #include "../renderSDL/SDL_Extensions.h"
 #include "../gui/CGuiHandler.h"
 #include "../widgets/Images.h"
@@ -44,8 +45,6 @@ CResDataBar::CResDataBar(const std::string & defname, int x, int y, int offx, in
 		txtpos[i].second = pos.y + offy;
 	}
 	txtpos[7].first = txtpos[6].first + datedist;
-	datetext =  CGI->generaltexth->allTexts[62]+": %s, " + CGI->generaltexth->allTexts[63]
-	+ ": %s, " + CGI->generaltexth->allTexts[64] + ": %s";
 	addUsedEvents(RCLICK);
 }
 
@@ -68,12 +67,23 @@ CResDataBar::CResDataBar()
 		txtpos[i].second = pos.y + ADVOPT.resOffsetY;
 	}
 	txtpos[7].first = txtpos[6].first + ADVOPT.resDateDist;
-	datetext =  CGI->generaltexth->allTexts[62]+": %s, " + CGI->generaltexth->allTexts[63]
-				+ ": %s, " + CGI->generaltexth->allTexts[64] + ": %s";
+
 }
 
 CResDataBar::~CResDataBar() = default;
 
+std::string CResDataBar::buildDateString()
+{
+	std::string pattern = "%s: %d, %s: %d, %s: %d";
+
+	auto formatted = boost::format(pattern)
+		% CGI->generaltexth->translate("core.genrltxt.62") % LOCPLINT->cb->getDate(Date::MONTH)
+		% CGI->generaltexth->translate("core.genrltxt.63") % LOCPLINT->cb->getDate(Date::WEEK)
+		% CGI->generaltexth->translate("core.genrltxt.64") % LOCPLINT->cb->getDate(Date::DAY_OF_WEEK);
+
+	return boost::str(formatted);
+}
+
 void CResDataBar::draw(SDL_Surface * to)
 {
 	//TODO: all this should be labels, but they require proper text update on change
@@ -81,15 +91,9 @@ void CResDataBar::draw(SDL_Surface * to)
 	{
 		std::string text = boost::lexical_cast<std::string>(LOCPLINT->cb->getResourceAmount(i));
 
-		graphics->fonts[FONT_SMALL]->renderTextLeft(to, text, Colors::WHITE, Point(txtpos[i].first,txtpos[i].second));
+		graphics->fonts[FONT_SMALL]->renderTextLeft(to, text, Colors::WHITE, Point(txtpos[i].first, txtpos[i].second));
 	}
-	std::vector<std::string> temp;
-
-	temp.push_back(boost::lexical_cast<std::string>(LOCPLINT->cb->getDate(Date::MONTH)));
-	temp.push_back(boost::lexical_cast<std::string>(LOCPLINT->cb->getDate(Date::WEEK)));
-	temp.push_back(boost::lexical_cast<std::string>(LOCPLINT->cb->getDate(Date::DAY_OF_WEEK)));
-
-	graphics->fonts[FONT_SMALL]->renderTextLeft(to, CSDL_Ext::processStr(datetext,temp), Colors::WHITE, Point(txtpos[7].first,txtpos[7].second));
+	graphics->fonts[FONT_SMALL]->renderTextLeft(to, buildDateString(), Colors::WHITE, Point(txtpos[7].first, txtpos[7].second));
 }
 
 void CResDataBar::show(SDL_Surface * to)

+ 2 - 1
client/adventureMap/CResDataBar.h

@@ -15,11 +15,12 @@
 /// Current date is displayed too
 class CResDataBar : public CIntObject
 {
+	std::string buildDateString();
+
 public:
 	std::shared_ptr<CPicture> background;
 
 	std::vector<std::pair<int,int> > txtpos;
-	std::string datetext;
 
 	void clickRight(tribool down, bool previousState) override;
 	CResDataBar();

+ 23 - 23
client/adventureMap/CTerrainRect.cpp

@@ -22,13 +22,14 @@
 #include "../render/IImage.h"
 #include "../renderSDL/SDL_Extensions.h"
 #include "../widgets/TextControls.h"
+#include "../CMT.h"
 
 #include "../../CCallback.h"
 #include "../../lib/CConfigHandler.h"
 #include "../../lib/mapping/CMap.h"
 #include "../../lib/CPathfinder.h"
 
-#include <SDL_events.h>
+#include <SDL_surface.h>
 
 #define ADVOPT (conf.go()->ac)
 
@@ -110,32 +111,31 @@ void CTerrainRect::clickMiddle(tribool down, bool previousState)
 	handleSwipeStateChange((bool)down == true);
 }
 
-void CTerrainRect::mouseMoved(const SDL_MouseMotionEvent & sEvent)
+void CTerrainRect::mouseMoved(const Point & cursorPosition)
 {
-	handleHover(sEvent);
+	handleHover(cursorPosition);
 
 	if(!adventureInt->swipeEnabled)
 		return;
 
-	handleSwipeMove(sEvent);
+	handleSwipeMove(cursorPosition);
 }
 
-void CTerrainRect::handleSwipeMove(const SDL_MouseMotionEvent & sEvent)
+void CTerrainRect::handleSwipeMove(const Point & cursorPosition)
 {
 #if defined(VCMI_ANDROID) || defined(VCMI_IOS)
-	if(sEvent.state == 0 || GH.multifinger) // any "button" is enough on mobile
+	if(!GH.isMouseButtonPressed() || GH.multifinger) // any "button" is enough on mobile
+		return;
 #else
-	if((sEvent.state & SDL_BUTTON_MMASK) == 0) // swipe only works with middle mouse on other platforms
-#endif
-	{
+	if(!GH.isMouseButtonPressed(MouseButton::MIDDLE)) // swipe only works with middle mouse on other platforms
 		return;
-	}
+#endif
 
 	if(!isSwiping)
 	{
 		// try to distinguish if this touch was meant to be a swipe or just fat-fingering press
-		if(abs(sEvent.x - swipeInitialRealPos.x) > SwipeTouchSlop ||
-		   abs(sEvent.y - swipeInitialRealPos.y) > SwipeTouchSlop)
+		if(abs(cursorPosition.x - swipeInitialRealPos.x) > SwipeTouchSlop ||
+		   abs(cursorPosition.y - swipeInitialRealPos.y) > SwipeTouchSlop)
 		{
 			isSwiping = true;
 		}
@@ -144,9 +144,9 @@ void CTerrainRect::handleSwipeMove(const SDL_MouseMotionEvent & sEvent)
 	if(isSwiping)
 	{
 		adventureInt->swipeTargetPosition.x =
-			swipeInitialMapPos.x + static_cast<si32>(swipeInitialRealPos.x - sEvent.x) / 32;
+			swipeInitialMapPos.x + static_cast<si32>(swipeInitialRealPos.x - cursorPosition.x) / 32;
 		adventureInt->swipeTargetPosition.y =
-			swipeInitialMapPos.y + static_cast<si32>(swipeInitialRealPos.y - sEvent.y) / 32;
+			swipeInitialMapPos.y + static_cast<si32>(swipeInitialRealPos.y - cursorPosition.y) / 32;
 		adventureInt->swipeMovementRequested = true;
 	}
 }
@@ -155,7 +155,7 @@ bool CTerrainRect::handleSwipeStateChange(bool btnPressed)
 {
 	if(btnPressed)
 	{
-		swipeInitialRealPos = int3(GH.getCursorPosition().x, GH.getCursorPosition().y, 0);
+		swipeInitialRealPos = Point(GH.getCursorPosition().x, GH.getCursorPosition().y);
 		swipeInitialMapPos = int3(adventureInt->position);
 		return true;
 	}
@@ -167,9 +167,9 @@ bool CTerrainRect::handleSwipeStateChange(bool btnPressed)
 	return false;
 }
 
-void CTerrainRect::handleHover(const SDL_MouseMotionEvent &sEvent)
+void CTerrainRect::handleHover(const Point & cursorPosition)
 {
-	int3 tHovered = whichTileIsIt(sEvent.x, sEvent.y);
+	int3 tHovered = whichTileIsIt(cursorPosition.x, cursorPosition.y);
 	int3 pom = adventureInt->verifyPos(tHovered);
 
 	if(tHovered != pom) //tile outside the map
@@ -277,17 +277,17 @@ void CTerrainRect::showPath(const Rect & extRect, SDL_Surface * to)
 				}
 				else if(hvx<0)
 				{
-					Rect srcRect = CSDL_Ext::genRect(arrow->height() - hvy, arrow->width(), 0, 0);
+					Rect srcRect (Point(0, 0), Point(arrow->height() - hvy, arrow->width()));
 					arrow->draw(to, x + moveX, y + moveY, &srcRect);
 				}
 				else if (hvy<0)
 				{
-					Rect srcRect = CSDL_Ext::genRect(arrow->height(), arrow->width() - hvx, 0, 0);
+					Rect srcRect (Point(0, 0), Point(arrow->height(), arrow->width() - hvx));
 					arrow->draw(to, x + moveX, y + moveY, &srcRect);
 				}
 				else
 				{
-					Rect srcRect = CSDL_Ext::genRect(arrow->height() - hvy, arrow->width() - hvx, 0, 0);
+					Rect srcRect (Point(0, 0), Point(arrow->height() - hvy, arrow->width() - hvx));
 					arrow->draw(to, x + moveX, y + moveY, &srcRect);
 				}
 			}
@@ -299,17 +299,17 @@ void CTerrainRect::showPath(const Rect & extRect, SDL_Surface * to)
 				}
 				else if(hvx<0)
 				{
-					Rect srcRect = CSDL_Ext::genRect(arrow->height() - hvy, arrow->width(), 0, 0);
+					Rect srcRect (Point(0, 0), Point(arrow->height() - hvy, arrow->width()));
 					arrow->draw(to, x, y, &srcRect);
 				}
 				else if (hvy<0)
 				{
-					Rect srcRect = CSDL_Ext::genRect(arrow->height(), arrow->width() - hvx, 0, 0);
+					Rect srcRect (Point(0, 0), Point(arrow->height(), arrow->width() - hvx));
 					arrow->draw(to, x, y, &srcRect);
 				}
 				else
 				{
-					Rect srcRect = CSDL_Ext::genRect(arrow->height() - hvy, arrow->width() - hvx, 0, 0);
+					Rect srcRect (Point(0, 0), Point(arrow->height() - hvy, arrow->width() - hvx));
 					arrow->draw(to, x, y, &srcRect);
 				}
 			}

+ 4 - 4
client/adventureMap/CTerrainRect.h

@@ -27,12 +27,12 @@ class CTerrainRect : public CIntObject
 	std::shared_ptr<CFadeAnimation> fadeAnim;
 
 	int3 swipeInitialMapPos;
-	int3 swipeInitialRealPos;
+	Point swipeInitialRealPos;
 	bool isSwiping;
 	static constexpr float SwipeTouchSlop = 16.0f;
 
-	void handleHover(const SDL_MouseMotionEvent & sEvent);
-	void handleSwipeMove(const SDL_MouseMotionEvent & sEvent);
+	void handleHover(const Point & cursorPosition);
+	void handleSwipeMove(const Point & cursorPosition);
 	/// handles start/finish of swipe (press/release of corresponding button); returns true if state change was handled
 	bool handleSwipeStateChange(bool btnPressed);
 public:
@@ -48,7 +48,7 @@ public:
 	void clickRight(tribool down, bool previousState) override;
 	void clickMiddle(tribool down, bool previousState) override;
 	void hover(bool on) override;
-	void mouseMoved (const SDL_MouseMotionEvent & sEvent) override;
+	void mouseMoved (const Point & cursorPosition) override;
 	void show(SDL_Surface * to) override;
 	void showAll(SDL_Surface * to) override;
 	void showAnim(SDL_Surface * to);

+ 1 - 0
client/adventureMap/mapHandler.cpp

@@ -13,6 +13,7 @@
 
 #include "../render/CAnimation.h"
 #include "../render/CFadeAnimation.h"
+#include "../render/Colors.h"
 #include "../renderSDL/SDL_Extensions.h"
 #include "../CGameInfo.h"
 #include "../render/Graphics.h"

File diff suppressed because it is too large
+ 537 - 468
client/battle/BattleActionsController.cpp


+ 59 - 33
client/battle/BattleActionsController.h

@@ -14,6 +14,10 @@
 VCMI_LIB_NAMESPACE_BEGIN
 
 class BattleAction;
+namespace spells {
+class Caster;
+enum class Mode;
+}
 
 VCMI_LIB_NAMESPACE_END
 
@@ -34,40 +38,46 @@ class BattleActionsController
 	/// all actions possible to call at the moment by player
 	std::vector<PossiblePlayerBattleAction> possibleActions;
 
-	/// actions possible to take on hovered hex
-	std::vector<PossiblePlayerBattleAction> localActions;
+	/// spell for which player's hero is choosing destination
+	std::shared_ptr<BattleAction> heroSpellToCast;
 
-	/// these actions display message in case of illegal target
-	std::vector<PossiblePlayerBattleAction> illegalActions;
+	/// cached message that was set by this class in status bar
+	std::string currentConsoleMsg;
 
-	/// action that will be performed on l-click
-	PossiblePlayerBattleAction currentAction;
+	/// if true, active stack could possibly cast some target spell
+	const CSpell * creatureSpellToCast;
 
-	/// last action chosen (and saved) by player
-	PossiblePlayerBattleAction selectedAction;
+	bool isCastingPossibleHere (const CStack *sactive, const CStack *shere, BattleHex myNumber);
+	bool canStackMoveHere (const CStack *sactive, BattleHex MyNumber) const; //TODO: move to BattleState / callback
+	std::vector<PossiblePlayerBattleAction> getPossibleActionsForStack (const CStack *stack) const; //called when stack gets its turn
+	void reorderPossibleActionsPriority(const CStack * stack, MouseHoveredHexContext context);
 
-	/// if there are not possible actions to choose from, this action should be show as "illegal" in UI
-	PossiblePlayerBattleAction illegalAction;
+	bool actionIsLegal(PossiblePlayerBattleAction action, BattleHex hoveredHex);
 
-	/// if true, stack currently aims to cats a spell
-	bool creatureCasting;
+	void actionSetCursor(PossiblePlayerBattleAction action, BattleHex hoveredHex);
+	void actionSetCursorBlocked(PossiblePlayerBattleAction action, BattleHex hoveredHex);
 
-	/// if true, player is choosing destination for his spell - only for GUI / console
-	bool spellDestSelectMode;
+	std::string actionGetStatusMessage(PossiblePlayerBattleAction action, BattleHex hoveredHex);
+	std::string actionGetStatusMessageBlocked(PossiblePlayerBattleAction action, BattleHex hoveredHex);
 
-	/// spell for which player is choosing destination
-	std::shared_ptr<BattleAction> spellToCast;
+	void actionRealize(PossiblePlayerBattleAction action, BattleHex hoveredHex);
 
-	/// spell for which player is choosing destination, pointer for convenience
-	const CSpell *currentSpell;
+	PossiblePlayerBattleAction selectAction(BattleHex myNumber);
 
-	/// cached message that was set by this class in status bar
-	std::string currentConsoleMsg;
+	const CStack * getStackForHex(BattleHex myNumber) ;
 
-	bool isCastingPossibleHere (const CStack *sactive, const CStack *shere, BattleHex myNumber);
-	bool canStackMoveHere (const CStack *sactive, BattleHex MyNumber) const; //TODO: move to BattleState / callback
-	std::vector<PossiblePlayerBattleAction> getPossibleActionsForStack (const CStack *stack) const; //called when stack gets its turn
-	void reorderPossibleActionsPriority(const CStack * stack, MouseHoveredHexContext context);
+	/// attempts to initialize spellcasting action for stack
+	/// will silently return if stack is not a spellcaster
+	void tryActivateStackSpellcasting(const CStack *casterStack);
+
+	/// returns spell that is currently being cast by hero or nullptr if none
+	const CSpell * getHeroSpellToCast() const;
+
+	/// if current stack is spellcaster, returns spell being cast, or null othervice
+	const CSpell * getStackSpellToCast( ) const;
+
+	/// returns true if current stack is a spellcaster
+	bool isActiveStackSpellcaster() const;
 
 public:
 	BattleActionsController(BattleInterface & owner);
@@ -75,24 +85,40 @@ public:
 	/// initialize list of potential actions for new active stack
 	void activateStack();
 
-	/// initialize potential actions for spells that can be cast by active stack
+	/// returns true if UI is currently in target selection mode
+	bool spellcastingModeActive() const;
+
+	/// returns true if one of the following is true:
+	/// - we are casting spell by hero
+	/// - we are casting spell by creature in targeted mode (F hotkey)
+	/// - current creature is spellcaster and preferred action for current hex is spellcast
+	bool currentActionSpellcasting(BattleHex hoveredHex);
+
+	/// enter targeted spellcasting mode for creature, e.g. via "F" hotkey
 	void enterCreatureCastingMode();
 
-	/// initialize potential actions for selected spell
+	/// initialize hero spellcasting mode, e.g. on selecting spell in spellbook
 	void castThisSpell(SpellID spellID);
 
 	/// ends casting spell (eg. when spell has been cast or canceled)
 	void endCastingSpell();
 
-	/// update UI (e.g. status bar/cursor) according to new active hex
-	void handleHex(BattleHex myNumber, int eventType);
+	/// update cursor and status bar according to new active hex
+	void onHexHovered(BattleHex hoveredHex);
 
-	/// returns currently selected spell or SpellID::NONE on error
-	SpellID selectedSpell() const;
+	/// called when cursor is no longer over battlefield and cursor/battle log should be reset
+	void onHoverEnded();
+
+	/// performs action according to selected hex
+	void onHexLeftClicked(BattleHex clickedHex);
+
+	/// performs action according to selected hex
+	void onHexRightClicked(BattleHex clickedHex);
+
+	const spells::Caster * getCurrentSpellcaster() const;
+	const CSpell * getCurrentSpell() const;
+	spells::Mode getCurrentCastMode() const;
 
-	/// returns true if UI is currently in target selection mode
-	bool spellcastingModeActive() const;
-	
 	/// methods to work with array of possible actions, needed to control special creatures abilities
 	const std::vector<PossiblePlayerBattleAction> & getPossibleActions() const;
 	void removePossibleAction(PossiblePlayerBattleAction);

+ 65 - 30
client/battle/BattleFieldController.cpp

@@ -34,6 +34,8 @@
 #include "../../lib/CStack.h"
 #include "../../lib/spells/ISpellMechanics.h"
 
+#include <SDL_events.h>
+
 BattleFieldController::BattleFieldController(BattleInterface & owner):
 	owner(owner)
 {
@@ -76,20 +78,11 @@ BattleFieldController::BattleFieldController(BattleInterface & owner):
 
 	backgroundWithHexes = std::make_unique<Canvas>(Point(background->width(), background->height()));
 
-	for (int h = 0; h < GameConstants::BFIELD_SIZE; ++h)
-	{
-		auto hex = std::make_shared<ClickableHex>();
-		hex->myNumber = h;
-		hex->pos = hexPositionAbsolute(h);
-		hex->myInterface = &owner;
-		bfield.push_back(hex);
-	}
-
 	auto accessibility = owner.curInt->cb->getAccesibility();
 	for(int i = 0; i < accessibility.size(); i++)
 		stackCountOutsideHexes[i] = (accessibility[i] == EAccessibility::ACCESSIBLE);
 
-	addUsedEvents(MOVE);
+	addUsedEvents(LCLICK | RCLICK | MOVE);
 	LOCPLINT->cingconsole->pos = this->pos;
 }
 
@@ -105,13 +98,40 @@ void BattleFieldController::createHeroes()
 		owner.defendingHero = std::make_shared<BattleHero>(owner, owner.defendingHeroInstance, true);
 }
 
-void BattleFieldController::mouseMoved(const SDL_MouseMotionEvent &event)
+void BattleFieldController::mouseMoved(const Point & cursorPosition)
 {
+	if (!pos.isInside(cursorPosition))
+	{
+		owner.actionsController->onHoverEnded();
+		return;
+	}
+
 	BattleHex selectedHex = getHoveredHex();
+	owner.actionsController->onHexHovered(selectedHex);
+}
+
+void BattleFieldController::clickLeft(tribool down, bool previousState)
+{
+	if(!down)
+	{
+		BattleHex selectedHex = getHoveredHex();
 
-	owner.actionsController->handleHex(selectedHex, MOVE);
+		if (selectedHex != BattleHex::INVALID)
+			owner.actionsController->onHexLeftClicked(selectedHex);
+	}
 }
 
+void BattleFieldController::clickRight(tribool down, bool previousState)
+{
+	if(down)
+	{
+		BattleHex selectedHex = getHoveredHex();
+
+		if (selectedHex != BattleHex::INVALID)
+			owner.actionsController->onHexRightClicked(selectedHex);
+
+	}
+}
 
 void BattleFieldController::renderBattlefield(Canvas & canvas)
 {
@@ -233,19 +253,9 @@ std::set<BattleHex> BattleFieldController::getHighlightedHexesSpellRange()
 	const spells::Caster *caster = nullptr;
 	const CSpell *spell = nullptr;
 
-	spells::Mode mode = spells::Mode::HERO;
-
-	if(owner.actionsController->spellcastingModeActive())//hero casts spell
-	{
-		spell = owner.actionsController->selectedSpell().toSpell();
-		caster = owner.getActiveHero();
-	}
-	else if(owner.stacksController->activeStackSpellToCast() != SpellID::NONE)//stack casts spell
-	{
-		spell = SpellID(owner.stacksController->activeStackSpellToCast()).toSpell();
-		caster = owner.stacksController->getActiveStack();
-		mode = spells::Mode::CREATURE_ACTIVE;
-	}
+	spells::Mode mode = owner.actionsController->getCurrentCastMode();
+	spell = owner.actionsController->getCurrentSpell();
+	caster = owner.actionsController->getCurrentSpellcaster();
 
 	if(caster && spell) //when casting spell
 	{
@@ -310,7 +320,10 @@ void BattleFieldController::showHighlightedHexes(Canvas & canvas)
 	std::set<BattleHex> hoveredSpell = getHighlightedHexesSpellRange();
 	std::set<BattleHex> hoveredMove  = getHighlightedHexesMovementTarget();
 
-	auto const & hoveredMouse = owner.actionsController->spellcastingModeActive() ? hoveredSpell : hoveredMove;
+	if (getHoveredHex() == BattleHex::INVALID)
+		return;
+
+	auto const & hoveredMouse = owner.actionsController->currentActionSpellcasting(getHoveredHex()) ? hoveredSpell : hoveredMove;
 
 	for(int b=0; b<GameConstants::BFIELD_SIZE; ++b)
 	{
@@ -355,9 +368,31 @@ bool BattleFieldController::isPixelInHex(Point const & position)
 
 BattleHex BattleFieldController::getHoveredHex()
 {
-	for ( auto const & hex : bfield)
-		if (hex->hovered && hex->strictHovered)
-			return hex->myNumber;
+	Point hoverPos = GH.getCursorPosition();
+
+	if (owner.attackingHero)
+	{
+		if (owner.attackingHero->pos.isInside(hoverPos))
+			return BattleHex::HERO_ATTACKER;
+	}
+
+	if (owner.defendingHero)
+	{
+		if (owner.attackingHero->pos.isInside(hoverPos))
+			return BattleHex::HERO_DEFENDER;
+	}
+
+
+	for (int h = 0; h < GameConstants::BFIELD_SIZE; ++h)
+	{
+		Rect hexPosition = hexPositionAbsolute(h);
+
+		if (!hexPosition.isInside(hoverPos))
+			continue;
+
+		if (isPixelInHex(hoverPos - hexPosition.topLeft()))
+			return h;
+	}
 
 	return BattleHex::INVALID;
 }
@@ -520,7 +555,7 @@ BattleHex BattleFieldController::fromWhichHexAttack(BattleHex attackTarget)
 		}
 		default:
 			assert(0);
-			return attackTarget.cloneInDirection(BattleHex::LEFT);
+			return BattleHex::INVALID;
 		}
 	}
 }

+ 4 - 5
client/battle/BattleFieldController.h

@@ -20,7 +20,6 @@ class Point;
 
 VCMI_LIB_NAMESPACE_END
 
-class ClickableHex;
 class BattleHero;
 class Canvas;
 class IImage;
@@ -50,8 +49,6 @@ class BattleFieldController : public CIntObject
 	/// hexes that when in front of a unit cause it's amount box to move back
 	std::array<bool, GameConstants::BFIELD_SIZE> stackCountOutsideHexes;
 
-	std::vector<std::shared_ptr<ClickableHex>> bfield;
-
 	void showHighlightedHex(Canvas & to, BattleHex hex, bool darkBorder);
 
 	std::set<BattleHex> getHighlightedHexesStackRange();
@@ -65,10 +62,12 @@ class BattleFieldController : public CIntObject
 
 	BattleHex::EDir selectAttackDirection(BattleHex myNumber, const Point & point);
 
-	void mouseMoved(const SDL_MouseMotionEvent &event) override;
+	void mouseMoved(const Point & cursorPosition) override;
+	void clickLeft(tribool down, bool previousState) override;
+	void clickRight(tribool down, bool previousState) override;
+
 	void showAll(SDL_Surface * to) override;
 	void show(SDL_Surface * to) override;
-
 public:
 	BattleFieldController(BattleInterface & owner);
 

+ 6 - 4
client/battle/BattleInterface.cpp

@@ -54,7 +54,6 @@ BattleInterface::BattleInterface(const CCreatureSet *army1, const CCreatureSet *
 	, attackerInt(att)
 	, defenderInt(defen)
 	, curInt(att)
-	, myTurn(false)
 	, moveSoundHander(-1)
 {
 	for ( auto & event : animationEvents)
@@ -249,7 +248,6 @@ void BattleInterface::sendCommand(BattleAction *& command, const CStack * actor)
 	if(!tacticsMode)
 	{
 		logGlobal->trace("Setting command for %s", (actor ? actor->nodeName() : "hero"));
-		myTurn = false;
 		stacksController->setActiveStack(nullptr);
 		givenCommand.setn(command);
 	}
@@ -260,6 +258,7 @@ void BattleInterface::sendCommand(BattleAction *& command, const CStack * actor)
 		stacksController->setActiveStack(nullptr);
 		//next stack will be activated when action ends
 	}
+	CCS->curh->set(Cursor::Combat::POINTER);
 }
 
 const CGHeroInstance * BattleInterface::getActiveHero()
@@ -499,7 +498,6 @@ void BattleInterface::displaySpellHit(const CSpell * spell, BattleHex destinatio
 		displaySpellAnimationQueue(spell, spell->animationInfo.hit, destinationTile, true);
 }
 
-
 CPlayerInterface *BattleInterface::getCurrentPlayerInterface() const
 {
 	return curInt.get();
@@ -522,7 +520,6 @@ void BattleInterface::activateStack()
 	if(!s)
 		return;
 
-	myTurn = true;
 	windowObject->updateQueue();
 	windowObject->blockUI(false);
 	fieldController->redrawBackgroundWithHexes();
@@ -530,6 +527,11 @@ void BattleInterface::activateStack()
 	GH.fakeMouseMove();
 }
 
+bool BattleInterface::makingTurn() const
+{
+	return stacksController->getActiveStack() != nullptr;
+}
+
 void BattleInterface::endAction(const BattleAction* action)
 {
 	const CStack *stack = curInt->cb->battleGetStackByID(action->stackNumber);

+ 2 - 2
client/battle/BattleInterface.h

@@ -37,7 +37,6 @@ class Canvas;
 class BattleResultWindow;
 class StackQueue;
 class CPlayerInterface;
-class ClickableHex;
 class CAnimation;
 struct BattleEffect;
 class IImage;
@@ -146,7 +145,8 @@ public:
 
 	static CondSh<BattleAction *> givenCommand; //data != nullptr if we have i.e. moved current unit
 
-	bool myTurn; //if true, interface is active (commands can be ordered)
+	bool makingTurn() const;
+
 	int moveSoundHander; // sound handler used when moving a unit
 
 	BattleInterface(const CCreatureSet *army1, const CCreatureSet *army2, const CGHeroInstance *hero1, const CGHeroInstance *hero2, std::shared_ptr<CPlayerInterface> att, std::shared_ptr<CPlayerInterface> defen, std::shared_ptr<CPlayerInterface> spectatorInt = nullptr);

+ 52 - 99
client/battle/BattleInterfaceClasses.cpp

@@ -30,10 +30,10 @@
 #include "../widgets/Images.h"
 #include "../widgets/TextControls.h"
 #include "../windows/CMessage.h"
-#include "../windows/CCreatureWindow.h"
 #include "../windows/CSpellWindow.h"
 #include "../render/CAnimation.h"
 #include "../adventureMap/CInGameConsole.h"
+#include "../renderSDL/SDL_Extensions.h"
 
 #include "../../CCallback.h"
 #include "../../lib/CStack.h"
@@ -48,9 +48,6 @@
 #include "../../lib/CondSh.h"
 #include "../../lib/mapObjects/CGTownInstance.h"
 
-#include <SDL_surface.h>
-#include <SDL_events.h>
-
 void BattleConsole::showAll(SDL_Surface * to)
 {
 	CIntObject::showAll(to);
@@ -163,12 +160,12 @@ void BattleConsole::setEnteringMode(bool on)
 	if (on)
 	{
 		assert(enteringText == false);
-		CSDL_Ext::startTextInput(pos);
+		GH.startTextInput(pos);
 	}
 	else
 	{
 		assert(enteringText == true);
-		CSDL_Ext::stopTextInput();
+		GH.stopTextInput();
 	}
 	enteringText = on;
 	redraw();
@@ -274,50 +271,29 @@ void BattleHero::setPhase(EHeroAnimType newPhase)
 	nextPhase = EHeroAnimType::HOLDING;
 }
 
-void BattleHero::hover(bool on)
-{
-	//TODO: BROKEN CODE
-	if (on)
-		CCS->curh->set(Cursor::Combat::HERO);
-	else
-		CCS->curh->set(Cursor::Combat::POINTER);
-}
-
-void BattleHero::clickLeft(tribool down, bool previousState)
+void BattleHero::heroLeftClicked()
 {
 	if(owner.actionsController->spellcastingModeActive()) //we are casting a spell
 		return;
 
-	if(boost::logic::indeterminate(down))
-		return;
-
-	if(!hero || down || !owner.myTurn)
+	if(!hero || !owner.makingTurn())
 		return;
 
 	if(owner.getCurrentPlayerInterface()->cb->battleCanCastSpell(hero, spells::Mode::HERO) == ESpellCastProblem::OK) //check conditions
 	{
-		BattleHex hoveredHex = owner.fieldController->getHoveredHex();
-		//do nothing when any hex is hovered - hero's animation overlaps battlefield
-		if ( hoveredHex != BattleHex::INVALID )
-			return;
-
 		CCS->curh->set(Cursor::Map::POINTER);
-
 		GH.pushIntT<CSpellWindow>(hero, owner.getCurrentPlayerInterface());
 	}
 }
 
-void BattleHero::clickRight(tribool down, bool previousState)
+void BattleHero::heroRightClicked()
 {
-	if(boost::logic::indeterminate(down))
-		return;
-
 	Point windowPosition;
 	windowPosition.x = (!defender) ? owner.fieldController->pos.left() + 1 : owner.fieldController->pos.right() - 79;
 	windowPosition.y = owner.fieldController->pos.y + 135;
 
 	InfoAboutHero targetHero;
-	if(down && (owner.myTurn || settings["session"]["spectate"].Bool()))
+	if(owner.makingTurn() || settings["session"]["spectate"].Bool())
 	{
 		auto h = defender ? owner.defendingHeroInstance : owner.attackingHeroInstance;
 		targetHero.initFromHero(h, InfoAboutHero::EInfoLevel::INBATTLE);
@@ -374,8 +350,6 @@ BattleHero::BattleHero(const BattleInterface & owner, const CGHeroInstance * her
 	flagAnimation->preload();
 	flagAnimation->playerColored(hero->tempOwner);
 
-	addUsedEvents(LCLICK | RCLICK | HOVER);
-
 	switchToNextPhase();
 	play();
 }
@@ -427,9 +401,9 @@ BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 
-	pos = CSDL_Ext::genRect(561, 470, (screen->w - 800)/2 + 165, (screen->h - 600)/2 + 19);
 	background = std::make_shared<CPicture>("CPRESULT");
 	background->colorize(owner.playerID);
+	pos = center(background->pos);
 
 	exit = std::make_shared<CButton>(Point(384, 505), "iok6432.def", std::make_pair("", ""), [&](){ bExitf();}, SDLK_RETURN);
 	exit->setBorderColor(Colors::METALLIC_GOLD);
@@ -591,7 +565,7 @@ void BattleResultWindow::activate()
 void BattleResultWindow::show(SDL_Surface * to)
 {
 	CIntObject::show(to);
-	CCS->videoh->update(pos.x + 107, pos.y + 70, screen, true, false);
+	CCS->videoh->update(pos.x + 107, pos.y + 70, to, true, false);
 }
 
 void BattleResultWindow::bExitf()
@@ -609,68 +583,6 @@ void BattleResultWindow::bExitf()
 	CCS->videoh->close();
 }
 
-void ClickableHex::hover(bool on)
-{
-	hovered = on;
-	//Hoverable::hover(on);
-	if(!on && setAlterText)
-	{
-		GH.statusbar->clear();
-		setAlterText = false;
-	}
-}
-
-ClickableHex::ClickableHex() : setAlterText(false), myNumber(-1), strictHovered(false), myInterface(nullptr)
-{
-	addUsedEvents(LCLICK | RCLICK | HOVER | MOVE);
-}
-
-void ClickableHex::mouseMoved(const SDL_MouseMotionEvent &sEvent)
-{
-	strictHovered = myInterface->fieldController->isPixelInHex(Point(sEvent.x-pos.x, sEvent.y-pos.y));
-
-	if(hovered && strictHovered) //print attacked creature to console
-	{
-		const CStack * attackedStack = myInterface->getCurrentPlayerInterface()->cb->battleGetStackByPos(myNumber);
-		if( attackedStack != nullptr &&
-			attackedStack->owner != myInterface->getCurrentPlayerInterface()->playerID &&
-			attackedStack->alive())
-		{
-			MetaString text;
-			text.addTxt(MetaString::GENERAL_TXT, 220);
-			attackedStack->addNameReplacement(text);
-			GH.statusbar->write(text.toString());
-			setAlterText = true;
-		}
-	}
-	else if(setAlterText)
-	{
-		GH.statusbar->clear();
-		setAlterText = false;
-	}
-}
-
-void ClickableHex::clickLeft(tribool down, bool previousState)
-{
-	if(!down && hovered && strictHovered) //we've been really clicked!
-	{
-		myInterface->actionsController->handleHex(myNumber, LCLICK);
-	}
-}
-
-void ClickableHex::clickRight(tribool down, bool previousState)
-{
-	const CStack * myst = myInterface->getCurrentPlayerInterface()->cb->battleGetStackByPos(myNumber); //stack info
-	if(hovered && strictHovered && myst!=nullptr)
-	{
-		if(!myst->alive()) return;
-		if(down)
-		{
-			GH.pushIntT<CStackWindow>(myst, true);
-		}
-	}
-}
-
 StackQueue::StackQueue(bool Embedded, BattleInterface & owner)
 	: embedded(Embedded),
 	owner(owner)
@@ -712,6 +624,14 @@ StackQueue::StackQueue(bool Embedded, BattleInterface & owner)
 
 void StackQueue::show(SDL_Surface * to)
 {
+	auto unitIdsToHighlight = owner.stacksController->getHoveredStacksUnitIds();
+
+	for(auto & stackBox : stackBoxes)
+	{
+		bool isBoundUnitCurrentlyHovered = vstd::contains(unitIdsToHighlight, stackBox->getBoundUnitID());
+		stackBox->toggleHighlight(isBoundUnitCurrentlyHovered);
+	}
+
 	if (embedded)
 		showAll(to);
 	CIntObject::show(to);
@@ -740,8 +660,21 @@ int32_t StackQueue::getSiegeShooterIconID()
 	return owner.siegeController->getSiegedTown()->town->faction->getIndex();
 }
 
+boost::optional<uint32_t> StackQueue::getHoveredUnitIdIfAny() const
+{
+	for(const auto & stackBox : stackBoxes)
+	{
+		if(stackBox->hovered || stackBox->mouseState(MouseButton::RIGHT))
+		{
+			return stackBox->getBoundUnitID();
+		}
+	}
+
+	return boost::none;
+}
+
 StackQueue::StackBox::StackBox(StackQueue * owner):
-	owner(owner)
+	CIntObject(RCLICK | HOVER), owner(owner)
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 	background = std::make_shared<CPicture>(owner->embedded ? "StackQueueSmall" : "StackQueueLarge");
@@ -771,6 +704,7 @@ void StackQueue::StackBox::setUnit(const battle::Unit * unit, size_t turn)
 {
 	if(unit)
 	{
+		boundUnitID = unit->unitId();
 		background->colorize(unit->unitOwner());
 		icon->visible = true;
 
@@ -783,7 +717,7 @@ void StackQueue::StackBox::setUnit(const battle::Unit * unit, size_t turn)
 		if (unit->unitType()->idNumber == CreatureID::ARROW_TOWERS)
 			icon->setFrame(owner->getSiegeShooterIconID(), 1);
 
-		amount->setText(CSDL_Ext::makeNumberShort(unit->getCount(), 4));
+		amount->setText(vstd::formatMetric(unit->getCount(), 4));
 
 		if(stateIcon)
 		{
@@ -805,6 +739,7 @@ void StackQueue::StackBox::setUnit(const battle::Unit * unit, size_t turn)
 	}
 	else
 	{
+		boundUnitID = boost::none;
 		background->colorize(PlayerColor::NEUTRAL);
 		icon->visible = false;
 		icon->setFrame(0);
@@ -814,3 +749,21 @@ void StackQueue::StackBox::setUnit(const battle::Unit * unit, size_t turn)
 			stateIcon->visible = false;
 	}
 }
+
+boost::optional<uint32_t> StackQueue::StackBox::getBoundUnitID() const
+{
+	return boundUnitID;
+}
+
+void StackQueue::StackBox::toggleHighlight(bool value)
+{
+	highlighted = value;
+}
+
+void StackQueue::StackBox::show(SDL_Surface *to)
+{
+	if(highlighted)
+		CSDL_Ext::drawBorder(to, background->pos.x, background->pos.y, background->pos.w, background->pos.h,  { 0, 255, 255, 255 }, 2);
+
+	CIntObject::show(to);
+}

+ 12 - 22
client/battle/BattleInterfaceClasses.h

@@ -123,9 +123,9 @@ public:
 	void pause();
 	void play();
 
-	void hover(bool on) override;
-	void clickLeft(tribool down, bool previousState) override; //call-in
-	void clickRight(tribool down, bool previousState) override; //call-in
+	void heroLeftClicked();
+	void heroRightClicked();
+
 	BattleHero(const BattleInterface & owner, const CGHeroInstance * hero, bool defender);
 };
 
@@ -157,38 +157,27 @@ public:
 	void show(SDL_Surface * to = 0) override;
 };
 
-/// Class which stands for a single hex field on a battlefield
-class ClickableHex : public CIntObject
-{
-private:
-	bool setAlterText; //if true, this hex has set alternative text in console and will clean it
-public:
-	ui32 myNumber; //number of hex in commonly used format
-	bool strictHovered; //for determining if hex is hovered by mouse (this is different problem than hex's graphic hovering)
-	BattleInterface * myInterface; //interface that owns me
-
-	//for user interactions
-	void hover (bool on) override;
-	void mouseMoved (const SDL_MouseMotionEvent &sEvent) override;
-	void clickLeft(tribool down, bool previousState) override;
-	void clickRight(tribool down, bool previousState) override;
-	ClickableHex();
-};
-
 /// Shows the stack queue
 class StackQueue : public CIntObject
 {
 	class StackBox : public CIntObject
 	{
 		StackQueue * owner;
+		boost::optional<uint32_t> boundUnitID;
+		bool highlighted = false;
+
 	public:
 		std::shared_ptr<CPicture> background;
 		std::shared_ptr<CAnimImage> icon;
 		std::shared_ptr<CLabel> amount;
 		std::shared_ptr<CAnimImage> stateIcon;
 
-		void setUnit(const battle::Unit * unit, size_t turn = 0);
 		StackBox(StackQueue * owner);
+		void setUnit(const battle::Unit * unit, size_t turn = 0);
+		void toggleHighlight(bool value);
+		boost::optional<uint32_t> getBoundUnitID() const;
+
+		void show(SDL_Surface * to) override;
 	};
 
 	static const int QUEUE_SIZE = 10;
@@ -205,6 +194,7 @@ public:
 
 	StackQueue(bool Embedded, BattleInterface & owner);
 	void update();
+	boost::optional<uint32_t> getHoveredUnitIdIfAny() const;
 
 	void show(SDL_Surface * to) override;
 };

+ 24 - 49
client/battle/BattleStacksController.cpp

@@ -26,8 +26,9 @@
 #include "../CMusicHandler.h"
 #include "../CGameInfo.h"
 #include "../gui/CGuiHandler.h"
-#include "../renderSDL/SDL_Extensions.h"
+#include "../render/Colors.h"
 #include "../render/Canvas.h"
+#include "../renderSDL/SDL_Extensions.h"
 
 #include "../../CCallback.h"
 #include "../../lib/spells/ISpellMechanics.h"
@@ -73,8 +74,6 @@ BattleStacksController::BattleStacksController(BattleInterface & owner):
 	activeStack(nullptr),
 	stackToActivate(nullptr),
 	selectedStack(nullptr),
-	stackCanCastSpell(false),
-	creatureSpellToCast(uint32_t(-1)),
 	animIDhelper(0)
 {
 	//preparing graphics for displaying amounts of creatures
@@ -317,7 +316,7 @@ void BattleStacksController::showStackAmountBox(Canvas & canvas, const CStack *
 	//blitting amount
 	Point textPos = stackAnimation[stack->ID]->pos.topLeft() + amountBG->dimensions()/2 + Point(xAdd, yAdd);
 
-	canvas.drawText(textPos, EFonts::FONT_TINY, Colors::WHITE, ETextAlignment::CENTER, CSDL_Ext::makeNumberShort(stack->getCount(), 4));
+	canvas.drawText(textPos, EFonts::FONT_TINY, Colors::WHITE, ETextAlignment::CENTER, vstd::formatMetric(stack->getCount(), 4));
 }
 
 void BattleStacksController::showStack(Canvas & canvas, const CStack * stack)
@@ -738,25 +737,6 @@ void BattleStacksController::activateStack()
 	const CStack * s = getActiveStack();
 	if(!s)
 		return;
-
-	//set casting flag to true if creature can use it to not check it every time
-	const auto spellcaster = s->getBonusLocalFirst(Selector::type()(Bonus::SPELLCASTER));
-	const auto randomSpellcaster = s->getBonusLocalFirst(Selector::type()(Bonus::RANDOM_SPELLCASTER));
-	if(s->canCast() && (spellcaster || randomSpellcaster))
-	{
-		stackCanCastSpell = true;
-		if(randomSpellcaster)
-			creatureSpellToCast = -1; //spell will be set later on cast
-		else
-			creatureSpellToCast = owner.curInt->cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), s, CBattleInfoCallback::RANDOM_AIMED); //faerie dragon can cast only one spell until their next move
-		//TODO: what if creature can cast BOTH random genie spell and aimed spell?
-		//TODO: faerie dragon type spell should be selected by server
-	}
-	else
-	{
-		stackCanCastSpell = false;
-		creatureSpellToCast = -1;
-	}
 }
 
 void BattleStacksController::setSelectedStack(const CStack *stack)
@@ -779,18 +759,6 @@ bool BattleStacksController::facingRight(const CStack * stack) const
 	return stackFacingRight.at(stack->ID);
 }
 
-bool BattleStacksController::activeStackSpellcaster()
-{
-	return stackCanCastSpell;
-}
-
-SpellID BattleStacksController::activeStackSpellToCast()
-{
-	if (!stackCanCastSpell)
-		return SpellID::NONE;
-	return SpellID(creatureSpellToCast);
-}
-
 Point BattleStacksController::getStackPositionAtHex(BattleHex hexNum, const CStack * stack) const
 {
 	Point ret(-500, -500); //returned value
@@ -896,6 +864,12 @@ std::vector<const CStack *> BattleStacksController::selectHoveredStacks()
 	if(owner.getAnimationCondition(EAnimationEvents::ACTION) == true)
 		return {};
 
+	auto hoveredQueueUnitId = owner.windowObject->getQueueHoveredUnitId();
+	if(hoveredQueueUnitId.is_initialized())
+	{
+		return { owner.curInt->cb->battleGetStackByID(hoveredQueueUnitId.value(), true) };
+	}
+
 	auto hoveredHex = owner.fieldController->getHoveredHex();
 
 	if (!hoveredHex.isValid())
@@ -904,21 +878,11 @@ std::vector<const CStack *> BattleStacksController::selectHoveredStacks()
 	const spells::Caster *caster = nullptr;
 	const CSpell *spell = nullptr;
 
-	spells::Mode mode = spells::Mode::HERO;
-
-	if(owner.actionsController->spellcastingModeActive())//hero casts spell
-	{
-		spell = owner.actionsController->selectedSpell().toSpell();
-		caster = owner.getActiveHero();
-	}
-	else if(owner.stacksController->activeStackSpellToCast() != SpellID::NONE)//stack casts spell
-	{
-		spell = SpellID(owner.stacksController->activeStackSpellToCast()).toSpell();
-		caster = owner.stacksController->getActiveStack();
-		mode = spells::Mode::CREATURE_ACTIVE;
-	}
+	spells::Mode mode = owner.actionsController->getCurrentCastMode();
+	spell = owner.actionsController->getCurrentSpell();
+	caster = owner.actionsController->getCurrentSpellcaster();
 
-	if(caster && spell) //when casting spell
+	if(caster && spell && owner.actionsController->currentActionSpellcasting(hoveredHex) ) //when casting spell
 	{
 		spells::Target target;
 		target.emplace_back(hoveredHex);
@@ -938,3 +902,14 @@ std::vector<const CStack *> BattleStacksController::selectHoveredStacks()
 
 	return {};
 }
+
+const std::vector<uint32_t> BattleStacksController::getHoveredStacksUnitIds() const
+{
+	auto result = std::vector<uint32_t>();
+	for (auto const * stack : mouseHoveredStacks)
+	{
+		result.push_back(stack->unitId());
+	}
+
+	return result;
+}

+ 2 - 8
client/battle/BattleStacksController.h

@@ -70,7 +70,7 @@ class BattleStacksController
 	/// currently active stack; nullptr - no one
 	const CStack *activeStack;
 
-	/// stacks below mouse pointer (multiple stacks possible while spellcasting), used for border animation
+	/// stacks or their battle queue images below mouse pointer (multiple stacks possible while spellcasting), used for border animation
 	std::vector<const CStack *> mouseHoveredStacks;
 
 	///when animation is playing, we should wait till the end to make the next stack active; nullptr of none
@@ -79,10 +79,6 @@ class BattleStacksController
 	/// stack that was selected for multi-target spells - Teleport / Sacrifice
 	const CStack *selectedStack;
 
-	/// if true, active stack could possibly cast some target spell
-	bool stackCanCastSpell;
-	si32 creatureSpellToCast;
-
 	/// for giving IDs for animations
 	ui32 animIDhelper;
 
@@ -123,9 +119,6 @@ public:
 	void startAction(const BattleAction* action);
 	void endAction(const BattleAction* action);
 
-	bool activeStackSpellcaster();
-	SpellID activeStackSpellToCast();
-
 	void activateStack(); //sets activeStack to stackToActivate etc. //FIXME: No, it's not clear at all
 
 	void setActiveStack(const CStack *stack);
@@ -144,6 +137,7 @@ public:
 
 	const CStack* getActiveStack() const;
 	const CStack* getSelectedStack() const;
+	const std::vector<uint32_t> getHoveredStacksUnitIds() const;
 
 	void update();
 

+ 14 - 11
client/battle/BattleWindow.cpp

@@ -37,9 +37,6 @@
 #include "../../lib/filesystem/ResourceID.h"
 #include "windows/BattleOptionsWindow.h"
 
-#include <SDL_surface.h>
-#include <SDL_events.h>
-
 BattleWindow::BattleWindow(BattleInterface & owner):
 	owner(owner)
 {
@@ -80,9 +77,9 @@ BattleWindow::BattleWindow(BattleInterface & owner):
 	std::string queueSize = settings["battle"]["queueSize"].String();
 
 	if(queueSize == "auto")
-		embedQueue = screen->h < 700;
+		embedQueue = GH.screenDimensions().y < 700;
 	else
-		embedQueue = screen->h < 700 || queueSize == "small";
+		embedQueue = GH.screenDimensions().y < 700 || queueSize == "small";
 
 	queue = std::make_shared<StackQueue>(embedQueue, owner);
 	if(!embedQueue && settings["battle"]["showQueue"].Bool())
@@ -166,9 +163,9 @@ void BattleWindow::deactivate()
 	LOCPLINT->cingconsole->deactivate();
 }
 
-void BattleWindow::keyPressed(const SDL_KeyboardEvent & key)
+void BattleWindow::keyPressed(const SDL_Keycode & key)
 {
-	if(key.keysym.sym == SDLK_q && key.state == SDL_PRESSED)
+	if(key == SDLK_q)
 	{
 		if(settings["battle"]["showQueue"].Bool()) //hide queue
 			hideQueue();
@@ -176,11 +173,11 @@ void BattleWindow::keyPressed(const SDL_KeyboardEvent & key)
 			showQueue();
 
 	}
-	else if(key.keysym.sym == SDLK_f && key.state == SDL_PRESSED)
+	else if(key == SDLK_f)
 	{
 		owner.actionsController->enterCreatureCastingMode();
 	}
-	else if(key.keysym.sym == SDLK_ESCAPE)
+	else if(key == SDLK_ESCAPE)
 	{
 		if(owner.getAnimationCondition(EAnimationEvents::OPENING) == true)
 			CCS->soundh->stopSound(owner.battleIntroSoundChannel);
@@ -346,6 +343,7 @@ void BattleWindow::showAlternativeActionIcon(PossiblePlayerBattleAction action)
 		
 	auto anim = std::make_shared<CAnimation>(iconName);
 	w->setImage(anim, false);
+	w->redraw();
 }
 
 void BattleWindow::setAlternativeActions(const std::list<PossiblePlayerBattleAction> & actions)
@@ -392,7 +390,7 @@ void BattleWindow::bSpellf()
 	if (owner.actionsController->spellcastingModeActive())
 		return;
 
-	if (!owner.myTurn)
+	if (!owner.makingTurn())
 		return;
 
 	auto myHero = owner.currentHero();
@@ -554,11 +552,16 @@ void BattleWindow::blockUI(bool on)
 	}
 }
 
+boost::optional<uint32_t> BattleWindow::getQueueHoveredUnitId()
+{
+	return queue->getHoveredUnitIdIfAny();
+}
+
 void BattleWindow::showAll(SDL_Surface *to)
 {
 	CIntObject::showAll(to);
 
-	if (screen->w != 800 || screen->h !=600)
+	if (GH.screenDimensions().x != 800 || GH.screenDimensions().y !=600)
 		CMessage::drawBorder(owner.curInt->playerID, to, pos.w+28, pos.h+29, pos.x-14, pos.y-15);
 }
 

+ 4 - 1
client/battle/BattleWindow.h

@@ -74,9 +74,12 @@ public:
 	/// Refresh queue after turn order changes
 	void updateQueue();
 
+	/// Get mouse-hovered battle queue unit ID if any found
+	boost::optional<uint32_t> getQueueHoveredUnitId();
+
 	void activate() override;
 	void deactivate() override;
-	void keyPressed(const SDL_KeyboardEvent & key) override;
+	void keyPressed(const SDL_Keycode & key) override;
 	void clickRight(tribool down, bool previousState) override;
 	void show(SDL_Surface *to) override;
 	void showAll(SDL_Surface *to) override;

+ 2 - 5
client/battle/CreatureAnimation.cpp

@@ -78,10 +78,7 @@ float AnimationControls::getCreatureAnimationSpeed(const CCreature * creature, c
 		return baseSpeed;
 
 	case ECreatureAnimType::HOLDING:
-			if ( creature->animation.idleAnimationTime > 0.01)
-				return speed / creature->animation.idleAnimationTime;
-			else
-				return 0.f; // this animation is disabled for current creature
+			return creature->animation.idleAnimationTime;
 
 	case ECreatureAnimType::SHOOT_UP:
 	case ECreatureAnimType::SHOOT_FRONT:
@@ -315,7 +312,7 @@ void CreatureAnimation::playOnce( ECreatureAnimType type )
 
 inline int getBorderStrength(float time)
 {
-	float borderStrength = fabs(vstd::round(time) - time) * 2; // generate value in range 0-1
+	float borderStrength = fabs(std::round(time) - time) * 2; // generate value in range 0-1
 
 	return static_cast<int>(borderStrength * 155 + 100); // scale to 0-255
 }

+ 157 - 31
client/gui/CGuiHandler.cpp

@@ -15,6 +15,7 @@
 #include "CursorHandler.h"
 
 #include "../CGameInfo.h"
+#include "../render/Colors.h"
 #include "../renderSDL/SDL_Extensions.h"
 #include "../CMT.h"
 #include "../CPlayerInterface.h"
@@ -27,6 +28,14 @@
 #include <SDL_timer.h>
 #include <SDL_events.h>
 
+#ifdef VCMI_APPLE
+#include <dispatch/dispatch.h>
+#endif
+
+#ifdef VCMI_IOS
+#include "ios/utils.h"
+#endif
+
 extern std::queue<SDL_Event> SDLEventsQueue;
 extern boost::mutex eventsM;
 
@@ -118,7 +127,7 @@ void CGuiHandler::popInt(std::shared_ptr<IShowActivatable> top)
 		listInt.front()->activate();
 	totalRedraw();
 
-	pushSDLEvent(SDL_USEREVENT, EUserEvent::INTERFACE_CHANGED);
+	pushUserEvent(EUserEvent::INTERFACE_CHANGED);
 }
 
 void CGuiHandler::pushInt(std::shared_ptr<IShowActivatable> newInt)
@@ -137,7 +146,7 @@ void CGuiHandler::pushInt(std::shared_ptr<IShowActivatable> newInt)
 	objsToBlit.push_back(newInt);
 	totalRedraw();
 
-	pushSDLEvent(SDL_USEREVENT, EUserEvent::INTERFACE_CHANGED);
+	pushUserEvent(EUserEvent::INTERFACE_CHANGED);
 }
 
 void CGuiHandler::popInts(int howMany)
@@ -160,7 +169,7 @@ void CGuiHandler::popInts(int howMany)
 	}
 	fakeMouseMove();
 
-	pushSDLEvent(SDL_USEREVENT, EUserEvent::INTERFACE_CHANGED);
+	pushUserEvent(EUserEvent::INTERFACE_CHANGED);
 }
 
 std::shared_ptr<IShowActivatable> CGuiHandler::topInt()
@@ -203,7 +212,12 @@ void CGuiHandler::handleEvents()
 	{
 		continueEventHandling = true;
 		SDL_Event currentEvent = SDLEventsQueue.front();
-		cursorPosition = Point(currentEvent.motion.x, currentEvent.motion.y);
+
+		if (currentEvent.type == SDL_MOUSEMOTION)
+		{
+			cursorPosition = Point(currentEvent.motion.x, currentEvent.motion.y);
+			mouseButtonsMask = currentEvent.motion.state;
+		}
 		SDLEventsQueue.pop();
 
 		// In a sequence of mouse motion events, skip all but the last one.
@@ -257,6 +271,59 @@ void CGuiHandler::fakeMouseMove()
 	fakeMoveCursor(0, 0);
 }
 
+void CGuiHandler::startTextInput(const Rect & whereInput)
+{
+#ifdef VCMI_APPLE
+	dispatch_async(dispatch_get_main_queue(), ^{
+#endif
+
+	// TODO ios: looks like SDL bug actually, try fixing there
+	auto renderer = SDL_GetRenderer(mainWindow);
+	float scaleX, scaleY;
+	SDL_Rect viewport;
+	SDL_RenderGetScale(renderer, &scaleX, &scaleY);
+	SDL_RenderGetViewport(renderer, &viewport);
+
+#ifdef VCMI_IOS
+	const auto nativeScale = iOS_utils::screenScale();
+	scaleX /= nativeScale;
+	scaleY /= nativeScale;
+#endif
+
+	SDL_Rect rectInScreenCoordinates;
+	rectInScreenCoordinates.x = (viewport.x + whereInput.x) * scaleX;
+	rectInScreenCoordinates.y = (viewport.y + whereInput.y) * scaleY;
+	rectInScreenCoordinates.w = whereInput.w * scaleX;
+	rectInScreenCoordinates.h = whereInput.h * scaleY;
+
+	SDL_SetTextInputRect(&rectInScreenCoordinates);
+
+	if (SDL_IsTextInputActive() == SDL_FALSE)
+	{
+		SDL_StartTextInput();
+	}
+
+#ifdef VCMI_APPLE
+	});
+#endif
+}
+
+void CGuiHandler::stopTextInput()
+{
+#ifdef VCMI_APPLE
+	dispatch_async(dispatch_get_main_queue(), ^{
+#endif
+
+	if (SDL_IsTextInputActive() == SDL_TRUE)
+	{
+		SDL_StopTextInput();
+	}
+
+#ifdef VCMI_APPLE
+	});
+#endif
+}
+
 void CGuiHandler::fakeMouseButtonEventRelativeMode(bool down, bool right)
 {
 	SDL_Event event;
@@ -280,9 +347,9 @@ void CGuiHandler::fakeMouseButtonEventRelativeMode(bool down, bool right)
 	SDL_RenderGetScale(mainRenderer, &xScale, &yScale);
 
 	SDL_EventState(SDL_MOUSEMOTION, SDL_IGNORE);
-	CSDL_Ext::warpMouse(
+	moveCursorToPosition( Point(
 		(int)(sme.x * xScale) + (w - rLogicalWidth * xScale) / 2,
-		(int)(sme.y * yScale + (h - rLogicalHeight * yScale) / 2));
+		(int)(sme.y * yScale + (h - rLogicalHeight * yScale) / 2)));
 	SDL_EventState(SDL_MOUSEMOTION, SDL_ENABLE);
 
 	event.button = sme;
@@ -345,7 +412,7 @@ void CGuiHandler::handleCurrentEvent( SDL_Event & current )
 		bool keysCaptured = false;
 		for(auto i = keyinterested.begin(); i != keyinterested.end() && continueEventHandling; i++)
 		{
-			if((*i)->captureThisEvent(key))
+			if((*i)->captureThisKey(key.keysym.sym))
 			{
 				keysCaptured = true;
 				break;
@@ -354,8 +421,13 @@ void CGuiHandler::handleCurrentEvent( SDL_Event & current )
 
 		std::list<CIntObject*> miCopy = keyinterested;
 		for(auto i = miCopy.begin(); i != miCopy.end() && continueEventHandling; i++)
-			if(vstd::contains(keyinterested,*i) && (!keysCaptured || (*i)->captureThisEvent(key)))
-				(**i).keyPressed(key);
+			if(vstd::contains(keyinterested,*i) && (!keysCaptured || (*i)->captureThisKey(key.keysym.sym)))
+			{
+				if (key.state == SDL_PRESSED)
+					(**i).keyPressed(key.keysym.sym);
+				if (key.state == SDL_RELEASED)
+					(**i).keyReleased(key.keysym.sym);
+			}
 	}
 	else if(current.type == SDL_MOUSEMOTION)
 	{
@@ -387,14 +459,14 @@ void CGuiHandler::handleCurrentEvent( SDL_Event & current )
 			lastClickTime = SDL_GetTicks();
 
 			if(!doubleClicked)
-				handleMouseButtonClick(lclickable, EIntObjMouseBtnType::LEFT, true);
+				handleMouseButtonClick(lclickable, MouseButton::LEFT, true);
 			break;
 		}
 		case SDL_BUTTON_RIGHT:
-			handleMouseButtonClick(rclickable, EIntObjMouseBtnType::RIGHT, true);
+			handleMouseButtonClick(rclickable, MouseButton::RIGHT, true);
 			break;
 		case SDL_BUTTON_MIDDLE:
-			handleMouseButtonClick(mclickable, EIntObjMouseBtnType::MIDDLE, true);
+			handleMouseButtonClick(mclickable, MouseButton::MIDDLE, true);
 			break;
 		default:
 			break;
@@ -416,14 +488,14 @@ void CGuiHandler::handleCurrentEvent( SDL_Event & current )
 	{
 		for(auto it : textInterested)
 		{
-			it->textInputed(current.text);
+			it->textInputed(current.text.text);
 		}
 	}
 	else if(current.type == SDL_TEXTEDITING)
 	{
 		for(auto it : textInterested)
 		{
-			it->textEdited(current.edit);
+			it->textEdited(current.edit.text);
 		}
 	}
 	else if(current.type == SDL_MOUSEBUTTONUP)
@@ -433,13 +505,13 @@ void CGuiHandler::handleCurrentEvent( SDL_Event & current )
 			switch(current.button.button)
 			{
 			case SDL_BUTTON_LEFT:
-				handleMouseButtonClick(lclickable, EIntObjMouseBtnType::LEFT, false);
+				handleMouseButtonClick(lclickable, MouseButton::LEFT, false);
 				break;
 			case SDL_BUTTON_RIGHT:
-				handleMouseButtonClick(rclickable, EIntObjMouseBtnType::RIGHT, false);
+				handleMouseButtonClick(rclickable, MouseButton::RIGHT, false);
 				break;
 			case SDL_BUTTON_MIDDLE:
-				handleMouseButtonClick(mclickable, EIntObjMouseBtnType::MIDDLE, false);
+				handleMouseButtonClick(mclickable, MouseButton::MIDDLE, false);
 				break;
 			}
 		}
@@ -471,7 +543,7 @@ void CGuiHandler::handleCurrentEvent( SDL_Event & current )
 		{
 			convertTouchToMouse(&current);
 			handleMouseMotion(current);
-			handleMouseButtonClick(rclickable, EIntObjMouseBtnType::RIGHT, true);
+			handleMouseButtonClick(rclickable, MouseButton::RIGHT, true);
 		}
 #endif //VCMI_IOS
 	}
@@ -495,14 +567,14 @@ void CGuiHandler::handleCurrentEvent( SDL_Event & current )
 		{
 			convertTouchToMouse(&current);
 			handleMouseMotion(current);
-			handleMouseButtonClick(rclickable, EIntObjMouseBtnType::RIGHT, false);
+			handleMouseButtonClick(rclickable, MouseButton::RIGHT, false);
 			multifinger = fingerCount != 0;
 		}
 #endif //VCMI_IOS
 	}
 } //event end
 
-void CGuiHandler::handleMouseButtonClick(CIntObjectList & interestedObjs, EIntObjMouseBtnType btn, bool isPressed)
+void CGuiHandler::handleMouseButtonClick(CIntObjectList & interestedObjs, MouseButton btn, bool isPressed)
 {
 	auto hlp = interestedObjs;
 	for(auto i = hlp.begin(); i != hlp.end() && continueEventHandling; i++)
@@ -546,7 +618,9 @@ void CGuiHandler::handleMouseMotion(const SDL_Event & current)
 		elem->hovered = true;
 	}
 
-	handleMoveInterested(current.motion);
+	// do not send motion events for events outside our window
+	//if (current.motion.windowID == 0)
+		handleMoveInterested(current.motion);
 }
 
 void CGuiHandler::simpleRedraw()
@@ -566,7 +640,7 @@ void CGuiHandler::handleMoveInterested(const SDL_MouseMotionEvent & motion)
 	{
 		if(elem->strongInterest || Rect::createAround(elem->pos, 1).isInside( motion.x, motion.y)) //checking bounds including border fixes bug #2476
 		{
-			(elem)->mouseMoved(motion);
+			(elem)->mouseMoved(Point(motion.x, motion.y));
 		}
 	}
 }
@@ -612,13 +686,16 @@ void CGuiHandler::renderFrame()
 
 
 CGuiHandler::CGuiHandler()
-	: lastClick(-500, -500),lastClickTime(0), defActionsDef(0), captureChildren(false),
-    multifinger(false)
+	: lastClick(-500, -500)
+	, lastClickTime(0)
+	, defActionsDef(0)
+	, captureChildren(false)
+	, multifinger(false)
+	, mouseButtonsMask(0)
+	, continueEventHandling(true)
+	, curInt(nullptr)
+	, statusbar(nullptr)
 {
-	continueEventHandling = true;
-	curInt = nullptr;
-	statusbar = nullptr;
-
 	// Creates the FPS manager and sets the framerate to 48 which is doubled the value of the original Heroes 3 FPS rate
 	mainFPSmng = new CFramerateManager(60);
 	//do not init CFramerateManager here --AVS
@@ -632,6 +709,27 @@ CGuiHandler::~CGuiHandler()
 	delete terminate_cond;
 }
 
+
+void CGuiHandler::moveCursorToPosition(const Point & position)
+{
+	SDL_WarpMouseInWindow(mainWindow, position.x, position.y);
+}
+
+bool CGuiHandler::isKeyboardCtrlDown() const
+{
+	return SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_LCTRL] || SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_RCTRL];
+}
+
+bool CGuiHandler::isKeyboardAltDown() const
+{
+	return SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_LALT] || SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_RALT];
+}
+
+bool CGuiHandler::isKeyboardShiftDown() const
+{
+	return SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_LSHIFT] || SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_RSHIFT];
+}
+
 void CGuiHandler::breakEventHandling()
 {
 	continueEventHandling = false;
@@ -642,6 +740,28 @@ const Point & CGuiHandler::getCursorPosition() const
 	return cursorPosition;
 }
 
+Point CGuiHandler::screenDimensions() const
+{
+	return Point(screen->w, screen->h);
+}
+
+bool CGuiHandler::isMouseButtonPressed() const
+{
+	return mouseButtonsMask > 0;
+}
+
+bool CGuiHandler::isMouseButtonPressed(MouseButton button) const
+{
+	static_assert(static_cast<uint32_t>(MouseButton::LEFT)   == SDL_BUTTON_LEFT,   "mismatch between VCMI and SDL enum!");
+	static_assert(static_cast<uint32_t>(MouseButton::MIDDLE) == SDL_BUTTON_MIDDLE, "mismatch between VCMI and SDL enum!");
+	static_assert(static_cast<uint32_t>(MouseButton::RIGHT)  == SDL_BUTTON_RIGHT,  "mismatch between VCMI and SDL enum!");
+	static_assert(static_cast<uint32_t>(MouseButton::EXTRA1) == SDL_BUTTON_X1,     "mismatch between VCMI and SDL enum!");
+	static_assert(static_cast<uint32_t>(MouseButton::EXTRA2) == SDL_BUTTON_X2,     "mismatch between VCMI and SDL enum!");
+
+	uint32_t index = static_cast<uint32_t>(button);
+	return mouseButtonsMask & SDL_BUTTON(index);
+}
+
 void CGuiHandler::drawFPSCounter()
 {
 	static SDL_Rect overlay = { 0, 0, 64, 32};
@@ -719,11 +839,17 @@ bool CGuiHandler::amIGuiThread()
 	return inGuiThread.get() && *inGuiThread;
 }
 
-void CGuiHandler::pushSDLEvent(int type, int usercode)
+void CGuiHandler::pushUserEvent(EUserEvent usercode)
+{
+	pushUserEvent(usercode, nullptr);
+}
+
+void CGuiHandler::pushUserEvent(EUserEvent usercode, void * userdata)
 {
 	SDL_Event event;
-	event.type = type;
-	event.user.code = usercode;	// not necessarily used
+	event.type = SDL_USEREVENT;
+	event.user.code = static_cast<int32_t>(usercode);
+	event.user.data1 = userdata;
 	SDL_PushEvent(&event);
 }
 

+ 29 - 5
client/gui/CGuiHandler.h

@@ -9,12 +9,15 @@
  */
 #pragma once
 
+#include "MouseButton.h"
 #include "../../lib/Point.h"
-#include "SDL_keycode.h"
+
+#include <SDL_keycode.h>
 
 VCMI_LIB_NAMESPACE_BEGIN
 
 template <typename T> struct CondSh;
+class Rect;
 
 VCMI_LIB_NAMESPACE_END
 
@@ -27,10 +30,9 @@ class CIntObject;
 class IUpdateable;
 class IShowActivatable;
 class IShowable;
-enum class EIntObjMouseBtnType;
 
 // TODO: event handling need refactoring
-enum EUserEvent
+enum class EUserEvent
 {
 	/*CHANGE_SCREEN_RESOLUTION = 1,*/
 	RETURN_TO_MAIN_MENU = 2,
@@ -71,6 +73,7 @@ public:
 
 private:
 	Point cursorPosition;
+	uint32_t mouseButtonsMask;
 
 	std::vector<std::shared_ptr<IShowActivatable>> disposed;
 
@@ -90,7 +93,7 @@ private:
 	CIntObjectList textInterested;
 
 
-	void handleMouseButtonClick(CIntObjectList & interestedObjs, EIntObjMouseBtnType btn, bool isPressed);
+	void handleMouseButtonClick(CIntObjectList & interestedObjs, MouseButton btn, bool isPressed);
 	void processLists(const ui16 activityFlag, std::function<void (std::list<CIntObject*> *)> cb);
 	void handleCurrentEvent(SDL_Event &current);
 	void handleMouseMotion(const SDL_Event & current);
@@ -107,8 +110,28 @@ public:
 	//objs to blit
 	std::vector<std::shared_ptr<IShowActivatable>> objsToBlit;
 
+	/// returns current position of mouse cursor, relative to vcmi window
 	const Point & getCursorPosition() const;
 
+	Point screenDimensions() const;
+
+	/// returns true if at least one mouse button is pressed
+	bool isMouseButtonPressed() const;
+
+	/// returns true if specified mouse button is pressed
+	bool isMouseButtonPressed(MouseButton button) const;
+
+	/// returns true if chosen keyboard key is currently pressed down
+	bool isKeyboardAltDown() const;
+	bool isKeyboardCtrlDown() const;
+	bool isKeyboardShiftDown() const;
+
+	void startTextInput(const Rect & where);
+	void stopTextInput();
+
+	/// moves mouse pointer into specified position inside vcmi window
+	void moveCursorToPosition(const Point & position);
+
 	IUpdateable *curInt;
 
 	Point lastClick;
@@ -155,7 +178,8 @@ public:
 	static bool isNumKey(SDL_Keycode key, bool number = true); //checks if key is on numpad (numbers - check only for numpad digits)
 	static bool isArrowKey(SDL_Keycode key);
 	static bool amIGuiThread();
-	static void pushSDLEvent(int type, int usercode = 0);
+	static void pushUserEvent(EUserEvent usercode);
+	static void pushUserEvent(EUserEvent usercode, void * userdata);
 
 	CondSh<bool> * terminate_cond; // confirm termination
 };

+ 27 - 16
client/gui/CIntObject.cpp

@@ -13,10 +13,9 @@
 #include "CGuiHandler.h"
 #include "../renderSDL/SDL_Extensions.h"
 #include "../windows/CMessage.h"
+#include "../CMT.h"
 
 #include <SDL_pixels.h>
-#include <SDL_surface.h>
-#include <SDL_events.h>
 
 IShowActivatable::IShowActivatable()
 {
@@ -146,18 +145,18 @@ void CIntObject::deactivate(ui16 what)
 	GH.handleElementDeActivate(this, what);
 }
 
-void CIntObject::click(EIntObjMouseBtnType btn, tribool down, bool previousState)
+void CIntObject::click(MouseButton btn, tribool down, bool previousState)
 {
 	switch(btn)
 	{
 	default:
-	case EIntObjMouseBtnType::LEFT:
+	case MouseButton::LEFT:
 		clickLeft(down, previousState);
 		break;
-	case EIntObjMouseBtnType::MIDDLE:
+	case MouseButton::MIDDLE:
 		clickMiddle(down, previousState);
 		break;
-	case EIntObjMouseBtnType::RIGHT:
+	case MouseButton::RIGHT:
 		clickRight(down, previousState);
 		break;
 	}
@@ -228,8 +227,8 @@ void CIntObject::fitToScreen(int borderWidth, bool propagate)
 	Point newPos = pos.topLeft();
 	vstd::amax(newPos.x, borderWidth);
 	vstd::amax(newPos.y, borderWidth);
-	vstd::amin(newPos.x, screen->w - borderWidth - pos.w);
-	vstd::amin(newPos.y, screen->h - borderWidth - pos.h);
+	vstd::amin(newPos.x, GH.screenDimensions().x - borderWidth - pos.w);
+	vstd::amin(newPos.y, GH.screenDimensions().y - borderWidth - pos.h);
 	if (newPos != pos.topLeft())
 		moveTo(newPos, propagate);
 }
@@ -309,7 +308,7 @@ const Rect & CIntObject::center( const Rect &r, bool propagate )
 {
 	pos.w = r.w;
 	pos.h = r.h;
-	return center(Point(screen->w/2, screen->h/2), propagate);
+	return center(Point(GH.screenDimensions().x/2, GH.screenDimensions().y/2), propagate);
 }
 
 const Rect & CIntObject::center( bool propagate )
@@ -325,7 +324,7 @@ const Rect & CIntObject::center(const Point & p, bool propagate)
 	return pos;
 }
 
-bool CIntObject::captureThisEvent(const SDL_KeyboardEvent & key)
+bool CIntObject::captureThisKey(const SDL_Keycode & key)
 {
 	return captureAllKeys;
 }
@@ -343,14 +342,26 @@ CKeyShortcut::CKeyShortcut(std::set<int> Keys)
 	:assignedKeys(Keys)
 {}
 
-void CKeyShortcut::keyPressed(const SDL_KeyboardEvent & key)
+void CKeyShortcut::keyPressed(const SDL_Keycode & key)
 {
-	if(vstd::contains(assignedKeys,key.keysym.sym)
-	 || vstd::contains(assignedKeys, CGuiHandler::numToDigit(key.keysym.sym)))
+	if(vstd::contains(assignedKeys,key)
+	 || vstd::contains(assignedKeys, CGuiHandler::numToDigit(key)))
 	{
-		bool prev = mouseState(EIntObjMouseBtnType::LEFT);
-		updateMouseState(EIntObjMouseBtnType::LEFT, key.state == SDL_PRESSED);
-		clickLeft(key.state == SDL_PRESSED, prev);
+		bool prev = mouseState(MouseButton::LEFT);
+		updateMouseState(MouseButton::LEFT, true);
+		clickLeft(true, prev);
+
+	}
+}
+
+void CKeyShortcut::keyReleased(const SDL_Keycode & key)
+{
+	if(vstd::contains(assignedKeys,key)
+	 || vstd::contains(assignedKeys, CGuiHandler::numToDigit(key)))
+	{
+		bool prev = mouseState(MouseButton::LEFT);
+		updateMouseState(MouseButton::LEFT, false);
+		clickLeft(false, prev);
 
 	}
 }

+ 16 - 18
client/gui/CIntObject.h

@@ -9,17 +9,15 @@
  */
 #pragma once
 
-#include "../../lib/Rect.h"
+#include "MouseButton.h"
 #include "../render/Graphics.h"
+#include "../../lib/Rect.h"
 
 struct SDL_Surface;
 class CGuiHandler;
 class CPicture;
 
-struct SDL_KeyboardEvent;
-struct SDL_TextInputEvent;
-struct SDL_TextEditingEvent;
-struct SDL_MouseMotionEvent;
+typedef int32_t SDL_Keycode;
 
 using boost::logic::tribool;
 
@@ -62,9 +60,6 @@ public:
 	virtual ~IShowActivatable(){};
 };
 
-enum class EIntObjMouseBtnType { LEFT, MIDDLE, RIGHT };
-//typedef ui16 ActivityFlag;
-
 // Base UI element
 class CIntObject : public IShowActivatable //interface object
 {
@@ -74,7 +69,7 @@ class CIntObject : public IShowActivatable //interface object
 	int toNextTick;
 	int timerDelay;
 
-	std::map<EIntObjMouseBtnType, bool> currentMouseState;
+	std::map<MouseButton, bool> currentMouseState;
 
 	void onTimer(int timePassed);
 
@@ -108,10 +103,10 @@ public:
 	CIntObject(int used=0, Point offset=Point());
 	virtual ~CIntObject();
 
-	void updateMouseState(EIntObjMouseBtnType btn, bool state) { currentMouseState[btn] = state; }
-	bool mouseState(EIntObjMouseBtnType btn) const { return currentMouseState.count(btn) ? currentMouseState.at(btn) : false; }
+	void updateMouseState(MouseButton btn, bool state) { currentMouseState[btn] = state; }
+	bool mouseState(MouseButton btn) const { return currentMouseState.count(btn) ? currentMouseState.at(btn) : false; }
 
-	virtual void click(EIntObjMouseBtnType btn, tribool down, bool previousState);
+	virtual void click(MouseButton btn, tribool down, bool previousState);
 	virtual void clickLeft(tribool down, bool previousState) {}
 	virtual void clickRight(tribool down, bool previousState) {}
 	virtual void clickMiddle(tribool down, bool previousState) {}
@@ -122,15 +117,16 @@ public:
 
 	//keyboard handling
 	bool captureAllKeys; //if true, only this object should get info about pressed keys
-	virtual void keyPressed(const SDL_KeyboardEvent & key){}
-	virtual bool captureThisEvent(const SDL_KeyboardEvent & key); //allows refining captureAllKeys against specific events (eg. don't capture ENTER)
+	virtual void keyPressed(const SDL_Keycode & key){}
+	virtual void keyReleased(const SDL_Keycode & key){}
+	virtual bool captureThisKey(const SDL_Keycode & key); //allows refining captureAllKeys against specific events (eg. don't capture ENTER)
 
-	virtual void textInputed(const SDL_TextInputEvent & event){};
-	virtual void textEdited(const SDL_TextEditingEvent & event){};
+	virtual void textInputed(const std::string & enteredText){};
+	virtual void textEdited(const std::string & enteredText){};
 
 	//mouse movement handling
 	bool strongInterest; //if true - report all mouse movements, if not - only when hovered
-	virtual void mouseMoved (const SDL_MouseMotionEvent & sEvent){}
+	virtual void mouseMoved (const Point & cursorPosition){}
 
 	//time handling
 	void setTimer(int msToTrigger);//set timer delay and activate timer if needed.
@@ -205,7 +201,9 @@ public:
 	CKeyShortcut();
 	CKeyShortcut(int key);
 	CKeyShortcut(std::set<int> Keys);
-	virtual void keyPressed(const SDL_KeyboardEvent & key) override; //call-in
+	void keyPressed(const SDL_Keycode & key) override;
+	void keyReleased(const SDL_Keycode & key) override;
+
 };
 
 class WindowBase : public CIntObject

+ 0 - 20
client/gui/CursorHandler.cpp

@@ -17,17 +17,9 @@
 #include "../render/CAnimation.h"
 #include "../render/IImage.h"
 #include "../renderSDL/SDL_Extensions.h"
-#include "../CMT.h"
 
 #include "../../lib/CConfigHandler.h"
 
-#include <SDL_render.h>
-#include <SDL_events.h>
-
-#ifdef VCMI_APPLE
-#include <dispatch/dispatch.h>
-#endif
-
 std::unique_ptr<ICursor> CursorHandler::createCursor()
 {
 	if (settings["video"]["cursor"].String() == "auto")
@@ -254,18 +246,6 @@ std::shared_ptr<IImage> CursorHandler::getCurrentImage()
 	return cursors[static_cast<size_t>(type)]->getImage(frame);
 }
 
-void CursorHandler::centerCursor()
-{
-	Point screenSize {screen->w, screen->h};
-	pos = screenSize / 2 - getPivotOffset();
-
-	SDL_EventState(SDL_MOUSEMOTION, SDL_IGNORE);
-	CSDL_Ext::warpMouse(pos.x, pos.y);
-	SDL_EventState(SDL_MOUSEMOTION, SDL_ENABLE);
-
-	cursor->setCursorPosition(pos);
-}
-
 void CursorHandler::updateSpellcastCursor()
 {
 	static const float frameDisplayDuration = 0.1f; // H3 uses 100 ms per frame

+ 0 - 3
client/gui/CursorHandler.h

@@ -178,7 +178,4 @@ public:
 
 	/// change cursor's positions to (x, y)
 	void cursorMove(const int & x, const int & y);
-	/// Move cursor to screen center
-	void centerCursor();
-
 };

+ 19 - 23
lib/abilities/Ability.h → client/gui/MouseButton.h

@@ -1,23 +1,19 @@
-/*
- * Ability.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
- *
- */
-
-VCMI_LIB_NAMESPACE_BEGIN
-
-namespace abilities
-{
-
-class DLL_LINKAGE Ability
-{
-
-};
-
-}
-
-VCMI_LIB_NAMESPACE_END
+/*
+ * MouseButton.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
+
+enum class MouseButton
+{
+	LEFT   = 1,
+	MIDDLE = 2,
+	RIGHT  = 3,
+	EXTRA1 = 4,
+	EXTRA2 = 5
+};

+ 1 - 1
client/lobby/CBonusSelection.cpp

@@ -454,7 +454,7 @@ void CBonusSelection::restartMap()
 	close();
 	LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[67], [=]()
 	{
-		LOCPLINT->sendCustomEvent(EUserEvent::RESTART_GAME);
+		GH.pushUserEvent(EUserEvent::RESTART_GAME);
 	}, 0);
 }
 

+ 2 - 4
client/lobby/CSelectionBase.cpp

@@ -43,8 +43,6 @@
 #include "../../lib/mapping/CMapInfo.h"
 #include "../../lib/serializer/Connection.h"
 
-#include <SDL_events.h>
-
 ISelectionScreenInfo::ISelectionScreenInfo(ESelectionScreen ScreenType)
 	: screenType(ScreenType)
 {
@@ -320,9 +318,9 @@ CChatBox::CChatBox(const Rect & rect)
 	chatHistory->label->color = Colors::GREEN;
 }
 
-void CChatBox::keyPressed(const SDL_KeyboardEvent & key)
+void CChatBox::keyPressed(const SDL_Keycode & key)
 {
-	if(key.keysym.sym == SDLK_RETURN && key.state == SDL_PRESSED && inputBox->getText().size())
+	if(key == SDLK_RETURN && inputBox->getText().size())
 	{
 		CSH->sendMessage(inputBox->getText());
 		inputBox->setText("");

+ 1 - 2
client/lobby/CSelectionBase.h

@@ -120,8 +120,7 @@ public:
 
 	CChatBox(const Rect & rect);
 
-	void keyPressed(const SDL_KeyboardEvent & key) override;
-
+	void keyPressed(const SDL_Keycode & key) override;
 	void addNewMessage(const std::string & text);
 };
 

+ 2 - 7
client/lobby/SelectionTab.cpp

@@ -35,8 +35,6 @@
 #include "../../lib/mapping/CMapInfo.h"
 #include "../../lib/serializer/Connection.h"
 
-#include <SDL_events.h>
-
 bool mapSorter::operator()(const std::shared_ptr<CMapInfo> aaa, const std::shared_ptr<CMapInfo> bbb)
 {
 	auto a = aaa->mapHeader.get();
@@ -280,13 +278,10 @@ void SelectionTab::clickLeft(tribool down, bool previousState)
 	}
 }
 
-void SelectionTab::keyPressed(const SDL_KeyboardEvent & key)
+void SelectionTab::keyPressed(const SDL_Keycode & key)
 {
-	if(key.state != SDL_PRESSED)
-		return;
-
 	int moveBy = 0;
-	switch(key.keysym.sym)
+	switch(key)
 	{
 	case SDLK_UP:
 		moveBy = -1;

+ 2 - 1
client/lobby/SelectionTab.h

@@ -66,7 +66,8 @@ public:
 	void toggleMode();
 
 	void clickLeft(tribool down, bool previousState) override;
-	void keyPressed(const SDL_KeyboardEvent & key) override;
+	void keyPressed(const SDL_Keycode & key) override;
+
 	void onDoubleClick() override;
 
 	void filter(int size, bool selectFirst = false); //0 - all

+ 23 - 25
client/mainmenu/CMainMenu.cpp

@@ -16,25 +16,8 @@
 #include "../lobby/CBonusSelection.h"
 #include "../lobby/CSelectionBase.h"
 #include "../lobby/CLobbyScreen.h"
-
-#include "../../lib/filesystem/Filesystem.h"
-#include "../../lib/filesystem/CCompressedStream.h"
-
 #include "../gui/CursorHandler.h"
-
-#include "../CGameInfo.h"
-#include "../../lib/CGeneralTextHandler.h"
-#include "../../lib/JsonNode.h"
-#include "../CMusicHandler.h"
-#include "../CVideoHandler.h"
-#include "../../lib/serializer/Connection.h"
-#include "../../lib/serializer/CTypeList.h"
-#include "../../lib/VCMIDirs.h"
-#include "../../lib/mapping/CMap.h"
 #include "../windows/GUIClasses.h"
-#include "../CPlayerInterface.h"
-#include "../../CCallback.h"
-#include "../Client.h"
 #include "../gui/CGuiHandler.h"
 #include "../widgets/CComponent.h"
 #include "../widgets/Buttons.h"
@@ -43,6 +26,24 @@
 #include "../widgets/TextControls.h"
 #include "../windows/InfoWindows.h"
 #include "../CServerHandler.h"
+
+#include "../CGameInfo.h"
+#include "../CMusicHandler.h"
+#include "../CVideoHandler.h"
+#include "../CPlayerInterface.h"
+#include "../Client.h"
+#include "../CMT.h"
+
+#include "../../CCallback.h"
+
+#include "../../lib/CGeneralTextHandler.h"
+#include "../../lib/JsonNode.h"
+#include "../../lib/serializer/Connection.h"
+#include "../../lib/serializer/CTypeList.h"
+#include "../../lib/filesystem/Filesystem.h"
+#include "../../lib/filesystem/CCompressedStream.h"
+#include "../../lib/VCMIDirs.h"
+#include "../../lib/mapping/CMap.h"
 #include "../../lib/CStopWatch.h"
 #include "../../lib/NetPacksLobby.h"
 #include "../../lib/CThreadHelper.h"
@@ -52,7 +53,6 @@
 #include "../../lib/CondSh.h"
 #include "../../lib/mapping/CCampaignHandler.h"
 
-#include <SDL_events.h>
 
 namespace fs = boost::filesystem;
 
@@ -61,9 +61,7 @@ ISelectionScreenInfo * SEL;
 
 static void do_quit()
 {
-	SDL_Event event;
-	event.quit.type = SDL_QUIT;
-	SDL_PushEvent(&event);
+	GH.pushUserEvent(EUserEvent::FORCE_QUIT);
 }
 
 CMenuScreen::CMenuScreen(const JsonNode & configNode)
@@ -73,7 +71,7 @@ CMenuScreen::CMenuScreen(const JsonNode & configNode)
 
 	background = std::make_shared<CPicture>(config["background"].String());
 	if(config["scalable"].Bool())
-		background->scaleTo(Point(screen->w, screen->h));
+		background->scaleTo(GH.screenDimensions());
 
 	pos = background->center();
 
@@ -276,8 +274,8 @@ const JsonNode & CMainMenuConfig::getCampaigns() const
 
 CMainMenu::CMainMenu()
 {
-	pos.w = screen->w;
-	pos.h = screen->h;
+	pos.w = GH.screenDimensions().x;
+	pos.h = GH.screenDimensions().y;
 
 	GH.defActionsDef = 63;
 	menu = std::make_shared<CMenuScreen>(CMainMenuConfig::get().getConfig()["window"]);
@@ -484,7 +482,7 @@ void CSimpleJoinScreen::connectToServer()
 {
 	textTitle->setText("Connecting...");
 	buttonOk->block(true);
-	CSDL_Ext::stopTextInput();
+	GH.stopTextInput();
 
 	boost::thread(&CSimpleJoinScreen::connectThread, this, inputAddress->getText(), boost::lexical_cast<ui16>(inputPort->getText()));
 }

+ 1 - 0
client/mainmenu/CPrologEpilogVideo.cpp

@@ -16,6 +16,7 @@
 #include "../CVideoHandler.h"
 #include "../gui/CGuiHandler.h"
 #include "../widgets/TextControls.h"
+#include "../renderSDL/SDL_Extensions.h"
 
 #include "../../lib/mapping/CCampaignHandler.h"
 

+ 1 - 1
client/render/CBitmapHandler.cpp

@@ -81,7 +81,7 @@ SDL_Surface * BitmapHandler::loadH3PCX(ui8 * pcx, size_t size)
 	}
 	else
 	{
-#if (SDL_BYTEORDER == SDL_BIG_ENDIAN)
+#ifdef VCMI_ENDIAN_BIG
 		int bmask = 0xff0000;
 		int gmask = 0x00ff00;
 		int rmask = 0x0000ff;

+ 26 - 0
client/render/Colors.cpp

@@ -0,0 +1,26 @@
+/*
+ * IFont.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 "Colors.h"
+
+#include <SDL_pixels.h>
+
+const SDL_Color Colors::YELLOW = { 229, 215, 123, SDL_ALPHA_OPAQUE };
+const SDL_Color Colors::WHITE = { 255, 243, 222, SDL_ALPHA_OPAQUE };
+const SDL_Color Colors::METALLIC_GOLD = { 173, 142, 66, SDL_ALPHA_OPAQUE };
+const SDL_Color Colors::GREEN = { 0, 255, 0, SDL_ALPHA_OPAQUE };
+const SDL_Color Colors::ORANGE = { 232, 184, 32, SDL_ALPHA_OPAQUE };
+const SDL_Color Colors::BRIGHT_YELLOW = { 242, 226, 110, SDL_ALPHA_OPAQUE };
+const SDL_Color Colors::DEFAULT_KEY_COLOR = {0, 255, 255, SDL_ALPHA_OPAQUE};
+const SDL_Color Colors::RED = {255, 0, 0, SDL_ALPHA_OPAQUE};
+const SDL_Color Colors::PURPLE = {255, 75, 125, SDL_ALPHA_OPAQUE};
+const SDL_Color Colors::BLACK = {0, 0, 0, SDL_ALPHA_OPAQUE};
+const SDL_Color Colors::TRANSPARENCY = {0, 0, 0, SDL_ALPHA_TRANSPARENT};

+ 50 - 0
client/render/Colors.h

@@ -0,0 +1,50 @@
+/*
+ * ICursor.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
+
+struct SDL_Color;
+
+/**
+ * The colors class defines color constants of type SDL_Color.
+ */
+class Colors
+{
+public:
+	/** the h3 yellow color, typically used for headlines */
+	static const SDL_Color YELLOW;
+
+	/** the standard h3 white color */
+	static const SDL_Color WHITE;
+
+	/** the metallic gold color used mostly as a border around buttons */
+	static const SDL_Color METALLIC_GOLD;
+
+	/** green color used for in-game console */
+	static const SDL_Color GREEN;
+
+	/** the h3 orange color, used for blocked buttons */
+	static const SDL_Color ORANGE;
+
+	/** the h3 bright yellow color, used for selection border */
+	static const SDL_Color BRIGHT_YELLOW;
+
+	/** default key color for all 8 & 24 bit graphics */
+	static const SDL_Color DEFAULT_KEY_COLOR;
+
+	/// Selected creature card
+	static const SDL_Color RED;
+
+	/// Minimap border
+	static const SDL_Color PURPLE;
+
+	static const SDL_Color BLACK;
+
+	static const SDL_Color TRANSPARENCY;
+};

+ 0 - 2
client/render/Graphics.cpp

@@ -42,8 +42,6 @@
 
 #include <SDL_surface.h>
 
-using namespace CSDL_Ext;
-
 Graphics * graphics = nullptr;
 
 void Graphics::loadPaletteAndColors()

+ 2 - 1
client/renderSDL/CTrueTypeFont.cpp

@@ -10,7 +10,8 @@
 #include "StdInc.h"
 #include "CTrueTypeFont.h"
 
-#include "SDL_Extensions.h"
+#include "../render/Colors.h"
+#include "../renderSDL/SDL_Extensions.h"
 
 #include "../../lib/JsonNode.h"
 #include "../../lib/CGeneralTextHandler.h"

+ 2 - 1
client/renderSDL/CursorHardware.cpp

@@ -11,8 +11,9 @@
 #include "StdInc.h"
 #include "CursorHardware.h"
 
-#include "SDL_Extensions.h"
+#include "../render/Colors.h"
 #include "../render/IImage.h"
+#include "SDL_Extensions.h"
 
 #include <SDL_render.h>
 #include <SDL_events.h>

+ 3 - 2
client/renderSDL/CursorSoftware.cpp

@@ -11,9 +11,10 @@
 #include "StdInc.h"
 #include "CursorSoftware.h"
 
-#include "SDL_Extensions.h"
-
+#include "../render/Colors.h"
 #include "../render/IImage.h"
+#include "../CMT.h"
+#include "SDL_Extensions.h"
 
 #include <SDL_render.h>
 #include <SDL_events.h>

+ 28 - 131
client/renderSDL/SDL_Extensions.cpp

@@ -13,40 +13,10 @@
 #include "SDL_PixelAccess.h"
 
 #include "../render/Graphics.h"
+#include "../render/Colors.h"
+#include "../CMT.h"
 
 #include <SDL_render.h>
-#include <SDL_video.h>
-#include <SDL_events.h>
-
-#ifdef VCMI_APPLE
-#include <dispatch/dispatch.h>
-#endif
-
-#ifdef VCMI_IOS
-#include "ios/utils.h"
-#endif
-
-const SDL_Color Colors::YELLOW = { 229, 215, 123, SDL_ALPHA_OPAQUE };
-const SDL_Color Colors::WHITE = { 255, 243, 222, SDL_ALPHA_OPAQUE };
-const SDL_Color Colors::METALLIC_GOLD = { 173, 142, 66, SDL_ALPHA_OPAQUE };
-const SDL_Color Colors::GREEN = { 0, 255, 0, SDL_ALPHA_OPAQUE };
-const SDL_Color Colors::ORANGE = { 232, 184, 32, SDL_ALPHA_OPAQUE };
-const SDL_Color Colors::BRIGHT_YELLOW = { 242, 226, 110, SDL_ALPHA_OPAQUE };
-const SDL_Color Colors::DEFAULT_KEY_COLOR = {0, 255, 255, SDL_ALPHA_OPAQUE};
-const SDL_Color Colors::RED = {255, 0, 0, SDL_ALPHA_OPAQUE};
-const SDL_Color Colors::PURPLE = {255, 75, 125, SDL_ALPHA_OPAQUE};
-const SDL_Color Colors::BLACK = {0, 0, 0, SDL_ALPHA_OPAQUE};
-const SDL_Color Colors::TRANSPARENCY = {0, 0, 0, SDL_ALPHA_TRANSPARENT};
-
-Rect CSDL_Ext::genRect(const int & hh, const int & ww, const int & xx, const int & yy)
-{
-	Rect ret;
-	ret.h=hh;
-	ret.w=ww;
-	ret.x=xx;
-	ret.y=yy;
-	return ret;
-}
 
 Rect CSDL_Ext::fromSDL(const SDL_Rect & rect)
 {
@@ -79,29 +49,17 @@ SDL_Color CSDL_Ext::toSDL(const ColorRGBA & color)
 	return result;
 }
 
-void CSDL_Ext::setColors(SDL_Surface *surface, SDL_Color *colors, int firstcolor, int ncolors)
+Rect CSDL_Ext::getDisplayBounds()
 {
-	SDL_SetPaletteColors(surface->format->palette,colors,firstcolor,ncolors);
-}
+	SDL_Rect displayBounds;
+	SDL_GetDisplayBounds(std::max(0, SDL_GetWindowDisplayIndex(mainWindow)), &displayBounds);
 
-void CSDL_Ext::warpMouse(int x, int y)
-{
-	SDL_WarpMouseInWindow(mainWindow,x,y);
+	return fromSDL(displayBounds);
 }
 
-bool CSDL_Ext::isCtrlKeyDown()
-{
-	return SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_LCTRL] || SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_RCTRL];
-}
-
-bool CSDL_Ext::isAltKeyDown()
-{
-	return SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_LALT] || SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_RALT];
-}
-
-bool CSDL_Ext::isShiftKeyDown()
+void CSDL_Ext::setColors(SDL_Surface *surface, SDL_Color *colors, int firstcolor, int ncolors)
 {
-	return SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_LSHIFT] || SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_RSHIFT];
+	SDL_SetPaletteColors(surface->format->palette,colors,firstcolor,ncolors);
 }
 
 void CSDL_Ext::setAlpha(SDL_Surface * bg, int value)
@@ -122,6 +80,11 @@ void CSDL_Ext::updateRect(SDL_Surface *surface, const Rect & rect )
 
 }
 
+SDL_Surface * CSDL_Ext::newSurface(int w, int h)
+{
+	return newSurface(w, h, screen);
+}
+
 SDL_Surface * CSDL_Ext::newSurface(int w, int h, SDL_Surface * mod) //creates new surface, with flags/format same as in surface given
 {
 	SDL_Surface * ret = SDL_CreateRGBSurface(0,w,h,mod->format->BitsPerPixel,mod->format->Rmask,mod->format->Gmask,mod->format->Bmask,mod->format->Amask);
@@ -409,14 +372,6 @@ uint32_t CSDL_Ext::colorTouint32_t(const SDL_Color * color)
 	return ret;
 }
 
-void CSDL_Ext::update(SDL_Surface * what)
-{
-	if(!what)
-		return;
-	if(0 !=SDL_UpdateTexture(screenTexture, nullptr, what->pixels, what->pitch))
-		logGlobal->error("%s SDL_UpdateTexture %s", __FUNCTION__, SDL_GetError());
-}
-
 static void drawLineX(SDL_Surface * sur, int x1, int y1, int x2, int y2, const SDL_Color & color1, const SDL_Color & color2)
 {
 	for(int x = x1; x <= x2; x++)
@@ -479,23 +434,27 @@ void CSDL_Ext::drawLine(SDL_Surface * sur, int x1, int y1, int x2, int y2, const
 	}
 }
 
-void CSDL_Ext::drawBorder(SDL_Surface * sur, int x, int y, int w, int h, const SDL_Color &color)
+void CSDL_Ext::drawBorder(SDL_Surface * sur, int x, int y, int w, int h, const SDL_Color &color, int depth)
 {
-	for(int i = 0; i < w; i++)
-	{
-		CSDL_Ext::putPixelWithoutRefreshIfInSurf(sur,x+i,y,color.r,color.g,color.b);
-		CSDL_Ext::putPixelWithoutRefreshIfInSurf(sur,x+i,y+h-1,color.r,color.g,color.b);
-	}
-	for(int i = 0; i < h; i++)
+	depth = std::max(1, depth);
+	for(int depthIterator = 0; depthIterator < depth; depthIterator++)
 	{
-		CSDL_Ext::putPixelWithoutRefreshIfInSurf(sur,x,y+i,color.r,color.g,color.b);
-		CSDL_Ext::putPixelWithoutRefreshIfInSurf(sur,x+w-1,y+i,color.r,color.g,color.b);
+		for(int i = 0; i < w; i++)
+		{
+			CSDL_Ext::putPixelWithoutRefreshIfInSurf(sur,x+i,y+depthIterator,color.r,color.g,color.b);
+			CSDL_Ext::putPixelWithoutRefreshIfInSurf(sur,x+i,y+h-1-depthIterator,color.r,color.g,color.b);
+		}
+		for(int i = 0; i < h; i++)
+		{
+			CSDL_Ext::putPixelWithoutRefreshIfInSurf(sur,x+depthIterator,y+i,color.r,color.g,color.b);
+			CSDL_Ext::putPixelWithoutRefreshIfInSurf(sur,x+w-1-depthIterator,y+i,color.r,color.g,color.b);
+		}
 	}
 }
 
-void CSDL_Ext::drawBorder( SDL_Surface * sur, const Rect &r, const SDL_Color &color )
+void CSDL_Ext::drawBorder( SDL_Surface * sur, const Rect &r, const SDL_Color &color, int depth)
 {
-	drawBorder(sur, r.x, r.y, r.w, r.h, color);
+	drawBorder(sur, r.x, r.y, r.w, r.h, color, depth);
 }
 
 void CSDL_Ext::drawDashedBorder(SDL_Surface * sur, const Rect &r, const SDL_Color &color)
@@ -581,15 +540,6 @@ uint8_t * CSDL_Ext::getPxPtr(const SDL_Surface * const &srf, const int x, const
 	return (uint8_t *)srf->pixels + y * srf->pitch + x * srf->format->BytesPerPixel;
 }
 
-std::string CSDL_Ext::processStr(std::string str, std::vector<std::string> & tor)
-{
-	for (size_t i=0; (i<tor.size())&&(boost::find_first(str,"%s")); ++i)
-	{
-		boost::replace_first(str,"%s",tor[i]);
-	}
-	return str;
-}
-
 bool CSDL_Ext::isTransparent( SDL_Surface * srf, const Point & position )
 {
 	return isTransparent(srf, position.x, position.y);
@@ -883,59 +833,6 @@ SDL_Color CSDL_Ext::makeColor(ui8 r, ui8 g, ui8 b, ui8 a)
 	return ret;
 }
 
-void CSDL_Ext::startTextInput(const Rect & whereInput)
-{
-#ifdef VCMI_APPLE
-	dispatch_async(dispatch_get_main_queue(), ^{
-#endif
-
-	// TODO ios: looks like SDL bug actually, try fixing there
-	auto renderer = SDL_GetRenderer(mainWindow);
-	float scaleX, scaleY;
-	SDL_Rect viewport;
-	SDL_RenderGetScale(renderer, &scaleX, &scaleY);
-	SDL_RenderGetViewport(renderer, &viewport);
-
-#ifdef VCMI_IOS
-	const auto nativeScale = iOS_utils::screenScale();
-	scaleX /= nativeScale;
-	scaleY /= nativeScale;
-#endif
-
-	SDL_Rect rectInScreenCoordinates;
-	rectInScreenCoordinates.x = (viewport.x + whereInput.x) * scaleX;
-	rectInScreenCoordinates.y = (viewport.y + whereInput.y) * scaleY;
-	rectInScreenCoordinates.w = whereInput.w * scaleX;
-	rectInScreenCoordinates.h = whereInput.h * scaleY;
-
-	SDL_SetTextInputRect(&rectInScreenCoordinates);
-
-	if (SDL_IsTextInputActive() == SDL_FALSE)
-	{
-		SDL_StartTextInput();
-	}
-
-#ifdef VCMI_APPLE
-	});
-#endif
-}
-
-void CSDL_Ext::stopTextInput()
-{
-#ifdef VCMI_APPLE
-	dispatch_async(dispatch_get_main_queue(), ^{
-#endif
-
-	if (SDL_IsTextInputActive() == SDL_TRUE)
-	{
-		SDL_StopTextInput();
-	}
-
-#ifdef VCMI_APPLE
-	});
-#endif
-}
-
 STRONG_INLINE static uint32_t mapColor(SDL_Surface * surface, SDL_Color color)
 {
 	return SDL_MapRGBA(surface->format, color.r, color.g, color.b, color.a);

+ 10 - 80
client/renderSDL/SDL_Extensions.h

@@ -20,11 +20,6 @@ struct SDL_Texture;
 struct SDL_Surface;
 struct SDL_Color;
 
-extern SDL_Window * mainWindow;
-extern SDL_Renderer * mainRenderer;
-extern SDL_Texture * screenTexture;
-extern SDL_Surface * screen, *screen2, *screenBuf;
-
 VCMI_LIB_NAMESPACE_BEGIN
 
 class Rect;
@@ -32,44 +27,6 @@ class Point;
 
 VCMI_LIB_NAMESPACE_END
 
-/**
- * The colors class defines color constants of type SDL_Color.
- */
-class Colors
-{
-public:
-	/** the h3 yellow color, typically used for headlines */
-	static const SDL_Color YELLOW;
-
-	/** the standard h3 white color */
-	static const SDL_Color WHITE;
-
-	/** the metallic gold color used mostly as a border around buttons */
-	static const SDL_Color METALLIC_GOLD;
-
-	/** green color used for in-game console */
-	static const SDL_Color GREEN;
-
-	/** the h3 orange color, used for blocked buttons */
-	static const SDL_Color ORANGE;
-
-	/** the h3 bright yellow color, used for selection border */
-	static const SDL_Color BRIGHT_YELLOW;
-
-	/** default key color for all 8 & 24 bit graphics */
-	static const SDL_Color DEFAULT_KEY_COLOR;
-
-	/// Selected creature card
-	static const SDL_Color RED;
-
-	/// Minimap border
-	static const SDL_Color PURPLE;
-
-	static const SDL_Color BLACK;
-
-	static const SDL_Color TRANSPARENCY;
-};
-
 namespace CSDL_Ext
 {
 
@@ -86,39 +43,13 @@ ColorRGBA fromSDL(const SDL_Color & color);
 SDL_Color toSDL(const ColorRGBA & color);
 
 void setColors(SDL_Surface *surface, SDL_Color *colors, int firstcolor, int ncolors);
-void warpMouse(int x, int y);
-bool isCtrlKeyDown();
-bool isAltKeyDown();
-bool isShiftKeyDown();
 void setAlpha(SDL_Surface * bg, int value);
 
-template<typename IntType>
-std::string makeNumberShort(IntType number, IntType maxLength = 3) //the output is a string containing at most 5 characters [4 if positive] (eg. intead 10000 it gives 10k)
-{
-	IntType max = pow(10, maxLength);
-	if (std::abs(number) < max)
-		return boost::lexical_cast<std::string>(number);
-
-	std::string symbols = " kMGTPE";
-	auto iter = symbols.begin();
-
-	while (number >= max)
-	{
-		number /= 1000;
-		iter++;
-
-		assert(iter != symbols.end());//should be enough even for int64
-	}
-	return boost::lexical_cast<std::string>(number) + *iter;
-}
-
-Rect genRect(const int & hh, const int & ww, const int & xx, const int & yy);
-
 typedef void (*TColorPutter)(uint8_t *&ptr, const uint8_t & R, const uint8_t & G, const uint8_t & B);
 typedef void (*TColorPutterAlpha)(uint8_t *&ptr, const uint8_t & R, const uint8_t & G, const uint8_t & B, const uint8_t & A);
 
-	void blitAt(SDL_Surface * src, int x, int y, SDL_Surface * dst=screen);
-	void blitAt(SDL_Surface * src, const Rect & pos, SDL_Surface * dst=screen);
+	void blitAt(SDL_Surface * src, int x, int y, SDL_Surface * dst);
+	void blitAt(SDL_Surface * src, const Rect & pos, SDL_Surface * dst);
 
 	void setClipRect(SDL_Surface * src, const Rect & other);
 	void getClipRect(SDL_Surface * src, Rect & other);
@@ -150,14 +81,17 @@ typedef void (*TColorPutterAlpha)(uint8_t *&ptr, const uint8_t & R, const uint8_
 	uint32_t colorTouint32_t(const SDL_Color * color); //little endian only
 	SDL_Color makeColor(ui8 r, ui8 g, ui8 b, ui8 a);
 
-	void update(SDL_Surface * what = screen); //updates whole surface (default - main screen)
+	/// returns dimensions of display on which VCMI window is located
+	Rect getDisplayBounds();
+
 	void drawLine(SDL_Surface * sur, int x1, int y1, int x2, int y2, const SDL_Color & color1, const SDL_Color & color2);
-	void drawBorder(SDL_Surface * sur, int x, int y, int w, int h, const SDL_Color &color);
-	void drawBorder(SDL_Surface * sur, const Rect &r, const SDL_Color &color);
+	void drawBorder(SDL_Surface * sur, int x, int y, int w, int h, const SDL_Color &color, int depth = 1);
+	void drawBorder(SDL_Surface * sur, const Rect &r, const SDL_Color &color, int depth = 1);
 	void drawDashedBorder(SDL_Surface * sur, const Rect &r, const SDL_Color &color);
 	void setPlayerColor(SDL_Surface * sur, PlayerColor player); //sets correct color of flags; -1 for neutral
-	std::string processStr(std::string str, std::vector<std::string> & tor); //replaces %s in string
-	SDL_Surface * newSurface(int w, int h, SDL_Surface * mod=screen); //creates new surface, with flags/format same as in surface given
+
+	SDL_Surface * newSurface(int w, int h, SDL_Surface * mod); //creates new surface, with flags/format same as in surface given
+	SDL_Surface * newSurface(int w, int h); //creates new surface, with flags/format same as in screen surface
 	SDL_Surface * copySurface(SDL_Surface * mod); //returns copy of given surface
 	template<int bpp>
 	SDL_Surface * createSurfaceWithBpp(int width, int height); //create surface with give bits per pixels value
@@ -173,14 +107,10 @@ typedef void (*TColorPutterAlpha)(uint8_t *&ptr, const uint8_t & R, const uint8_
 	void applyEffectBpp( SDL_Surface * surf, const Rect & rect, int mode );
 	void applyEffect(SDL_Surface * surf, const Rect & rect, int mode); //mode: 0 - sepia, 1 - grayscale
 
-	void startTextInput(const Rect & where);
-	void stopTextInput();
-
 	void setColorKey(SDL_Surface * surface, SDL_Color color);
 
 	///set key-color to 0,255,255
 	void setDefaultColorKey(SDL_Surface * surface);
-
 	///set key-color to 0,255,255 only if it exactly mapped
 	void setDefaultColorKeyPresize(SDL_Surface * surface);
 

+ 1 - 1
client/renderSDL/SDL_PixelAccess.h

@@ -68,7 +68,7 @@ namespace Channels
 		static channel_empty a;
 	};
 
-#if (SDL_BYTEORDER == SDL_BIG_ENDIAN)
+#ifdef VCMI_ENDIAN_BIG
 
 	template<>
 	struct px<4>

+ 9 - 12
client/widgets/Buttons.cpp

@@ -21,12 +21,11 @@
 #include "../gui/CGuiHandler.h"
 #include "../windows/InfoWindows.h"
 #include "../render/CAnimation.h"
+#include "../renderSDL/SDL_Extensions.h"
 
 #include "../../lib/CConfigHandler.h"
 #include "../../lib/CGeneralTextHandler.h"
 
-#include <SDL_events.h>
-
 void CButton::update()
 {
 	if (overlay)
@@ -555,22 +554,22 @@ void CSlider::sliderClicked()
 		addUsedEvents(MOVE);
 }
 
-void CSlider::mouseMoved (const SDL_MouseMotionEvent & sEvent)
+void CSlider::mouseMoved (const Point & cursorPosition)
 {
 	double v = 0;
 	if(horizontal)
 	{
-		if(	std::abs(sEvent.y-(pos.y+pos.h/2)) > pos.h/2+40  ||  std::abs(sEvent.x-(pos.x+pos.w/2)) > pos.w/2  )
+		if(	std::abs(cursorPosition.y-(pos.y+pos.h/2)) > pos.h/2+40  ||  std::abs(cursorPosition.x-(pos.x+pos.w/2)) > pos.w/2  )
 			return;
-		v = sEvent.x - pos.x - 24;
+		v = cursorPosition.x - pos.x - 24;
 		v *= positions;
 		v /= (pos.w - 48);
 	}
 	else
 	{
-		if(std::abs(sEvent.x-(pos.x+pos.w/2)) > pos.w/2+40  ||  std::abs(sEvent.y-(pos.y+pos.h/2)) > pos.h/2  )
+		if(std::abs(cursorPosition.x-(pos.x+pos.w/2)) > pos.w/2+40  ||  std::abs(cursorPosition.y-(pos.y+pos.h/2)) > pos.h/2  )
 			return;
-		v = sEvent.y - pos.y - 24;
+		v = cursorPosition.y - pos.y - 24;
 		v *= positions;
 		v /= (pos.h - 48);
 	}
@@ -679,7 +678,7 @@ void CSlider::clickLeft(tribool down, bool previousState)
 			return;
 		// 		if (rw>1) return;
 		// 		if (rw<0) return;
-		slider->clickLeft(true, slider->mouseState(EIntObjMouseBtnType::LEFT));
+		slider->clickLeft(true, slider->mouseState(MouseButton::LEFT));
 		moveTo((int)(rw * positions  +  0.5));
 		return;
 	}
@@ -780,12 +779,10 @@ void CSlider::wheelScrolled(bool down, bool in)
 	moveTo(value + 3 * (down ? +scrollStep : -scrollStep));
 }
 
-void CSlider::keyPressed(const SDL_KeyboardEvent & key)
+void CSlider::keyPressed(const SDL_Keycode & key)
 {
-	if(key.state != SDL_PRESSED) return;
-
 	int moveDest = value;
-	switch(key.keysym.sym)
+	switch(key)
 	{
 	case SDLK_UP:
 		if (!horizontal)

+ 3 - 3
client/widgets/Buttons.h

@@ -10,7 +10,7 @@
 #pragma once
 
 #include "../gui/CIntObject.h"
-#include "../renderSDL/SDL_Extensions.h"
+#include "../render/Colors.h"
 #include "../../lib/FunctionList.h"
 
 #include <SDL_pixels.h>
@@ -270,10 +270,10 @@ public:
 
 	void addCallback(std::function<void(int)> callback);
 
-	void keyPressed(const SDL_KeyboardEvent & key) override;
+	void keyPressed(const SDL_Keycode & key) override;
 	void wheelScrolled(bool down, bool in) override;
 	void clickLeft(tribool down, bool previousState) override;
-	void mouseMoved (const SDL_MouseMotionEvent & sEvent) override;
+	void mouseMoved (const Point & cursorPosition) override;
 	void showAll(SDL_Surface * to) override;
 
 	 /// @param position coordinates of slider

+ 1 - 0
client/widgets/CArtifactHolder.cpp

@@ -19,6 +19,7 @@
 #include "../windows/CHeroWindow.h"
 #include "../windows/CSpellWindow.h"
 #include "../windows/GUIClasses.h"
+#include "../renderSDL/SDL_Extensions.h"
 #include "../CPlayerInterface.h"
 #include "../CGameInfo.h"
 

+ 8 - 15
client/widgets/CGarrisonInt.cpp

@@ -14,11 +14,11 @@
 #include "TextControls.h"
 
 #include "../gui/CGuiHandler.h"
-
-#include "../CGameInfo.h"
-#include "../CPlayerInterface.h"
+#include "../renderSDL/SDL_Extensions.h"
 #include "../windows/CCreatureWindow.h"
 #include "../windows/GUIClasses.h"
+#include "../CGameInfo.h"
+#include "../CPlayerInterface.h"
 
 #include "../../CCallback.h"
 
@@ -28,12 +28,6 @@
 
 #include "../../lib/CGameState.h"
 
-#ifdef VCMI_MAC
-#define SDL_SCANCODE_LCTRL SDL_SCANCODE_LGUI
-#endif
-
-#include <SDL_keyboard.h>
-
 void CGarrisonSlot::setHighlight(bool on)
 {
 	if (on)
@@ -336,7 +330,7 @@ void CGarrisonSlot::clickLeft(tribool down, bool previousState)
 				lastHeroStackSelected = true;
 			}
 
-			if((owner->getSplittingMode() || LOCPLINT->shiftPressed()) // split window
+			if((owner->getSplittingMode() || GH.isKeyboardShiftDown()) // split window
 				&& (!creature || creature == selection->creature))
 			{
 				refr = split();
@@ -380,7 +374,7 @@ void CGarrisonSlot::update()
 		creatureImage->setFrame(creature->getIconIndex());
 
 		stackCount->enable();
-		stackCount->setText(CSDL_Ext::makeNumberShort(myStack->count, 4));
+		stackCount->setText(vstd::formatMetric(myStack->count, 4));
 	}
 	else
 	{
@@ -439,10 +433,9 @@ void CGarrisonSlot::splitIntoParts(CGarrisonSlot::EGarrisonType type, int amount
 
 bool CGarrisonSlot::handleSplittingShortcuts()
 {
-	const uint8_t * state = SDL_GetKeyboardState(NULL);
-	const bool isAlt = !!state[SDL_SCANCODE_LALT];
-	const bool isLShift = !!state[SDL_SCANCODE_LSHIFT];
-	const bool isLCtrl = !!state[SDL_SCANCODE_LCTRL];
+	const bool isAlt = GH.isKeyboardAltDown();
+	const bool isLShift = GH.isKeyboardShiftDown();
+	const bool isLCtrl = GH.isKeyboardCtrlDown();
 
 	if(!isAlt && !isLShift && !isLCtrl)
 		return false; // This is only case when return false

+ 14 - 11
client/widgets/MiscWidgets.cpp

@@ -182,6 +182,18 @@ void CMinorResDataBar::show(SDL_Surface * to)
 {
 }
 
+std::string CMinorResDataBar::buildDateString()
+{
+	std::string pattern = "%s: %d, %s: %d, %s: %d";
+
+	auto formatted = boost::format(pattern)
+		% CGI->generaltexth->translate("core.genrltxt.62") % LOCPLINT->cb->getDate(Date::MONTH)
+		% CGI->generaltexth->translate("core.genrltxt.63") % LOCPLINT->cb->getDate(Date::WEEK)
+		% CGI->generaltexth->translate("core.genrltxt.64") % LOCPLINT->cb->getDate(Date::DAY_OF_WEEK);
+
+	return boost::str(formatted);
+}
+
 void CMinorResDataBar::showAll(SDL_Surface * to)
 {
 	CIntObject::showAll(to);
@@ -192,16 +204,7 @@ void CMinorResDataBar::showAll(SDL_Surface * to)
 
 		graphics->fonts[FONT_SMALL]->renderTextCenter(to, text, Colors::WHITE, Point(pos.x + 50 + 76 * i, pos.y + pos.h/2));
 	}
-	std::vector<std::string> temp;
-
-	temp.push_back(boost::lexical_cast<std::string>(LOCPLINT->cb->getDate(Date::MONTH)));
-	temp.push_back(boost::lexical_cast<std::string>(LOCPLINT->cb->getDate(Date::WEEK)));
-	temp.push_back(boost::lexical_cast<std::string>(LOCPLINT->cb->getDate(Date::DAY_OF_WEEK)));
-
-	std::string datetext =  CGI->generaltexth->allTexts[62]+": %s, " + CGI->generaltexth->allTexts[63]
-							+ ": %s, " + CGI->generaltexth->allTexts[64] + ": %s";
-
-	graphics->fonts[FONT_SMALL]->renderTextCenter(to, CSDL_Ext::processStr(datetext,temp), Colors::WHITE, Point(pos.x+545+(pos.w-545)/2,pos.y+pos.h/2));
+	graphics->fonts[FONT_SMALL]->renderTextCenter(to, buildDateString(), Colors::WHITE, Point(pos.x+545+(pos.w-545)/2,pos.y+pos.h/2));
 }
 
 CMinorResDataBar::CMinorResDataBar()
@@ -248,7 +251,7 @@ void CArmyTooltip::init(const InfoAboutArmy &army)
 		std::string subtitle;
 		if(army.army.isDetailed)
 		{
-			subtitle = CSDL_Ext::makeNumberShort(slot.second.count, 4);
+			subtitle = vstd::formatMetric(slot.second.count, 4);
 		}
 		else
 		{

+ 2 - 0
client/widgets/MiscWidgets.h

@@ -117,6 +117,8 @@ public:
 class CMinorResDataBar : public CIntObject
 {
 	std::shared_ptr<CPicture> background;
+
+	std::string buildDateString();
 public:
 	void show(SDL_Surface * to) override;
 	void showAll(SDL_Surface * to) override;

+ 16 - 17
client/widgets/TextControls.cpp

@@ -17,14 +17,14 @@
 #include "../gui/CGuiHandler.h"
 #include "../windows/CMessage.h"
 #include "../adventureMap/CInGameConsole.h"
+#include "../renderSDL/SDL_Extensions.h"
+
 #include "../../lib/CGeneralTextHandler.h"
 
 #ifdef VCMI_ANDROID
 #include "lib/CAndroidVMHelper.h"
 #endif
 
-#include <SDL_events.h>
-
 std::list<CFocusable*> CFocusable::focusables;
 CFocusable * CFocusable::inputWithFocus;
 
@@ -353,14 +353,14 @@ void CGStatusBar::setEnteringMode(bool on)
 	{
 		assert(enteringText == false);
 		alignment = ETextAlignment::TOPLEFT;
-		CSDL_Ext::startTextInput(pos);
+		GH.startTextInput(pos);
 		setText(consoleText);
 	}
 	else
 	{
 		assert(enteringText == true);
 		alignment = ETextAlignment::CENTER;
-		CSDL_Ext::stopTextInput();
+		GH.stopTextInput();
 		setText(hoverText);
 	}
 	enteringText = on;
@@ -526,7 +526,7 @@ CKeyboardFocusListener::CKeyboardFocusListener(CTextInput * textInput)
 
 void CKeyboardFocusListener::focusGot()
 {
-	CSDL_Ext::startTextInput(textInput->pos);
+	GH.startTextInput(textInput->pos);
 	usageIndex++;
 }
 
@@ -534,7 +534,7 @@ void CKeyboardFocusListener::focusLost()
 {
 	if(0 == --usageIndex)
 	{
-		CSDL_Ext::stopTextInput();
+		GH.stopTextInput();
 	}
 }
 
@@ -549,13 +549,12 @@ void CTextInput::clickLeft(tribool down, bool previousState)
 		giveFocus();
 }
 
-void CTextInput::keyPressed(const SDL_KeyboardEvent & key)
+void CTextInput::keyPressed(const SDL_Keycode & key)
 {
-
-	if(!focus || key.state != SDL_PRESSED)
+	if(!focus)
 		return;
 
-	if(key.keysym.sym == SDLK_TAB)
+	if(key == SDLK_TAB)
 	{
 		moveFocus();
 		GH.breakEventHandling();
@@ -564,7 +563,7 @@ void CTextInput::keyPressed(const SDL_KeyboardEvent & key)
 
 	bool redrawNeeded = false;
 
-	switch(key.keysym.sym)
+	switch(key)
 	{
 	case SDLK_DELETE: // have index > ' ' so it won't be filtered out by default section
 		return;
@@ -603,21 +602,21 @@ void CTextInput::setText(const std::string & nText, bool callCb)
 		cb(text);
 }
 
-bool CTextInput::captureThisEvent(const SDL_KeyboardEvent & key)
+bool CTextInput::captureThisKey(const SDL_Keycode & key)
 {
-	if(key.keysym.sym == SDLK_RETURN || key.keysym.sym == SDLK_KP_ENTER || key.keysym.sym == SDLK_ESCAPE)
+	if(key == SDLK_RETURN || key == SDLK_KP_ENTER || key == SDLK_ESCAPE)
 		return false;
 
 	return true;
 }
 
-void CTextInput::textInputed(const SDL_TextInputEvent & event)
+void CTextInput::textInputed(const std::string & enteredText)
 {
 	if(!focus)
 		return;
 	std::string oldText = text;
 
-	text += event.text;
+	text += enteredText;
 
 	filters(text, oldText);
 	if(text != oldText)
@@ -628,12 +627,12 @@ void CTextInput::textInputed(const SDL_TextInputEvent & event)
 	newText.clear();
 }
 
-void CTextInput::textEdited(const SDL_TextEditingEvent & event)
+void CTextInput::textEdited(const std::string & enteredText)
 {
 	if(!focus)
 		return;
 
-	newText = event.text;
+	newText = enteredText;
 	redraw();
 	cb(text + newText);
 }

+ 6 - 5
client/widgets/TextControls.h

@@ -11,7 +11,7 @@
 
 #include "../gui/CIntObject.h"
 #include "../gui/TextAlignment.h"
-#include "../renderSDL/SDL_Extensions.h"
+#include "../render/Colors.h"
 #include "../render/Graphics.h"
 #include "../../lib/FunctionList.h"
 
@@ -225,11 +225,12 @@ public:
 	CTextInput(const Rect & Pos, std::shared_ptr<IImage> srf);
 
 	void clickLeft(tribool down, bool previousState) override;
-	void keyPressed(const SDL_KeyboardEvent & key) override;
-	bool captureThisEvent(const SDL_KeyboardEvent & key) override;
+	void keyPressed(const SDL_Keycode & key) override;
 
-	void textInputed(const SDL_TextInputEvent & event) override;
-	void textEdited(const SDL_TextEditingEvent & event) override;
+	bool captureThisKey(const SDL_Keycode & key) override;
+
+	void textInputed(const std::string & enteredText) override;
+	void textEdited(const std::string & enteredText) override;
 
 	//Filter that will block all characters not allowed in filenames
 	static void filenameFilter(std::string & text, const std::string & oldText);

+ 2 - 2
client/windows/BattleOptionsWindow.cpp

@@ -102,7 +102,7 @@ BattleOptionsWindow::BattleOptionsWindow(BattleInterface * owner):
 int BattleOptionsWindow::getAnimSpeed() const
 {
 	if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-battle-speed"].isNull())
-		return static_cast<int>(vstd::round(settings["session"]["spectate-battle-speed"].Float()));
+		return static_cast<int>(std::round(settings["session"]["spectate-battle-speed"].Float()));
 
-	return static_cast<int>(vstd::round(settings["battle"]["speedFactor"].Float()));
+	return static_cast<int>(std::round(settings["battle"]["speedFactor"].Float()));
 }

+ 7 - 10
client/windows/CCastleInterface.cpp

@@ -43,7 +43,6 @@
 #include "../../lib/mapObjects/CGHeroInstance.h"
 #include "../../lib/mapObjects/CGTownInstance.h"
 
-#include <SDL_events.h>
 
 CBuildingRect::CBuildingRect(CCastleBuildings * Par, const CGTownInstance * Town, const CStructure * Str)
 	: CShowableAnim(0, 0, Str->defName, CShowableAnim::BASE, BUILDING_FRAME_TIME),
@@ -146,7 +145,7 @@ void CBuildingRect::clickRight(tribool down, bool previousState)
 		else
 		{
 			int level = ( bid - BuildingID::DWELL_FIRST ) % GameConstants::CREATURES_PER_TOWN;
-			GH.pushIntT<CDwellingInfoBox>(parent->pos.x+parent->pos.w/2, parent->pos.y+parent->pos.h/2, town, level);
+			GH.pushIntT<CDwellingInfoBox>(parent->pos.x+parent->pos.w / 2, parent->pos.y+parent->pos.h  /2, town, level);
 		}
 	}
 }
@@ -241,11 +240,11 @@ std::string CBuildingRect::getSubtitle()//hover text for building
 	}
 }
 
-void CBuildingRect::mouseMoved (const SDL_MouseMotionEvent & sEvent)
+void CBuildingRect::mouseMoved (const Point & cursorPosition)
 {
-	if(area && pos.isInside(sEvent.x, sEvent.y))
+	if(area && pos.isInside(cursorPosition.x, cursorPosition.y))
 	{
-		if(area->isTransparent(GH.getCursorPosition() - pos.topLeft())) //hovered pixel is inside this building
+		if(area->isTransparent(cursorPosition - pos.topLeft())) //hovered pixel is inside this building
 		{
 			if(parent->selectedBuilding == this)
 			{
@@ -1066,7 +1065,7 @@ void CCreaInfo::clickRight(tribool down, bool previousState)
 	if(down)
 	{
 		if (showAvailable)
-			GH.pushIntT<CDwellingInfoBox>(screen->w/2, screen->h/2, town, level);
+			GH.pushIntT<CDwellingInfoBox>(GH.screenDimensions().x / 2, GH.screenDimensions().y / 2, town, level);
 		else
 			CRClickPopup::createAndPush(genGrowthText(), std::make_shared<CComponent>(CComponent::creature, creature->idNumber));
 	}
@@ -1255,11 +1254,9 @@ void CCastleInterface::recreateIcons()
 		creainfo.push_back(std::make_shared<CCreaInfo>(Point(14+55*(int)i, 507), town, (int)i+4));
 }
 
-void CCastleInterface::keyPressed(const SDL_KeyboardEvent & key)
+void CCastleInterface::keyPressed(const SDL_Keycode & key)
 {
-	if(key.state != SDL_PRESSED) return;
-
-	switch(key.keysym.sym)
+	switch(key)
 	{
 	case SDLK_UP:
 		townlist->selectPrev();

+ 3 - 2
client/windows/CCastleInterface.h

@@ -68,7 +68,7 @@ public:
 	void hover(bool on) override;
 	void clickLeft(tribool down, bool previousState) override;
 	void clickRight(tribool down, bool previousState) override;
-	void mouseMoved (const SDL_MouseMotionEvent & sEvent) override;
+	void mouseMoved (const Point & cursorPosition) override;
 	void show(SDL_Surface * to) override;
 	void showAll(SDL_Surface * to) override;
 };
@@ -245,7 +245,8 @@ public:
 
 	void castleTeleport(int where);
 	void townChange();
-	void keyPressed(const SDL_KeyboardEvent & key) override;
+	void keyPressed(const SDL_Keycode & key) override;
+
 	void close();
 	void addBuilding(BuildingID bid);
 	void removeBuilding(BuildingID bid);

+ 4 - 5
client/windows/CCreatureWindow.cpp

@@ -22,6 +22,7 @@
 #include "../widgets/TextControls.h"
 #include "../widgets/ObjectLists.h"
 #include "../gui/CGuiHandler.h"
+#include "../renderSDL/SDL_Extensions.h"
 
 #include "../../CCallback.h"
 #include "../../lib/CStack.h"
@@ -31,8 +32,6 @@
 #include "../../lib/CHeroHandler.h"
 #include "../../lib/CGameState.h"
 
-using namespace CSDL_Ext;
-
 class CCreatureArtifactInstance;
 class CSelectableSkill;
 
@@ -518,8 +517,8 @@ CStackWindow::MainSection::MainSection(CStackWindow * owner, int yOffset, bool s
 
 	const CStack * battleStack = parent->info->stack;
 
-	morale = std::make_shared<MoraleLuckBox>(true, genRect(42, 42, 321, 110));
-	luck = std::make_shared<MoraleLuckBox>(false, genRect(42, 42, 375, 110));
+	morale = std::make_shared<MoraleLuckBox>(true, Rect(Point(321, 110), Point(42, 42) ));
+	luck = std::make_shared<MoraleLuckBox>(false,  Rect(Point(375, 110), Point(42, 42) ));
 
 	if(battleStack != nullptr) // in battle
 	{
@@ -583,7 +582,7 @@ CStackWindow::MainSection::MainSection(CStackWindow * owner, int yOffset, bool s
 		}
 		expLabel = std::make_shared<CLabel>(
 				pos.x + 21, pos.y + 52, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE,
-				makeNumberShort<TExpType>(stack->experience, 6));
+				vstd::formatMetric(stack->experience, 6));
 	}
 
 	if(showArt)

+ 0 - 1
client/windows/CHeroWindow.cpp

@@ -15,7 +15,6 @@
 #include "GUIClasses.h"
 
 #include "../CGameInfo.h"
-#include "../CMT.h"
 #include "../CPlayerInterface.h"
 
 #include "../gui/CGuiHandler.h"

+ 0 - 1
client/windows/CKingdomInterface.cpp

@@ -14,7 +14,6 @@
 #include "InfoWindows.h"
 
 #include "../CGameInfo.h"
-#include "../CMT.h"
 #include "../CPlayerInterface.h"
 #include "../gui/CGuiHandler.h"
 #include "../widgets/CComponent.h"

+ 5 - 3
client/windows/CMessage.cpp

@@ -18,8 +18,10 @@
 #include "../widgets/Buttons.h"
 #include "../widgets/CComponent.h"
 #include "../widgets/TextControls.h"
+#include "../gui/CGuiHandler.h"
 #include "../render/CAnimation.h"
 #include "../render/IImage.h"
+#include "../renderSDL/SDL_Extensions.h"
 
 #include <SDL_surface.h>
 
@@ -210,8 +212,8 @@ void CMessage::drawIWindow(CInfoWindow * ret, std::string text, PlayerColor play
 	assert(ret && ret->text);
 	for(int i = 0;
 		i < ARRAY_COUNT(sizes)
-			&& sizes[i][0] < screen->w - 150
-			&& sizes[i][1] < screen->h - 150
+			&& sizes[i][0] < GH.screenDimensions().x - 150
+			&& sizes[i][1] < GH.screenDimensions().y - 150
 			&& ret->text->slider;
 		i++)
 	{
@@ -253,7 +255,7 @@ void CMessage::drawIWindow(CInfoWindow * ret, std::string text, PlayerColor play
 	vstd::amax(winSize.first, comps.w);
 	vstd::amax(winSize.first, bw);
 
-	vstd::amin(winSize.first, screen->w - 150);
+	vstd::amin(winSize.first, GH.screenDimensions().x - 150);
 
 	ret->bitmap = drawDialogBox (winSize.first + 2*SIDE_MARGIN, winSize.second + 2*SIDE_MARGIN, player);
 	ret->pos.h=ret->bitmap->h;

+ 1 - 0
client/windows/CQuestLog.cpp

@@ -18,6 +18,7 @@
 #include "../adventureMap/CAdvMapInt.h"
 #include "../widgets/Buttons.h"
 #include "../adventureMap/CMinimap.h"
+#include "../renderSDL/SDL_Extensions.h"
 
 #include "../../CCallback.h"
 #include "../../lib/CArtHandler.h"

+ 1 - 1
client/windows/CQuestLog.h

@@ -66,7 +66,7 @@ class CQuestMinimap : public CMinimap
 
 	void clickLeft(tribool down, bool previousState) override{}; //minimap ignores clicking on its surface
 	void iconClicked();
-	void mouseMoved (const SDL_MouseMotionEvent & sEvent) override{};
+	void mouseMoved (const Point & cursorPosition) override{};
 
 public:
 	const QuestInfo * currentQuest;

+ 21 - 37
client/windows/CSpellWindow.cpp

@@ -17,7 +17,6 @@
 #include "CCastleInterface.h"
 
 #include "../CGameInfo.h"
-#include "../CMT.h"
 #include "../CPlayerInterface.h"
 #include "../CVideoHandler.h"
 
@@ -28,6 +27,7 @@
 #include "../widgets/TextControls.h"
 #include "../adventureMap/CAdvMapInt.h"
 #include "../render/CAnimation.h"
+#include "../renderSDL/SDL_Extensions.h"
 
 #include "../../CCallback.h"
 
@@ -39,8 +39,6 @@
 
 #include "../../lib/mapObjects/CGHeroInstance.h"
 
-#include <SDL_events.h>
-
 CSpellWindow::InteractiveArea::InteractiveArea(const Rect & myRect, std::function<void()> funcL, int helpTextId, CSpellWindow * _owner)
 {
 	addUsedEvents(LCLICK | RCLICK | HOVER);
@@ -185,38 +183,25 @@ CSpellWindow::CSpellWindow(const CGHeroInstance * _myHero, CPlayerInterface * _m
 	mana = std::make_shared<CLabel>(435, 426, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, boost::lexical_cast<std::string>(myHero->mana));
 	statusBar = CGStatusBar::create(7, 569, "Spelroll.bmp");
 
-	Rect temp_rect = CSDL_Ext::genRect(45, 35, 479 + pos.x, 405 + pos.y);
-	interactiveAreas.push_back(std::make_shared<InteractiveArea>(temp_rect, std::bind(&CSpellWindow::fexitb, this), 460, this));
-	temp_rect = CSDL_Ext::genRect(45, 35, 221 + pos.x, 405 + pos.y);
-	interactiveAreas.push_back(std::make_shared<InteractiveArea>(temp_rect, std::bind(&CSpellWindow::fbattleSpellsb, this), 453, this));
-	temp_rect = CSDL_Ext::genRect(45, 35, 355 + pos.x, 405 + pos.y);
-	interactiveAreas.push_back(std::make_shared<InteractiveArea>(temp_rect, std::bind(&CSpellWindow::fadvSpellsb, this), 452, this));
-	temp_rect = CSDL_Ext::genRect(45, 35, 418 + pos.x, 405 + pos.y);
-	interactiveAreas.push_back(std::make_shared<InteractiveArea>(temp_rect, std::bind(&CSpellWindow::fmanaPtsb, this), 459, this));
-
-	temp_rect = CSDL_Ext::genRect(36, 56, 549 + pos.x, 94 + pos.y);
-	interactiveAreas.push_back(std::make_shared<InteractiveArea>(temp_rect, std::bind(&CSpellWindow::selectSchool, this, 0), 454, this));
-	temp_rect = CSDL_Ext::genRect(36, 56, 549 + pos.x, 151 + pos.y);
-	interactiveAreas.push_back(std::make_shared<InteractiveArea>(temp_rect, std::bind(&CSpellWindow::selectSchool, this, 3), 457, this));
-	temp_rect = CSDL_Ext::genRect(36, 56, 549 + pos.x, 210 + pos.y);
-	interactiveAreas.push_back(std::make_shared<InteractiveArea>(temp_rect, std::bind(&CSpellWindow::selectSchool, this, 1), 455, this));
-	temp_rect = CSDL_Ext::genRect(36, 56, 549 + pos.x, 270 + pos.y);
-	interactiveAreas.push_back(std::make_shared<InteractiveArea>(temp_rect, std::bind(&CSpellWindow::selectSchool, this, 2), 456, this));
-	temp_rect = CSDL_Ext::genRect(36, 56, 549 + pos.x, 330 + pos.y);
-	interactiveAreas.push_back(std::make_shared<InteractiveArea>(temp_rect, std::bind(&CSpellWindow::selectSchool, this, 4), 458, this));
-
-	temp_rect = CSDL_Ext::genRect(leftCorner->pos.h, leftCorner->pos.w, 97 + pos.x, 77 + pos.y);
-	interactiveAreas.push_back(std::make_shared<InteractiveArea>(temp_rect, std::bind(&CSpellWindow::fLcornerb, this), 450, this));
-	temp_rect = CSDL_Ext::genRect(rightCorner->pos.h, rightCorner->pos.w, 487 + pos.x, 72 + pos.y);
-	interactiveAreas.push_back(std::make_shared<InteractiveArea>(temp_rect, std::bind(&CSpellWindow::fRcornerb, this), 451, this));
+	interactiveAreas.push_back(std::make_shared<InteractiveArea>( Rect( 479 + pos.x, 405 + pos.y, 36, 56), std::bind(&CSpellWindow::fexitb,         this),    460, this));
+	interactiveAreas.push_back(std::make_shared<InteractiveArea>( Rect( 221 + pos.x, 405 + pos.y, 36, 56), std::bind(&CSpellWindow::fbattleSpellsb, this),    453, this));
+	interactiveAreas.push_back(std::make_shared<InteractiveArea>( Rect( 355 + pos.x, 405 + pos.y, 36, 56), std::bind(&CSpellWindow::fadvSpellsb,    this),    452, this));
+	interactiveAreas.push_back(std::make_shared<InteractiveArea>( Rect( 418 + pos.x, 405 + pos.y, 36, 56), std::bind(&CSpellWindow::fmanaPtsb,      this),    459, this));
+	interactiveAreas.push_back(std::make_shared<InteractiveArea>( Rect( 549 + pos.x,  94 + pos.y, 36, 56), std::bind(&CSpellWindow::selectSchool,   this, 0), 454, this));
+	interactiveAreas.push_back(std::make_shared<InteractiveArea>( Rect( 549 + pos.x, 151 + pos.y, 45, 35), std::bind(&CSpellWindow::selectSchool,   this, 3), 457, this));
+	interactiveAreas.push_back(std::make_shared<InteractiveArea>( Rect( 549 + pos.x, 210 + pos.y, 45, 35), std::bind(&CSpellWindow::selectSchool,   this, 1), 455, this));
+	interactiveAreas.push_back(std::make_shared<InteractiveArea>( Rect( 549 + pos.x, 270 + pos.y, 45, 35), std::bind(&CSpellWindow::selectSchool,   this, 2), 456, this));
+	interactiveAreas.push_back(std::make_shared<InteractiveArea>( Rect( 549 + pos.x, 330 + pos.y, 45, 35), std::bind(&CSpellWindow::selectSchool,   this, 4), 458, this));
+
+	interactiveAreas.push_back(std::make_shared<InteractiveArea>( Rect(  97 + pos.x, 77 + pos.y, leftCorner->pos.h,  leftCorner->pos.w  ), std::bind(&CSpellWindow::fLcornerb, this), 450, this));
+	interactiveAreas.push_back(std::make_shared<InteractiveArea>( Rect( 487 + pos.x, 72 + pos.y, rightCorner->pos.h, rightCorner->pos.w ), std::bind(&CSpellWindow::fRcornerb, this), 451, this));
 
 	//areas for spells
 	int xpos = 117 + pos.x, ypos = 90 + pos.y;
 
 	for(int v=0; v<12; ++v)
 	{
-		temp_rect = CSDL_Ext::genRect(65, 78, xpos, ypos);
-		spellAreas[v] = std::make_shared<SpellArea>(temp_rect, this);
+		spellAreas[v] = std::make_shared<SpellArea>( Rect(xpos, ypos, 65, 78), this);
 
 		if(v == 5) //to right page
 		{
@@ -422,17 +407,16 @@ void CSpellWindow::turnPageRight()
 		CCS->videoh->openAndPlayVideo("PGTRNRGH.SMK", pos.x+13, pos.y+15);
 }
 
-void CSpellWindow::keyPressed(const SDL_KeyboardEvent & key)
+void CSpellWindow::keyPressed(const SDL_Keycode & key)
 {
-	if(key.keysym.sym == SDLK_ESCAPE ||  key.keysym.sym == SDLK_RETURN)
+	if(key == SDLK_ESCAPE ||  key == SDLK_RETURN)
 	{
 		fexitb();
 		return;
 	}
-
-	if(key.state == SDL_PRESSED)
+	else
 	{
-		switch(key.keysym.sym)
+		switch(key)
 		{
 		case SDLK_LEFT:
 			fLcornerb();
@@ -443,7 +427,7 @@ void CSpellWindow::keyPressed(const SDL_KeyboardEvent & key)
 		case SDLK_UP:
 		case SDLK_DOWN:
 		{
-			bool down = key.keysym.sym == SDLK_DOWN;
+			bool down = key == SDLK_DOWN;
 			static const int schoolsOrder[] = { 0, 3, 1, 2, 4 };
 			int index = -1;
 			while(schoolsOrder[++index] != selectedTab);
@@ -464,9 +448,9 @@ void CSpellWindow::keyPressed(const SDL_KeyboardEvent & key)
 		}
 
 		//alt + 1234567890-= casts spell from 1 - 12 slot
-		if(myInt->altPressed())
+		if(GH.isKeyboardAltDown())
 		{
-			SDL_Keycode hlpKey = key.keysym.sym;
+			SDL_Keycode hlpKey = key;
 			if(CGuiHandler::isNumKey(hlpKey, false))
 			{
 				if(hlpKey == SDLK_KP_PLUS)

+ 2 - 1
client/windows/CSpellWindow.h

@@ -112,6 +112,7 @@ public:
 	void selectSchool(int school); //schools: 0 - air magic, 1 - fire magic, 2 - water magic, 3 - earth magic, 4 - all schools
 	int pagesWithinCurrentTab();
 
-	void keyPressed(const SDL_KeyboardEvent & key) override;
+	void keyPressed(const SDL_Keycode & key) override;
+
 	void show(SDL_Surface * to) override;
 };

+ 7 - 3
client/windows/CTradeWindow.cpp

@@ -504,9 +504,13 @@ void CTradeWindow::getPositionsFor(std::vector<Rect> &poss, bool Left, EType typ
 
 		const std::vector<Rect> tmp =
 		{
-			CSDL_Ext::genRect(h, w, x, y), CSDL_Ext::genRect(h, w, x + dx, y), CSDL_Ext::genRect(h, w, x + 2*dx, y),
-			CSDL_Ext::genRect(h, w, x, y + dy), CSDL_Ext::genRect(h, w, x + dx, y + dy), CSDL_Ext::genRect(h, w, x + 2*dx, y + dy),
-			CSDL_Ext::genRect(h, w, x + dx, y + 2*dy)
+			Rect(Point(x + 0 * dx, y + 0 * dx), Point(h, w) ),
+			Rect(Point(x + 1 * dx, y + 0 * dx), Point(h, w) ),
+			Rect(Point(x + 2 * dx, y + 0 * dx), Point(h, w) ),
+			Rect(Point(x + 0 * dx, y + 1 * dy), Point(h, w) ),
+			Rect(Point(x + 1 * dx, y + 1 * dy), Point(h, w) ),
+			Rect(Point(x + 2 * dx, y + 1 * dy), Point(h, w) ),
+			Rect(Point(x + 1 * dx, y + 2 * dy), Point(h, w) )
 		};
 
 		vstd::concatenate(poss, tmp);

+ 1 - 1
client/windows/CWindowObject.cpp

@@ -69,7 +69,7 @@ CWindowObject::CWindowObject(int options_, std::string imageName):
 	if(background)
 		pos = background->center();
 	else
-		center(Point(screen->w/2, screen->h/2));
+		center(GH.screenDimensions() / 2);
 
 	if(!(options & SHADOW_DISABLED))
 		setShadow(true);

+ 22 - 29
client/windows/GUIClasses.cpp

@@ -63,10 +63,6 @@
 #include "../lib/NetPacksBase.h"
 #include "../lib/StartInfo.h"
 
-#include <SDL_events.h>
-
-using namespace CSDL_Ext;
-
 CRecruitmentWindow::CCreatureCard::CCreatureCard(CRecruitmentWindow * window, const CCreature * crea, int totalAmount)
 	: CIntObject(LCLICK | RCLICK),
 	parent(window),
@@ -103,9 +99,9 @@ void CRecruitmentWindow::CCreatureCard::showAll(SDL_Surface * to)
 {
 	CIntObject::showAll(to);
 	if(selected)
-		drawBorder(to, pos, Colors::RED);
+		CSDL_Ext::drawBorder(to, pos, Colors::RED);
 	else
-		drawBorder(to, pos, Colors::YELLOW);
+		CSDL_Ext::drawBorder(to, pos, Colors::YELLOW);
 }
 
 void CRecruitmentWindow::select(std::shared_ptr<CCreatureCard> card)
@@ -180,17 +176,17 @@ void CRecruitmentWindow::showAll(SDL_Surface * to)
 	CWindowObject::showAll(to);
 
 	// recruit\total values
-	drawBorder(to, pos.x + 172, pos.y + 222, 67, 42, Colors::YELLOW);
-	drawBorder(to, pos.x + 246, pos.y + 222, 67, 42, Colors::YELLOW);
+	CSDL_Ext::drawBorder(to, pos.x + 172, pos.y + 222, 67, 42, Colors::YELLOW);
+	CSDL_Ext::drawBorder(to, pos.x + 246, pos.y + 222, 67, 42, Colors::YELLOW);
 
 	//cost boxes
-	drawBorder(to, pos.x + 64,  pos.y + 222, 99, 76, Colors::YELLOW);
-	drawBorder(to, pos.x + 322, pos.y + 222, 99, 76, Colors::YELLOW);
+	CSDL_Ext::drawBorder(to, pos.x + 64,  pos.y + 222, 99, 76, Colors::YELLOW);
+	CSDL_Ext::drawBorder(to, pos.x + 322, pos.y + 222, 99, 76, Colors::YELLOW);
 
 	//buttons borders
-	drawBorder(to, pos.x + 133, pos.y + 312, 66, 34, Colors::METALLIC_GOLD);
-	drawBorder(to, pos.x + 211, pos.y + 312, 66, 34, Colors::METALLIC_GOLD);
-	drawBorder(to, pos.x + 289, pos.y + 312, 66, 34, Colors::METALLIC_GOLD);
+	CSDL_Ext::drawBorder(to, pos.x + 133, pos.y + 312, 66, 34, Colors::METALLIC_GOLD);
+	CSDL_Ext::drawBorder(to, pos.x + 211, pos.y + 312, 66, 34, Colors::METALLIC_GOLD);
+	CSDL_Ext::drawBorder(to, pos.x + 289, pos.y + 312, 66, 34, Colors::METALLIC_GOLD);
 }
 
 CRecruitmentWindow::CRecruitmentWindow(const CGDwelling * Dwelling, int Level, const CArmedInstance * Dst, const std::function<void(CreatureID,int)> & Recruit, int y_offset):
@@ -921,9 +917,9 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2,
 	{
 		primSkillAreas.push_back(std::make_shared<LRClickableAreaWTextComp>());
 		if (qeLayout)
-			primSkillAreas[g]->pos = genRect(22, 152, pos.x + 324, pos.y + 12 + 26 * g);
+			primSkillAreas[g]->pos = Rect(Point(pos.x + 324, pos.y + 12 + 26 * g), Point(22, 152));
 		else
-			primSkillAreas[g]->pos = genRect(32, 140, pos.x + 329, pos.y + 19 + 36 * g);
+			primSkillAreas[g]->pos = Rect(Point(pos.x + 329, pos.y + 19 + 36 * g), Point(32, 140));
 		primSkillAreas[g]->text = CGI->generaltexth->arraytxt[2+g];
 		primSkillAreas[g]->type = g;
 		primSkillAreas[g]->bonusValue = -1;
@@ -943,7 +939,7 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2,
 			int skill = hero->secSkills[g].first,
 				level = hero->secSkills[g].second; // <1, 3>
 			secSkillAreas[b].push_back(std::make_shared<LRClickableAreaWTextComp>());
-			secSkillAreas[b][g]->pos = genRect(32, 32, pos.x + 32 + g*36 + b*454 , pos.y + (qeLayout ? 83 : 88));
+			secSkillAreas[b][g]->pos = Rect(Point(pos.x + 32 + g * 36 + b * 454 , pos.y + (qeLayout ? 83 : 88)), Point(32, 32) );
 			secSkillAreas[b][g]->baseType = 1;
 
 			secSkillAreas[b][g]->type = skill;
@@ -958,12 +954,12 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2,
 		heroAreas[b] = std::make_shared<CHeroArea>(257 + 228*b, 13, hero);
 
 		specialtyAreas[b] = std::make_shared<LRClickableAreaWText>();
-		specialtyAreas[b]->pos = genRect(32, 32, pos.x + 69 + 490*b, pos.y + (qeLayout ? 41 : 45));
+		specialtyAreas[b]->pos = Rect(Point(pos.x + 69 + 490 * b, pos.y + (qeLayout ? 41 : 45)), Point(32, 32));
 		specialtyAreas[b]->hoverText = CGI->generaltexth->heroscrn[27];
 		specialtyAreas[b]->text = hero->type->getSpecialtyDescriptionTranslated();
 
 		experienceAreas[b] = std::make_shared<LRClickableAreaWText>();
-		experienceAreas[b]->pos = genRect(32, 32, pos.x + 105 + 490*b, pos.y + (qeLayout ? 41 : 45));
+		experienceAreas[b]->pos = Rect(Point(pos.x + 105 + 490 * b, pos.y + (qeLayout ? 41 : 45)), Point(32, 32));
 		experienceAreas[b]->hoverText = CGI->generaltexth->heroscrn[9];
 		experienceAreas[b]->text = CGI->generaltexth->allTexts[2];
 		boost::algorithm::replace_first(experienceAreas[b]->text, "%d", boost::lexical_cast<std::string>(hero->level));
@@ -971,15 +967,15 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2,
 		boost::algorithm::replace_first(experienceAreas[b]->text, "%d", boost::lexical_cast<std::string>(hero->exp));
 
 		spellPointsAreas[b] = std::make_shared<LRClickableAreaWText>();
-		spellPointsAreas[b]->pos = genRect(32, 32, pos.x + 141 + 490*b, pos.y + (qeLayout ? 41 : 45));
+		spellPointsAreas[b]->pos = Rect(Point(pos.x + 141 + 490 * b, pos.y + (qeLayout ? 41 : 45)), Point(32, 32));
 		spellPointsAreas[b]->hoverText = CGI->generaltexth->heroscrn[22];
 		spellPointsAreas[b]->text = CGI->generaltexth->allTexts[205];
 		boost::algorithm::replace_first(spellPointsAreas[b]->text, "%s", hero->getNameTranslated());
 		boost::algorithm::replace_first(spellPointsAreas[b]->text, "%d", boost::lexical_cast<std::string>(hero->mana));
 		boost::algorithm::replace_first(spellPointsAreas[b]->text, "%d", boost::lexical_cast<std::string>(hero->manaLimit()));
 
-		morale[b] = std::make_shared<MoraleLuckBox>(true, genRect(32, 32, 176 + 490 * b, 39), true);
-		luck[b] = std::make_shared<MoraleLuckBox>(false, genRect(32, 32, 212 + 490 * b, 39), true);
+		morale[b] = std::make_shared<MoraleLuckBox>(true, Rect(Point(176 + 490 * b, 39), Point(32, 32)), true);
+		luck[b] = std::make_shared<MoraleLuckBox>(false,  Rect(Point(212 + 490 * b, 39), Point(32, 32)), true);
 	}
 
 	quit = std::make_shared<CButton>(Point(732, 567), "IOKAY.DEF", CGI->generaltexth->zelp[600], std::bind(&CExchangeWindow::close, this), SDLK_RETURN);
@@ -1067,8 +1063,8 @@ void CExchangeWindow::updateWidgets()
 			secSkillIcons[leftRight][m]->setFrame(2 + id * 3 + level);
 		}
 
-		expValues[leftRight]->setText(makeNumberShort(hero->exp));
-		manaValues[leftRight]->setText(makeNumberShort(hero->mana));
+		expValues[leftRight]->setText(vstd::formatMetric(hero->exp, 3));
+		manaValues[leftRight]->setText(vstd::formatMetric(hero->mana, 3));
 
 		morale[leftRight]->set(hero);
 		luck[leftRight]->set(hero);
@@ -1161,7 +1157,7 @@ CPuzzleWindow::CPuzzleWindow(const int3 & GrailPos, double discoveredRatio)
 void CPuzzleWindow::showAll(SDL_Surface * to)
 {
 	int3 moveInt = int3(8, 9, 0);
-	Rect mapRect = genRect(544, 591, pos.x + 8, pos.y + 7);
+	Rect mapRect = Rect(Point(pos.x + 8, pos.y + 7), Point(544, 591));
 	int3 topTile = grailPos - moveInt;
 
 	MapDrawingInfo info(topTile, LOCPLINT->cb->getVisibilityMap(), mapRect);
@@ -1951,14 +1947,11 @@ void CObjectListWindow::changeSelection(size_t which)
 	selected = which;
 }
 
-void CObjectListWindow::keyPressed (const SDL_KeyboardEvent & key)
+void CObjectListWindow::keyPressed (const SDL_Keycode & key)
 {
-	if(key.state != SDL_PRESSED)
-		return;
-
 	int sel = static_cast<int>(selected);
 
-	switch(key.keysym.sym)
+	switch(key)
 	{
 	break; case SDLK_UP:
 		sel -=1;

+ 3 - 1
client/windows/GUIClasses.h

@@ -43,6 +43,8 @@ class CTextBox;
 class CResDataBar;
 class CHeroWithMaybePickedArtifact;
 
+enum class EUserEvent;
+
 /// Recruitment window where you can recruit creatures
 class CRecruitmentWindow : public CStatusbarWindow
 {
@@ -193,7 +195,7 @@ public:
 	std::shared_ptr<CIntObject> genItem(size_t index);
 	void elementSelected();//call callback and close this window
 	void changeSelection(size_t which);
-	void keyPressed (const SDL_KeyboardEvent & key) override;
+	void keyPressed(const SDL_Keycode & key) override;
 };
 
 class CTavernWindow : public CStatusbarWindow

+ 4 - 4
client/windows/InfoWindows.cpp

@@ -245,8 +245,8 @@ CInfoPopup::CInfoPopup(SDL_Surface *Bitmap, bool Free)
 
 	if(bitmap)
 	{
-		pos.x = screen->w/2 - bitmap->w/2;
-		pos.y = screen->h/2 - bitmap->h/2;
+		pos.x = GH.screenDimensions().x / 2 - bitmap->w / 2;
+		pos.y = GH.screenDimensions().y / 2 - bitmap->h / 2;
 		pos.h = bitmap->h;
 		pos.w = bitmap->w;
 	}
@@ -281,8 +281,8 @@ void CInfoPopup::init(int x, int y)
 	// Put the window back on screen if necessary
 	vstd::amax(pos.x, 0);
 	vstd::amax(pos.y, 0);
-	vstd::amin(pos.x, screen->w - bitmap->w);
-	vstd::amin(pos.y, screen->h - bitmap->h);
+	vstd::amin(pos.x, GH.screenDimensions().x - bitmap->w);
+	vstd::amin(pos.y, GH.screenDimensions().y - bitmap->h);
 }
 
 

+ 2 - 2
client/windows/SystemOptionsWindow.cpp

@@ -26,6 +26,7 @@
 #include "VcmiSettingsWindow.h"
 #include "GUIClasses.h"
 #include "CServerHandler.h"
+#include "renderSDL/SDL_Extensions.h"
 
 
 static void setIntSetting(std::string group, std::string field, int value)
@@ -110,8 +111,7 @@ void SystemOptionsWindow::selectGameResolution()
 	std::vector<std::string> items;
 
 #ifndef VCMI_IOS
-	SDL_Rect displayBounds;
-	SDL_GetDisplayBounds(std::max(0, SDL_GetWindowDisplayIndex(mainWindow)), &displayBounds);
+	Rect displayBounds = CSDL_Ext::getDisplayBounds();
 #endif
 
 	size_t currentResolutionIndex = 0;

+ 0 - 3
cmake_modules/VCMI_lib.cmake

@@ -239,8 +239,6 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 		${MAIN_LIB_DIR}/../include/vcmi/Environment.h
 		${MAIN_LIB_DIR}/../include/vcmi/Services.h
 
-		${MAIN_LIB_DIR}/abilities/Ability.h
-
 		${MAIN_LIB_DIR}/battle/AccessibilityInfo.h
 		${MAIN_LIB_DIR}/battle/BattleAction.h
 		${MAIN_LIB_DIR}/battle/BattleAttackInfo.h
@@ -477,7 +475,6 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 		PUBLIC ${MAIN_LIB_DIR}/..
 		PUBLIC ${MAIN_LIB_DIR}/../include
 		PUBLIC ${MAIN_LIB_DIR}
-		PRIVATE ${SDL2_INCLUDE_DIR}
 	)
 
 	if(WIN32)

+ 1 - 1
cmake_modules/VersionDefinition.cmake

@@ -10,4 +10,4 @@ add_definitions(
 set(APP_SHORT_VERSION "${VCMI_VERSION_MAJOR}.${VCMI_VERSION_MINOR}")
 if(NOT VCMI_VERSION_PATCH EQUAL 0)
 	string(APPEND APP_SHORT_VERSION ".${VCMI_VERSION_PATCH}")
-endif()
+endif()

+ 2 - 1
config/heroes/tower.json

@@ -213,8 +213,9 @@
 		"specialty" : {
 			"bonuses" : {
 				"fortune" : {
+					"addInfo" : 3,
 					"subtype" : "spell.fortune",
-					"type" : "MAXED_SPELL"
+					"type" : "SPECIAL_FIXED_VALUE_ENCHANT"
 				}
 			}
 		}

+ 7 - 1
debian/changelog

@@ -2,8 +2,14 @@ vcmi (1.2.0) jammy; urgency=medium
 
   * New upstream release
 
- -- Ivan Savenko <[email protected]>  Fri, 23 Dec 2022 16:00:00 +0200
+ -- Ivan Savenko <[email protected]>  Sat, 04 Feb 2023 16:00:00 +0200
  
+vcmi (1.1.1) jammy; urgency=medium
+
+  * New upstream release
+
+ -- Ivan Savenko <[email protected]>  Fri, 03 Feb 2023 12:00:00 +0200
+
 vcmi (1.1.0) jammy; urgency=medium
 
   * New upstream release

+ 2 - 2
include/vstd/StringUtils.h

@@ -5,8 +5,8 @@ VCMI_LIB_NAMESPACE_BEGIN
 namespace vstd
 {
 
-	DLL_LINKAGE std::vector<std::string> split(std::string s, std::string separators);
-	DLL_LINKAGE std::pair<std::string, std::string> splitStringToPair(std::string input, char separator);
+	DLL_LINKAGE std::vector<std::string> split(std::string s, const std::string& separators);
+	DLL_LINKAGE std::pair<std::string, std::string> splitStringToPair(const std::string& input, char separator);
 
 }
 

+ 2 - 1
launcher/eu.vcmi.VCMI.metainfo.xml

@@ -38,7 +38,8 @@
 	<url type="bugtracker">https://github.com/vcmi/vcmi/issues</url>
 	<url type="faq">https://vcmi.eu/faq/</url>
 	<releases>
-		<release version="1.2.0" date="2022-12-24" type="development" />
+		<release version="1.2.0" date="2023-02-04" type="development" />
+		<release version="1.1.1" date="2023-02-03" />
 		<release version="1.1.0" date="2022-12-23" />
 		<release version="1.0.0" date="2022-09-11" />
 		<release version="0.99" date="2016-11-01" />

+ 2 - 1
lib/CHeroHandler.cpp

@@ -611,8 +611,9 @@ std::vector<std::shared_ptr<Bonus>> SpecialtyInfoToBonuses(const SSpecialtyInfo
 		result.push_back(bonus);
 		break;
 	case 7: //maxed mastery for spell
-		bonus->type = Bonus::MAXED_SPELL;
+		bonus->type = Bonus::SPECIAL_FIXED_VALUE_ENCHANT;
 		bonus->subtype = spec.subtype; //spell id
+		bonus->val = 3; //to match MAXED_BONUS
 		result.push_back(bonus);
 		break;
 	case 8: //peculiar spells - enchantments

+ 1 - 1
lib/HeroBonus.h

@@ -285,7 +285,7 @@ public:
 	BONUS_NAME(SPELL_DAMAGE) /*val = value*/\
 	BONUS_NAME(SPECIFIC_SPELL_DAMAGE) /*subtype = id of spell, val = value*/\
 	BONUS_NAME(SPECIAL_BLESS_DAMAGE) /*val = spell (bless), additionalInfo = value per level in percent*/\
-	BONUS_NAME(MAXED_SPELL) /*val = id*/\
+	BONUS_NAME(MAXED_SPELL) /*val = id. deprecated in favour of SPECIAL_FIXED_VALUE_ENCHANT*/\
 	BONUS_NAME(SPECIAL_PECULIAR_ENCHANT) /*blesses and curses with id = val dependent on unit's level, subtype = 0 or 1 for Coronius*/\
 	BONUS_NAME(SPECIAL_UPGRADE) /*subtype = base, additionalInfo = target */\
 	BONUS_NAME(DRAGON_NATURE) \

+ 0 - 1
lib/VCMI_lib.cbp

@@ -281,7 +281,6 @@
 		<Unit filename="VCMIDirs.h" />
 		<Unit filename="VCMI_Lib.cpp" />
 		<Unit filename="VCMI_Lib.h" />
-		<Unit filename="abilities/Ability.h" />
 		<Unit filename="battle/AccessibilityInfo.cpp" />
 		<Unit filename="battle/AccessibilityInfo.h" />
 		<Unit filename="battle/BattleAction.cpp" />

+ 4 - 0
lib/battle/BattleHex.h

@@ -39,6 +39,10 @@ struct DLL_LINKAGE BattleHex //TODO: decide if this should be changed to class f
 	static const si16 CASTLE_BOTTOM_TOWER = -3;
 	static const si16 CASTLE_UPPER_TOWER = -4;
 
+	// hexes for interaction with heroes
+	static const si16 HERO_ATTACKER = 0;
+	static const si16 HERO_DEFENDER = GameConstants::BFIELD_WIDTH - 1;
+
 	// helpers for rendering
 	static const si16 HEX_BEFORE_ALL = std::numeric_limits<si16>::min();
 	static const si16 HEX_AFTER_ALL = std::numeric_limits<si16>::max();

+ 22 - 7
lib/battle/CBattleInfoCallback.h

@@ -44,13 +44,28 @@ struct DLL_LINKAGE AttackableTiles
 
 enum class PossiblePlayerBattleAction // actions performed at l-click
 {
-	INVALID = -1, CREATURE_INFO,
-	MOVE_TACTICS, CHOOSE_TACTICS_STACK,
-	MOVE_STACK, ATTACK, WALK_AND_ATTACK, ATTACK_AND_RETURN, SHOOT, //OPEN_GATE, //we can open castle gate during siege
-	NO_LOCATION, ANY_LOCATION, OBSTACLE, TELEPORT, SACRIFICE, RANDOM_GENIE_SPELL,
-	FREE_LOCATION, //used with Force Field and Fire Wall - all tiles affected by spell must be free
-	CATAPULT, HEAL,
-	AIMED_SPELL_CREATURE
+	INVALID = -1,
+	CREATURE_INFO,
+	HERO_INFO,
+	MOVE_TACTICS,
+	CHOOSE_TACTICS_STACK,
+
+	MOVE_STACK,
+	ATTACK,
+	WALK_AND_ATTACK,
+	ATTACK_AND_RETURN,
+	SHOOT,
+	CATAPULT,
+	HEAL,
+
+	NO_LOCATION,          // massive spells that affect every possible target, automatic casts
+	ANY_LOCATION,
+	OBSTACLE,
+	TELEPORT,
+	SACRIFICE,
+	RANDOM_GENIE_SPELL,   // random spell on a friendly creature
+	FREE_LOCATION,        // used with Force Field and Fire Wall - all tiles affected by spell must be free
+	AIMED_SPELL_CREATURE, // spell targeted at creature
 };
 
 struct DLL_LINKAGE BattleClientInterfaceData

+ 1 - 3
lib/filesystem/CBinaryReader.cpp

@@ -10,14 +10,12 @@
 #include "StdInc.h"
 #include "CBinaryReader.h"
 
-//FIXME:library file depends on SDL - make cause troubles
-#include <SDL_endian.h>
 #include "CInputStream.h"
 #include "../CGeneralTextHandler.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-#if SDL_BYTEORDER == SDL_BIG_ENDIAN
+#ifdef VCMI_ENDIAN_BIG
 template <typename CData>
 CData readLE(CData data)
 {

+ 1 - 4
lib/mapObjects/CGHeroInstance.cpp

@@ -647,10 +647,7 @@ int64_t CGHeroInstance::getSpecificSpellBonus(const spells::Spell * spell, int64
 
 int32_t CGHeroInstance::getEffectLevel(const spells::Spell * spell) const
 {
-	if(hasBonusOfType(Bonus::MAXED_SPELL, spell->getIndex()))
-		return 3;//todo: recheck specialty from where this bonus is. possible bug
-	else
-		return getSpellSchoolLevel(spell);
+	return getSpellSchoolLevel(spell);
 }
 
 int32_t CGHeroInstance::getEffectPower(const spells::Spell * spell) const

+ 2 - 2
lib/mapObjects/CRewardableConstructor.cpp

@@ -81,8 +81,8 @@ void CRandomRewardObjectInfo::configureLimiter(CRewardableObject * object, CRand
 
 	limiter.primary = JsonRandom::loadPrimary(source["primary"], rng);
 	limiter.secondary = JsonRandom::loadSecondary(source["secondary"], rng);
-	limiter.artifacts = JsonRandom::loadArtifacts(source["spells"], rng);
-	limiter.spells  = JsonRandom::loadSpells(source["artifacts"], rng, spells);
+	limiter.artifacts = JsonRandom::loadArtifacts(source["artifacts"], rng);
+	limiter.spells  = JsonRandom::loadSpells(source["spells"], rng, spells);
 	limiter.creatures = JsonRandom::loadCreatures(source["creatures"], rng);
 
 	limiter.allOf  = configureSublimiters(object, rng, source["allOf"] );

Some files were not shown because too many files changed in this diff