Преглед на файлове

Merge pull request #2282 from IvanSavenko/adventure_map_fixes

Adventure map fixes for 1.3
Ivan Savenko преди 2 години
родител
ревизия
ae8579558d
променени са 48 файла, в които са добавени 256 реда и са изтрити 220 реда
  1. 6 0
      AI/Nullkiller/AIGateway.cpp
  2. 1 0
      AI/Nullkiller/AIGateway.h
  3. 5 0
      AI/VCAI/VCAI.cpp
  4. 1 0
      AI/VCAI/VCAI.h
  5. 27 6
      client/CPlayerInterface.cpp
  6. 1 0
      client/CPlayerInterface.h
  7. 2 1
      client/ClientNetPackVisitors.h
  8. 15 4
      client/NetPacksClient.cpp
  9. 12 16
      client/adventureMap/AdventureMapInterface.cpp
  10. 1 1
      client/adventureMap/AdventureMapInterface.h
  11. 1 1
      client/adventureMap/AdventureMapShortcuts.cpp
  12. 29 17
      client/adventureMap/CInGameConsole.cpp
  13. 1 0
      client/adventureMap/CInGameConsole.h
  14. 1 1
      client/adventureMap/CMinimap.cpp
  15. 1 1
      client/adventureMap/CMinimap.h
  16. 12 0
      client/eventsSDL/InputSourceKeyboard.cpp
  17. 6 0
      client/eventsSDL/InputSourceMouse.cpp
  18. 2 0
      client/eventsSDL/InputSourceMouse.h
  19. 44 15
      client/gui/CIntObject.cpp
  20. 12 8
      client/gui/CIntObject.h
  21. 3 1
      client/gui/EventDispatcher.cpp
  22. 1 1
      client/gui/InterfaceObjectConfigurable.cpp
  23. 0 84
      client/ios/startSDL.mm
  24. 2 3
      client/lobby/CSelectionBase.cpp
  25. 1 1
      client/lobby/RandomMapTab.cpp
  26. 14 3
      client/lobby/SelectionTab.cpp
  27. 3 2
      client/lobby/SelectionTab.h
  28. 3 3
      client/mainmenu/CMainMenu.cpp
  29. 1 1
      client/mainmenu/CreditsScreen.cpp
  30. 1 8
      client/mapView/MapView.cpp
  31. 0 5
      client/mapView/MapView.h
  32. 9 1
      client/mapView/MapViewController.cpp
  33. 1 0
      client/mapView/MapViewController.h
  34. 4 4
      client/widgets/CComponent.cpp
  35. 1 1
      client/widgets/CreatureCostBox.cpp
  36. 1 0
      client/widgets/MiscWidgets.h
  37. 3 0
      client/widgets/ObjectLists.cpp
  38. 8 0
      client/widgets/Slider.cpp
  39. 3 17
      client/widgets/TextControls.cpp
  40. 1 1
      client/widgets/TextControls.h
  41. 1 1
      client/windows/CCastleInterface.cpp
  42. 2 2
      client/windows/GUIClasses.cpp
  43. 1 1
      client/windows/settings/AdventureOptionsTab.cpp
  44. 1 1
      client/windows/settings/BattleOptionsTab.cpp
  45. 1 1
      client/windows/settings/GeneralOptionsTab.cpp
  46. 1 1
      client/windows/settings/SettingsMainWindow.cpp
  47. 1 0
      lib/IGameEventsReceiver.h
  48. 8 6
      server/CGameHandler.cpp

+ 6 - 0
AI/Nullkiller/AIGateway.cpp

@@ -448,6 +448,12 @@ void AIGateway::battleResultsApplied()
 	status.setBattle(NO_BATTLE);
 }
 
+void AIGateway::beforeObjectPropertyChanged(const SetObjectProperty * sop)
+{
+
+}
+
+
 void AIGateway::objectPropertyChanged(const SetObjectProperty * sop)
 {
 	LOG_TRACE(logAi);

+ 1 - 0
AI/Nullkiller/AIGateway.h

@@ -161,6 +161,7 @@ public:
 	void heroManaPointsChanged(const CGHeroInstance * hero) override;
 	void heroSecondarySkillChanged(const CGHeroInstance * hero, int which, int val) override;
 	void battleResultsApplied() override;
+	void beforeObjectPropertyChanged(const SetObjectProperty * sop) override;
 	void objectPropertyChanged(const SetObjectProperty * sop) override;
 	void buildChanged(const CGTownInstance * town, BuildingID buildingID, int what) override;
 	void heroBonusChanged(const CGHeroInstance * hero, const Bonus & bonus, bool gain) override;

+ 5 - 0
AI/VCAI/VCAI.cpp

@@ -538,6 +538,11 @@ void VCAI::battleResultsApplied()
 	status.setBattle(NO_BATTLE);
 }
 
+void VCAI::beforeObjectPropertyChanged(const SetObjectProperty * sop)
+{
+
+}
+
 void VCAI::objectPropertyChanged(const SetObjectProperty * sop)
 {
 	LOG_TRACE(logAi);

+ 1 - 0
AI/VCAI/VCAI.h

@@ -194,6 +194,7 @@ public:
 	void heroManaPointsChanged(const CGHeroInstance * hero) override;
 	void heroSecondarySkillChanged(const CGHeroInstance * hero, int which, int val) override;
 	void battleResultsApplied() override;
+	void beforeObjectPropertyChanged(const SetObjectProperty * sop) override;
 	void objectPropertyChanged(const SetObjectProperty * sop) override;
 	void buildChanged(const CGTownInstance * town, BuildingID buildingID, int what) override;
 	void heroBonusChanged(const CGHeroInstance * hero, const Bonus & bonus, bool gain) override;

+ 27 - 6
client/CPlayerInterface.cpp

@@ -183,7 +183,10 @@ void CPlayerInterface::playerStartsTurn(PlayerColor player)
 	if (player != playerID && LOCPLINT == this)
 	{
 		waitWhileDialog();
-		adventureInt->onEnemyTurnStarted(player);
+
+		bool isHuman = cb->getStartInfo()->playerInfos.count(player) && cb->getStartInfo()->playerInfos.at(player).isControlledByHuman();
+
+		adventureInt->onEnemyTurnStarted(player, isHuman);
 	}
 }
 
@@ -1314,11 +1317,29 @@ void CPlayerInterface::heroExchangeStarted(ObjectInstanceID hero1, ObjectInstanc
 	GH.windows().createAndPushWindow<CExchangeWindow>(hero1, hero2, query);
 }
 
