From abacb5f0ead18874d6c548f39992cb31a7307b8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Sat, 12 Aug 2023 10:53:25 +0200 Subject: [PATCH 01/14] Fix resources placed behind mines --- lib/rmg/modificators/ObjectManager.cpp | 46 ++++++++++++++++++-------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/lib/rmg/modificators/ObjectManager.cpp b/lib/rmg/modificators/ObjectManager.cpp index d1afd557e..02267aaa8 100644 --- a/lib/rmg/modificators/ObjectManager.cpp +++ b/lib/rmg/modificators/ObjectManager.cpp @@ -413,23 +413,41 @@ bool ObjectManager::createRequiredObjects() zone.connectPath(path); placeObject(rmgObject, guarded, true); - - for(const auto & nearby : nearbyObjects) + } + + for(const auto & nearby : nearbyObjects) + { + auto * targetObject = nearby.nearbyTarget; + if (!targetObject || !targetObject->appearance) { - if(nearby.nearbyTarget != objInfo.obj) - continue; - - rmg::Object rmgNearObject(*nearby.obj); - rmg::Area possibleArea(rmgObject.instances().front()->getBlockedArea().getBorderOutside()); - possibleArea.intersect(zone.areaPossible()); - if(possibleArea.empty()) + continue; + } + + rmg::Object rmgNearObject(*nearby.obj); + rmg::Area possibleArea(rmg::Area(targetObject->getBlockedPos()).getBorderOutside()); + possibleArea.intersect(zone.areaPossible()); + if(possibleArea.empty()) + { + rmgNearObject.clear(); + continue; + } + + rmgNearObject.setPosition(*RandomGeneratorUtil::nextItem(possibleArea.getTiles(), zone.getRand())); + placeObject(rmgNearObject, false, false); + auto path = zone.searchPath(rmgNearObject.getVisitablePosition(), false); + if (path.valid()) + { + zone.connectPath(path); + } + else + { + for (auto* instance : rmgNearObject.instances()) { - rmgNearObject.clear(); - continue; + logGlobal->error("Failed to connect nearby object %s at %s", + instance->object().getObjectName(), instance->getPosition(true).toString()); + mapProxy->removeObject(&instance->object()); } - - rmgNearObject.setPosition(*RandomGeneratorUtil::nextItem(possibleArea.getTiles(), zone.getRand())); - placeObject(rmgNearObject, false, false); + rmgNearObject.clear(); } } From 626664b8f6ea0bd14a4959ae85c5088a8325cc0d Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 12 Aug 2023 14:16:47 +0300 Subject: [PATCH 02/14] Fix possible thread race on accessing GuiHandler from server handler thread without acquiring lock --- client/CServerHandler.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 18e9ee444..667684b2b 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -88,6 +88,8 @@ template class CApplyOnLobby : public CBaseForLobbyApply public: bool applyOnLobbyHandler(CServerHandler * handler, void * pack) const override { + boost::unique_lock un(*CPlayerInterface::pim); + T * ptr = static_cast(pack); ApplyOnLobbyHandlerNetPackVisitor visitor(*handler); From 1eabb738dcaa7c1c3bafc6366d62b426efb09387 Mon Sep 17 00:00:00 2001 From: Andrii Danylchenko Date: Sat, 12 Aug 2023 22:02:37 +0300 Subject: [PATCH 03/14] NKAI: fix heroes not recruited --- AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp index 581c725e6..8f9b80f6d 100644 --- a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp +++ b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp @@ -84,7 +84,7 @@ Goals::TGoalVec RecruitHeroBehavior::decompose() const } } - if(treasureSourcesCount < 5) + if(treasureSourcesCount < 5 && (town->garrisonHero || town->getUpperArmy()->getArmyStrength() < 10000)) continue; if(cb->getHeroesInfo().size() < cb->getTownsInfo().size() + 1 From a7859dae39ec37101f7d879d9a8ebab341403d44 Mon Sep 17 00:00:00 2001 From: Andrii Danylchenko Date: Sat, 12 Aug 2023 22:08:15 +0300 Subject: [PATCH 04/14] Battle AI: archangels cast again --- lib/battle/CBattleInfoCallback.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index f207c7170..ff44c2ba9 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -1732,6 +1732,10 @@ SpellID CBattleInfoCallback::getRandomCastedSpell(CRandomGenerator & rand,const TConstBonusListPtr bl = caster->getBonuses(Selector::type()(BonusType::SPELLCASTER)); if (!bl->size()) return SpellID::NONE; + + if(bl->size() == 1) + return SpellID(bl->front()->subtype); + int totalWeight = 0; for(const auto & b : *bl) { From ef0cd0ba6ed40c0f9c2a0759ce6d3f1b748c6563 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 14 Aug 2023 23:37:16 +0300 Subject: [PATCH 05/14] Fixed mod dependencies check on save loading to prevent crashes: - mods that don't affect game objects can be ignored when loading save - gameplay-affecting mods that were present in save must be active - gameplay-affecting mods that were not in save must not be active --- lib/CModHandler.cpp | 42 +++++++++++++++++++++++++++++++++++++++++ lib/CModHandler.h | 46 +++++++++++++++++++++++++++++++++++++++------ 2 files changed, 82 insertions(+), 6 deletions(-) diff --git a/lib/CModHandler.cpp b/lib/CModHandler.cpp index 1a78d708f..9b8247295 100644 --- a/lib/CModHandler.cpp +++ b/lib/CModHandler.cpp @@ -649,6 +649,48 @@ void CModInfo::updateChecksum(ui32 newChecksum) } } +bool CModInfo::checkModGameplayAffecting() const +{ + if (modGameplayAffecting.has_value()) + return *modGameplayAffecting; + + static const std::vector keysToTest = { + "heroClasses", + "artifacts", + "creatures", + "factions", + "objects", + "heroes", + "spells", + "skills", + "templates", + "scripts", + "battlefields", + "terrains", + "rivers", + "roads", + "obstacles" + }; + + ResourceID modFileResource(CModInfo::getModFile(identifier)); + + if(CResourceHandler::get("initial")->existsResource(modFileResource)) + { + const JsonNode modConfig(modFileResource); + + for (auto const & key : keysToTest) + { + if (!modConfig[key].isNull()) + { + modGameplayAffecting = true; + return *modGameplayAffecting; + } + } + } + modGameplayAffecting = false; + return *modGameplayAffecting; +} + void CModInfo::loadLocalData(const JsonNode & data) { bool validated = false; diff --git a/lib/CModHandler.h b/lib/CModHandler.h index 30325f246..887422dbc 100644 --- a/lib/CModHandler.h +++ b/lib/CModHandler.h @@ -179,6 +179,10 @@ using TModID = std::string; class DLL_LINKAGE CModInfo { + /// cached result of checkModGameplayAffecting() call + /// Do not serialize - depends on local mod version, not server/save mod version + mutable std::optional modGameplayAffecting; + public: enum EValidationStatus { @@ -223,6 +227,9 @@ public: JsonNode saveLocalData() const; void updateChecksum(ui32 newChecksum); + /// return true if this mod can affect gameplay, e.g. adds or modifies any game objects + bool checkModGameplayAffecting() const; + bool isEnabled() const; void setEnabled(bool on); @@ -351,19 +358,46 @@ public: else { loadMods(); + std::vector saveActiveMods; std::vector newActiveMods; - h & newActiveMods; + h & saveActiveMods; Incompatibility::ModList missingMods; - for(const auto & m : newActiveMods) + for(const auto & m : activeMods) + { + if (vstd::contains(saveActiveMods, m)) + continue; + + auto & modInfo = allMods.at(m); + if(modInfo.checkModGameplayAffecting()) + missingMods.emplace_back(m, modInfo.version.toString()); + } + + for(const auto & m : saveActiveMods) { CModVersion mver; h & mver; - - if(allMods.count(m) && (allMods[m].version.isNull() || mver.isNull() || allMods[m].version.compatible(mver))) - allMods[m].setEnabled(true); - else + + if (allMods.count(m) == 0) + { + missingMods.emplace_back(m, mver.toString()); + continue; + } + + auto & modInfo = allMods.at(m); + + bool modAffectsGameplay = modInfo.checkModGameplayAffecting(); + bool modVersionCompatible = modInfo.version.isNull() || mver.isNull() || modInfo.version.compatible(mver); + bool modEnabledLocally = vstd::contains(activeMods, m); + bool modCanBeEnabled = modEnabledLocally && modVersionCompatible; + + allMods[m].setEnabled(modCanBeEnabled); + + if (modCanBeEnabled) + newActiveMods.push_back(m); + + if (!modCanBeEnabled && modAffectsGameplay) missingMods.emplace_back(m, mver.toString()); } From 00ac8eb3069488127b80f59f04ebd67ae83c743f Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 15 Aug 2023 00:17:59 +0300 Subject: [PATCH 06/14] Attempt to fix crash on quitApplication on Android --- server/CGameHandler.cpp | 1 - server/CGameHandler.h | 2 ++ server/NetPacksServer.cpp | 6 ++---- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 3599c399a..72b6e2438 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -77,7 +77,6 @@ #define COMPLAIN_RETF(txt, FORMAT) {complain(boost::str(boost::format(txt) % FORMAT)); return false;} CondSh battleMadeAction(false); -boost::recursive_mutex battleActionMutex; CondSh battleResult(nullptr); template class CApplyOnGH; diff --git a/server/CGameHandler.h b/server/CGameHandler.h index a3f906408..49a6dfcd6 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -102,6 +102,8 @@ class CGameHandler : public IGameCallback, public CBattleInfoCallback, public En std::unique_ptr battleThread; public: + boost::recursive_mutex battleActionMutex; + std::unique_ptr heroPool; using FireShieldInfo = std::vector>; diff --git a/server/NetPacksServer.cpp b/server/NetPacksServer.cpp index 7660bc073..3027242c0 100644 --- a/server/NetPacksServer.cpp +++ b/server/NetPacksServer.cpp @@ -25,8 +25,6 @@ #include "../lib/spells/ISpellMechanics.h" #include "../lib/serializer/Cast.h" -extern boost::recursive_mutex battleActionMutex; - void ApplyGhNetPackVisitor::visitSaveGame(SaveGame & pack) { gh.save(pack.fname); @@ -282,7 +280,7 @@ void ApplyGhNetPackVisitor::visitQueryReply(QueryReply & pack) void ApplyGhNetPackVisitor::visitMakeAction(MakeAction & pack) { - boost::unique_lock lock(battleActionMutex); + boost::unique_lock lock(gh.battleActionMutex); const BattleInfo * b = gs.curB; if(!b) @@ -311,7 +309,7 @@ void ApplyGhNetPackVisitor::visitMakeAction(MakeAction & pack) void ApplyGhNetPackVisitor::visitMakeCustomAction(MakeCustomAction & pack) { - boost::unique_lock lock(battleActionMutex); + boost::unique_lock lock(gh.battleActionMutex); const BattleInfo * b = gs.curB; if(!b) From 8b628221ec637b922164fac7c2708f7d3441e691 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 12 Aug 2023 14:16:47 +0300 Subject: [PATCH 07/14] Fix possible thread race on accessing GuiHandler from server handler thread without acquiring lock --- client/CServerHandler.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 18e9ee444..667684b2b 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -88,6 +88,8 @@ template class CApplyOnLobby : public CBaseForLobbyApply public: bool applyOnLobbyHandler(CServerHandler * handler, void * pack) const override { + boost::unique_lock un(*CPlayerInterface::pim); + T * ptr = static_cast(pack); ApplyOnLobbyHandlerNetPackVisitor visitor(*handler); From 3aead5f32f38f5aa48130ba64d8628f75ac80c65 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 15 Aug 2023 13:30:31 +0300 Subject: [PATCH 08/14] Android build ID bump --- android/vcmi-app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/vcmi-app/build.gradle b/android/vcmi-app/build.gradle index 5e67599a8..34c9dcc70 100644 --- a/android/vcmi-app/build.gradle +++ b/android/vcmi-app/build.gradle @@ -10,7 +10,7 @@ android { applicationId "is.xyz.vcmi" minSdk 19 targetSdk 31 - versionCode 1305 + versionCode 1306 versionName "1.3.0" setProperty("archivesBaseName", "vcmi") } From 9509974a5d629018a9fe5e0dd977cac9041a197a Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 12 Aug 2023 16:58:24 +0300 Subject: [PATCH 09/14] Updated changelog --- ChangeLog.md | 40 +++++++++++++++++++++++++++--- launcher/eu.vcmi.VCMI.metainfo.xml | 2 +- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 3ba074a01..584fd2319 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,10 +1,44 @@ # 1.3.0 -> 1.3.1 -(unreleased) -* Fixed crash on starting game with outdated mods -* Fixed Android mod manager crash +### GENERAL: * Fixed framerate drops on hero movement with active hota mod +* Fade-out animations will now be skipped when instant hero movement speed is used +* Restarting loaded campaing scenario will now correctly reapply starting bonus * Reverted FPS limit on mobile systems back to 60 fps +* Fixed loading of translations for maps and campaigns +* Fixed loading of preconfigured starting army for heroes with preconfigured spells +* Background battlefield obstacles will now appear below creatures +* it is now possible to load save game located inside mod +* Added option to configure reserved screen area in Launcher on iOS + +### AI PLAYER: +* BattleAI: Improved performance of AI spell selection +* NKAI: Fixed freeze on attempt to exchange army between garrisoned and visiting hero +* NKAI: Fixed town threat calculation +* VCAI: Added workaround to avoid freeze on attempting to reach unreachable location + +### RANDOM MAP GENERATOR: +* Fixed placement of roads inside rock in underground +* Fixed placement of shifted creature animations from HotA +* Fixed placement of treasures at the boundary of wide connections +* Added more potential locations for quest artifacts in zone + +### STABILITY: +* When starting client without H3 data game will now show message instead of silently crashing +* When starting invalid map in campaign, game will now show message instead of silently crashing +* Fixed crash on starting game with outdated mods +* Fixed crash on attempt to sacrifice all your artifacts in Altar of Sacrifice +* Fixed crash on leveling up after winning battle as defender +* Fixed possible crash on end of battle opening sound +* Fixed crash on accepting battle result after winning battle as defender +* Fixed possible crash on casting spell in battle by AI +* Fixed multiple possible crashes on managing mods on Android +* Fixed multiple possible crashes on importing data on Android +* Fixed crash on refusing rewards from town building +* Fixed possible crash on threat evaluation by NKAI +* Fixed crash on using haptic feedback on some Android systems +* Fixed crash on right-clicking flags area in RMG setup mode +* Fixed crash on opening Blacksmith window and Build Structure dialogs in some localizations # 1.2.1 -> 1.3.0 diff --git a/launcher/eu.vcmi.VCMI.metainfo.xml b/launcher/eu.vcmi.VCMI.metainfo.xml index f89354b39..bfb05414f 100644 --- a/launcher/eu.vcmi.VCMI.metainfo.xml +++ b/launcher/eu.vcmi.VCMI.metainfo.xml @@ -51,7 +51,7 @@ StrategyGame - + From 074fd42d842fe67345dcb418241d014ab566ce54 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 17 Aug 2023 10:20:46 +0300 Subject: [PATCH 10/14] Added missing includes --- Global.h | 2 ++ client/CServerHandler.cpp | 1 + 2 files changed, 3 insertions(+) diff --git a/Global.h b/Global.h index dda61c804..3c308f54c 100644 --- a/Global.h +++ b/Global.h @@ -118,6 +118,7 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size."); #include #include #include +#include #include #include #include @@ -126,6 +127,7 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size."); #include #include #include +#include #include //The only available version is 3, as of Boost 1.50 diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 667684b2b..7a21a5b84 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -54,6 +54,7 @@ #include #include #include +#include #include "../lib/serializer/Cast.h" #include "LobbyClientNetPackVisitors.h" From bfa12080ac136b25cbf91f5366a11e1e2fadb854 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 17 Aug 2023 10:21:03 +0300 Subject: [PATCH 11/14] Added downloads counter for 1.3.1 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 18ca646d1..b635ac00c 100644 --- a/README.md +++ b/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) [![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.3.0/total)](https://github.com/vcmi/vcmi/releases/tag/1.3.0) +[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.3.1/total)](https://github.com/vcmi/vcmi/releases/tag/1.3.1) [![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/total)](https://github.com/vcmi/vcmi/releases) # VCMI Project VCMI is work-in-progress attempt to recreate engine for Heroes III, giving it new and extended possibilities. From 6e7b7932a0b56034549acf9162ee53db2e82d249 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 17 Aug 2023 10:25:31 +0300 Subject: [PATCH 12/14] Fixed recruitment of hero from towns located on map border --- server/HeroPoolProcessor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/HeroPoolProcessor.cpp b/server/HeroPoolProcessor.cpp index 4c92e5963..f32a88be0 100644 --- a/server/HeroPoolProcessor.cpp +++ b/server/HeroPoolProcessor.cpp @@ -244,7 +244,7 @@ bool HeroPoolProcessor::hireHero(const ObjectInstanceID & objectID, const HeroTy hr.hid = recruitedHero->subID; hr.player = player; hr.tile = recruitedHero->convertFromVisitablePos(targetPos ); - if(gameHandler->getTile(hr.tile)->isWater() && !recruitedHero->boat) + if(gameHandler->getTile(targetPos)->isWater() && !recruitedHero->boat) { //Create a new boat for hero gameHandler->createObject(targetPos , Obj::BOAT, recruitedHero->getBoatType().getNum()); From 8b8d2b881094f9ac70eb6fc9aee9f650195883d4 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 17 Aug 2023 10:25:46 +0300 Subject: [PATCH 13/14] Updated changelog --- ChangeLog.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index 584fd2319..b3c964975 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -15,7 +15,9 @@ * BattleAI: Improved performance of AI spell selection * NKAI: Fixed freeze on attempt to exchange army between garrisoned and visiting hero * NKAI: Fixed town threat calculation +* NKAI: Fixed recruitment of new heroes * VCAI: Added workaround to avoid freeze on attempting to reach unreachable location +* VCAI: Fixed spellcasting by Archangels ### RANDOM MAP GENERATOR: * Fixed placement of roads inside rock in underground @@ -26,6 +28,7 @@ ### STABILITY: * When starting client without H3 data game will now show message instead of silently crashing * When starting invalid map in campaign, game will now show message instead of silently crashing +* Blocked loading of saves made with different set of mods to prevent crashes * Fixed crash on starting game with outdated mods * Fixed crash on attempt to sacrifice all your artifacts in Altar of Sacrifice * Fixed crash on leveling up after winning battle as defender @@ -39,6 +42,8 @@ * Fixed crash on using haptic feedback on some Android systems * Fixed crash on right-clicking flags area in RMG setup mode * Fixed crash on opening Blacksmith window and Build Structure dialogs in some localizations +* Fixed possible crash on displaying animated main menu +* Fixed crash on recruiting hero in town located on the border of map # 1.2.1 -> 1.3.0 From ec0a51bd5c37903d2be883fe4be61b280c1dcf1a Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 17 Aug 2023 12:25:02 +0300 Subject: [PATCH 14/14] Fixed border scrolling when game window is maximized --- ChangeLog.md | 1 + client/adventureMap/AdventureMapInterface.cpp | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index b3c964975..31a09a49b 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -10,6 +10,7 @@ * Background battlefield obstacles will now appear below creatures * it is now possible to load save game located inside mod * Added option to configure reserved screen area in Launcher on iOS +* Fixed border scrolling when game window is maximized ### AI PLAYER: * BattleAI: Improved performance of AI spell selection diff --git a/client/adventureMap/AdventureMapInterface.cpp b/client/adventureMap/AdventureMapInterface.cpp index 45ea69a63..7a8d4b3ea 100644 --- a/client/adventureMap/AdventureMapInterface.cpp +++ b/client/adventureMap/AdventureMapInterface.cpp @@ -169,10 +169,10 @@ void AdventureMapInterface::tick(uint32_t msPassed) void AdventureMapInterface::handleMapScrollingUpdate(uint32_t timePassed) { /// Width of window border, in pixels, that triggers map scrolling - static constexpr uint32_t borderScrollWidth = 15; + static constexpr int32_t borderScrollWidth = 15; - uint32_t scrollSpeedPixels = settings["adventure"]["scrollSpeedPixels"].Float(); - uint32_t scrollDistance = scrollSpeedPixels * timePassed / 1000; + int32_t scrollSpeedPixels = settings["adventure"]["scrollSpeedPixels"].Float(); + int32_t scrollDistance = scrollSpeedPixels * timePassed / 1000; Point cursorPosition = GH.getCursorPosition(); Point scrollDirection;