+void CPlayerInterface::beforeObjectPropertyChanged(const SetObjectProperty * sop)
+{
+	if (sop->what == ObjProperty::OWNER)
+	{
+		const CGObjectInstance * obj = cb->getObj(sop->id);
+
+		if(obj->ID == Obj::TOWN)
+		{
+			auto town = static_cast<const CGTownInstance *>(obj);
+
+			if(obj->tempOwner == playerID)
+			{
+				localState->removeOwnedTown(town);
+				adventureInt->onTownChanged(town);
+			}
+		}
+	}
+}
+
 void CPlayerInterface::objectPropertyChanged(const SetObjectProperty * sop)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 
-	//redraw minimap if owner changed
 	if (sop->what == ObjProperty::OWNER)
 	{
 		const CGObjectInstance * obj = cb->getObj(sop->id);
@@ -1328,13 +1349,13 @@ void CPlayerInterface::objectPropertyChanged(const SetObjectProperty * sop)
 			auto town = static_cast<const CGTownInstance *>(obj);
 
 			if(obj->tempOwner == playerID)
+			{
 				localState->addOwnedTown(town);
-			else
-				localState->removeOwnedTown(town);
-
-			adventureInt->onTownChanged(town);
+				adventureInt->onTownChanged(town);
+			}
 		}
 
+		//redraw minimap if owner changed
 		std::set<int3> pos = obj->getBlockedPos();
 		std::unordered_set<int3> upos(pos.begin(), pos.end());
 		adventureInt->onMapTilesChanged(upos);

+ 1 - 0
client/CPlayerInterface.h

@@ -144,6 +144,7 @@ protected: // Call-ins from server, should not be called directly, but only via
 	void requestRealized(PackageApplied *pa) override;
 	void heroExchangeStarted(ObjectInstanceID hero1, ObjectInstanceID hero2, QueryID query) override;
 	void centerView (int3 pos, int focusTime) override;
+	void beforeObjectPropertyChanged(const SetObjectProperty * sop) override;
 	void objectPropertyChanged(const SetObjectProperty * sop) override;
 	void objectRemoved(const CGObjectInstance *obj) override;
 	void objectRemovedAfter() override;

+ 2 - 1
client/ClientNetPackVisitors.h

@@ -128,4 +128,5 @@ public:
 	virtual void visitBattleStackMoved(BattleStackMoved & pack) override;
 	virtual void visitBattleAttack(BattleAttack & pack) override;
 	virtual void visitStartAction(StartAction & pack) override;
-};
+	virtual void visitSetObjectProperty(SetObjectProperty & pack) override;
+};

+ 15 - 4
client/NetPacksClient.cpp

@@ -611,6 +611,20 @@ void ApplyClientNetPackVisitor::visitInfoWindow(InfoWindow & pack)
 		logNetwork->warn("We received InfoWindow for not our player...");
 }
 
+void ApplyFirstClientNetPackVisitor::visitSetObjectProperty(SetObjectProperty & pack)
+{
+	//inform all players that see this object
+	for(auto it = cl.playerint.cbegin(); it != cl.playerint.cend(); ++it)
+	{
+		if(gs.isVisible(gs.getObjInstance(pack.id), it->first))
+			callInterfaceIfPresent(cl, it->first, &IGameEventsReceiver::beforeObjectPropertyChanged, &pack);
+	}
+
+	// invalidate section of map view with our object and force an update with new flag color
+	if (pack.what == ObjProperty::OWNER)
+		CGI->mh->onObjectInstantRemove(gs.getObjInstance(pack.id));
+}
+
 void ApplyClientNetPackVisitor::visitSetObjectProperty(SetObjectProperty & pack)
 {
 	//inform all players that see this object
@@ -620,12 +634,9 @@ void ApplyClientNetPackVisitor::visitSetObjectProperty(SetObjectProperty & pack)
 			callInterfaceIfPresent(cl, it->first, &IGameEventsReceiver::objectPropertyChanged, &pack);
 	}
 
+	// invalidate section of map view with our object and force an update with new flag color
 	if (pack.what == ObjProperty::OWNER)
-	{
-		// invalidate section of map view with our object and force an update with new flag color
-		CGI->mh->onObjectInstantRemove(gs.getObjInstance(pack.id));
 		CGI->mh->onObjectInstantAdd(gs.getObjInstance(pack.id));
-	}
 }
 
 void ApplyClientNetPackVisitor::visitHeroLevelUp(HeroLevelUp & pack)

+ 12 - 16
client/adventureMap/AdventureMapInterface.cpp

@@ -83,8 +83,11 @@ void AdventureMapInterface::onAudioPaused()
 
 void AdventureMapInterface::onHeroMovementStarted(const CGHeroInstance * hero)
 {
-	widget->getInfoBar()->popAll();
-	widget->getInfoBar()->showSelection();
+	if (shortcuts->optionMapViewActive())
+	{
+		widget->getInfoBar()->popAll();
+		widget->getInfoBar()->showSelection();
+	}
 }
 
 void AdventureMapInterface::onHeroChanged(const CGHeroInstance *h)
@@ -137,6 +140,9 @@ void AdventureMapInterface::deactivate()
 {
 	CIntObject::deactivate();
 	CCS->curh->set(Cursor::Map::POINTER);
+
+	if(LOCPLINT)
+		LOCPLINT->cingconsole->deactivate();
 }
 
 void AdventureMapInterface::showAll(Canvas & to)
@@ -309,16 +315,15 @@ void AdventureMapInterface::onHotseatWaitStarted(PlayerColor playerID)
 	setState(EAdventureState::HOTSEAT_WAIT);
 }
 
-void AdventureMapInterface::onEnemyTurnStarted(PlayerColor playerID)
+void AdventureMapInterface::onEnemyTurnStarted(PlayerColor playerID, bool isHuman)
 {
 	if(settings["session"]["spectate"].Bool())
 		return;
 
 	mapAudio->onEnemyTurnStarted();
-	widget->getMinimap()->setAIRadar(true);
+	widget->getMinimap()->setAIRadar(!isHuman);
 	widget->getInfoBar()->startEnemyTurn(LOCPLINT->cb->getCurrentPlayer());
 	setState(EAdventureState::ENEMY_TURN);
-
 }
 
 void AdventureMapInterface::setState(EAdventureState state)
@@ -333,17 +338,8 @@ void AdventureMapInterface::adjustActiveness()
 	bool widgetMustBeActive = isActive() && shortcuts->optionSidePanelActive();
 	bool mapViewMustBeActive = isActive() && (shortcuts->optionMapViewActive());
 
-	if (widgetMustBeActive && !widget->isActive())
-		widget->activate();
-
-	if (!widgetMustBeActive && widget->isActive())
-		widget->deactivate();
-
-	if (mapViewMustBeActive && !widget->getMapView()->isActive())
-		widget->getMapView()->activate();
-
-	if (!mapViewMustBeActive && widget->getMapView()->isActive())
-		widget->getMapView()->deactivate();
+	widget->setInputEnabled(widgetMustBeActive);
+	widget->getMapView()->setInputEnabled(mapViewMustBeActive);
 }
 
 void AdventureMapInterface::onCurrentPlayerChanged(PlayerColor playerID)

+ 1 - 1
client/adventureMap/AdventureMapInterface.h

@@ -115,7 +115,7 @@ public:
 	void onHotseatWaitStarted(PlayerColor playerID);
 
 	/// Called by PlayerInterface when AI or remote human player starts his turn
-	void onEnemyTurnStarted(PlayerColor playerID);
+	void onEnemyTurnStarted(PlayerColor playerID, bool isHuman);
 
 	/// Called by PlayerInterface when local human player starts his turn
 	void onPlayerTurnStarted(PlayerColor playerID);

+ 1 - 1
client/adventureMap/AdventureMapShortcuts.cpp

@@ -402,7 +402,7 @@ bool AdventureMapShortcuts::optionCanViewQuests()
 
 bool AdventureMapShortcuts::optionCanToggleLevel()
 {
-	return optionInMapView() && LOCPLINT->cb->getMapSize().z > 0;
+	return optionInMapView() && LOCPLINT->cb->getMapSize().z > 1;
 }
 
 bool AdventureMapShortcuts::optionMapLevelSurface()

+ 29 - 17
client/adventureMap/CInGameConsole.cpp

@@ -34,7 +34,7 @@ CInGameConsole::CInGameConsole()
 	: CIntObject(KEYBOARD | TIME | TEXTINPUT)
 	, prevEntDisp(-1)
 {
-	type |= REDRAW_PARENT;
+	setRedrawParent(true);
 }
 
 void CInGameConsole::showAll(Canvas & to)
@@ -96,7 +96,7 @@ void CInGameConsole::print(const std::string & txt)
 
 		auto splitText = CMessage::breakText(txt, maxWidth, FONT_MEDIUM);
 
-		for (auto const & entry : splitText)
+		for(const auto & entry : splitText)
 			texts.push_back({entry, 0});
 
 		while(texts.size() > maxDisplayedTexts)
@@ -104,6 +104,27 @@ void CInGameConsole::print(const std::string & txt)
 	}
 
 	GH.windows().totalRedraw(); // FIXME: ingame console has no parent widget set
+	CCS->soundh->playSound("CHAT");
+}
+
+bool CInGameConsole::captureThisKey(EShortcut key)
+{
+	if (enteredText.empty())
+		return false;
+
+	switch (key)
+	{
+		case EShortcut::GLOBAL_ACCEPT:
+		case EShortcut::GLOBAL_CANCEL:
+		case EShortcut::GAME_ACTIVATE_CONSOLE:
+		case EShortcut::GLOBAL_BACKSPACE:
+		case EShortcut::MOVE_UP:
+		case EShortcut::MOVE_DOWN:
+			return true;
+
+		default:
+			return false;
+	}
 }
 
 void CInGameConsole::keyPressed (EShortcut key)
@@ -111,18 +132,18 @@ void CInGameConsole::keyPressed (EShortcut key)
 	if (LOCPLINT->cingconsole != this)
 		return;
 
-	if(!captureAllKeys && key != EShortcut::GAME_ACTIVATE_CONSOLE)
+	if(enteredText.empty() && key != EShortcut::GAME_ACTIVATE_CONSOLE)
 		return; //because user is not entering any text
 
 	switch(key)
 	{
 	case EShortcut::GLOBAL_CANCEL:
-		if(captureAllKeys)
+		if(!enteredText.empty())
 			endEnteringText(false);
 		break;
 
 	case EShortcut::GAME_ACTIVATE_CONSOLE:
-		if(captureAllKeys)
+		if(!enteredText.empty())
 			endEnteringText(false);
 		else
 			startEnteringText();
@@ -130,15 +151,10 @@ void CInGameConsole::keyPressed (EShortcut key)
 
 	case EShortcut::GLOBAL_ACCEPT:
 		{
-			if(!enteredText.empty() && captureAllKeys)
+			if(!enteredText.empty())
 			{
 				bool anyTextExceptCaret = enteredText.size() > 1;
 				endEnteringText(anyTextExceptCaret);
-
-				if(anyTextExceptCaret)
-				{
-					CCS->soundh->playSound("CHAT");
-				}
 			}
 			break;
 		}
@@ -195,8 +211,9 @@ void CInGameConsole::textInputed(const std::string & inputtedText)
 	if (LOCPLINT->cingconsole != this)
 		return;
 
-	if(!captureAllKeys || enteredText.empty())
+	if(enteredText.empty())
 		return;
+
 	enteredText.resize(enteredText.size()-1);
 
 	enteredText += inputtedText;
@@ -215,14 +232,10 @@ void CInGameConsole::startEnteringText()
 	if (!isActive())
 		return;
 
-	if (captureAllKeys)
-		return;
-
 	assert(currentStatusBar.expired());//effectively, nullptr check
 
 	currentStatusBar = GH.statusbar();
 
-	captureAllKeys = true;
 	enteredText = "_";
 
 	GH.statusbar()->setEnteringMode(true);
@@ -231,7 +244,6 @@ void CInGameConsole::startEnteringText()
 
 void CInGameConsole::endEnteringText(bool processEnteredText)
 {
-	captureAllKeys = false;
 	prevEntDisp = -1;
 	if(processEnteredText)
 	{

+ 1 - 0
client/adventureMap/CInGameConsole.h

@@ -50,6 +50,7 @@ public:
 	void keyPressed(EShortcut key) override;
 	void textInputed(const std::string & enteredText) override;
 	void textEdited(const std::string & enteredText) override;
+	bool captureThisKey(EShortcut key) override;
 
 	void startEnteringText();
 	void endEnteringText(bool processEnteredText);

+ 1 - 1
client/adventureMap/CMinimap.cpp

@@ -231,7 +231,7 @@ void CMinimap::setAIRadar(bool on)
 	redraw();
 }
 
-void CMinimap::updateTiles(std::unordered_set<int3> positions)
+void CMinimap::updateTiles(const std::unordered_set<int3> & positions)
 {
 	if(minimap)
 	{

+ 1 - 1
client/adventureMap/CMinimap.h

@@ -68,6 +68,6 @@ public:
 
 	void showAll(Canvas & to) override;
 
-	void updateTiles(std::unordered_set<int3> positions);
+	void updateTiles(const std::unordered_set<int3> & positions);
 };
 

+ 12 - 0
client/eventsSDL/InputSourceKeyboard.cpp

@@ -33,6 +33,12 @@ void InputSourceKeyboard::handleEventKeyDown(const SDL_KeyboardEvent & key)
 	if(key.repeat != 0)
 		return; // ignore periodic event resends
 
+	if (SDL_IsTextInputActive() == SDL_TRUE)
+	{
+		if (key.keysym.sym >= ' ' && key.keysym.sym < 0x80)
+			return; // printable character - will be handled as text input
+	}
+
 	assert(key.state == SDL_PRESSED);
 
 	if(key.keysym.sym >= SDLK_F1 && key.keysym.sym <= SDLK_F15 && settings["session"]["spectate"].Bool())
@@ -77,6 +83,12 @@ void InputSourceKeyboard::handleEventKeyUp(const SDL_KeyboardEvent & key)
 	if(key.repeat != 0)
 		return; // ignore periodic event resends
 
+	if (SDL_IsTextInputActive() == SDL_TRUE)
+	{
+		if (key.keysym.sym >= ' ' && key.keysym.sym < 0x80)
+			return; // printable character - will be handled as text input
+	}
+
 	assert(key.state == SDL_RELEASED);
 
 	auto shortcutsVector = GH.shortcuts().translateKeycode(key.keysym.sym);

+ 6 - 0
client/eventsSDL/InputSourceMouse.cpp

@@ -18,6 +18,12 @@
 #include "../gui/MouseButton.h"
 
 #include <SDL_events.h>
+#include <SDL_hints.h>
+
+InputSourceMouse::InputSourceMouse()
+{
+	SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
+}
 
 void InputSourceMouse::handleEventMouseMotion(const SDL_MouseMotionEvent & motion)
 {

+ 2 - 0
client/eventsSDL/InputSourceMouse.h

@@ -24,6 +24,8 @@ class InputSourceMouse
 	Point middleClickPosition;
 	int mouseButtonsMask = 0;
 public:
+	InputSourceMouse();
+
 	void handleEventMouseMotion(const SDL_MouseMotionEvent & current);
 	void handleEventMouseButtonDown(const SDL_MouseButtonEvent & current);
 	void handleEventMouseWheel(const SDL_MouseWheelEvent & current);

+ 44 - 15
client/gui/CIntObject.cpp

@@ -12,6 +12,7 @@
 
 #include "CGuiHandler.h"
 #include "WindowHandler.h"
+#include "EventDispatcher.h"
 #include "Shortcut.h"
 #include "../render/Canvas.h"
 #include "../windows/CMessage.h"
@@ -20,18 +21,13 @@
 CIntObject::CIntObject(int used_, Point pos_):
 	parent_m(nullptr),
 	parent(parent_m),
-	type(0)
+	redrawParent(false),
+	inputEnabled(true),
+	used(used_),
+	recActions(GH.defActionsDef),
+	defActions(GH.defActionsDef),
+	pos(pos_, Point())
 {
-	captureAllKeys = false;
-	used = used_;
-
-	recActions = defActions = GH.defActionsDef;
-
-	pos.x = pos_.x;
-	pos.y = pos_.y;
-	pos.w = 0;
-	pos.h = 0;
-
 	if(GH.captureChildren)
 		GH.createdObj.front()->addChild(this, true);
 }
@@ -76,7 +72,11 @@ void CIntObject::activate()
 	if (isActive())
 		return;
 
-	activateEvents(used | GENERAL);
+	if (inputEnabled)
+		activateEvents(used | GENERAL);
+	else
+		activateEvents(GENERAL);
+
 	assert(isActive());
 
 	if(defActions & ACTIVATE)
@@ -102,7 +102,7 @@ void CIntObject::deactivate()
 
 void CIntObject::addUsedEvents(ui16 newActions)
 {
-	if (isActive())
+	if (isActive() && inputEnabled)
 		activateEvents(~used & newActions);
 	used |= newActions;
 }
@@ -141,6 +141,32 @@ void CIntObject::setEnabled(bool on)
 		disable();
 }
 
+void CIntObject::setInputEnabled(bool on)
+{
+	if (inputEnabled == on)
+		return;
+
+	inputEnabled = on;
+
+	if (isActive())
+	{
+		assert((used & GENERAL) == 0);
+
+		if (on)
+			activateEvents(used);
+		else
+			deactivateEvents(used);
+	}
+
+	for(auto & elem : children)
+		elem->setInputEnabled(on);
+}
+
+void CIntObject::setRedrawParent(bool on)
+{
+	redrawParent = on;
+}
+
 void CIntObject::fitToScreen(int borderWidth, bool propagate)
 {
 	Point newPos = pos.topLeft();
@@ -181,6 +207,9 @@ void CIntObject::addChild(CIntObject * child, bool adjustPosition)
 	if(adjustPosition)
 		child->moveBy(pos.topLeft(), adjustPosition);
 
+	if (inputEnabled != child->inputEnabled)
+		child->setInputEnabled(inputEnabled);
+
 	if (!isActive() && child->isActive())
 		child->deactivate();
 	if (isActive()&& !child->isActive())
@@ -210,7 +239,7 @@ void CIntObject::redraw()
 	//it should fix glitches when called by inactive elements located below active window
 	if (isActive())
 	{
-		if (parent_m && (type & REDRAW_PARENT))
+		if (parent_m && redrawParent)
 		{
 			parent_m->redraw();
 		}
@@ -266,7 +295,7 @@ const Rect & CIntObject::center(const Point & p, bool propagate)
 
 bool CIntObject::captureThisKey(EShortcut key)
 {
-	return captureAllKeys;
+	return false;
 }
 
 CKeyShortcut::CKeyShortcut()

+ 12 - 8
client/gui/CIntObject.h

@@ -47,11 +47,10 @@ class CIntObject : public IShowActivatable, public AEventsReceiver //interface o
 	//non-const versions of fields to allow changing them in CIntObject
 	CIntObject *parent_m; //parent object
 
-public:
-	//redraw parent flag - this int may be semi-transparent and require redraw of parent window
-	enum {REDRAW_PARENT=8};
-	int type; //bin flags using etype
+	bool inputEnabled;
+	bool redrawParent;
 
+public:
 	std::vector<CIntObject *> children;
 
 	/// read-only parent access. May not be a "clean" solution but allows some compatibility
@@ -63,10 +62,8 @@ public:
 	CIntObject(int used=0, Point offset=Point());
 	virtual ~CIntObject();
 
-	//keyboard handling
-	bool captureAllKeys; //if true, only this object should get info about pressed keys
-
-	bool captureThisKey(EShortcut key) override; //allows refining captureAllKeys against specific events (eg. don't capture ENTER)
+	/// allows capturing key input so it will be delivered only to this element
+	bool captureThisKey(EShortcut key) override;
 
 	void addUsedEvents(ui16 newActions);
 	void removeUsedEvents(ui16 newActions);
@@ -82,6 +79,13 @@ public:
 	/// deactivates or activates UI element based on flag
 	void setEnabled(bool on);
 
+	/// Block (or allow) all user input, e.g. mouse/keyboard/touch without hiding element
+	void setInputEnabled(bool on);
+
+	/// Mark this input as one that requires parent redraw on update,
+	/// for example if current control might have semi-transparent elements and requires redrawing of background
+	void setRedrawParent(bool on);
+
 	// activate or deactivate object. Inactive object won't receive any input events (keyboard\mouse)
 	// usually used automatically by parent
 	void activate() override;

+ 3 - 1
client/gui/EventDispatcher.cpp

@@ -63,7 +63,9 @@ void EventDispatcher::dispatchTimer(uint32_t msPassed)
 	EventReceiversList hlp = timeinterested;
 	for (auto & elem : hlp)
 	{
-		if(!vstd::contains(timeinterested,elem)) continue;
+		if(!vstd::contains(timeinterested,elem))
+			continue;
+
 		elem->tick(msPassed);
 	}
 }

+ 1 - 1
client/gui/InterfaceObjectConfigurable.cpp

@@ -493,7 +493,7 @@ public:
 InterfaceLayoutWidget::InterfaceLayoutWidget()
 	:CIntObject() 
 {
-	type |= REDRAW_PARENT;
+	setRedrawParent(true);
 }
 
 std::shared_ptr<CIntObject> InterfaceObjectConfigurable::buildLayout(const JsonNode & config)

+ 0 - 84
client/ios/startSDL.mm

@@ -28,98 +28,14 @@
 
 @implementation SDLViewObserver
 
-- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
-	[object removeObserver:self forKeyPath:keyPath];
-
-    UIView * view = [object valueForKeyPath:keyPath];
-
-    auto longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)];
-    longPress.minimumPressDuration = 0.2;
-    [view addGestureRecognizer:longPress];
-
-    auto pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinch:)];
-    [view addGestureRecognizer:pinch];
-}
-
-#pragma mark - Gestures
-
-- (void)handleLongPress:(UIGestureRecognizer *)gesture {
-    // send RMB click
-    SDL_EventType mouseButtonType;
-    switch (gesture.state)
-    {
-        case UIGestureRecognizerStateBegan:
-            mouseButtonType = SDL_MOUSEBUTTONDOWN;
-            break;
-        case UIGestureRecognizerStateEnded:
-            mouseButtonType = SDL_MOUSEBUTTONUP;
-            break;
-        default:
-            return;
-    }
-
-    auto renderer = SDL_GetRenderer(mainWindow);
-    float scaleX, scaleY;
-    SDL_Rect viewport;
-    SDL_RenderGetScale(renderer, &scaleX, &scaleY);
-    SDL_RenderGetViewport(renderer, &viewport);
-
-    auto touchedPoint = [gesture locationInView:gesture.view];
-    auto screenScale = UIScreen.mainScreen.nativeScale;
-    Sint32 x = (int)touchedPoint.x * screenScale / scaleX - viewport.x;
-    Sint32 y = (int)touchedPoint.y * screenScale / scaleY - viewport.y;
-
-    SDL_Event rmbEvent;
-    rmbEvent.button = (SDL_MouseButtonEvent){
-        .type = mouseButtonType,
-        .button = SDL_BUTTON_RIGHT,
-        .clicks = 1,
-        .x = x,
-        .y = y,
-    };
-    SDL_PushEvent(&rmbEvent);
-
-    // small hack to prevent cursor jumping
-    if (mouseButtonType == SDL_MOUSEBUTTONUP)
-        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.025 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
-            SDL_Event motionEvent;
-            motionEvent.motion = (SDL_MouseMotionEvent){
-                .type = SDL_MOUSEMOTION,
-                .x = x,
-                .y = y,
-            };
-            SDL_PushEvent(&motionEvent);
-        });
-}
-
-- (void)handlePinch:(UIGestureRecognizer *)gesture {
-    if(gesture.state != UIGestureRecognizerStateBegan || CSH->state != EClientState::GAMEPLAY)
-        return;
-	[GameChatKeyboardHandler sendKeyEventWithKeyCode:SDLK_SPACE];
-}
-
-#pragma mark - UIGestureRecognizerDelegate
-
-- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
-    return [gestureRecognizer isKindOfClass:[UIPinchGestureRecognizer class]];
-}
-
 @end
 
-
 int startSDL(int argc, char * argv[], BOOL startManually)
 {
 	@autoreleasepool {
 		auto observer = [SDLViewObserver new];
 		observer.gameChatHandler = [GameChatKeyboardHandler new];
 
-		id __block sdlWindowCreationObserver = [NSNotificationCenter.defaultCenter addObserverForName:UIWindowDidBecomeKeyNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
-			[NSNotificationCenter.defaultCenter removeObserver:sdlWindowCreationObserver];
-			sdlWindowCreationObserver = nil;
-
-			UIWindow * sdlWindow = note.object;
-			[sdlWindow.rootViewController addObserver:observer forKeyPath:NSStringFromSelector(@selector(view)) options:NSKeyValueObservingOptionNew context:NULL];
-		}];
 		id textFieldObserver = [NSNotificationCenter.defaultCenter addObserverForName:UITextFieldTextDidEndEditingNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
 			removeFocusFromActiveInput();
 		}];

+ 2 - 3
client/lobby/CSelectionBase.cpp

@@ -115,7 +115,7 @@ InfoCard::InfoCard()
 	: showChat(true)
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
-	CIntObject::type |= REDRAW_PARENT;
+	setRedrawParent(true);
 	pos.x += 393;
 	pos.y += 6;
 
@@ -310,8 +310,7 @@ CChatBox::CChatBox(const Rect & rect)
 {
 	OBJ_CONSTRUCTION;
 	pos += rect.topLeft();
-	captureAllKeys = true;
-	type |= REDRAW_PARENT;
+	setRedrawParent(true);
 
 	const int height = static_cast<int>(graphics->fonts[FONT_SMALL]->getLineHeight());
 	inputBox = std::make_shared<CTextInput>(Rect(0, rect.h - height, rect.w, height), EFonts::FONT_SMALL, 0);

+ 1 - 1
client/lobby/RandomMapTab.cpp

@@ -371,7 +371,7 @@ TemplatesDropBox::ListItem::ListItem(const JsonNode & config, TemplatesDropBox &
 		pos.w = w->pos.w;
 		pos.h = w->pos.h;
 	}
-	type |= REDRAW_PARENT;
+	setRedrawParent(true);
 }
 
 void TemplatesDropBox::ListItem::updateItem(int idx, const CRmgTemplate * _item)

+ 14 - 3
client/lobby/SelectionTab.cpp

@@ -179,7 +179,7 @@ SelectionTab::SelectionTab(ESelectionScreen Type)
 		break;
 	case ESelectionScreen::campaignList:
 		tabTitle = CGI->generaltexth->allTexts[726];
-		type |= REDRAW_PARENT; // we use parent background so we need to make sure it's will be redrawn too
+		setRedrawParent(true); // we use parent background so we need to make sure it's will be redrawn too
 		pos.w = parent->pos.w;
 		pos.h = parent->pos.h;
 		pos.x += 3;
@@ -457,10 +457,21 @@ void SelectionTab::updateListItems()
 	}
 }
 
-int SelectionTab::getLine()
+bool SelectionTab::receiveEvent(const Point & position, int eventType) const
+{
+	// FIXME: widget should instead have well-defined pos so events will be filtered using standard routine
+	return getLine(position - pos.topLeft()) != -1;
+}
+
+int SelectionTab::getLine() const
 {
-	int line = -1;
 	Point clickPos = GH.getCursorPosition() - pos.topLeft();
+	return getLine(clickPos);
+}
+
+int SelectionTab::getLine(const Point & clickPos) const
+{
+	int line = -1;
 
 	// Ignore clicks on save name area
 	int maxPosY;

+ 3 - 2
client/lobby/SelectionTab.h

@@ -67,8 +67,8 @@ public:
 
 	void clickLeft(tribool down, bool previousState) override;
 	void keyPressed(EShortcut key) override;
-
 	void clickDouble() override;
+	bool receiveEvent(const Point & position, int eventType) const override;
 
 	void filter(int size, bool selectFirst = false); //0 - all
 	void sortBy(int criteria);
@@ -77,7 +77,8 @@ public:
 	void selectAbs(int position); //position: absolute position in curItems vector
 	void sliderMove(int slidPos);
 	void updateListItems();
-	int getLine();
+	int getLine() const;
+	int getLine(const Point & position) const;
 	void selectFileName(std::string fname);
 	std::shared_ptr<CMapInfo> getSelectedMapInfo() const;
 	void rememberCurrentSelection();

+ 3 - 3
client/mainmenu/CMainMenu.cpp

@@ -95,7 +95,7 @@ CMenuScreen::CMenuScreen(const JsonNode & configNode)
 	menuNameToEntry.push_back("credits");
 
 	tabs = std::make_shared<CTabbedInt>(std::bind(&CMenuScreen::createTab, this, _1));
-	tabs->type |= REDRAW_PARENT;
+	tabs->setRedrawParent(true);
 }
 
 std::shared_ptr<CIntObject> CMenuScreen::createTab(size_t index)
@@ -248,7 +248,7 @@ std::shared_ptr<CButton> CMenuEntry::createButton(CMenuScreen * parent, const Js
 CMenuEntry::CMenuEntry(CMenuScreen * parent, const JsonNode & config)
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
-	type |= REDRAW_PARENT;
+	setRedrawParent(true);
 	pos = parent->pos;
 
 	for(const JsonNode & node : config["images"].Vector())
@@ -258,7 +258,7 @@ CMenuEntry::CMenuEntry(CMenuScreen * parent, const JsonNode & config)
 	{
 		buttons.push_back(createButton(parent, node));
 		buttons.back()->hoverable = true;
-		buttons.back()->type |= REDRAW_PARENT;
+		buttons.back()->setRedrawParent(true);
 	}
 }
 

+ 1 - 1
client/mainmenu/CreditsScreen.cpp

@@ -24,7 +24,7 @@ CreditsScreen::CreditsScreen(Rect rect)
 {
 	pos.w = rect.w;
 	pos.h = rect.h;
-	type |= REDRAW_PARENT;
+	setRedrawParent(true);
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
 	auto textFile = CResourceHandler::get()->load(ResourceID("DATA/CREDITS.TXT"))->readAll();
 	std::string text((char *)textFile.first.get(), textFile.second);

+ 1 - 8
client/mapView/MapView.cpp

@@ -90,7 +90,6 @@ void MapView::show(Canvas & to)
 
 MapView::MapView(const Point & offset, const Point & dimensions)
 	: BasicMapView(offset, dimensions)
-	, isSwiping(false)
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
 	actions = std::make_shared<MapViewActions>(*this, model);
@@ -110,21 +109,15 @@ void MapView::onMapLevelSwitched()
 
 void MapView::onMapScrolled(const Point & distance)
 {
-	if(!isSwiping)
+	if(!isGesturing())
 		controller->setViewCenter(model->getMapViewCenter() + distance, model->getLevel());
 }
 
 void MapView::onMapSwiped(const Point & viewPosition)
 {
-	isSwiping = true;
 	controller->setViewCenter(model->getMapViewCenter() + viewPosition, model->getLevel());
 }
 
-void MapView::onMapSwipeEnded()
-{
-	isSwiping = false;
-}
-
 void MapView::onCenteredTile(const int3 & tile)
 {
 	controller->setViewCenter(tile);

+ 0 - 5
client/mapView/MapView.h

@@ -48,8 +48,6 @@ class MapView : public BasicMapView
 {
 	std::shared_ptr<MapViewActions> actions;
 
-	bool isSwiping;
-
 public:
 	void show(Canvas & to) override;
 
@@ -64,9 +62,6 @@ public:
 	/// Moves current view to specified position, in pixels
 	void onMapSwiped(const Point & viewPosition);
 
-	/// Ends swiping mode and allows normal map scrolling once again
-	void onMapSwipeEnded();
-
 	/// Moves current view to specified tile
 	void onCenteredTile(const int3 & tile);
 

+ 9 - 1
client/mapView/MapViewController.cpp

@@ -185,9 +185,16 @@ void MapViewController::tick(uint32_t timeDelta)
 		fadingInContext->progress = std::min( 1.0, fadingInContext->progress);
 	}
 
+	if (adventureContext)
+		adventureContext->animationTime += timeDelta;
+
+	updateState();
+}
+
+void MapViewController::updateState()
+{
 	if(adventureContext)
 	{
-		adventureContext->animationTime += timeDelta;
 		adventureContext->settingsSessionSpectate = settings["session"]["spectate"].Bool();
 		adventureContext->settingsAdventureObjectAnimation = settings["adventure"]["objectAnimation"].Bool();
 		adventureContext->settingsAdventureTerrainAnimation = settings["adventure"]["terrainAnimation"].Bool();
@@ -511,6 +518,7 @@ void MapViewController::activateAdventureContext(uint32_t animationTime)
 	adventureContext = std::make_shared<MapRendererAdventureContext>(*state);
 	adventureContext->animationTime = animationTime;
 	context = adventureContext;
+	updateState();
 }
 
 void MapViewController::activateAdventureContext()

+ 1 - 0
client/mapView/MapViewController.h

@@ -74,6 +74,7 @@ private:
 	void onAfterHeroDisembark(const CGHeroInstance * obj, const int3 & from, const int3 & dest) override;
 
 	void resetContext();
+	void updateState();
 
 public:
 	MapViewController(std::shared_ptr<MapViewModel> model, std::shared_ptr<MapViewCache> view);

+ 4 - 4
client/widgets/CComponent.cpp

@@ -281,7 +281,7 @@ void CSelectableComponent::init()
 CSelectableComponent::CSelectableComponent(const Component &c, std::function<void()> OnSelect):
 	CComponent(c),onSelect(OnSelect)
 {
-	type |= REDRAW_PARENT;
+	setRedrawParent(true);
 	addUsedEvents(LCLICK | KEYBOARD);
 	init();
 }
@@ -289,7 +289,7 @@ CSelectableComponent::CSelectableComponent(const Component &c, std::function<voi
 CSelectableComponent::CSelectableComponent(Etype Type, int Sub, int Val, ESize imageSize, std::function<void()> OnSelect):
 	CComponent(Type,Sub,Val, imageSize),onSelect(OnSelect)
 {
-	type |= REDRAW_PARENT;
+	setRedrawParent(true);
 	addUsedEvents(LCLICK | KEYBOARD);
 	init();
 }
@@ -466,7 +466,7 @@ CComponentBox::CComponentBox(std::vector<std::shared_ptr<CComponent>> _component
 	betweenRows(betweenRows),
 	componentsInRow(componentsInRow)
 {
-	type |= REDRAW_PARENT;
+	setRedrawParent(true);
 	pos = position + pos.topLeft();
 	placeComponents(false);
 }
@@ -484,7 +484,7 @@ CComponentBox::CComponentBox(std::vector<std::shared_ptr<CSelectableComponent>>
 	betweenRows(betweenRows),
 	componentsInRow(componentsInRow)
 {
-	type |= REDRAW_PARENT;
+	setRedrawParent(true);
 	pos = position + pos.topLeft();
 	placeComponents(true);
 

+ 1 - 1
client/widgets/CreatureCostBox.cpp

@@ -17,7 +17,7 @@ CreatureCostBox::CreatureCostBox(Rect position, std::string titleText)
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 
-	type |= REDRAW_PARENT;
+	setRedrawParent(true);
 	pos = position + pos.topLeft();
 
 	title = std::make_shared<CLabel>(pos.w/2, 10, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, titleText);

+ 1 - 0
client/widgets/MiscWidgets.h

@@ -143,6 +143,7 @@ public:
 class LRClickableAreaWTextComp: public LRClickableAreaWText
 {
 public:
+	int type;
 	int baseType;
 	int bonusValue;
 	virtual void clickLeft(tribool down, bool previousState) override;

+ 3 - 0
client/widgets/ObjectLists.cpp

@@ -138,6 +138,9 @@ void CListBox::reset()
 
 void CListBox::resize(size_t newSize)
 {
+	if (totalSize == newSize)
+		return;
+
 	totalSize = newSize;
 	if (slider)
 		slider->setAmount((int)totalSize);

+ 8 - 0
client/widgets/Slider.cpp

@@ -138,6 +138,10 @@ void CSlider::clickLeft(tribool down, bool previousState)
 			rw = pw / (pos.h-48);
 		}
 
+		// click on area covered by buttons -> ignore, will be handled by left/right buttons
+		if (!vstd::iswithin(rw, 0, 1))
+			return;
+
 		slider->clickLeft(true, slider->isMouseLeftButtonPressed());
 		scrollTo((int)(rw * positions  +  0.5));
 		return;
@@ -214,6 +218,10 @@ CSlider::CSlider(Point position, int totalw, std::function<void(int)> Moved, int
 		pos.h = totalw;
 	}
 
+	// for horizontal sliders that act as values selection - add keyboard event to receive left/right click
+	if (getOrientation() == Orientation::HORIZONTAL)
+		addUsedEvents(KEYBOARD);
+
 	updateSliderPos();
 }
 

+ 3 - 17
client/widgets/TextControls.cpp

@@ -47,7 +47,7 @@ void CLabel::showAll(Canvas & to)
 CLabel::CLabel(int x, int y, EFonts Font, ETextAlignment Align, const SDL_Color & Color, const std::string & Text)
 	: CTextContainer(Align, Font, Color), text(Text)
 {
-	type |= REDRAW_PARENT;
+	setRedrawParent(true);
 	autoRedraw = true;
 	pos.x += x;
 	pos.y += y;
@@ -299,7 +299,7 @@ CTextBox::CTextBox(std::string Text, const Rect & rect, int SliderStyle, EFonts
 	OBJECT_CONSTRUCTION_CAPTURING(255 - DISPOSE);
 	label = std::make_shared<CMultiLineLabel>(rect, Font, Align, Color);
 
-	type |= REDRAW_PARENT;
+	setRedrawParent(true);
 	pos.x += rect.x;
 	pos.y += rect.y;
 	pos.h = rect.h;
@@ -492,10 +492,9 @@ CTextInput::CTextInput(const Rect & Pos, EFonts font, const CFunctionList<void(c
 	cb(CB),
 	CFocusable(std::make_shared<CKeyboardFocusListener>(this))
 {
-	type |= REDRAW_PARENT;
+	setRedrawParent(true);
 	pos.h = Pos.h;
 	pos.w = Pos.w;
-	captureAllKeys = true;
 	background.reset();
 	addUsedEvents(LCLICK | KEYBOARD | TEXTINPUT);
 
@@ -511,7 +510,6 @@ CTextInput::CTextInput(const Rect & Pos, const Point & bgOffset, const std::stri
 	pos.h = Pos.h;
 	pos.w = Pos.w;
 
-	captureAllKeys = true;
 	OBJ_CONSTRUCTION;
 	background = std::make_shared<CPicture>(bgName, bgOffset.x, bgOffset.y);
 	addUsedEvents(LCLICK | KEYBOARD | TEXTINPUT);
@@ -525,7 +523,6 @@ CTextInput::CTextInput(const Rect & Pos, std::shared_ptr<IImage> srf)
 	:CFocusable(std::make_shared<CKeyboardFocusListener>(this))
 {
 	pos += Pos.topLeft();
-	captureAllKeys = true;
 	OBJ_CONSTRUCTION;
 	background = std::make_shared<CPicture>(srf, Pos);
 	pos.w = background->pos.w;
@@ -620,17 +617,6 @@ void CTextInput::setText(const std::string & nText, bool callCb)
 		cb(text);
 }
 
-bool CTextInput::captureThisKey(EShortcut key)
-{
-	if(key == EShortcut::GLOBAL_RETURN)
-		return false;
-
-	if (!focus)
-		return false;
-
-	return true;
-}
-
 void CTextInput::textInputed(const std::string & enteredText)
 {
 	if(!focus)

+ 1 - 1
client/widgets/TextControls.h

@@ -227,7 +227,7 @@ public:
 	void clickLeft(tribool down, bool previousState) override;
 	void keyPressed(EShortcut key) override;
 
-	bool captureThisKey(EShortcut key) override;
+	//bool captureThisKey(EShortcut key) override;
 
 	void textInputed(const std::string & enteredText) override;
 	void textEdited(const std::string & enteredText) override;

+ 1 - 1
client/windows/CCastleInterface.cpp

@@ -1160,7 +1160,7 @@ CCastleInterface::CCastleInterface(const CGTownInstance * Town, const CGTownInst
 	updateShadow();
 
 	garr = std::make_shared<CGarrisonInt>(305, 387, 4, Point(0,96), town->getUpperArmy(), town->visitingHero);
-	garr->type |= REDRAW_PARENT;
+	garr->setRedrawParent(true);
 
 	heroes = std::make_shared<HeroSlots>(town, Point(241, 387), Point(241, 483), garr, true);
 	title = std::make_shared<CLabel>(85, 387, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, town->getNameTranslated());

+ 2 - 2
client/windows/GUIClasses.cpp

@@ -1776,7 +1776,7 @@ CObjectListWindow::CItem::CItem(CObjectListWindow * _parent, size_t _id, std::st
 	border = std::make_shared<CPicture>("TPGATES");
 	pos = border->pos;
 
-	type |= REDRAW_PARENT;
+	setRedrawParent(true);
 
 	text = std::make_shared<CLabel>(pos.w/2, pos.h/2, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, _text);
 	select(index == parent->selected);
@@ -1850,7 +1850,7 @@ void CObjectListWindow::init(std::shared_ptr<CIntObject> titleWidget_, std::stri
 	}
 	list = std::make_shared<CListBox>(std::bind(&CObjectListWindow::genItem, this, _1),
 		Point(14, 151), Point(0, 25), 9, items.size(), 0, 1, Rect(262, -32, 256, 256) );
-	list->type |= REDRAW_PARENT;
+	list->setRedrawParent(true);
 
 	ok = std::make_shared<CButton>(Point(15, 402), "IOKAY.DEF", CButton::tooltip(), std::bind(&CObjectListWindow::elementSelected, this), EShortcut::GLOBAL_ACCEPT);
 	ok->block(!list->size());

+ 1 - 1
client/windows/settings/AdventureOptionsTab.cpp

@@ -34,7 +34,7 @@ AdventureOptionsTab::AdventureOptionsTab()
 		: InterfaceObjectConfigurable()
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
-	type |= REDRAW_PARENT;
+	setRedrawParent(true);
 
 	const JsonNode config(ResourceID("config/widgets/settings/adventureOptionsTab.json"));
 	addCallback("playerHeroSpeedChanged", [this](int value)

+ 1 - 1
client/windows/settings/BattleOptionsTab.cpp

@@ -21,7 +21,7 @@
 BattleOptionsTab::BattleOptionsTab(BattleInterface * owner)
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
-	type |= REDRAW_PARENT;
+	setRedrawParent(true);
 
 	const JsonNode config(ResourceID("config/widgets/settings/battleOptionsTab.json"));
 	addCallback("viewGridChanged", [this, owner](bool value)

+ 1 - 1
client/windows/settings/GeneralOptionsTab.cpp

@@ -94,7 +94,7 @@ GeneralOptionsTab::GeneralOptionsTab()
 		  onFullscreenChanged(settings.listen["video"]["fullscreen"])
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
-	type |= REDRAW_PARENT;
+	setRedrawParent(true);
 
 	addConditional("touchscreen", GH.input().hasTouchInputDevice());
 #ifdef VCMI_MOBILE

+ 1 - 1
client/windows/settings/SettingsMainWindow.cpp

@@ -71,7 +71,7 @@ SettingsMainWindow::SettingsMainWindow(BattleInterface * parentBattleUi) : Inter
 
 	parentBattleInterface = parentBattleUi;
 	tabContentArea = std::make_shared<CTabbedInt>(std::bind(&SettingsMainWindow::createTab, this, _1), Point(0, 0), defaultTabIndex);
-	tabContentArea->type |= REDRAW_PARENT;
+	tabContentArea->setRedrawParent(true);
 
 	std::shared_ptr<CToggleGroup> mainTabs = widget<CToggleGroup>("settingsTabs");
 	mainTabs->setSelected(defaultTabIndex);

+ 1 - 0
lib/IGameEventsReceiver.h

@@ -127,6 +127,7 @@ public:
 	virtual void playerBonusChanged(const Bonus &bonus, bool gain){};//if gain hero received bonus, else he lost it
 	virtual void requestSent(const CPackForServer *pack, int requestID){};
 	virtual void requestRealized(PackageApplied *pa){};
+	virtual void beforeObjectPropertyChanged(const SetObjectProperty * sop){}; //eg. mine has been flagged
 	virtual void objectPropertyChanged(const SetObjectProperty * sop){}; //eg. mine has been flagged
 	virtual void objectRemoved(const CGObjectInstance *obj){}; //eg. collected resource, picked artifact, beaten hero
 	virtual void objectRemovedAfter(){}; //eg. collected resource, picked artifact, beaten hero

+ 8 - 6
server/CGameHandler.cpp

@@ -4980,12 +4980,6 @@ void CGameHandler::playerMessage(PlayerColor player, const std::string &message,
 {
 	bool cheated = false;
 	
-	if(!getPlayerSettings(player)->isControlledByAI())
-	{
-		PlayerMessageClient temp_message(player, message);
-		sendAndApply(&temp_message);
-	}
-
 	std::vector<std::string> words;
 	boost::split(words, message, boost::is_any_of(" "));
 	
@@ -5106,6 +5100,14 @@ void CGameHandler::playerMessage(PlayerColor player, const std::string &message,
 		if(!player.isSpectator())
 			checkVictoryLossConditionsForPlayer(player);//Player enter win code or got required art\creature
 	}
+	else
+	{
+		if(!getPlayerSettings(player)->isControlledByAI())
+		{
+			PlayerMessageClient temp_message(player, message);
+			sendAndApply(&temp_message);
+		}
+	}
 }
 
 bool CGameHandler::makeCustomAction(BattleAction & ba)