From f2d92296ebdc1d7a14665264093d1d2b69eb834b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?--=3DFurtiF=E2=84=A2=3D--?= Date: Wed, 13 Dec 2017 08:37:54 +0100 Subject: [PATCH] New api * Update module ferox for new heads * move logic submodule to folder * move logic submodule to folder pt2 * base submodule feroxrev * update, common requests have changed: - checkChallenge is not more - downloadSettings is always there now * release RB * new api support * set versionint * base modules --- .gitmodules | 4 - FeroxRev | 2 +- KillSwitch.txt | 6 +- PoGo.NecroBot.CLI/PoGo.NecroBot.CLI.csproj | 8 +- PoGo.NecroBot.CLI/packages.config | 4 +- PoGo.NecroBot.Logic | 1 - PoGo.NecroBot.Logic/Caching.cs | 21 + .../Captcha/Anti-Captcha/AntiCaptchaClient.cs | 65 + .../Anti-Captcha/AnticaptchaApiWrapper.cs | 511 ++ .../Captcha/Anti-Captcha/AnticaptchaResult.cs | 105 + .../Captcha/Anti-Captcha/AnticaptchaTask.cs | 48 + .../Captcha/Anti-Captcha/HttpHelper.cs | 45 + PoGo.NecroBot.Logic/Captcha/CaptchaManager.cs | 274 ++ .../Captcha/CaptchaSolutionClient.cs | 76 + .../Captcha/TwoCaptchaClient.cs | 116 + .../Common/PokemonGradeHelper.cs | 310 ++ PoGo.NecroBot.Logic/Common/Translations.cs | 1941 ++++++++ PoGo.NecroBot.Logic/Common/UITranslation.cs | 418 ++ PoGo.NecroBot.Logic/Config/log4net.config | 73 + .../Config/log4net.unix.config | 73 + PoGo.NecroBot.Logic/DataDumper/Dumper.cs | 167 + PoGo.NecroBot.Logic/DataDumper/IDumper.cs | 12 + PoGo.NecroBot.Logic/Event/BotSwitchedEvent.cs | 14 + .../Event/DisplayHighestsPokemonEvent.cs | 19 + PoGo.NecroBot.Logic/Event/EggHatchedEvent.cs | 22 + .../Event/EggIncubatorStatusEvent.cs | 12 + PoGo.NecroBot.Logic/Event/EggsListEvent.cs | 16 + PoGo.NecroBot.Logic/Event/EncounteredEvent.cs | 40 + PoGo.NecroBot.Logic/Event/ErrorEvent.cs | 12 + PoGo.NecroBot.Logic/Event/EventDispatcher.cs | 20 + PoGo.NecroBot.Logic/Event/EventUsedPotion.cs | 10 + PoGo.NecroBot.Logic/Event/EventUsedRevive.cs | 10 + PoGo.NecroBot.Logic/Event/EvolveCountEvent.cs | 7 + PoGo.NecroBot.Logic/Event/FortFailedEvent.cs | 10 + PoGo.NecroBot.Logic/Event/FortTargetEvent.cs | 12 + PoGo.NecroBot.Logic/Event/FortUsedEvent.cs | 24 + PoGo.NecroBot.Logic/Event/GetRouteEvent.cs | 10 + .../Event/Gym/GymBattleStarted.cs | 7 + .../Event/Gym/GymDeployEvent.cs | 11 + .../Event/Gym/GymDetailInfoEvent.cs | 11 + .../Event/Gym/GymErrorUnset.cs | 7 + .../Event/Gym/GymEventMessages.cs | 10 + PoGo.NecroBot.Logic/Event/Gym/GymListEvent.cs | 10 + .../Event/Gym/GymTeamJoinEvent.cs | 12 + .../Event/Gym/GymWalkToTargetEvent.cs | 10 + .../Event/HumanWalkSnipeEvent.cs | 53 + .../Event/HumanWalkingEvent.cs | 8 + PoGo.NecroBot.Logic/Event/IEvent.cs | 6 + PoGo.NecroBot.Logic/Event/InfoEvent.cs | 10 + .../Event/Inventory/FavoriteEvent.cs | 18 + .../Inventory/InventoryItemUpdateEvent.cs | 9 + .../Inventory/InventoryRefreshedEvent.cs | 30 + .../Event/Inventory/UpgradeFinishEvent.cs | 10 + .../Event/InventoryListEvent.cs | 10 + .../Event/ItemRecycledEvent.cs | 14 + PoGo.NecroBot.Logic/Event/KillSwitchEvent.cs | 13 + PoGo.NecroBot.Logic/Event/LogEvent.cs | 8 + .../Event/LootPokestopEvent.cs | 9 + .../Event/NicknameUpdateEvent.cs | 7 + PoGo.NecroBot.Logic/Event/NoPokeballEvent.cs | 14 + PoGo.NecroBot.Logic/Event/NoticeEvent.cs | 12 + .../Event/Player/BuddyUpdateEvent.cs | 17 + .../Event/Player/LoggedEvent.cs | 9 + .../Event/Player/LoginEvent.cs | 16 + .../Event/Player/TargetLocationEvent.cs | 16 + .../Event/PokeStopListEvent.cs | 26 + .../Event/PokemonCaptureEvent.cs | 46 + .../Event/PokemonEvolveEvent.cs | 27 + .../Event/PokemonLevelUpEvent.cs | 18 + PoGo.NecroBot.Logic/Event/PokemonListEvent.cs | 15 + .../Event/PokemonsEncounterEvent.cs | 10 + .../Event/PokestopLimitUpdate.cs | 27 + PoGo.NecroBot.Logic/Event/ProfileEvent.cs | 17 + .../Event/RenamePokemonEvent.cs | 16 + .../Event/Snipe/AllBotSnipeEvent.cs | 11 + .../Event/Snipe/AutoSnipePokemonAddedEvent.cs | 12 + .../Event/Snipe/SnipePokemonStarted.cs | 14 + .../Event/Snipe/SnipePokemonUpdateEvent.cs | 29 + PoGo.NecroBot.Logic/Event/SnipeEvent.cs | 29 + PoGo.NecroBot.Logic/Event/SnipeModeEvent.cs | 7 + .../Event/SnipePokemonFoundEvent.cs | 14 + PoGo.NecroBot.Logic/Event/SnipeScanEvent.cs | 17 + .../Event/TransferPokemonEvent.cs | 22 + .../Event/UI/StatusBarEvent.cs | 12 + PoGo.NecroBot.Logic/Event/UpdateEvent.cs | 12 + .../Event/UpdatePositionEvent.cs | 9 + .../Event/UpgradePokemonEvent.cs | 24 + PoGo.NecroBot.Logic/Event/UseBerryEvent.cs | 10 + PoGo.NecroBot.Logic/Event/UseLuckyEggEvent.cs | 7 + PoGo.NecroBot.Logic/Event/WarnEvent.cs | 17 + .../ActiveSwitchAccountManualException.cs | 15 + .../ActiveSwitchByPokemonException.cs | 17 + .../Exceptions/ActiveSwitchByRuleException.cs | 33 + PoGo.NecroBot.Logic/Extensions.cs | 30 + .../Forms/AutoUpdateForm.Designer.cs | 216 + PoGo.NecroBot.Logic/Forms/AutoUpdateForm.cs | 131 + PoGo.NecroBot.Logic/Forms/AutoUpdateForm.resx | 120 + .../Forms/CaptchaSolveForm.Designer.cs | 48 + PoGo.NecroBot.Logic/Forms/CaptchaSolveForm.cs | 31 + .../Forms/CaptchaSolveForm.resx | 120 + PoGo.NecroBot.Logic/Forms/EXComboBox.cs | 165 + PoGo.NecroBot.Logic/Forms/EXListView.cs | 1072 ++++ PoGo.NecroBot.Logic/Forms/Extentions.cs | 60 + .../Forms/InitialTutorialForm.Designer.cs | 346 ++ .../Forms/InitialTutorialForm.cs | 283 ++ .../Forms/InitialTutorialForm.resx | 980 ++++ .../Forms/SelectAccountForm.Designer.cs | 147 + .../Forms/SelectAccountForm.cs | 89 + .../Forms/SelectAccountForm.resx | 123 + .../Configuration/ILogicSettings.cs | 264 + PoGo.NecroBot.Logic/Inventory.cs | 1021 ++++ PoGo.NecroBot.Logic/Localization/Localizer.cs | 27 + PoGo.NecroBot.Logic/Logging/APILogListener.cs | 62 + PoGo.NecroBot.Logic/Logging/FileLogger.cs | 106 + PoGo.NecroBot.Logic/Logging/ILogger.cs | 29 + PoGo.NecroBot.Logic/Logging/Logger.cs | 253 + PoGo.NecroBot.Logic/Logging/LoggingStrings.cs | 121 + .../Logging/WebSocketLogger.cs | 88 + PoGo.NecroBot.Logic/MKS.txt | 3 + PoGo.NecroBot.Logic/Model/Account.cs | 96 + .../Model/AccountConfigContext.cs | 43 + PoGo.NecroBot.Logic/Model/BotAccount.cs | 59 + PoGo.NecroBot.Logic/Model/BotActions.cs | 19 + .../Model/ElevationConfigContext.cs | 32 + .../Model/ElevationLocation.cs | 88 + PoGo.NecroBot.Logic/Model/GeoLocation.cs | 166 + .../Model/GeoLocationConfigContext.cs | 32 + .../GoogleObjects/Address_Components.cs | 9 + .../Model/Google/GoogleObjects/Bounds.cs | 8 + .../GoogleObjects/DirectionsResponse.cs | 9 + .../GoogleObjects/DistanceMatrixResponse.cs | 10 + .../Model/Google/GoogleObjects/Element.cs | 10 + .../Model/Google/GoogleObjects/Geo.cs | 8 + .../Google/GoogleObjects/GeocodedWaypoints.cs | 9 + .../Google/GoogleObjects/GeocodingResponse.cs | 8 + .../Model/Google/GoogleObjects/Geometry.cs | 9 + .../Model/Google/GoogleObjects/GoogleType.cs | 8 + .../Model/Google/GoogleObjects/Leg.cs | 14 + .../Model/Google/GoogleObjects/Polyline.cs | 68 + .../Model/Google/GoogleObjects/Result.cs | 11 + .../Model/Google/GoogleObjects/Route.cs | 13 + .../Model/Google/GoogleObjects/Row.cs | 7 + .../Model/Google/GoogleObjects/Step.cs | 14 + .../Model/Google/GoogleObjects/ValueText.cs | 8 + .../Model/Google/GoogleResult.cs | 45 + .../Model/Google/GoogleWalk.cs | 64 + PoGo.NecroBot.Logic/Model/IGeoLocation.cs | 71 + .../Model/Mapzen/MapzenWalk.cs | 139 + PoGo.NecroBot.Logic/Model/PokemonGrade.cs | 14 + PoGo.NecroBot.Logic/Model/PokemonTimestamp.cs | 12 + .../Model/PokestopTimestamp.cs | 12 + .../Model/Settings/APIConfig.cs | 41 + .../Model/Settings/AuthConfig.cs | 77 + .../Model/Settings/AuthSettings.cs | 608 +++ .../Model/Settings/BaseConfig.cs | 50 + .../Model/Settings/BerryUseFilter.cs | 87 + .../Model/Settings/CaptchaConfig.cs | 89 + .../Model/Settings/CatchConfig.cs | 42 + .../Model/Settings/CatchFilter.cs | 75 + .../Model/Settings/CatchSettings.cs | 86 + .../Model/Settings/ClientSettings.cs | 304 ++ .../Model/Settings/ConsoleConfig.cs | 40 + .../Model/Settings/CustomCatchConfig.cs | 83 + .../Model/Settings/DataSharingConfig.cs | 55 + .../Model/Settings/DeviceConfig.cs | 155 + .../Model/Settings/EvolveConfig.cs | 139 + .../Model/Settings/EvolveFilter.cs | 138 + .../Model/Settings/ExcelConfigAttribute.cs | 14 + .../Model/Settings/FilterUtil.cs | 49 + .../Model/Settings/GUIConfig.cs | 30 + .../Model/Settings/GlobalSettings.cs | 1161 +++++ .../Model/Settings/GoogleWalkConfig.cs | 65 + .../Model/Settings/GpxConfig.cs | 26 + .../Model/Settings/GymConfig.cs | 201 + .../Model/Settings/HumanWalkSnipeConfig.cs | 146 + .../Model/Settings/HumanWalkSnipeFilter.cs | 135 + .../Model/Settings/HumanlikeDelays.cs | 53 + .../Model/Settings/IPokemonFilter.cs | 11 + .../Model/Settings/ItemRecycleConfig.cs | 103 + .../Model/Settings/ItemRecycleFilter.cs | 72 + .../Model/Settings/LevelUpConfig.cs | 54 + .../Model/Settings/Location.cs | 27 + .../Model/Settings/LocationConfig.cs | 82 + .../Model/Settings/LogicSettings.cs | 304 ++ .../Model/Settings/MapzenWalkConfig.cs | 42 + .../Model/Settings/MultipleBotConfig.cs | 201 + .../Model/Settings/NotificationConfig.cs | 42 + .../Model/Settings/PlayerConfig.cs | 84 + .../Model/Settings/PokeStopConfig.cs | 31 + .../Model/Settings/PokemonConfig.cs | 419 ++ .../Model/Settings/ProxyConfig.cs | 42 + .../Model/Settings/SnipeConfig.cs | 133 + .../Model/Settings/SnipeFIlter.cs | 157 + .../Model/Settings/SoftBanConfig.cs | 23 + .../Model/Settings/TelegramConfig.cs | 33 + .../Model/Settings/TransferConfig.cs | 54 + .../Model/Settings/TransferFilter.cs | 169 + .../Model/Settings/UpdateConfig.cs | 30 + .../Model/Settings/UpgradeFilter.cs | 110 + .../Model/Settings/WebsocketsConfig.cs | 25 + .../Model/Settings/YoursWalkConfig.cs | 38 + PoGo.NecroBot.Logic/Model/Yours/YoursWalk.cs | 56 + PoGo.NecroBot.Logic/MultiAccountManager.cs | 447 ++ PoGo.NecroBot.Logic/Navigation.cs | 249 + .../PoGo.NecroBot.Logic.csproj | 719 +++ .../PoGoUtils/PokemonEvolutionHelper.cs | 28 + PoGo.NecroBot.Logic/PoGoUtils/PokemonInfo.cs | 71 + .../Properties/AssemblyInfo.cs | 45 + .../Properties/Resources.Designer.cs | 83 + PoGo.NecroBot.Logic/Properties/Resources.resx | 127 + PoGo.NecroBot.Logic/Resources/ajax-loader.gif | Bin 0 -> 847 bytes PoGo.NecroBot.Logic/Resources/msvc.cer | 36 + .../Service/AnalyticsService.cs | 85 + .../Service/BotDataSocketClient.cs | 610 +++ .../Service/ConsoleEventListener.cs | 827 ++++ .../Service/Elevation/BaseElevationService.cs | 34 + .../Service/Elevation/ElevationService.cs | 105 + .../Elevation/GoogleElevationService.cs | 79 + .../Service/Elevation/IElevationService.cs | 10 + .../Elevation/MapzenElevationService.cs | 58 + .../Elevation/RandomElevationService.cs | 30 + .../Service/GoogleDirectionsService.cs | 151 + .../Service/MapzenDirectionsService.cs | 53 + .../Service/PushNotificationListener.cs | 44 + .../Service/SniperEventListener.cs | 56 + .../TelegramCommand/AccountsCommand.cs | 54 + .../Service/TelegramCommand/AllCommand.cs | 68 + .../TelegramCommand/CommandLocation.cs | 54 + .../Service/TelegramCommand/CommandMessage.cs | 53 + .../Service/TelegramCommand/ExitCommand.cs | 30 + .../Service/TelegramCommand/HelpCommand.cs | 48 + .../Service/TelegramCommand/ICommand.cs | 21 + .../TelegramCommand/ICommandGenerify.cs | 11 + .../Service/TelegramCommand/ItemsCommand.cs | 61 + .../Service/TelegramCommand/LocCommand.cs | 33 + .../Service/TelegramCommand/LogsCommand.cs | 57 + .../Service/TelegramCommand/PokedexCommand.cs | 80 + .../Service/TelegramCommand/ProfileCommand.cs | 54 + .../Service/TelegramCommand/RecycleCommand.cs | 33 + .../Service/TelegramCommand/RestartCommand.cs | 38 + .../Service/TelegramCommand/SnipeCommand.cs | 43 + .../Service/TelegramCommand/StatusCommand.cs | 81 + .../Service/TelegramCommand/TopCommand.cs | 84 + .../Service/TelegramService.cs | 155 + PoGo.NecroBot.Logic/Service/TelegramUtils.cs | 68 + .../ActionCommands/DropItemHandler.cs | 27 + .../ActionCommands/EvolvePokemonHandler.cs | 26 + .../ActionCommands/FastpokemapHandler.cs | 29 + .../ActionCommands/FavoritePokemonHandler.cs | 26 + .../HumanSnipePriorityPokemonHandler.cs | 26 + .../HumanSnipeRemovePokemonHandler.cs | 26 + .../ActionCommands/LevelUpPokemonHandler.cs | 26 + .../ActionCommands/SetConfigHandler.cs | 69 + .../ActionCommands/SetMoveToTargetHandler.cs | 22 + .../SetTrainerNicknameHandler.cs | 61 + .../ActionCommands/TransferPokemonHandler.cs | 34 + .../ActionCommands/UpgradePokemonHandler.cs | 26 + .../WebSocketHandler/EncodingHelper.cs | 43 + .../GetCommands/Events/ConfigResponce.cs | 16 + .../GetCommands/Events/EggListResponce.cs | 16 + .../GetCommands/Events/ItemListResponce.cs | 16 + .../GetCommands/Events/PokemonListResponce.cs | 22 + .../GetCommands/Events/SnipeListResponce.cs | 16 + .../Events/TrainerProfileResponce.cs | 16 + .../GetCommands/Events/WebResponce.cs | 9 + .../GetCommands/GetConfigHandler.cs | 26 + .../GetCommands/GetEggListHandler.cs | 26 + .../GetCommands/GetItemsListHandler.cs | 26 + .../GetCommands/GetPokemonListHandler.cs | 26 + .../GetCommands/GetPokemonSettingsHandler.cs | 26 + .../GetCommands/GetPokemonSnipeListHandler.cs | 23 + .../GetCommands/GetTrainerProfileHandler.cs | 26 + .../GetCommands/Helpers/ConfigWeb.cs | 10 + .../GetCommands/Helpers/EggListWeb.cs | 8 + .../GetCommands/Helpers/PokemonListWeb.cs | 26 + .../GetCommands/Helpers/TrainerProfileWeb.cs | 21 + .../GetCommands/Tasks/GetConfigTask.cs | 46 + .../GetCommands/Tasks/GetEggListTask.cs | 43 + .../GetCommands/Tasks/GetItemListTask.cs | 25 + .../GetCommands/Tasks/GetPokemonListTask.cs | 30 + .../Tasks/GetPokemonSettingsTask.cs | 30 + .../Tasks/GetPokemonSnipeListTask.cs | 22 + .../Tasks/GetTrainerProfileTask.cs | 30 + .../IWebSocketRequestHandler.cs | 16 + .../WebSocketHandler/IWebSocketResponce.cs | 9 + .../WebSocketHandler/WebSocketEventManager.cs | 59 + .../Service/WebSocketInterface.cs | 233 + .../Service/YoursDirectionsService.cs | 55 + PoGo.NecroBot.Logic/State/BlockableScope.cs | 29 + PoGo.NecroBot.Logic/State/BotSwitcherState.cs | 52 + PoGo.NecroBot.Logic/State/CatchState.cs | 174 + PoGo.NecroBot.Logic/State/CheckTosState.cs | 354 ++ PoGo.NecroBot.Logic/State/FarmState.cs | 56 + PoGo.NecroBot.Logic/State/GymTeamState.cs | 219 + PoGo.NecroBot.Logic/State/IState.cs | 14 + PoGo.NecroBot.Logic/State/IdleState.cs | 60 + PoGo.NecroBot.Logic/State/InfoState.cs | 21 + PoGo.NecroBot.Logic/State/LoadSaveState.cs | 156 + PoGo.NecroBot.Logic/State/LoginState.cs | 379 ++ PoGo.NecroBot.Logic/State/Session.cs | 290 ++ PoGo.NecroBot.Logic/State/SessionStats.cs | 217 + PoGo.NecroBot.Logic/State/StateMachine.cs | 417 ++ .../State/VersionCheckState.cs | 252 + PoGo.NecroBot.Logic/StatisticsAggregator.cs | 182 + .../Strategies/Walk/BaseWalkStrategy.cs | 249 + .../Strategies/Walk/FlyStrategy.cs | 86 + .../Strategies/Walk/GoogleStrategy.cs | 100 + .../Walk/HumanPathWalkingStrategy.cs | 100 + .../Strategies/Walk/HumanStrategy.cs | 99 + .../Strategies/Walk/IWalkStrategy.cs | 23 + .../Walk/MapzenNavigationStrategy.cs | 92 + .../Walk/YoursNavigationStrategy.cs | 80 + .../Tasks/BaseTransferPokemonTask.cs | 88 + .../Tasks/CatchIncensePokemonsTask.cs | 119 + .../Tasks/CatchLurePokemonsTask.cs | 162 + .../Tasks/CatchNearbyPokemonsTask.cs | 242 + PoGo.NecroBot.Logic/Tasks/CatchPokemonTask.cs | 841 ++++ .../Tasks/DisplayPokemonStatsTask.cs | 174 + PoGo.NecroBot.Logic/Tasks/EggsListTask.cs | 48 + .../Tasks/EvolvePokemonTask.cs | 283 ++ .../Tasks/EvolveSpecificPokemonTask.cs | 77 + .../Tasks/FarmPokestopsGPXTask.cs | 120 + .../Tasks/FarmPokestopsTask.cs | 63 + .../Tasks/FavoritePokemonTask.cs | 91 + .../Tasks/GetGymBadgeDetailsTask.cs | 14 + PoGo.NecroBot.Logic/Tasks/GetPokeDexCount.cs | 34 + .../Tasks/GymFeedPokemonTask.cs | 66 + .../Tasks/HumanRandomActionTask.cs | 85 + .../Tasks/HumanWalkSnipeTask.FastPokemap.cs | 267 + .../Tasks/HumanWalkSnipeTask.PokeWatchers.cs | 84 + .../Tasks/HumanWalkSnipeTask.PokeZZ.cs | 104 + .../Tasks/HumanWalkSnipeTask.Pokecrew.cs | 72 + .../Tasks/HumanWalkSnipeTask.Pokeradar.cs | 71 + .../Tasks/HumanWalkSnipeTask.Pokesnipers.cs | 79 + .../Tasks/HumanWalkSnipeTask.Skiplagged.cs | 109 + .../Tasks/HumanWalkSnipeTask.cs | 705 +++ .../Tasks/InventoryListTask.cs | 27 + .../Tasks/LevelUpPokemonTask.cs | 66 + .../Tasks/LevelUpSpecificPokemonTask.cs | 52 + .../Tasks/MSniperServiceTask.cs | 1015 ++++ PoGo.NecroBot.Logic/Tasks/PokemonListTask.cs | 49 + PoGo.NecroBot.Logic/Tasks/RecycleItemsTask.cs | 456 ++ .../Tasks/RenamePokemonTask.cs | 104 + .../Tasks/RenameSinglePokemonTask.cs | 46 + .../Tasks/SelectBuddyPokemonTask.cs | 73 + .../Tasks/SetMoveToTargetTask.cs | 100 + PoGo.NecroBot.Logic/Tasks/SnipePokemonTask.cs | 507 ++ .../Tasks/TransferDuplicatePokemonTask.cs | 72 + .../Tasks/TransferPokemonTask.cs | 76 + .../Tasks/TransferWeakPokemonTask.cs | 47 + .../Tasks/UpgradeSinglePokemonTask.cs | 117 + PoGo.NecroBot.Logic/Tasks/UseFortItemsTask.cs | 42 + PoGo.NecroBot.Logic/Tasks/UseGymBattleTask.cs | 1581 ++++++ .../Tasks/UseIncenseConstantlyTask.cs | 50 + PoGo.NecroBot.Logic/Tasks/UseIncenseTask.cs | 36 + .../Tasks/UseIncubatorsTask.cs | 229 + .../Tasks/UseItemMoveRerollTask.cs | 47 + .../Tasks/UseLuckyEggConstantlyTask.cs | 31 + PoGo.NecroBot.Logic/Tasks/UseLuckyEggTask.cs | 13 + .../Tasks/UseNearbyPokestopsTask.cs | 649 +++ PoGo.NecroBot.Logic/Tasks/UseRareCandyTask.cs | 42 + PoGo.NecroBot.Logic/TinyIoC.cs | 4365 +++++++++++++++++ PoGo.NecroBot.Logic/Utils/AsyncLock.cs | 37 + PoGo.NecroBot.Logic/Utils/DelayingUtils.cs | 49 + PoGo.NecroBot.Logic/Utils/EggWalker.cs | 41 + PoGo.NecroBot.Logic/Utils/ErrorHandler.cs | 35 + .../Utils/ExcelConfigHelper.cs | 820 ++++ PoGo.NecroBot.Logic/Utils/GPXReader.cs | 733 +++ PoGo.NecroBot.Logic/Utils/JitterUtils.cs | 19 + PoGo.NecroBot.Logic/Utils/LocationUtils.cs | 151 + PoGo.NecroBot.Logic/Utils/NecroWebClient.cs | 15 + .../Utils/PushNotificationClient.cs | 156 + .../Utils/RouteOptimizeUtil.cs | 120 + PoGo.NecroBot.Logic/Utils/Statistics.cs | 290 ++ PoGo.NecroBot.Logic/Utils/StringUtils.cs | 69 + PoGo.NecroBot.Logic/Utils/WebUtils.cs | 50 + PoGo.NecroBot.Logic/config.xlsm | Bin 0 -> 51509 bytes PoGo.NecroBot.Logic/packages.config | 106 + .../PoGo.Necrobot.Window.csproj | 8 +- PoGo.NecroBot.Window/packages.config | 4 +- README.md | 2 +- RocketBot2/Properties/AssemblyInfo.cs | 6 +- RocketBot2/RocketBot2.csproj | 8 +- RocketBot2/packages.config | 4 +- 384 files changed, 45489 insertions(+), 31 deletions(-) delete mode 160000 PoGo.NecroBot.Logic create mode 100644 PoGo.NecroBot.Logic/Caching.cs create mode 100644 PoGo.NecroBot.Logic/Captcha/Anti-Captcha/AntiCaptchaClient.cs create mode 100644 PoGo.NecroBot.Logic/Captcha/Anti-Captcha/AnticaptchaApiWrapper.cs create mode 100644 PoGo.NecroBot.Logic/Captcha/Anti-Captcha/AnticaptchaResult.cs create mode 100644 PoGo.NecroBot.Logic/Captcha/Anti-Captcha/AnticaptchaTask.cs create mode 100644 PoGo.NecroBot.Logic/Captcha/Anti-Captcha/HttpHelper.cs create mode 100644 PoGo.NecroBot.Logic/Captcha/CaptchaManager.cs create mode 100644 PoGo.NecroBot.Logic/Captcha/CaptchaSolutionClient.cs create mode 100644 PoGo.NecroBot.Logic/Captcha/TwoCaptchaClient.cs create mode 100644 PoGo.NecroBot.Logic/Common/PokemonGradeHelper.cs create mode 100644 PoGo.NecroBot.Logic/Common/Translations.cs create mode 100644 PoGo.NecroBot.Logic/Common/UITranslation.cs create mode 100644 PoGo.NecroBot.Logic/Config/log4net.config create mode 100644 PoGo.NecroBot.Logic/Config/log4net.unix.config create mode 100644 PoGo.NecroBot.Logic/DataDumper/Dumper.cs create mode 100644 PoGo.NecroBot.Logic/DataDumper/IDumper.cs create mode 100644 PoGo.NecroBot.Logic/Event/BotSwitchedEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/DisplayHighestsPokemonEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/EggHatchedEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/EggIncubatorStatusEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/EggsListEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/EncounteredEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/ErrorEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/EventDispatcher.cs create mode 100644 PoGo.NecroBot.Logic/Event/EventUsedPotion.cs create mode 100644 PoGo.NecroBot.Logic/Event/EventUsedRevive.cs create mode 100644 PoGo.NecroBot.Logic/Event/EvolveCountEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/FortFailedEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/FortTargetEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/FortUsedEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/GetRouteEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/Gym/GymBattleStarted.cs create mode 100644 PoGo.NecroBot.Logic/Event/Gym/GymDeployEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/Gym/GymDetailInfoEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/Gym/GymErrorUnset.cs create mode 100644 PoGo.NecroBot.Logic/Event/Gym/GymEventMessages.cs create mode 100644 PoGo.NecroBot.Logic/Event/Gym/GymListEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/Gym/GymTeamJoinEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/Gym/GymWalkToTargetEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/HumanWalkSnipeEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/HumanWalkingEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/IEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/InfoEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/Inventory/FavoriteEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/Inventory/InventoryItemUpdateEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/Inventory/InventoryRefreshedEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/Inventory/UpgradeFinishEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/InventoryListEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/ItemRecycledEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/KillSwitchEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/LogEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/LootPokestopEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/NicknameUpdateEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/NoPokeballEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/NoticeEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/Player/BuddyUpdateEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/Player/LoggedEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/Player/LoginEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/Player/TargetLocationEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/PokeStopListEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/PokemonCaptureEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/PokemonEvolveEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/PokemonLevelUpEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/PokemonListEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/PokemonsEncounterEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/PokestopLimitUpdate.cs create mode 100644 PoGo.NecroBot.Logic/Event/ProfileEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/RenamePokemonEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/Snipe/AllBotSnipeEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/Snipe/AutoSnipePokemonAddedEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/Snipe/SnipePokemonStarted.cs create mode 100644 PoGo.NecroBot.Logic/Event/Snipe/SnipePokemonUpdateEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/SnipeEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/SnipeModeEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/SnipePokemonFoundEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/SnipeScanEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/TransferPokemonEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/UI/StatusBarEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/UpdateEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/UpdatePositionEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/UpgradePokemonEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/UseBerryEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/UseLuckyEggEvent.cs create mode 100644 PoGo.NecroBot.Logic/Event/WarnEvent.cs create mode 100644 PoGo.NecroBot.Logic/Exceptions/ActiveSwitchAccountManualException.cs create mode 100644 PoGo.NecroBot.Logic/Exceptions/ActiveSwitchByPokemonException.cs create mode 100644 PoGo.NecroBot.Logic/Exceptions/ActiveSwitchByRuleException.cs create mode 100644 PoGo.NecroBot.Logic/Extensions.cs create mode 100644 PoGo.NecroBot.Logic/Forms/AutoUpdateForm.Designer.cs create mode 100644 PoGo.NecroBot.Logic/Forms/AutoUpdateForm.cs create mode 100644 PoGo.NecroBot.Logic/Forms/AutoUpdateForm.resx create mode 100644 PoGo.NecroBot.Logic/Forms/CaptchaSolveForm.Designer.cs create mode 100644 PoGo.NecroBot.Logic/Forms/CaptchaSolveForm.cs create mode 100644 PoGo.NecroBot.Logic/Forms/CaptchaSolveForm.resx create mode 100644 PoGo.NecroBot.Logic/Forms/EXComboBox.cs create mode 100644 PoGo.NecroBot.Logic/Forms/EXListView.cs create mode 100644 PoGo.NecroBot.Logic/Forms/Extentions.cs create mode 100644 PoGo.NecroBot.Logic/Forms/InitialTutorialForm.Designer.cs create mode 100644 PoGo.NecroBot.Logic/Forms/InitialTutorialForm.cs create mode 100644 PoGo.NecroBot.Logic/Forms/InitialTutorialForm.resx create mode 100644 PoGo.NecroBot.Logic/Forms/SelectAccountForm.Designer.cs create mode 100644 PoGo.NecroBot.Logic/Forms/SelectAccountForm.cs create mode 100644 PoGo.NecroBot.Logic/Forms/SelectAccountForm.resx create mode 100644 PoGo.NecroBot.Logic/Interfaces/Configuration/ILogicSettings.cs create mode 100644 PoGo.NecroBot.Logic/Inventory.cs create mode 100644 PoGo.NecroBot.Logic/Localization/Localizer.cs create mode 100644 PoGo.NecroBot.Logic/Logging/APILogListener.cs create mode 100644 PoGo.NecroBot.Logic/Logging/FileLogger.cs create mode 100644 PoGo.NecroBot.Logic/Logging/ILogger.cs create mode 100644 PoGo.NecroBot.Logic/Logging/Logger.cs create mode 100644 PoGo.NecroBot.Logic/Logging/LoggingStrings.cs create mode 100644 PoGo.NecroBot.Logic/Logging/WebSocketLogger.cs create mode 100644 PoGo.NecroBot.Logic/MKS.txt create mode 100644 PoGo.NecroBot.Logic/Model/Account.cs create mode 100644 PoGo.NecroBot.Logic/Model/AccountConfigContext.cs create mode 100644 PoGo.NecroBot.Logic/Model/BotAccount.cs create mode 100644 PoGo.NecroBot.Logic/Model/BotActions.cs create mode 100644 PoGo.NecroBot.Logic/Model/ElevationConfigContext.cs create mode 100644 PoGo.NecroBot.Logic/Model/ElevationLocation.cs create mode 100644 PoGo.NecroBot.Logic/Model/GeoLocation.cs create mode 100644 PoGo.NecroBot.Logic/Model/GeoLocationConfigContext.cs create mode 100644 PoGo.NecroBot.Logic/Model/Google/GoogleObjects/Address_Components.cs create mode 100644 PoGo.NecroBot.Logic/Model/Google/GoogleObjects/Bounds.cs create mode 100644 PoGo.NecroBot.Logic/Model/Google/GoogleObjects/DirectionsResponse.cs create mode 100644 PoGo.NecroBot.Logic/Model/Google/GoogleObjects/DistanceMatrixResponse.cs create mode 100644 PoGo.NecroBot.Logic/Model/Google/GoogleObjects/Element.cs create mode 100644 PoGo.NecroBot.Logic/Model/Google/GoogleObjects/Geo.cs create mode 100644 PoGo.NecroBot.Logic/Model/Google/GoogleObjects/GeocodedWaypoints.cs create mode 100644 PoGo.NecroBot.Logic/Model/Google/GoogleObjects/GeocodingResponse.cs create mode 100644 PoGo.NecroBot.Logic/Model/Google/GoogleObjects/Geometry.cs create mode 100644 PoGo.NecroBot.Logic/Model/Google/GoogleObjects/GoogleType.cs create mode 100644 PoGo.NecroBot.Logic/Model/Google/GoogleObjects/Leg.cs create mode 100644 PoGo.NecroBot.Logic/Model/Google/GoogleObjects/Polyline.cs create mode 100644 PoGo.NecroBot.Logic/Model/Google/GoogleObjects/Result.cs create mode 100644 PoGo.NecroBot.Logic/Model/Google/GoogleObjects/Route.cs create mode 100644 PoGo.NecroBot.Logic/Model/Google/GoogleObjects/Row.cs create mode 100644 PoGo.NecroBot.Logic/Model/Google/GoogleObjects/Step.cs create mode 100644 PoGo.NecroBot.Logic/Model/Google/GoogleObjects/ValueText.cs create mode 100644 PoGo.NecroBot.Logic/Model/Google/GoogleResult.cs create mode 100644 PoGo.NecroBot.Logic/Model/Google/GoogleWalk.cs create mode 100644 PoGo.NecroBot.Logic/Model/IGeoLocation.cs create mode 100644 PoGo.NecroBot.Logic/Model/Mapzen/MapzenWalk.cs create mode 100644 PoGo.NecroBot.Logic/Model/PokemonGrade.cs create mode 100644 PoGo.NecroBot.Logic/Model/PokemonTimestamp.cs create mode 100644 PoGo.NecroBot.Logic/Model/PokestopTimestamp.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/APIConfig.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/AuthConfig.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/AuthSettings.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/BaseConfig.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/BerryUseFilter.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/CaptchaConfig.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/CatchConfig.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/CatchFilter.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/CatchSettings.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/ClientSettings.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/ConsoleConfig.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/CustomCatchConfig.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/DataSharingConfig.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/DeviceConfig.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/EvolveConfig.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/EvolveFilter.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/ExcelConfigAttribute.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/FilterUtil.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/GUIConfig.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/GlobalSettings.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/GoogleWalkConfig.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/GpxConfig.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/GymConfig.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/HumanWalkSnipeConfig.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/HumanWalkSnipeFilter.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/HumanlikeDelays.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/IPokemonFilter.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/ItemRecycleConfig.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/ItemRecycleFilter.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/LevelUpConfig.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/Location.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/LocationConfig.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/LogicSettings.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/MapzenWalkConfig.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/MultipleBotConfig.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/NotificationConfig.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/PlayerConfig.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/PokeStopConfig.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/PokemonConfig.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/ProxyConfig.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/SnipeConfig.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/SnipeFIlter.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/SoftBanConfig.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/TelegramConfig.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/TransferConfig.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/TransferFilter.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/UpdateConfig.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/UpgradeFilter.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/WebsocketsConfig.cs create mode 100644 PoGo.NecroBot.Logic/Model/Settings/YoursWalkConfig.cs create mode 100644 PoGo.NecroBot.Logic/Model/Yours/YoursWalk.cs create mode 100644 PoGo.NecroBot.Logic/MultiAccountManager.cs create mode 100644 PoGo.NecroBot.Logic/Navigation.cs create mode 100644 PoGo.NecroBot.Logic/PoGo.NecroBot.Logic.csproj create mode 100644 PoGo.NecroBot.Logic/PoGoUtils/PokemonEvolutionHelper.cs create mode 100644 PoGo.NecroBot.Logic/PoGoUtils/PokemonInfo.cs create mode 100644 PoGo.NecroBot.Logic/Properties/AssemblyInfo.cs create mode 100644 PoGo.NecroBot.Logic/Properties/Resources.Designer.cs create mode 100644 PoGo.NecroBot.Logic/Properties/Resources.resx create mode 100644 PoGo.NecroBot.Logic/Resources/ajax-loader.gif create mode 100644 PoGo.NecroBot.Logic/Resources/msvc.cer create mode 100644 PoGo.NecroBot.Logic/Service/AnalyticsService.cs create mode 100644 PoGo.NecroBot.Logic/Service/BotDataSocketClient.cs create mode 100644 PoGo.NecroBot.Logic/Service/ConsoleEventListener.cs create mode 100644 PoGo.NecroBot.Logic/Service/Elevation/BaseElevationService.cs create mode 100644 PoGo.NecroBot.Logic/Service/Elevation/ElevationService.cs create mode 100644 PoGo.NecroBot.Logic/Service/Elevation/GoogleElevationService.cs create mode 100644 PoGo.NecroBot.Logic/Service/Elevation/IElevationService.cs create mode 100644 PoGo.NecroBot.Logic/Service/Elevation/MapzenElevationService.cs create mode 100644 PoGo.NecroBot.Logic/Service/Elevation/RandomElevationService.cs create mode 100644 PoGo.NecroBot.Logic/Service/GoogleDirectionsService.cs create mode 100644 PoGo.NecroBot.Logic/Service/MapzenDirectionsService.cs create mode 100644 PoGo.NecroBot.Logic/Service/PushNotificationListener.cs create mode 100644 PoGo.NecroBot.Logic/Service/SniperEventListener.cs create mode 100644 PoGo.NecroBot.Logic/Service/TelegramCommand/AccountsCommand.cs create mode 100644 PoGo.NecroBot.Logic/Service/TelegramCommand/AllCommand.cs create mode 100644 PoGo.NecroBot.Logic/Service/TelegramCommand/CommandLocation.cs create mode 100644 PoGo.NecroBot.Logic/Service/TelegramCommand/CommandMessage.cs create mode 100644 PoGo.NecroBot.Logic/Service/TelegramCommand/ExitCommand.cs create mode 100644 PoGo.NecroBot.Logic/Service/TelegramCommand/HelpCommand.cs create mode 100644 PoGo.NecroBot.Logic/Service/TelegramCommand/ICommand.cs create mode 100644 PoGo.NecroBot.Logic/Service/TelegramCommand/ICommandGenerify.cs create mode 100644 PoGo.NecroBot.Logic/Service/TelegramCommand/ItemsCommand.cs create mode 100644 PoGo.NecroBot.Logic/Service/TelegramCommand/LocCommand.cs create mode 100644 PoGo.NecroBot.Logic/Service/TelegramCommand/LogsCommand.cs create mode 100644 PoGo.NecroBot.Logic/Service/TelegramCommand/PokedexCommand.cs create mode 100644 PoGo.NecroBot.Logic/Service/TelegramCommand/ProfileCommand.cs create mode 100644 PoGo.NecroBot.Logic/Service/TelegramCommand/RecycleCommand.cs create mode 100644 PoGo.NecroBot.Logic/Service/TelegramCommand/RestartCommand.cs create mode 100644 PoGo.NecroBot.Logic/Service/TelegramCommand/SnipeCommand.cs create mode 100644 PoGo.NecroBot.Logic/Service/TelegramCommand/StatusCommand.cs create mode 100644 PoGo.NecroBot.Logic/Service/TelegramCommand/TopCommand.cs create mode 100644 PoGo.NecroBot.Logic/Service/TelegramService.cs create mode 100644 PoGo.NecroBot.Logic/Service/TelegramUtils.cs create mode 100644 PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/DropItemHandler.cs create mode 100644 PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/EvolvePokemonHandler.cs create mode 100644 PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/FastpokemapHandler.cs create mode 100644 PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/FavoritePokemonHandler.cs create mode 100644 PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/HumanSnipePriorityPokemonHandler.cs create mode 100644 PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/HumanSnipeRemovePokemonHandler.cs create mode 100644 PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/LevelUpPokemonHandler.cs create mode 100644 PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/SetConfigHandler.cs create mode 100644 PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/SetMoveToTargetHandler.cs create mode 100644 PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/SetTrainerNicknameHandler.cs create mode 100644 PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/TransferPokemonHandler.cs create mode 100644 PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/UpgradePokemonHandler.cs create mode 100644 PoGo.NecroBot.Logic/Service/WebSocketHandler/EncodingHelper.cs create mode 100644 PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Events/ConfigResponce.cs create mode 100644 PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Events/EggListResponce.cs create mode 100644 PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Events/ItemListResponce.cs create mode 100644 PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Events/PokemonListResponce.cs create mode 100644 PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Events/SnipeListResponce.cs create mode 100644 PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Events/TrainerProfileResponce.cs create mode 100644 PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Events/WebResponce.cs create mode 100644 PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/GetConfigHandler.cs create mode 100644 PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/GetEggListHandler.cs create mode 100644 PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/GetItemsListHandler.cs create mode 100644 PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/GetPokemonListHandler.cs create mode 100644 PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/GetPokemonSettingsHandler.cs create mode 100644 PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/GetPokemonSnipeListHandler.cs create mode 100644 PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/GetTrainerProfileHandler.cs create mode 100644 PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Helpers/ConfigWeb.cs create mode 100644 PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Helpers/EggListWeb.cs create mode 100644 PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Helpers/PokemonListWeb.cs create mode 100644 PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Helpers/TrainerProfileWeb.cs create mode 100644 PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Tasks/GetConfigTask.cs create mode 100644 PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Tasks/GetEggListTask.cs create mode 100644 PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Tasks/GetItemListTask.cs create mode 100644 PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Tasks/GetPokemonListTask.cs create mode 100644 PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Tasks/GetPokemonSettingsTask.cs create mode 100644 PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Tasks/GetPokemonSnipeListTask.cs create mode 100644 PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Tasks/GetTrainerProfileTask.cs create mode 100644 PoGo.NecroBot.Logic/Service/WebSocketHandler/IWebSocketRequestHandler.cs create mode 100644 PoGo.NecroBot.Logic/Service/WebSocketHandler/IWebSocketResponce.cs create mode 100644 PoGo.NecroBot.Logic/Service/WebSocketHandler/WebSocketEventManager.cs create mode 100644 PoGo.NecroBot.Logic/Service/WebSocketInterface.cs create mode 100644 PoGo.NecroBot.Logic/Service/YoursDirectionsService.cs create mode 100644 PoGo.NecroBot.Logic/State/BlockableScope.cs create mode 100644 PoGo.NecroBot.Logic/State/BotSwitcherState.cs create mode 100644 PoGo.NecroBot.Logic/State/CatchState.cs create mode 100644 PoGo.NecroBot.Logic/State/CheckTosState.cs create mode 100644 PoGo.NecroBot.Logic/State/FarmState.cs create mode 100644 PoGo.NecroBot.Logic/State/GymTeamState.cs create mode 100644 PoGo.NecroBot.Logic/State/IState.cs create mode 100644 PoGo.NecroBot.Logic/State/IdleState.cs create mode 100644 PoGo.NecroBot.Logic/State/InfoState.cs create mode 100644 PoGo.NecroBot.Logic/State/LoadSaveState.cs create mode 100644 PoGo.NecroBot.Logic/State/LoginState.cs create mode 100644 PoGo.NecroBot.Logic/State/Session.cs create mode 100644 PoGo.NecroBot.Logic/State/SessionStats.cs create mode 100644 PoGo.NecroBot.Logic/State/StateMachine.cs create mode 100644 PoGo.NecroBot.Logic/State/VersionCheckState.cs create mode 100644 PoGo.NecroBot.Logic/StatisticsAggregator.cs create mode 100644 PoGo.NecroBot.Logic/Strategies/Walk/BaseWalkStrategy.cs create mode 100644 PoGo.NecroBot.Logic/Strategies/Walk/FlyStrategy.cs create mode 100644 PoGo.NecroBot.Logic/Strategies/Walk/GoogleStrategy.cs create mode 100644 PoGo.NecroBot.Logic/Strategies/Walk/HumanPathWalkingStrategy.cs create mode 100644 PoGo.NecroBot.Logic/Strategies/Walk/HumanStrategy.cs create mode 100644 PoGo.NecroBot.Logic/Strategies/Walk/IWalkStrategy.cs create mode 100644 PoGo.NecroBot.Logic/Strategies/Walk/MapzenNavigationStrategy.cs create mode 100644 PoGo.NecroBot.Logic/Strategies/Walk/YoursNavigationStrategy.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/BaseTransferPokemonTask.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/CatchIncensePokemonsTask.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/CatchLurePokemonsTask.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/CatchNearbyPokemonsTask.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/CatchPokemonTask.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/DisplayPokemonStatsTask.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/EggsListTask.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/EvolvePokemonTask.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/EvolveSpecificPokemonTask.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/FarmPokestopsGPXTask.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/FarmPokestopsTask.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/FavoritePokemonTask.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/GetGymBadgeDetailsTask.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/GetPokeDexCount.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/GymFeedPokemonTask.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/HumanRandomActionTask.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/HumanWalkSnipeTask.FastPokemap.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/HumanWalkSnipeTask.PokeWatchers.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/HumanWalkSnipeTask.PokeZZ.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/HumanWalkSnipeTask.Pokecrew.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/HumanWalkSnipeTask.Pokeradar.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/HumanWalkSnipeTask.Pokesnipers.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/HumanWalkSnipeTask.Skiplagged.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/HumanWalkSnipeTask.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/InventoryListTask.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/LevelUpPokemonTask.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/LevelUpSpecificPokemonTask.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/MSniperServiceTask.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/PokemonListTask.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/RecycleItemsTask.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/RenamePokemonTask.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/RenameSinglePokemonTask.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/SelectBuddyPokemonTask.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/SetMoveToTargetTask.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/SnipePokemonTask.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/TransferDuplicatePokemonTask.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/TransferPokemonTask.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/TransferWeakPokemonTask.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/UpgradeSinglePokemonTask.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/UseFortItemsTask.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/UseGymBattleTask.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/UseIncenseConstantlyTask.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/UseIncenseTask.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/UseIncubatorsTask.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/UseItemMoveRerollTask.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/UseLuckyEggConstantlyTask.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/UseLuckyEggTask.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/UseNearbyPokestopsTask.cs create mode 100644 PoGo.NecroBot.Logic/Tasks/UseRareCandyTask.cs create mode 100644 PoGo.NecroBot.Logic/TinyIoC.cs create mode 100644 PoGo.NecroBot.Logic/Utils/AsyncLock.cs create mode 100644 PoGo.NecroBot.Logic/Utils/DelayingUtils.cs create mode 100644 PoGo.NecroBot.Logic/Utils/EggWalker.cs create mode 100644 PoGo.NecroBot.Logic/Utils/ErrorHandler.cs create mode 100644 PoGo.NecroBot.Logic/Utils/ExcelConfigHelper.cs create mode 100644 PoGo.NecroBot.Logic/Utils/GPXReader.cs create mode 100644 PoGo.NecroBot.Logic/Utils/JitterUtils.cs create mode 100644 PoGo.NecroBot.Logic/Utils/LocationUtils.cs create mode 100644 PoGo.NecroBot.Logic/Utils/NecroWebClient.cs create mode 100644 PoGo.NecroBot.Logic/Utils/PushNotificationClient.cs create mode 100644 PoGo.NecroBot.Logic/Utils/RouteOptimizeUtil.cs create mode 100644 PoGo.NecroBot.Logic/Utils/Statistics.cs create mode 100644 PoGo.NecroBot.Logic/Utils/StringUtils.cs create mode 100644 PoGo.NecroBot.Logic/Utils/WebUtils.cs create mode 100644 PoGo.NecroBot.Logic/config.xlsm create mode 100644 PoGo.NecroBot.Logic/packages.config diff --git a/.gitmodules b/.gitmodules index f6eba3b06..45bf6077f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,10 +2,6 @@ path = FeroxRev url = https://github.com/Necrobot-Private/PokemonGo.RocketAPI.git branch = master -[submodule "PoGo.NecroBot.Logic"] - path = PoGo.NecroBot.Logic - url = https://github.com/Necrobot-Private/PoGo.NecroBot.Logic.git - branch = master [submodule "PokeEase"] path = PokeEase url = https://github.com/Necrobot-Private/PokeEase.git diff --git a/FeroxRev b/FeroxRev index d3ea91c81..494d294cc 160000 --- a/FeroxRev +++ b/FeroxRev @@ -1 +1 @@ -Subproject commit d3ea91c81193800f003deaf8ef5fcbc93fd63fee +Subproject commit 494d294ccd1fa8c1a8db6c649ce148a14674ed56 diff --git a/KillSwitch.txt b/KillSwitch.txt index f9e1a1385..52faf4c88 100644 --- a/KillSwitch.txt +++ b/KillSwitch.txt @@ -1,12 +1,12 @@ BotStatus=ENABLED; //============================================// - POKEMON GO 0.83.2 + POKEMON GO 0.85.1 //============================================// - Necrobot is now compatible with 0.83.2 API. + Necrobot is now compatible with 0.85.1 API. //============================================// - Updated: 10/29/2017 + Updated: 12/08/2017 For support/questions: diff --git a/PoGo.NecroBot.CLI/PoGo.NecroBot.CLI.csproj b/PoGo.NecroBot.CLI/PoGo.NecroBot.CLI.csproj index bfe305343..d8df8abbf 100644 --- a/PoGo.NecroBot.CLI/PoGo.NecroBot.CLI.csproj +++ b/PoGo.NecroBot.CLI/PoGo.NecroBot.CLI.csproj @@ -123,8 +123,8 @@ $(SolutionDir)\packages\Costura.Fody.1.6.2\lib\dotnet\Costura.dll False - - $(SolutionDir)\packages\Google.Protobuf.3.4.1\lib\net45\Google.Protobuf.dll + + $(SolutionDir)\packages\Google.Protobuf.3.5.0\lib\net45\Google.Protobuf.dll $(SolutionDir)\packages\log4net.2.0.8\lib\net45-full\log4net.dll @@ -159,8 +159,8 @@ $(SolutionDir)\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll - - $(SolutionDir)\packages\POGOProtos.Core.2.19.1\lib\net45\POGOProtos.Core.dll + + $(SolutionDir)\packages\POGOProtos.Core.2.20.0\lib\net45\POGOProtos.Core.dll $(SolutionDir)\packages\Remotion.Linq.2.1.2\lib\net45\Remotion.Linq.dll diff --git a/PoGo.NecroBot.CLI/packages.config b/PoGo.NecroBot.CLI/packages.config index 4c266ed79..f700d09df 100644 --- a/PoGo.NecroBot.CLI/packages.config +++ b/PoGo.NecroBot.CLI/packages.config @@ -4,7 +4,7 @@ - + @@ -19,7 +19,7 @@ - + diff --git a/PoGo.NecroBot.Logic b/PoGo.NecroBot.Logic deleted file mode 160000 index 5326e9cdc..000000000 --- a/PoGo.NecroBot.Logic +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5326e9cdc525badf68ac19805ca378d5793fc380 diff --git a/PoGo.NecroBot.Logic/Caching.cs b/PoGo.NecroBot.Logic/Caching.cs new file mode 100644 index 000000000..2fea3edae --- /dev/null +++ b/PoGo.NecroBot.Logic/Caching.cs @@ -0,0 +1,21 @@ +using PoGo.NecroBot.Logic.State; +using System; +using System.Runtime.Caching; +using TinyIoC; + +namespace PoGo.NecroBot.Logic +{ + public class Caching + { + private const int CACH_DURATION = 15; //minutes + private static MemoryCache encounteredPokemons = new MemoryCache("encounteredPokemons"); + + public static void AddEncounteredPokemon(ulong encounterId) + { + ISession session = TinyIoCContainer.Current.Resolve(); + string uniqueKey = $"{session.Settings.Username}-{encounterId}"; + encounteredPokemons.Add(uniqueKey, encounterId, DateTime.Now.AddMinutes(CACH_DURATION)); + + } + } +} diff --git a/PoGo.NecroBot.Logic/Captcha/Anti-Captcha/AntiCaptchaClient.cs b/PoGo.NecroBot.Logic/Captcha/Anti-Captcha/AntiCaptchaClient.cs new file mode 100644 index 000000000..a51297931 --- /dev/null +++ b/PoGo.NecroBot.Logic/Captcha/Anti-Captcha/AntiCaptchaClient.cs @@ -0,0 +1,65 @@ +using PoGo.NecroBot.Logic.Logging; +using System.Threading.Tasks; + +namespace PoGo.NecroBot.Logic.Captcha.Anti_Captcha +{ + public class AntiCaptchaClient + { + + private const string Host = "api.anti-captcha.com"; + //private string ClientKey = "xxxxxxx"; + private const string ProxyHost = "xx.xx.xx.xx"; + private const int ProxyPort = 8282; + private const string ProxyLogin = ""; + private const string ProxyPassword = ""; + + private const string UserAgent = + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36" + ; + + + public static async Task SolveCaptcha(string captchaURL, string apiKey, string googleSiteKey, + string proxyHost, int proxyPort, string proxyAccount = "", string proxyPassword = "") + { + var task1 = AnticaptchaApiWrapper.CreateNoCaptchaTaskProxyless( + Host, + apiKey, + captchaURL, //target website address + googleSiteKey, + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36" + ); + + return await ProcessTask(task1, apiKey).ConfigureAwait(false); + } + + private static async Task ProcessTask(AnticaptchaTask task, string apikey) + { + AnticaptchaResult response; + + do + { + response = AnticaptchaApiWrapper.GetTaskResult(Host, apikey, task); + + if (response.GetStatus().Equals(AnticaptchaResult.Status.ready)) + { + break; + } + + await Task.Delay(3000).ConfigureAwait(false); + } while (response != null && response.GetStatus().Equals(AnticaptchaResult.Status.processing)); + + if (response == null || response.GetSolution() == null) + { + Logger.Write("Unknown error occurred...", LogLevel.Error); + //Console.WriteLine("Response dump:"); + //Console.WriteLine(response); + } + else + { + //Console.WriteLine("The answer is '" + response.GetSolution() + "'"); + } + + return response.GetSolution(); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Captcha/Anti-Captcha/AnticaptchaApiWrapper.cs b/PoGo.NecroBot.Logic/Captcha/Anti-Captcha/AnticaptchaApiWrapper.cs new file mode 100644 index 000000000..a0a6db213 --- /dev/null +++ b/PoGo.NecroBot.Logic/Captcha/Anti-Captcha/AnticaptchaApiWrapper.cs @@ -0,0 +1,511 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Globalization; +using System.IO; +using System.Net.NetworkInformation; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace PoGo.NecroBot.Logic.Captcha.Anti_Captcha +{ + public class AnticaptchaApiWrapper + { + public enum ProxyType + { + http + } + + public static Dictionary HostsChecked = new Dictionary(); + + public static bool CheckHost(string host) + { + if (!HostsChecked.ContainsKey(host)) + { + HostsChecked[host] = Ping(host); + } + + return HostsChecked[host]; + } + + private static dynamic JsonPostRequest(string host, string methodName, string postData) + { + return HttpHelper.Post(new Uri("http://" + host + "/" + methodName), postData); + } + + public static bool Ping(string host) + { + try + { + new Ping().Send(host, 1000); + + return true; + } + catch + { + return false; + } + } + + public static AnticaptchaTask CreateNoCaptchaTaskProxyless(string host, string clientKey, string websiteUrl, + string websiteKey, string userAgent, string websiteSToken = "") + { + return CreateNoCaptchaTask( + "NoCaptchaTaskProxyless", + host, + clientKey, + websiteUrl, + websiteKey, + null, + null, + null, + null, + null, + userAgent, + websiteSToken + ); + } + + public static AnticaptchaTask CreateNoCaptchaTask(string host, string clientKey, string websiteUrl, + string websiteKey, ProxyType proxyType, string proxyAddress, int proxyPort, string proxyLogin, + string proxyPassword, string userAgent, string websiteSToken = "") + { + return CreateNoCaptchaTask( + "NoCaptchaTask", + host, + clientKey, + websiteUrl, + websiteKey, + proxyType, + proxyAddress, + proxyPort, + proxyLogin, + proxyPassword, + userAgent, + websiteSToken + ); + } + + private static AnticaptchaTask CreateNoCaptchaTask( + string type, + string host, + string clientKey, + string websiteUrl, + string websiteKey, + ProxyType? proxyType, + string proxyAddress, + int? proxyPort, + string proxyLogin, + string proxyPassword, + string userAgent, + string websiteSToken = "" + ) + { + if (proxyType != null && (string.IsNullOrEmpty(proxyAddress) || !CheckHost(proxyAddress))) + { + throw new Exception("Proxy address is incorrect!"); + } + + if (proxyType != null && (proxyPort < 1 || proxyPort > 65535)) + { + throw new Exception("Proxy port is incorrect!"); + } + + if (string.IsNullOrEmpty(userAgent)) + { + throw new Exception("User-Agent is incorrect!"); + } + + if (string.IsNullOrEmpty(websiteUrl) || !websiteUrl.Contains(".") || !websiteUrl.Contains("/") || + !websiteUrl.Contains("http")) + { + throw new Exception("Website URL is incorrect!"); + } + + if (string.IsNullOrEmpty(websiteKey)) + { + throw new Exception("Recaptcha Website Key is incorrect!"); + } + + var jObj = new JObject(); + + jObj["softId"] = 2; + jObj["clientKey"] = clientKey; + jObj["task"] = new JObject(); + jObj["task"]["type"] = type; + jObj["task"]["websiteURL"] = websiteUrl; + jObj["task"]["websiteKey"] = websiteKey; + jObj["task"]["websiteSToken"] = websiteSToken; + jObj["task"]["userAgent"] = userAgent; + + if (proxyType != null) + { + jObj["task"]["proxyType"] = proxyType.ToString(); + jObj["task"]["proxyAddress"] = proxyAddress; + jObj["task"]["proxyPort"] = proxyPort; + jObj["task"]["proxyLogin"] = proxyLogin; + jObj["task"]["proxyPassword"] = proxyPassword; + } + + try + { + var resultJson = JsonPostRequest(host, "createTask", + JsonConvert.SerializeObject(jObj, Formatting.Indented)); + + int? taskId = null; + int? errorId = null; + string errorCode = null; + string errorDescription = null; + + try + { + taskId = int.Parse(resultJson.taskId.ToString()); + } + catch + { + // ignored + } + + try + { + errorId = int.Parse(resultJson.errorId.ToString()); + } + catch + { + // ignored + } + + try + { + errorCode = resultJson.errorCode.ToString(); + } + catch + { + // ignored + } + + try + { + errorDescription = resultJson.errorDescription.ToString(); + } + catch + { + // ignored + } + + return new AnticaptchaTask( + taskId, + errorId, + errorCode, + errorDescription + ); + } + catch + { + // ignored + } + + return null; + } + + private static string ImagePathToBase64String(string path) + { + try + { + using (var image = Image.FromFile(path)) + { + using (var m = new MemoryStream()) + { + image.Save(m, image.RawFormat); + var imageBytes = m.ToArray(); + + // Convert byte[] to Base64 String + var base64String = Convert.ToBase64String(imageBytes); + + return base64String; + } + } + } + catch + { + return null; + } + } + + /// + /// Creates "image to text" task and sends it to anti-captcha.com + /// + /// + /// + /// You can set just a path to your image in the filesystem or a base64-encoded image + /// + /// + /// + /// + /// + /// + /// AnticaptchaTask with taskId or error information + public static AnticaptchaTask CreateImageToTextTask(string host, string clientKey, + string pathToImageOrBase64Body, + bool? phrase = null, bool? _case = null, int? numeric = null, + bool? math = null, int? minLength = null, int? maxLength = null) + { + try + { + if (File.Exists(pathToImageOrBase64Body)) + { + pathToImageOrBase64Body = ImagePathToBase64String(pathToImageOrBase64Body); + } + } + catch + { + // ignored + } + + var jObj = new JObject(); + + jObj["softId"] = 2; + jObj["clientKey"] = clientKey; + jObj["task"] = new JObject(); + jObj["task"]["type"] = "ImageToTextTask"; + jObj["task"]["body"] = pathToImageOrBase64Body.Replace("\r", "").Replace("\n", "").Trim(); + + if (phrase != null) + { + jObj["task"]["phrase"] = phrase; + } + + if (_case != null) + { + jObj["task"]["case"] = _case; + } + + if (numeric != null) + { + jObj["task"]["numeric"] = numeric; + } + + if (math != null) + { + jObj["task"]["math"] = math; + } + + if (minLength != null) + { + jObj["task"]["minLength"] = minLength; + } + + if (maxLength != null) + { + jObj["task"]["maxLength"] = maxLength; + } + + try + { + var resultJson = JsonPostRequest( + host, + "createTask", + JsonConvert.SerializeObject(jObj, Formatting.Indented) + ); + + int? taskId = null; + int? errorId = null; + string errorCode = null; + string errorDescription = null; + + try + { + taskId = int.Parse(resultJson.taskId.ToString()); + } + catch + { + // ignored + } + + try + { + errorId = int.Parse(resultJson.errorId.ToString()); + } + catch + { + // ignored + } + + try + { + errorCode = resultJson.errorCode.ToString(); + } + catch + { + // ignored + } + + try + { + errorDescription = resultJson.errorDescription.ToString(); + } + catch + { + // ignored + } + + return new AnticaptchaTask( + taskId, + errorId, + errorCode, + errorDescription + ); + } + catch + { + // ignored + } + + return null; + } + + public static AnticaptchaResult GetTaskResult(string host, string clientKey, AnticaptchaTask task) + { + var jObj = new JObject(); + + jObj["clientKey"] = clientKey; + jObj["taskId"] = task.GetTaskId(); + + try + { + dynamic resultJson = JsonPostRequest(host, "getTaskResult", + JsonConvert.SerializeObject(jObj, Formatting.Indented)); + + var status = AnticaptchaResult.Status.unknown; + + try + { + status = resultJson.status.ToString().Equals("ready") + ? AnticaptchaResult.Status.ready + : AnticaptchaResult.Status.processing; + } + catch + { + // ignored + } + + string solution; + int? errorId = null; + string errorCode = null; + string errorDescription = null; + double? cost = null; + string ip = null; + int? createTime = null; + int? endTime = null; + int? solveCount = null; + + try + { + solution = resultJson.solution.gRecaptchaResponse.ToString(); + } + catch + { + try + { + solution = resultJson.solution.text.ToString(); + } + catch + { + solution = null; + } + } + + try + { + errorId = resultJson.errorId; + } + catch + { + // ignored + } + + try + { + errorCode = resultJson.errorCode; + } + catch + { + // ignored + } + + try + { + errorDescription = resultJson.errorDescription; + } + catch + { + // ignored + } + + try + { + cost = double.Parse(resultJson.cost.ToString().Replace(',', '.'), CultureInfo.InvariantCulture); + } + catch + { + // ignored + } + + try + { + createTime = resultJson.createTime; + } + catch + { + // ignored + } + + try + { + endTime = resultJson.endTime; + } + catch + { + // ignored + } + + try + { + solveCount = resultJson.solveCount; + } + catch + { + // ignored + } + + try + { + ip = resultJson.ip; + } + catch + { + // ignored + } + + return new AnticaptchaResult( + status, + solution, + errorId, + errorCode, + errorDescription, + cost, + ip, + createTime, + endTime, + solveCount + ); + } + catch + { + // ignored + } + + return null; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Captcha/Anti-Captcha/AnticaptchaResult.cs b/PoGo.NecroBot.Logic/Captcha/Anti-Captcha/AnticaptchaResult.cs new file mode 100644 index 000000000..48818ebf1 --- /dev/null +++ b/PoGo.NecroBot.Logic/Captcha/Anti-Captcha/AnticaptchaResult.cs @@ -0,0 +1,105 @@ +namespace PoGo.NecroBot.Logic.Captcha.Anti_Captcha +{ + public class AnticaptchaResult + { + public enum Status + { + ready, + unknown, + processing + } + + private readonly double? _cost; + private readonly int? _createTime; + private readonly int? _endTime; + private readonly string _errorCode; + private readonly string _errorDescription; + private readonly int? _errorId; + private readonly string _ip; + private readonly string _solution; + private readonly int? _solveCount; + private readonly Status? _status; + + public AnticaptchaResult(Status? status, string solution, int? errorId, string errorCode, + string errorDescription, + double? cost, string ip, int? createTime, int? endTime, int? solveCount) + { + _errorId = errorId; + _errorCode = errorCode; + _errorDescription = errorDescription; + _status = status; + _solution = solution; + _cost = cost; + _ip = ip; + _createTime = createTime; + _endTime = endTime; + _solveCount = solveCount; + } + + public override string ToString() + { + return "AnticaptchaResult{" + + "errorId=" + _errorId + + ", errorCode='" + _errorCode + '\'' + + ", errorDescription='" + _errorDescription + '\'' + + ", status=" + _status + + ", solution='" + _solution + '\'' + + ", cost=" + _cost + + ", ip='" + _ip + '\'' + + ", createTime=" + _createTime + + ", endTime=" + _endTime + + ", solveCount=" + _solveCount + + '}'; + } + + public int? GetErrorId() + { + return _errorId; + } + + public string GetErrorCode() + { + return _errorCode; + } + + public string GetErrorDescription() + { + return _errorDescription; + } + + public Status? GetStatus() + { + return _status; + } + + public string GetSolution() + { + return _solution; + } + + public double? GetCost() + { + return _cost; + } + + public string GetIp() + { + return _ip; + } + + public int? GetCreateTime() + { + return _createTime; + } + + public int? GetEndTime() + { + return _endTime; + } + + public int? GetSolveCount() + { + return _solveCount; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Captcha/Anti-Captcha/AnticaptchaTask.cs b/PoGo.NecroBot.Logic/Captcha/Anti-Captcha/AnticaptchaTask.cs new file mode 100644 index 000000000..14f7b40a4 --- /dev/null +++ b/PoGo.NecroBot.Logic/Captcha/Anti-Captcha/AnticaptchaTask.cs @@ -0,0 +1,48 @@ +namespace PoGo.NecroBot.Logic.Captcha.Anti_Captcha +{ + public class AnticaptchaTask + { + private readonly string _errorCode; + private readonly string _errorDescription; + private readonly int? _errorId; + private readonly int? _taskId; + + public AnticaptchaTask(int? taskId, int? errorId, string errorCode, string errorDescription) + { + _errorId = errorId; + _taskId = taskId; + _errorCode = errorCode; + _errorDescription = errorDescription; + } + + public string GetErrorCode() + { + return _errorCode; + } + + public string GetErrorDescription() + { + return _errorDescription; + } + + public int? GetTaskId() + { + return _taskId; + } + + public int? GetErrorId() + { + return _errorId; + } + + public override string ToString() + { + return "AnticaptchaTask{" + + "errorId=" + _errorId + + ", taskId=" + _taskId + + ", errorCode='" + _errorCode + '\'' + + ", errorDescription='" + _errorDescription + '\'' + + '}'; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Captcha/Anti-Captcha/HttpHelper.cs b/PoGo.NecroBot.Logic/Captcha/Anti-Captcha/HttpHelper.cs new file mode 100644 index 000000000..079ea54aa --- /dev/null +++ b/PoGo.NecroBot.Logic/Captcha/Anti-Captcha/HttpHelper.cs @@ -0,0 +1,45 @@ +using System; +using System.IO; +using System.Net; +using System.Text; +using Newtonsoft.Json; + +namespace PoGo.NecroBot.Logic.Captcha.Anti_Captcha +{ + public class HttpHelper + { + public static dynamic Post(Uri url, string post) + { + dynamic result = null; + var postBody = Encoding.UTF8.GetBytes(post); + var request = (HttpWebRequest) WebRequest.Create(url); + + request.Method = "POST"; + request.ContentType = "application/json"; + request.ContentLength = postBody.Length; + + try + { + using (var stream = request.GetRequestStream()) + { + stream.Write(postBody, 0, postBody.Length); + stream.Close(); + } + + using (var response = (HttpWebResponse) request.GetResponse()) + { + var strreader = new StreamReader(response.GetResponseStream(), Encoding.UTF8); + result = JsonConvert.DeserializeObject(strreader.ReadToEnd()); + + response.Close(); + } + } + catch + { + return false; + } + + return result; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Captcha/CaptchaManager.cs b/PoGo.NecroBot.Logic/Captcha/CaptchaManager.cs new file mode 100644 index 000000000..b4cc58478 --- /dev/null +++ b/PoGo.NecroBot.Logic/Captcha/CaptchaManager.cs @@ -0,0 +1,274 @@ +using System; +using System.IO; +using System.Media; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using OpenQA.Selenium; +using OpenQA.Selenium.Chrome; +using OpenQA.Selenium.Support.UI; +using PoGo.NecroBot.Logic.Captcha.Anti_Captcha; +using PoGo.NecroBot.Logic.Forms; +using PoGo.NecroBot.Logic.Logging; +using PoGo.NecroBot.Logic.State; +using LogLevel = PoGo.NecroBot.Logic.Logging.LogLevel; + +namespace PoGo.NecroBot.Logic.Captcha +{ + public class CaptchaManager + { + const string POKEMON_GO_GOOGLE_KEY = "6LeeTScTAAAAADqvhqVMhPpr_vB9D364Ia-1dSgK"; + + public static async Task SolveCaptcha(ISession session, string captchaUrl) + { + string captchaResponse = ""; + var cfg = session.LogicSettings.CaptchaConfig; + bool resolved = false; + bool needGetNewCaptcha = false; + int retry = cfg.AutoCaptchaRetries; + + while (retry-- > 0 && !resolved) + { + //Use captcha solution to resolve captcha + if (cfg.EnableCaptchaSolutions) + { + if (needGetNewCaptcha) + { + captchaUrl = await GetNewCaptchaURL(session).ConfigureAwait(false); + } + + Logger.Write("Auto resolving captcha by using captcha solution service, please wait.........."); + CaptchaSolutionClient client = new CaptchaSolutionClient(cfg.CaptchaSolutionAPIKey, + cfg.CaptchaSolutionsSecretKey, cfg.AutoCaptchaTimeout); + captchaResponse = await client.ResolveCaptcha(POKEMON_GO_GOOGLE_KEY, captchaUrl).ConfigureAwait(false); + needGetNewCaptcha = true; + if (!string.IsNullOrEmpty(captchaResponse)) + { + resolved = await Resolve(session, captchaResponse).ConfigureAwait(false); + } + } + + + + //use 2 captcha + if (!resolved && session.LogicSettings.CaptchaConfig.Enable2Captcha && + !string.IsNullOrEmpty(session.LogicSettings.CaptchaConfig.TwoCaptchaAPIKey)) + { + if (needGetNewCaptcha) + { + captchaUrl = await GetNewCaptchaURL(session).ConfigureAwait(false); + } + if (string.IsNullOrEmpty(captchaUrl)) return true; + + Logger.Write("Auto resolving captcha by using 2Captcha service"); + captchaResponse = await GetCaptchaResposeBy2Captcha(session, captchaUrl).ConfigureAwait(false); + needGetNewCaptcha = true; + if (!string.IsNullOrEmpty(captchaResponse)) + { + resolved = await Resolve(session, captchaResponse).ConfigureAwait(false); + } + } + + if (!resolved && session.LogicSettings.CaptchaConfig.EnableAntiCaptcha && !string.IsNullOrEmpty(session.LogicSettings.CaptchaConfig.AntiCaptchaAPIKey)) + { + if (needGetNewCaptcha) + { + captchaUrl = await GetNewCaptchaURL(session).ConfigureAwait(false); + } + if (string.IsNullOrEmpty(captchaUrl)) return true; + + Logger.Write("Auto resolving captcha by using anti captcha service"); + captchaResponse = await GetCaptchaResposeByAntiCaptcha(session, captchaUrl).ConfigureAwait(false); + needGetNewCaptcha = true; + if (!string.IsNullOrEmpty(captchaResponse)) + { + resolved = await Resolve(session, captchaResponse).ConfigureAwait(false); + } + + } + + } + + //captchaRespose = ""; + if (!resolved) + { + if (needGetNewCaptcha) + { + captchaUrl = await GetNewCaptchaURL(session).ConfigureAwait(false); + } + + if (session.LogicSettings.CaptchaConfig.PlaySoundOnCaptcha) + { + SystemSounds.Asterisk.Play(); + } + + captchaResponse = GetCaptchaResposeManually(session, captchaUrl); + //captchaResponse = await GetCaptchaTokenWithInternalForm(captchaUrl).ConfigureAwait(false); + + if (!string.IsNullOrEmpty(captchaResponse)) + { + resolved = await Resolve(session, captchaResponse).ConfigureAwait(false); + } + } + + return resolved; + } + + + //NOT WORKING SINCE WEB BROWSER CONTROL IS IE7, doesn't work with captcha page + + private static async Task GetCaptchaTokenWithInternalForm(string captchaUrl) + { + string response = ""; + var t = new Thread(() => + { + CaptchaSolveForm captcha = new CaptchaSolveForm(captchaUrl); + if (captcha.ShowDialog() == DialogResult.OK) + { + response = "Aaa"; + } + + //captcha.TopMost = true; + }); + t.SetApartmentState(ApartmentState.STA); + t.Start(); + int count = 120; + while (true && count > 0) + { + count--; + //Thread.Sleep(1000); + await Task.Delay(1000).ConfigureAwait(false); + } + + return response; + } + + private static async Task GetNewCaptchaURL(ISession session) + { + var res = await session.Client.Player.CheckChallenge().ConfigureAwait(false); + if (res.ShowChallenge) + { + return res.ChallengeUrl; + } + return string.Empty; + } + + private static async Task Resolve(ISession session, string captchaRespose) + { + if (string.IsNullOrEmpty(captchaRespose)) return false; + try + { + var verifyChallengeResponse = await session.Client.Player.VerifyChallenge(captchaRespose).ConfigureAwait(false); + if (!verifyChallengeResponse.Success) + { + Logger.Write($"(CAPTCHA) Failed to resolve captcha, try resolved captcha by official app. "); + return false; + } + Logger.Write($"(CAPTCHA) Great!!! Captcha has been by passed", color: ConsoleColor.Green); + return verifyChallengeResponse.Success; + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + } + return false; + } + + private static async Task GetCaptchaResposeByAntiCaptcha(ISession session, string captchaUrl) + { + bool solved = false; + string result = null; + + { + result = await AntiCaptchaClient.SolveCaptcha(captchaUrl, + session.LogicSettings.CaptchaConfig.AntiCaptchaAPIKey, + POKEMON_GO_GOOGLE_KEY, + session.LogicSettings.CaptchaConfig.ProxyHost, + session.LogicSettings.CaptchaConfig.ProxyPort).ConfigureAwait(false); + solved = !string.IsNullOrEmpty(result); + } + if (solved) + { + Logger.Write("Captcha has been resolved automatically by Anti-Captcha "); + } + return result; + } + + + private static async Task GetCaptchaResposeBy2Captcha(ISession session, string captchaUrl) + { + bool solved = false; + int retries = session.LogicSettings.CaptchaConfig.AutoCaptchaRetries; + string result = null; + + while (retries-- > 0 && !solved) + { + TwoCaptchaClient client = new TwoCaptchaClient(session.LogicSettings.CaptchaConfig.TwoCaptchaAPIKey); + + result = await client.SolveRecaptchaV2(POKEMON_GO_GOOGLE_KEY, captchaUrl, string.Empty, ProxyType.HTTP).ConfigureAwait(false); + solved = !string.IsNullOrEmpty(result); + } + if (solved) + { + Logger.Write("Captcha has been resolved automatically by 2Captcha "); + } + return result; + } + + public static string GetCaptchaResposeManually(ISession session, string url) + { + if (!session.LogicSettings.CaptchaConfig.AllowManualCaptchaResolve) return null; + + if (!File.Exists("chromedriver.exe")) + { + Logger.Write( + $"You enable manual captcha resolve but bot didn't setup files properly, please download chromedriver.exe put in same folder.", + LogLevel.Error + ); + return null; + } + IWebDriver webDriver = null; + try + { + webDriver = new ChromeDriver(Environment.CurrentDirectory, new ChromeOptions() + { + }); + + webDriver.Navigate().GoToUrl(url); + Logger.Write($"Captcha is being show in separate thread window, please check your chrome browser and resolve it before {session.LogicSettings.CaptchaConfig.ManualCaptchaTimeout} seconds"); + + var wait = new WebDriverWait( + webDriver, + TimeSpan.FromSeconds(session.LogicSettings.CaptchaConfig.ManualCaptchaTimeout) + ); + //wait.Until(ExpectedConditions.ElementIsVisible(By.Id("recaptcha-verify-button"))); + wait.Until(d => + { + var ele = d.FindElement(By.Id("g-recaptcha-response")); + + if (ele == null) return false; + return ele.GetAttribute("value").Length > 0; + }); + + string token = wait.Until(driver => + { + var ele = driver.FindElement(By.Id("g-recaptcha-response")); + string t = ele.GetAttribute("value"); + return t; + }); + + return token; + } + catch (Exception ex) + { + Logger.Write($"You didn't resolve captcha in the given time: {ex.Message} ", LogLevel.Error); + } + finally + { + if (webDriver != null) webDriver.Close(); + } + + return null; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Captcha/CaptchaSolutionClient.cs b/PoGo.NecroBot.Logic/Captcha/CaptchaSolutionClient.cs new file mode 100644 index 000000000..8141ca748 --- /dev/null +++ b/PoGo.NecroBot.Logic/Captcha/CaptchaSolutionClient.cs @@ -0,0 +1,76 @@ +using System; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Newtonsoft.Json; +using PoGo.NecroBot.Logic.Logging; + +namespace PoGo.NecroBot.Logic.Captcha +{ + public class CaptchaSolutionClient + { + private const string API_ENDPOINT = "http://api.captchasolutions.com/solve?"; + + public class APIObjectResponse + { + public string Captchasolutions { get; set; } + } + + public string APIKey { get; set; } + public string APISecret { get; set; } + + public int Timeout { get; set; } + + public CaptchaSolutionClient(string key, string secret, int timeout = 120) + { + APIKey = key; + APISecret = secret; + Timeout = timeout; + } + + public async Task ResolveCaptcha(string googleSiteKey, string captchaUrl) + { + if (string.IsNullOrEmpty(APIKey) || string.IsNullOrEmpty(APISecret)) + { + Logger.Write( + $"(CAPTCHA) - CaptchaSolutions API key or API Secret not setup properly.", + LogLevel.Error + ); + + return string.Empty; + } + + string contentstring = $"p=nocaptcha&googlekey={googleSiteKey}&pageurl={captchaUrl}&key={APIKey}&secret={APISecret}&out=json"; + + //HttpContent content = new StringContent(contentstring); + //content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/x-www-form-urlencoded"); + //string url = "http://api.captchasolutions.com/solve?p=nocaptcha&googlekey=6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-&pageurl=https://www.google.com/recaptcha/api2/demo&key=dcbeece13cb658697f7e39264603fc70&secret=fb14ac29&out=json"; + var url = $"{API_ENDPOINT}{contentstring}"; + using (HttpClient client = new HttpClient()) + { + client.Timeout = TimeSpan.FromSeconds(Timeout); + + try + { + var responseContent = await client.GetAsync(url).ConfigureAwait(false); + if (responseContent.StatusCode != HttpStatusCode.OK) + { + Logger.Write( + $"(CAPTCHA) - Could not connect to solution captcha, please check your API config", + LogLevel.Error + ); + return string.Empty; + } + var responseJSON = await responseContent.Content.ReadAsStringAsync().ConfigureAwait(false); + var response = JsonConvert.DeserializeObject(responseJSON); + return response.Captchasolutions; + } + catch (Exception) + { + Logger.Write($"(CAPTCHA) - An Error has occurred when solving captcha with Captcha Solutions"); + } + } + return string.Empty; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Captcha/TwoCaptchaClient.cs b/PoGo.NecroBot.Logic/Captcha/TwoCaptchaClient.cs new file mode 100644 index 000000000..aff524fc9 --- /dev/null +++ b/PoGo.NecroBot.Logic/Captcha/TwoCaptchaClient.cs @@ -0,0 +1,116 @@ +using System; +using System.IO; +using System.Net; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Logging; + +namespace PoGo.NecroBot.Logic.Captcha +{ + public class TwoCaptchaClient + { + public string APIKey { get; private set; } + + public TwoCaptchaClient(string apiKey) + { + APIKey = apiKey; + } + + /// + /// Sends a solve request and waits for a response + /// + /// The "sitekey" value from site your captcha is located on + /// The page the captcha is located on + /// The proxy used, format: "username:password@ip:port + /// The type of proxy used + /// If solving was successful this contains the answer + /// Returns true if solving was successful, otherwise false + public async Task SolveRecaptchaV2(string googleKey, string pageUrl, string proxy, ProxyType proxyType) + { + string requestUrl = "http://2captcha.com/in.php?key=" + APIKey + "&method=userrecaptcha&googlekey=" + googleKey + "&pageurl=" + pageUrl + "&proxy=" + proxy + "&proxytype="; + + switch (proxyType) + { + case ProxyType.HTTP: + requestUrl += "HTTP"; + break; + case ProxyType.HTTPS: + requestUrl += "HTTPS"; + break; + case ProxyType.SOCKS4: + requestUrl += "SOCKS4"; + break; + case ProxyType.SOCKS5: + requestUrl += "SOCKS5"; + break; + } + + try + { + WebRequest req = WebRequest.Create(requestUrl); + + using (WebResponse resp = req.GetResponse()) + using (StreamReader read = new StreamReader(resp.GetResponseStream())) + { + string response = read.ReadToEnd(); + + if (response.Length < 3) + { + return string.Empty; + } + else + { + if (response.Substring(0, 3) == "OK|") + { + string captchaID = response.Remove(0, 3); + Logger.Write($"Captcha has been sent to 2Captcha, Your captcha ID : {captchaID}"); + + for (int i = 0; i < 29; i++) + { + WebRequest getAnswer = WebRequest.Create("http://2captcha.com/res.php?key=" + APIKey + "&action=get&id=" + captchaID); + + using (WebResponse answerResp = getAnswer.GetResponse()) + using (StreamReader answerStream = new StreamReader(answerResp.GetResponseStream())) + { + string answerResponse = answerStream.ReadToEnd(); + + if (answerResponse.Length < 3) + { + return string.Empty; + } + else + { + if (answerResponse.Substring(0, 3) == "OK|") + { + return answerResponse.Remove(0, 3); + } + else if (answerResponse != "CAPCHA_NOT_READY") + { + return string.Empty; + } + } + Logger.Write($"Waiting response captcha from 2Captcha workers..."); + } + + await Task.Delay(3000); + } + return string.Empty; + } + } + } + } + catch (Exception ex) + { + Logger.Write($"2Captcha Error : {ex.Message}"); + } + return string.Empty; + } + } + + public enum ProxyType + { + HTTP, + HTTPS, + SOCKS4, + SOCKS5 + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Common/PokemonGradeHelper.cs b/PoGo.NecroBot.Logic/Common/PokemonGradeHelper.cs new file mode 100644 index 000000000..0c3afa9c6 --- /dev/null +++ b/PoGo.NecroBot.Logic/Common/PokemonGradeHelper.cs @@ -0,0 +1,310 @@ +using System.Collections.Generic; +using System.Linq; +using PoGo.NecroBot.Logic.Model; +using POGOProtos.Enums; + +namespace PoGo.NecroBot.Logic.Common +{ + public static class PokemonGradeHelper + { + private static Dictionary RarityColors = new Dictionary() + { + { PokemonGrades.Common,"#7FFF8E" }, + { PokemonGrades.Epic,"#E16FA2" }, + { PokemonGrades.Legendary,"#B05895" }, + { PokemonGrades.Popular,"#DBFE80" }, + { PokemonGrades.Rare,"#FFB382" }, + { PokemonGrades.Special,"Red" }, + { PokemonGrades.VeryCommon,"#DBFE80" }, + { PokemonGrades.VeryRare,"#FF807F" } + }; + + public static string GetGradeColor(PokemonId pokemonId) + { + var g = GetPokemonGrade(pokemonId); + return RarityColors[g]; + } + + public static string GetGradeColor(PokemonGrades g) + { + return RarityColors[g]; + } + public static PokemonGrades GetPokemonGrade(PokemonId id) + { + var first = pokemonByGrades.FirstOrDefault(p => p.Value.Contains(id)); + return first.Key; + } + + private static Dictionary> pokemonByGrades = + new Dictionary>() + { + { + PokemonGrades.VeryCommon, new List() + { + PokemonId.Caterpie, + PokemonId.Weedle, + PokemonId.Pidgey, + PokemonId.Rattata, + PokemonId.Ekans, + PokemonId.Sandshrew, + PokemonId.NidoranFemale, + PokemonId.NidoranMale, + PokemonId.Zubat, + PokemonId.Oddish, + PokemonId.Paras, + PokemonId.Venonat, + PokemonId.Mankey, + PokemonId.Poliwag, + PokemonId.Machop, + PokemonId.Bellsprout, + PokemonId.Geodude, + PokemonId.Slowpoke, + PokemonId.Magnemite, + PokemonId.Gastly, + PokemonId.Krabby, + PokemonId.Voltorb, + PokemonId.Goldeen, + PokemonId.Eevee, + PokemonId.Magikarp, + PokemonId.Natu + } + }, + { + PokemonGrades.Common, new List + { + PokemonId.Bulbasaur, + PokemonId.Charmander, + PokemonId.Squirtle, + PokemonId.Kakuna, + PokemonId.Pidgeotto, + PokemonId.Raticate, + PokemonId.Spearow, + PokemonId.Arbok, + PokemonId.Pikachu, + PokemonId.Sandslash, + PokemonId.Clefable, + PokemonId.Jigglypuff, + PokemonId.Golbat, + PokemonId.Diglett, + PokemonId.Persian, + PokemonId.Psyduck, + PokemonId.Growlithe, + PokemonId.Abra, + PokemonId.Machoke, + PokemonId.Graveler, + PokemonId.Ponyta, + PokemonId.Magneton, + PokemonId.Doduo, + PokemonId.Seel, + PokemonId.Grimer, + PokemonId.Shellder, + PokemonId.Haunter, + PokemonId.Electrode, + PokemonId.Exeggcute, + PokemonId.Cubone, + PokemonId.Hitmonlee, + PokemonId.Koffing, + PokemonId.Rhyhorn, + PokemonId.Horsea, + PokemonId.Staryu, + PokemonId.Jynx, + PokemonId.Chikorita, + PokemonId.Totodile, + PokemonId.Sentret, + PokemonId.Hoothoot, + PokemonId.Ledyba, + PokemonId.Spinarak, + PokemonId.Sunkern, + PokemonId.Wooper, + PokemonId.Murkrow, + PokemonId.Snubbull, + PokemonId.Teddiursa , + PokemonId.Slugma , + PokemonId.Swinub , + PokemonId.Remoraid , + PokemonId.Houndour , + PokemonId.Phanpy , + PokemonId.Marill + } + }, + { + PokemonGrades.Popular, new List() + { + PokemonId.Dratini, + PokemonId.Butterfree, + PokemonId.Spearow, + PokemonId.Nidorina, + PokemonId.Nidorino, + PokemonId.Ninetales, + PokemonId.Wigglytuff, + PokemonId.Gloom, + PokemonId.Parasect, + PokemonId.Golduck, + PokemonId.Primeape, + PokemonId.Chansey, + PokemonId.Poliwhirl, + PokemonId.Kadabra, + PokemonId.Machamp, + PokemonId.Tentacruel, + PokemonId.Golem, + PokemonId.Kabuto, + PokemonId.Dodrio, + PokemonId.Cloyster, + PokemonId.Scyther, + PokemonId.Hypno, + PokemonId.Seadra, + PokemonId.Hitmonchan, + PokemonId.Lickitung, + PokemonId.Weezing, + PokemonId.Seaking, + PokemonId.Starmie , + PokemonId.Bayleef, + PokemonId.Cyndaquil, + PokemonId.Croconaw, + PokemonId.Furret, + PokemonId.Ledian, + PokemonId.Ariados, + PokemonId.Chinchou, + PokemonId.Sudowoodo, + PokemonId.Hoppip, + PokemonId.Aipom, + PokemonId.Yanma, + PokemonId.Misdreavus, + PokemonId.Wobbuffet, + PokemonId.Dunsparce, + PokemonId.Gligar, + PokemonId.Granbull, + PokemonId.Qwilfish, + PokemonId.Shuckle, + PokemonId.Heracross, + PokemonId.Sneasel, + PokemonId.Magcargo, + PokemonId.Piloswine, + PokemonId.Corsola, + PokemonId.Octillery, + PokemonId.Mantine, + PokemonId.Skarmory, + PokemonId.Stantler, + PokemonId.Larvitar, + + } + }, + { + PokemonGrades.Rare, new List() + { + PokemonId.Beedrill, + PokemonId.Pidgeot, + PokemonId.Pinsir, + PokemonId.Snorlax, + PokemonId.Slowbro, + PokemonId.MrMime, + PokemonId.Farfetchd, + PokemonId.Onix, + PokemonId.Jolteon, + PokemonId.Flareon, + PokemonId.Magmar, + PokemonId.Kingler, + PokemonId.Rhydon, + PokemonId.Rapidash, + PokemonId.Arcanine, + PokemonId.Muk, + PokemonId.Exeggutor, + PokemonId.Tangela, + PokemonId.Meganium, + PokemonId.Quilava, + PokemonId.Feraligatr, + PokemonId.Noctowl, + PokemonId.Lanturn, + PokemonId.Skiploom, + PokemonId.Quagsire, + PokemonId.Unown, + PokemonId.Girafarig, + PokemonId.Pineco, + PokemonId.Ursaring, + PokemonId.Delibird, + PokemonId.Houndoom, + PokemonId.Donphan, + PokemonId.Smeargle, + PokemonId.Hitmontop, + PokemonId.Miltank, + PokemonId.Blissey, + PokemonId.Pupitar, + PokemonId.Xatu , + PokemonId.Mareep , + PokemonId.Flaaffy , + PokemonId.Azumarill + + } + }, + { + PokemonGrades.VeryRare, new List() + { + + PokemonId.Gyarados, + PokemonId.Lapras, + PokemonId.Vaporeon, + PokemonId.Kabutops, + PokemonId.Dragonair, + PokemonId.Dragonite, + PokemonId.Raichu, + PokemonId.Nidoqueen, + PokemonId.Nidoking, + PokemonId.Vileplume, + PokemonId.Venomoth, + PokemonId.Poliwrath, + PokemonId.Alakazam, + PokemonId.Electabuzz, + PokemonId.Victreebel, + PokemonId.Kangaskhan, + PokemonId.Dewgong, + PokemonId.Marowak, + PokemonId.Gengar, + PokemonId.Tyranitar , + PokemonId.Pupitar, + PokemonId.Togetic, + PokemonId.Blissey, + PokemonId.Steelix , + PokemonId.Crobat, + PokemonId.Espeon, + PokemonId.Umbreon, + PokemonId.Typhlosion, + PokemonId.Jumpluff, + PokemonId.Forretress, + PokemonId.Ampharos + } + }, + { + PokemonGrades.Epic, new List() + { + PokemonId.Venusaur, + PokemonId.Charmeleon, + PokemonId.Wartortle, + PokemonId.Porygon, + PokemonId.Porygon2, + PokemonId.Omanyte, + PokemonId.Aerodactyl, + PokemonId.Charizard, + PokemonId.Blastoise, + PokemonId.Ivysaur , + PokemonId.Politoed, + PokemonId.Sunflora, + PokemonId.Steelix, + PokemonId.Scizor, + PokemonId.Kingdra, + PokemonId.Porygon, + + } + }, + { + PokemonGrades.Legendary, new List() + { + PokemonId.Ditto, + PokemonId.Articuno, + PokemonId.Zapdos, + PokemonId.Moltres, + PokemonId.Mewtwo, + } + } + }; + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Common/Translations.cs b/PoGo.NecroBot.Logic/Common/Translations.cs new file mode 100644 index 000000000..0c29dd033 --- /dev/null +++ b/PoGo.NecroBot.Logic/Common/Translations.cs @@ -0,0 +1,1941 @@ +#region using directives + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using PoGo.NecroBot.Logic.Interfaces.Configuration; +using PoGo.NecroBot.Logic.Logging; +using PoGo.NecroBot.Logic.Utils; +using POGOProtos.Enums; + +#endregion + +namespace PoGo.NecroBot.Logic.Common +{ + public interface ITranslation + { + string GetTranslation(TranslationString translationString, params object[] data); + string GetTranslation(TranslationString translationString); + string GetPokemonTranslation(PokemonId id); + string GetPokemonMovesetTranslation(PokemonMove move); + } + + public enum TranslationString + { + Pokeball, + GreatPokeball, + UltraPokeball, + MasterPokeball, + LogLevelDebug, + LogLevelPokestop, + WrongAuthType, + LoginInvalid, + FarmPokestopsOutsideRadius, + FarmPokestopsNoUsableFound, + EventFortUsed, + EventFortFailed, + Gym, + Pokestop, + EventFortTargeted, + EventProfileLogin, + EventUsedLuckyEgg, + EventPokemonEvolvedSuccess, + EventPokemonEvolvedFailed, + EventPokemonTransferred, + EventItemRecycled, + EventPokemonCaptureSuccess, + EventPokemonCaptureFailed, + EventNoPokeballs, + WaitingForMorePokemonToEvolve, + WaitingForMoreEvolutionsToEvolve, + UseLuckyEggsMinPokemonAmountTooHigh, + CatchMorePokemonToUseLuckyEgg, + EventUseBerry, + ItemRazzBerry, + ItemPinapBerry, + ItemNanabBerry, + ItemBlukBerry, + ItemWeparBerry, + CatchStatusAttempt, + CatchStatus, + Candies, + UnhandledGpxData, + DisplayHighestsHeader, + CommonWordPerfect, + CommonWordName, + DisplayHighestsCpHeader, + DisplayHighestsPerfectHeader, + WelcomeWarning, + IncubatorPuttingEgg, + IncubatorStatusUpdate, + DisplayHighestsLevelHeader, + LogEntryError, + LogEntryAttention, + LogEntryInfo, + LogEntryPokestop, + LogEntryFarming, + LogEntrySniper, + LogEntryRecycling, + LogEntryPkmn, + LogEntryLevelUp, + LogEntryBotStats, + LogEntryTransfered, + LogEntryEvolved, + LogEntryBerry, + LogEntryEgg, + LogEntryDebug, + LogEntryUpdate, + LogEntryNew, + LogEntrySoftBan, + LogEntryGym, + LogEntryGymDisk, + LogEntryService, + LoggingIn, + PtcOffline, + AccessTokenExpired, + InvalidResponse, + TryingAgainIn, + AccountNotVerified, + CommonWordUnknown, + OpeningGoogleDevicePage, + CouldntCopyToClipboard, + CouldntCopyToClipboard2, + RealisticTravelDetected, + NotRealisticTravel, + CoordinatesAreInvalid, + GotUpToDateVersion, + CheckForUpdatesDisabled, + AutoUpdaterDisabled, + DownloadingUpdate, + FinishedDownloadingRelease, + FinishedUnpackingFiles, + FinishedTransferringConfig, + UpdateFinished, + LookingForIncensePokemon, + PokemonSkipped, + ZeroPokeballInv, + CatchPokemonDisable, + CurrentPokeballInv, + CurrentPotionInv, + CurrentReviveInv, + CurrentMiscItemInv, + MaxItemsCombinedOverMaxItemStorage, + TotalRecyclePercentGreaterThan100, + UsingRecyclePercentsInsteadOfTotals, + PercentPokeballsToKeep, + PercentPotionsToKeep, + PercentRevivesToKeep, + PercentBerriesToKeep, + PercentEvolutionToKeep, + RecyclingQuietly, + InvFullTransferring, + InvFullTransferManually, + InvFullPokestopLooting, + IncubatorEggHatched, + EncounterProblem, + EncounterProblemLurePokemon, + LookingForPokemon, + LookingForLurePokemon, + DesiredDestTooFar, + PokemonRename, + PokemonFavorite, + PokemonUnFavorite, + PokemonIgnoreFilter, + CatchStatusError, + CatchStatusEscape, + CatchStatusFlee, + CatchStatusMissed, + CatchStatusSuccess, + CatchTypeNormal, + CatchTypeLure, + CatchTypeIncense, + WebSocketFailStart, + StatsTemplateString, + ProfileStatsTemplateString, + ShowPokeTemplate, + ShowPokeSkillTemplate, + StatsXpTemplateString, + RequireInputText, + GoogleTwoFactorAuth, + GoogleTwoFactorAuthExplanation, + GoogleError, + GoogleOffline, + MissingCredentials, + MissingCredentialsGoogle, + MissingCredentialsPtc, + SnipeScan, + SnipeScanEx, + NoPokemonToSnipe, + NotEnoughPokeballsToSnipe, + DisplayHighestMove1Header, + DisplayHighestMove2Header, + DisplayHighestCandy, + IPBannedError, + NoEggsAvailable, + UseLuckyEggActive, + UsedLuckyEgg, + UseLuckyEggAmount, + NoIncenseAvailable, + UseIncenseActive, + UseIncenseAmount, + UsedIncense, + AmountPkmSeenCaught, + PkmPotentialEvolveCount, + PkmNotEnoughRessources, + EventUsedIncense, + SnipeServerOffline, + PromptError, + PromptErrorDouble, + PromptErrorInteger, + PromptErrorString, + SoftBanBypassed, + FirstStartLanguagePrompt, + FirstStartLanguageCodePrompt, + FirstStartLanguageConfirm, + FirstStartSetupTelegramPrompt, + FirstStartSetupTelegramCodePrompt, + FirstStartSetupTelegramCodeConfirm, + FirstStartSetupTelegramPasswordPrompt, + FirstStartSetupTelegramPasswordConfirm, + FirstStartPrompt, + FirstStartAutoGenSettings, + FirstStartSetupAccount, + FirstStartSetupTypePrompt, + FirstStartSetupTypeConfirm, + FirstStartSetupTypePromptError, + FirstStartSetupUsernamePrompt, + FirstStartSetupUsernameConfirm, + FirstStartSetupPasswordPrompt, + FirstStartSetupPasswordConfirm, + FirstStartAccountCompleted, + FirstStartDefaultLocationPrompt, + FirstStartDefaultLocationSet, + FirstStartDefaultLocation, + FirstStartSetupDefaultLocationError, + FirstStartSetupDefaultLatLongPrompt, + FirstStartSetupDefaultLatLongConfirm, + FirstStartSetupCompleted, + PokedexCatchedTelegram, + PokedexPokemonCatchedTelegram, + PokedexNeededTelegram, + PokedexPokemonNeededTelegram, + LoggedInTelegram, + LoginFailedTelegram, + NotLoggedInTelegram, + Unproxied, + Proxied, + FixProxySettings, + UsageHelp, + LoginRemainingTime, + HighestsPokemoHeader, + HighestsPokemoCell, + HumanWalkingVariant, + AccountBanned, + GoogleAPIWarning, + Only10kmEggs, + SniperCount, + SnipeExceeds, + CatchExceeds, + PokeStopExceeds, + CatchLimitReached, + CatchTimerReached, + PokestopLimitReached, + PokestopTimerReached, + ExitDueToLimitsReached, + HumanWalkSnipe, + HumanWalkSnipeUpdate, + HumanWalkSnipeAddedPokestop, + HumanWalkSnipeDestinationReached, + HumanWalkSnipeNotEnoughtBalls, + HumanWalkSnipePokemonEncountered, + EventPokemonUpgraded, + FirstStartSetupProxyPrompt, + FirstStartSetupProxyHostPrompt, + FirstStartSetupProxyHostConfirm, + FirstStartSetupProxyPortConfirm, + FirstStartSetupProxyPortPrompt, + FirstStartSetupProxyAuthPrompt, + FirstStartSetupProxyUsernamePrompt, + FirstStartSetupProxyUsernameConfirm, + FirstStartSetupProxyPasswordPrompt, + FirstStartSetupProxyPasswordConfirm, + FirstStartSetupAutoCompleteTutPrompt, + FirstStartSetupAutoCompleteTutNicknamePrompt, + FirstStartSetupAutoCompleteTutNicknameConfirm, + FirstStartSetupAutoCompleteTutGenderPrompt, + FirstStartSetupAutoCompleteTutGenderConfirm, + FirstStartSetupAutoCompleteTutStarterPrompt, + FirstStartSetupAutoCompleteTutStarterConfirm, + FirstStartSetupWebSocketPrompt, + FirstStartSetupWebSocketPortPrompt, + FirstStartSetupWebSocketPortConfirm, + FirstStartSetupWalkingSpeedPrompt, + FirstStartSetupWalkingSpeedKmHPrompt, + FirstStartSetupWalkingSpeedKmHConfirm, + FirstStartSetupUseWalkingSpeedVariantPrompt, + FirstStartSetupWalkingSpeedVariantPrompt, + FirstStartSetupWalkingSpeedVariantConfirm, + MinimumClientVersionException, + ExitNowAfterEnterKey, + CaptchaShown, + FailedSendNotification, + TelegramBotStarted, + TelegramNeedChatId, + BulkTransferFailed, + AutoSnipeDisabled, + SnipePokemonNotInPokedex, + BuddyPokemonUpdate, + TargetLocationSet, + WebSocketStarted, + TelegramCommandAccountsDescription, + TelegramCommandAllDescription, + TelegramCommandExitDescription, + TelegramCommandHelpDescription, + TelegramCommandItemsDescription, + TelegramCommandLogsDescription, + TelegramCommandPokedexDescription, + TelegramCommandProfileDescription, + TelegramCommandRecycleDescription, + TelegramCommandRestartDescription, + TelegramCommandSnipeDescription, + TelegramCommandStatusDescription, + TelegramCommandTopDescription, + TelegramCommandLocDescription, + TelegramCommandAccountsMsgHead, + TelegramCommandAllMsgHead, + TelegramCommandExitMsgHead, + TelegramCommandHelpMsgHead, + TelegramCommandItemsMsgHead, + TelegramCommandLogsMsgHead, + TelegramCommandPokedexMsgHead, + TelegramCommandProfileMsgHead, + TelegramCommandRecycleMsgHead, + TelegramCommandRestartMsgHead, + TelegramCommandSnipeMsgHead, + TelegramCommandStatusMsgHead, + TelegramCommandTopMsgHead, + TelegramCommandLocMsgHead, + TelegramCommandProfileMsgBody, + TelegramCommandStatusMsgBody, + MultiAccountAutoSelect, + PtcLoginFail + } + + public class Translation : ITranslation + { + [JsonProperty("TranslationStrings", + ItemTypeNameHandling = TypeNameHandling.Arrays, + ItemConverterType = typeof(KeyValuePairConverter), + ObjectCreationHandling = ObjectCreationHandling.Replace, + DefaultValueHandling = DefaultValueHandling.Populate)] + //Default Translations (ENGLISH) + private readonly List> _translationStrings = new List + > + { + new KeyValuePair(TranslationString.PtcLoginFail, "Your PTC account is wrong password, or being lock, or not active"), + new KeyValuePair(TranslationString.MultiAccountAutoSelect, "PLEASE SELECT AN ACCOUNT TO START. AUTO START AFTER {0} SECOND"), + new KeyValuePair(TranslationString.WebSocketStarted, "Websocket listening on at wss://localhost:{0} or ws://localhost:{1} "), + new KeyValuePair(TranslationString.TargetLocationSet, + "Target location [{0},{1}] has been accepted. Bot will go there soon."), + new KeyValuePair(TranslationString.Pokeball, "PokeBall"), + new KeyValuePair(TranslationString.GreatPokeball, "GreatBall"), + new KeyValuePair(TranslationString.UltraPokeball, "UltraBall"), + new KeyValuePair(TranslationString.MasterPokeball, "MasterBall"), + new KeyValuePair(TranslationString.WrongAuthType, + "Unknown AuthType in config.json"), + new KeyValuePair(TranslationString.LoginInvalid, + "User credentials are invalid and login failed."), + new KeyValuePair(TranslationString.FarmPokestopsOutsideRadius, + "You're outside of your defined radius! Walking to start ({0}m away) in 5 seconds. Is your LastPos.ini file correct?"), + new KeyValuePair(TranslationString.FarmPokestopsNoUsableFound, + "No usable PokeStops found in your area. Is your maximum distance too small?"), + new KeyValuePair(TranslationString.EventFortUsed, + "Name: {0} | XP: {1} | Gems: {2} | Items: {3} | Badges: {4} | BonusLoot: {5} | RaidTickets: {6} | TeamBonusLoot: {7} | PokemonEgg: {8} (Eggs: {9}) | Lat: {10} Long: {11} | Alt: {12}"), + new KeyValuePair(TranslationString.EventFortFailed, + "Name: {0} INFO: Looting failed, possible softban. Unban in: {1}/{2}"), + new KeyValuePair(TranslationString.Gym, "Gym"), + new KeyValuePair(TranslationString.Pokestop, "Pokestop"), + new KeyValuePair(TranslationString.EventFortTargeted, + "Traveling {0}m({1} sec) via '{2}' to next {3}: {4}"), + new KeyValuePair(TranslationString.EventProfileLogin, "Playing as {0}"), + new KeyValuePair(TranslationString.EventUsedIncense, + "Used Incense, remaining: {0}"), + new KeyValuePair(TranslationString.EventUsedLuckyEgg, + "Used Lucky Egg, remaining: {0}"), + new KeyValuePair(TranslationString.EventPokemonEvolvedSuccess, + "{0} to {1} | CP: {2} | IV: {3}% | XP: {4} | Candies: {5} | Lvl: {6}"), + new KeyValuePair(TranslationString.EventPokemonEvolvedFailed, + "Failed {0}. Result was {1}, stopping evolving {2}"), + new KeyValuePair(TranslationString.EventPokemonTransferred, + "{0} | CP: {1}/{3} | IV: {2}%/{4}% | Candies: {5} | Lvl: {6} | Slashed: {7}"), + new KeyValuePair(TranslationString.EventPokemonUpgraded, + "{0} | CP: {2}/{4} | IV: {3}%/{5}% | SD: {6} | Candies: {7} | Lvl: {1}"), + + new KeyValuePair(TranslationString.EventItemRecycled, "{0}x {1}"), //"{0,3:0}x {1}"), + //Logging Cleanup (mostly uneccessary information, may want a verbose pokemon capture logger setting) + new KeyValuePair(TranslationString.EventPokemonCaptureSuccess, + "({0}) | ({1}) {2} | Lvl: {3} | CP: {4}/{5} | IV: {6}% | Chance: {7}% | {8}m dist | with a {9} ({10} left) | XP: {11} | SD: {12} | {13} | lat: {14} long: {15} | Move1: {16} Move2: {17} | Rarity: {18} | Capture Reason: {19} | Shiny: {20} | Form: {21} | Costume: {22} | Sex: {23}"), + new KeyValuePair(TranslationString.EventPokemonCaptureFailed, + "({0}) | ({1}) {2} | Lvl: {3} | CP: {4}/{5} | IV: {6}% | Chance: {7}% | {8}m dist | with a {9} ({10} left) | XP: {11} | lat: {12} long: {13} | Move1: {14} Move2: {15} | Rarity: {16}"), + + new KeyValuePair(TranslationString.EventNoPokeballs, + "No Pokeballs - We missed a {0} with CP {1}"), + new KeyValuePair(TranslationString.WaitingForMorePokemonToEvolve, + "Waiting to evolve {0} Pokemons until {1} more are in storage! (In storage: {2} | Target: {3})"), + new KeyValuePair(TranslationString.WaitingForMoreEvolutionsToEvolve, + "Waiting for {0} more evolutions to start evolving! (Possible: {1} | Needed: {2})"), + new KeyValuePair(TranslationString.UseLuckyEggsMinPokemonAmountTooHigh, + "Lucky eggs will never be used with UseLuckyEggsMinPokemonAmount set to {0}, use <= {1} instead"), + new KeyValuePair(TranslationString.CatchMorePokemonToUseLuckyEgg, + "Missing {0} more evolutions to use a Lucky Egg!"), + new KeyValuePair(TranslationString.EventUseBerry, + "Used {0} | {1} remaining"), + new KeyValuePair(TranslationString.ItemRazzBerry, "Razz Berry"), + new KeyValuePair(TranslationString.ItemPinapBerry, "Pinap Berry"), + new KeyValuePair(TranslationString.ItemNanabBerry, "Nanab Berry"), + new KeyValuePair(TranslationString.ItemBlukBerry, "Bluk Berry"), + new KeyValuePair(TranslationString.ItemWeparBerry, "Wepar Berry"), + new KeyValuePair(TranslationString.CatchStatusAttempt, "{0} Attempt #{1}"), + new KeyValuePair(TranslationString.CatchStatus, "{0}"), + new KeyValuePair(TranslationString.Candies, "Candies: {0,4:###0}"), + new KeyValuePair(TranslationString.UnhandledGpxData, + "Unhandled data in GPX file, attempting to skip."), + new KeyValuePair(TranslationString.DisplayHighestsHeader, "Pokemons"), + new KeyValuePair(TranslationString.CommonWordPerfect, "perfect"), + new KeyValuePair(TranslationString.CommonWordName, "name"), + new KeyValuePair(TranslationString.CommonWordUnknown, "Unknown"), + new KeyValuePair(TranslationString.DisplayHighestsCpHeader, + "DisplayHighestsCP"), + new KeyValuePair(TranslationString.DisplayHighestsPerfectHeader, + "DisplayHighestsPerfect"), + new KeyValuePair(TranslationString.DisplayHighestsLevelHeader, + "DisplayHighestsLevel"), + new KeyValuePair(TranslationString.WelcomeWarning, + "Make sure Lat & Lng are right. Exit Program if not! Lat: {0} Lng: {1}"), + new KeyValuePair(TranslationString.IncubatorPuttingEgg, + "Putting egg in incubator: {0} km left"), + new KeyValuePair(TranslationString.IncubatorStatusUpdate, + "Incubator status update: {0} Km left of {1} Km egg."), + new KeyValuePair(TranslationString.IncubatorEggHatched, + "{0} Km egg has hatched: {1} | Lvl: {2} | CP: {3}/{4} | IV: {5}% | XP: {6} | SD: {7} | Candies: {8}"), + new KeyValuePair(TranslationString.LogEntryError, "ERROR"), + new KeyValuePair(TranslationString.LogEntryAttention, "ATTENTION"), + new KeyValuePair(TranslationString.LogEntryInfo, "INFO"), + new KeyValuePair(TranslationString.LogEntryPokestop, "POKESTOP"), + new KeyValuePair(TranslationString.LogEntryFarming, "FARMING"), + new KeyValuePair(TranslationString.LogEntrySniper, "SNIPER"), + new KeyValuePair(TranslationString.LogEntryRecycling, "RECYCLING"), + new KeyValuePair(TranslationString.LogEntryPkmn, "PKMN"), + new KeyValuePair(TranslationString.LogEntryLevelUp, "LEVEL UP"), + new KeyValuePair(TranslationString.LogEntryBotStats, "BOT STATS"), + new KeyValuePair(TranslationString.LogEntryTransfered, "TRANSFERRED"), + new KeyValuePair(TranslationString.LogEntryEvolved, "EVOLVED"), + new KeyValuePair(TranslationString.LogEntryBerry, "BERRY"), + new KeyValuePair(TranslationString.LogEntryEgg, "EGG"), + new KeyValuePair(TranslationString.LogEntryDebug, "DEBUG"), + new KeyValuePair(TranslationString.LogEntryUpdate, "UPDATE"), + new KeyValuePair(TranslationString.LogEntryNew, "NEW"), + new KeyValuePair(TranslationString.LogEntryGym, "GYM"), + new KeyValuePair(TranslationString.LogEntryGymDisk, "GYM DISK"), + new KeyValuePair(TranslationString.LogEntryService, "SERVICE"), + new KeyValuePair(TranslationString.LogEntrySoftBan, "SOFTBAN"), + new KeyValuePair(TranslationString.LoggingIn, "Logging in using {0} {1}"), + new KeyValuePair(TranslationString.PtcOffline, + "PTC Servers are probably down OR your credentials are wrong."), + new KeyValuePair(TranslationString.AccessTokenExpired, + "PTC Login Token expired. Relogging..."), + new KeyValuePair(TranslationString.InvalidResponse, + "Received an invalid response from Niantic server"), + new KeyValuePair(TranslationString.TryingAgainIn, + "Trying again in {0} seconds..."), + new KeyValuePair(TranslationString.AccountNotVerified, + "Account not verified! Exiting..."), + new KeyValuePair(TranslationString.OpeningGoogleDevicePage, + "Opening Google Device page. Please paste the code using CTRL+V"), + new KeyValuePair(TranslationString.CouldntCopyToClipboard, + "Couldnt copy to clipboard, do it manually"), + new KeyValuePair(TranslationString.CouldntCopyToClipboard2, + "Goto: {0} & enter {1}"), + new KeyValuePair(TranslationString.RealisticTravelDetected, + "Detected realistic Traveling, using Default Settings inside config.json"), + new KeyValuePair(TranslationString.NotRealisticTravel, + "Non realistic traveling detected at {0}Km/h, using last saved LastPos.ini"), + new KeyValuePair(TranslationString.CoordinatesAreInvalid, + "Coordinates in \"LastPos.ini\" file are invalid, using the default coordinates"), + new KeyValuePair(TranslationString.GotUpToDateVersion, + "Perfect! You already have the newest Version {0}"), + new KeyValuePair(TranslationString.CheckForUpdatesDisabled, + "Update checking is disabled. Current Version: {0}"), + new KeyValuePair(TranslationString.AutoUpdaterDisabled, + "AutoUpdater is disabled. Get the latest release from: {0}\n "), + new KeyValuePair(TranslationString.DownloadingUpdate, + "Downloading and apply Update..."), + new KeyValuePair(TranslationString.FinishedDownloadingRelease, + "Finished downloading newest Release..."), + new KeyValuePair(TranslationString.FinishedUnpackingFiles, + "Finished unpacking files..."), + new KeyValuePair(TranslationString.FinishedTransferringConfig, + "Finished transferring your config to the new version..."), + new KeyValuePair(TranslationString.UpdateFinished, + "Update finished, you can close this window now."), + new KeyValuePair(TranslationString.LookingForIncensePokemon, + "Looking for incense Pokemon..."), + new KeyValuePair(TranslationString.LookingForPokemon, + "Looking for Pokemon..."), + new KeyValuePair(TranslationString.LookingForLurePokemon, + "Looking for lure Pokemon..."), + new KeyValuePair(TranslationString.PokemonSkipped, "Skipped {0}"), + new KeyValuePair(TranslationString.ZeroPokeballInv, + "Out of Pokeballs. Waiting {0} minute(s) until catching again."), + new KeyValuePair(TranslationString.CurrentPokeballInv, + "Pokeballs: {0} | Greatballs: {1} | Ultraballs: {2} | Masterballs: {3}"), + new KeyValuePair(TranslationString.CurrentPotionInv, + "Potions: {0} | SuperPotions: {1} | HyperPotions: {2} | MaxPotions: {3}"), + new KeyValuePair(TranslationString.CurrentReviveInv, + "Revives: {0} | MaxRevives: {1} | Evolve Items: {2} | Raid Tickets: {3}"), + new KeyValuePair(TranslationString.CurrentMiscItemInv, + "Berries: {0} | Incense: {1} | LuckyEggs: {2} | Lures: {3}"), + new KeyValuePair(TranslationString.MaxItemsCombinedOverMaxItemStorage, + "[Configuration Invalid] Your maximum items combined (Balls+Potions+Revives+Berries+Evolution={0}) is over your max item storage ({1})"), + new KeyValuePair(TranslationString.TotalRecyclePercentGreaterThan100, + "[Configuration Invalid] Your total recycle percents combined (Balls+Potions+Revives+Berries+Evolution) is greater than 100. It must be less than or equal to 100."), + new KeyValuePair(TranslationString.UsingRecyclePercentsInsteadOfTotals, + "[Recycle Configuration] Using recycle percents. Max Items: {0}"), + new KeyValuePair(TranslationString.PercentPokeballsToKeep, + "[Recycle Configuration] Pokeballs ({0,2:##}%): {1,3:###}"), + new KeyValuePair(TranslationString.PercentPotionsToKeep, + "[Recycle Configuration] Potions ({0,2:##}%): {1,3:###}"), + new KeyValuePair(TranslationString.PercentRevivesToKeep, + "[Recycle Configuration] Revives ({0,2:##}%): {1,3:###}"), + new KeyValuePair(TranslationString.PercentBerriesToKeep, + "[Recycle Configuration] Berries ({0,2:##}%): {1,3:###}"), + new KeyValuePair(TranslationString.PercentEvolutionToKeep, + "[Recycle Configuration] Evolution ({0,2:##}%): {1,3:###}"), + new KeyValuePair(TranslationString.RecyclingQuietly, "Recycling Quietly..."), + new KeyValuePair(TranslationString.InvFullTransferring, + "Pokemon Inventory is full, transferring Pokemon..."), + new KeyValuePair(TranslationString.BulkTransferFailed, + "Bulk transfer {0} pokemons was failed..."), + new KeyValuePair(TranslationString.InvFullTransferManually, + "Pokemon Inventory is full! Please transfer Pokemon manually or set TransferDuplicatePokemon to true in settings..."), + new KeyValuePair(TranslationString.InvFullPokestopLooting, + "Inventory is full, no items looted!"), + new KeyValuePair(TranslationString.EncounterProblem, + "Encounter problem: {0}"), + new KeyValuePair(TranslationString.EncounterProblemLurePokemon, + "Encounter problem: Lure Pokemon {0}"), + new KeyValuePair(TranslationString.DesiredDestTooFar, + "Your desired destination of {0}, {1} is too far from your current position of {2}, {3}"), + new KeyValuePair(TranslationString.PokemonRename, + "{0} renamed from {1} to {2}"), + new KeyValuePair(TranslationString.PokemonFavorite, + "{0,6:0.00}% perfect {1,-12} | CP {2,4} | *Favorited*"), + new KeyValuePair(TranslationString.PokemonUnFavorite, + "{0,6:0.00}% perfect {1,-12} | CP {2,4} | *Un-Favorited*"), + new KeyValuePair(TranslationString.PokemonIgnoreFilter, + "[Pokemon ignore filter] - Ignoring {0} as defined in settings"), + new KeyValuePair(TranslationString.CatchStatusAttempt, "CatchAttempt"), + new KeyValuePair(TranslationString.CatchStatusError, "CatchError"), + new KeyValuePair(TranslationString.CatchStatusEscape, "CatchEscape"), + new KeyValuePair(TranslationString.CatchStatusFlee, "CatchFlee"), + new KeyValuePair(TranslationString.CatchStatusMissed, "CatchMissed"), + new KeyValuePair(TranslationString.CatchStatusSuccess, "CatchSuccess"), + new KeyValuePair(TranslationString.CatchTypeNormal, "Normal"), + new KeyValuePair(TranslationString.CatchTypeLure, "Lure"), + new KeyValuePair(TranslationString.CatchTypeIncense, "Incense"), + new KeyValuePair(TranslationString.WebSocketFailStart, + "Failed to start WebSocketServer on port : {0}"), + new KeyValuePair(TranslationString.StatsTemplateString, + "{0} - Runtime {1} - Lvl: {2} | EXP/H: {3:n0} | P/H: {4:n0} | Stardust: {5:n0} | Transferred: {6:n0} | Recycled: {7:n0}"), + new KeyValuePair(TranslationString.ProfileStatsTemplateString, + "----- LVL {0} | {1} ----- \n Experience: {2}/{3} \n Pokemons caught: {4} \n Pokemons deployed: {5} \n Pokestops visited: {6} \n Eggs hatched: {7} \n Pokemons evolved: {8} \n Pokedex entries: {9} \n KM walked: {10} \n Pokemons: {11}/{12}"), + new KeyValuePair(TranslationString.ShowPokeTemplate, + "\n CP: {0} | IV: {1}% | Name: {2}"), + new KeyValuePair(TranslationString.ShowPokeSkillTemplate, + "\n CP: {0} | IV: {1}% | Move: {2} + {3} | Name: {4}"), + new KeyValuePair(TranslationString.StatsXpTemplateString, + "{0} (Advance in {1}h {2}m | {3:n0}/{4:n0} XP)"), + new KeyValuePair(TranslationString.RequireInputText, + "Program will continue after the key press..."), + new KeyValuePair(TranslationString.GoogleTwoFactorAuth, + "As you have Google Two Factor Auth enabled, you will need to insert an App Specific Password into the auth.json"), + new KeyValuePair(TranslationString.GoogleTwoFactorAuthExplanation, + "Opening Google App-Passwords. Please make a new App Password (use Other as Device)"), + new KeyValuePair(TranslationString.GoogleError, + "Make sure you have entered the right Email & Password."), + new KeyValuePair(TranslationString.GoogleOffline, + "Google servers are probably down, Please be patient and start the bot later."), + new KeyValuePair(TranslationString.MissingCredentials, + "You need to fill out Username and Password in auth.json!"), + new KeyValuePair(TranslationString.MissingCredentialsGoogle, + "You need to fill out GoogleUsername and GooglePassword in auth.json!"), + new KeyValuePair(TranslationString.MissingCredentialsPtc, + "You need to fill out PtcUsername and PtcPassword in auth.json!"), + new KeyValuePair(TranslationString.SnipeScan, + "Scanning for Snipeable Pokemon at {0}..."), + new KeyValuePair(TranslationString.SnipePokemonNotInPokedex, + "Auto sniper detected a pokemon not in your pokedex: {0}. He will be snipped as priority!"), + new KeyValuePair(TranslationString.SnipeScanEx, + "Sniping a {0} with {1} IV at {2}..."), + new KeyValuePair(TranslationString.NoPokemonToSnipe, + "Did not find a Pokemon within the Location, pokemon despawned?"), + new KeyValuePair(TranslationString.NotEnoughPokeballsToSnipe, + "Not enough Pokeballs to start sniping! ({0}/{1})"), + new KeyValuePair(TranslationString.DisplayHighestMove1Header, "MOVE1"), + new KeyValuePair(TranslationString.DisplayHighestMove2Header, "MOVE2"), + new KeyValuePair(TranslationString.DisplayHighestCandy, "Candy"), + new KeyValuePair(TranslationString.IPBannedError, + "Connection refused. Your IP might have been Blacklisted by Niantic. Exiting.."), + new KeyValuePair(TranslationString.NoEggsAvailable, "No Eggs Available"), + new KeyValuePair(TranslationString.UseLuckyEggActive, + "Lucky Egg Already Active. Time remaining: {0}m{1}s"), + new KeyValuePair(TranslationString.UsedLuckyEgg, "Used Lucky Egg"), + new KeyValuePair(TranslationString.UseLuckyEggAmount, + "Lucky Eggs in Inventory: {0}"), + new KeyValuePair(TranslationString.NoIncenseAvailable, + "No Incense Available"), + new KeyValuePair(TranslationString.UseIncenseActive, + "Incense Already Active"), + new KeyValuePair(TranslationString.UseIncenseAmount, + "Incense in Inventory: {0}"), + new KeyValuePair(TranslationString.UsedIncense, "Used an Incense"), + new KeyValuePair(TranslationString.AmountPkmSeenCaught, + "Pokemon Seen: {0}/{1}, Pokemon Caught: {2}/{3}"), + new KeyValuePair(TranslationString.PkmPotentialEvolveCount, + "Potential Evolutions: {0}"), + new KeyValuePair(TranslationString.PkmNotEnoughRessources, + "Pokemon Upgrade Failed Not Enough Resources"), + new KeyValuePair(TranslationString.SnipeServerOffline, + "Sniping server is offline. Skipping..."), + new KeyValuePair(TranslationString.FirstStartPrompt, + "This is your first start, would you like to begin setup? {0}/{1}"), + new KeyValuePair(TranslationString.FirstStartLanguagePrompt, + "Would you like to change the default language? {0}/{1}"), + new KeyValuePair(TranslationString.FirstStartLanguageCodePrompt, + "Please enter a new language code"), + new KeyValuePair(TranslationString.FirstStartLanguageConfirm, + "Language Code Applied: {0}"), + new KeyValuePair(TranslationString.FirstStartSetupTelegramPrompt, + "Would you like to enable the Telegram Feature? {0}/{1}"), + new KeyValuePair(TranslationString.FirstStartSetupTelegramCodePrompt, + "Please enter your Telegram API Key (Right click to paste)"), + new KeyValuePair(TranslationString.FirstStartSetupTelegramCodeConfirm, + "Accepted Telegram API Key: {0}"), + new KeyValuePair(TranslationString.FirstStartSetupTelegramPasswordPrompt, + "Please enter your Telegram Password (DEFAULT: 12345)"), + new KeyValuePair(TranslationString.FirstStartSetupTelegramPasswordConfirm, + "Accepted Telegram Password: {0}"), + new KeyValuePair(TranslationString.FirstStartSetupProxyPrompt, + "Would you like to enable the Proxy Feature? {0}/{1}"), + new KeyValuePair(TranslationString.FirstStartSetupProxyHostPrompt, + "Please enter your Proxy Host"), + new KeyValuePair(TranslationString.FirstStartSetupProxyHostConfirm, + "Accepted Proxy Host: {0}"), + new KeyValuePair(TranslationString.FirstStartSetupProxyPortPrompt, + "Please enter your Proxy Port"), + new KeyValuePair(TranslationString.FirstStartSetupProxyPortConfirm, + "Accepted Proxy Port: {0}"), + new KeyValuePair(TranslationString.FirstStartSetupProxyAuthPrompt, + "Would you like to enable the Proxy Authentication? {0}/{1}"), + new KeyValuePair(TranslationString.FirstStartSetupProxyUsernamePrompt, + "Please enter your Proxy Username (Right click to paste)"), + new KeyValuePair(TranslationString.FirstStartSetupProxyUsernameConfirm, + "Accepted Proxy Username: {0}"), + new KeyValuePair(TranslationString.FirstStartSetupProxyPasswordPrompt, + "Please enter your Proxy Password (Right click to paste)"), + new KeyValuePair(TranslationString.FirstStartSetupProxyPasswordConfirm, + "Accepted Proxy Password: {0}"), + //new KeyValuePair(TranslationString.FirstStartSetupAutoCompleteTutPrompt, "Would you like to enable the Auto-Complete Tutorial Feature? {0}/{1}"), + new KeyValuePair( + TranslationString.FirstStartSetupAutoCompleteTutNicknamePrompt, + "Please enter your desired Nickname"), + //new KeyValuePair(TranslationString.FirstStartSetupAutoCompleteTutNicknameConfirm, "Accepted Nickname: {0}"), + new KeyValuePair( + TranslationString.FirstStartSetupAutoCompleteTutGenderPrompt, + "Please enter your desired Gender (ENGLISH WORD: Male or Female)"), + //new KeyValuePair(TranslationString.FirstStartSetupAutoCompleteTutGenderConfirm, "Accepted Gender: {0}"), + new KeyValuePair( + TranslationString.FirstStartSetupAutoCompleteTutStarterPrompt, + "Please enter your desired Starter (ENGLISH NAME: Bulbasaur, Charmander, Squirtle)"), + //new KeyValuePair(TranslationString.FirstStartSetupAutoCompleteTutStarterConfirm, "Accepted Starter: {0}"), + new KeyValuePair(TranslationString.FirstStartSetupWebSocketPrompt, + "Would you like to enable the WebSocket Feature? {0}/{1}"), + new KeyValuePair(TranslationString.FirstStartSetupWebSocketPortPrompt, + "Please enter your WebSocket Port (default 14251)"), + new KeyValuePair(TranslationString.FirstStartSetupWebSocketPortConfirm, + "Accepted Port: {0}"), + new KeyValuePair(TranslationString.FirstStartSetupWalkingSpeedPrompt, + "Would you like to edit the Walking Speed Settings? {0}/{1}"), + new KeyValuePair(TranslationString.FirstStartSetupWalkingSpeedKmHPrompt, + "Please enter your desired walking speed (km/h) Ex: 5.85 [MAX 20]"), + new KeyValuePair(TranslationString.FirstStartSetupWalkingSpeedKmHConfirm, + "Accepted Walking Speed: {0}"), + new KeyValuePair( + TranslationString.FirstStartSetupUseWalkingSpeedVariantPrompt, + "Would you like to enable the Walking Speed Variant Setting? {0}/{1}"), + new KeyValuePair(TranslationString.FirstStartSetupWalkingSpeedVariantPrompt, + "Please enter the walking speed variant (km/h) Ex: 1.2"), + new KeyValuePair(TranslationString.FirstStartSetupWalkingSpeedVariantConfirm, + "Accepted Variant: {0}"), + new KeyValuePair(TranslationString.PromptError, + "[INPUT ERROR] Error with input, please enter '{0}' or '{1}"), + new KeyValuePair(TranslationString.PromptErrorDouble, + "[INPUT ERROR] Error with input, please enter a valid number"), + new KeyValuePair(TranslationString.PromptErrorInteger, + "[INPUT ERROR] Error with input, please enter a valid integer number"), + new KeyValuePair(TranslationString.PromptErrorString, + "[INPUT ERROR] Error with input, please enter one of the following options: {0}"), + new KeyValuePair(TranslationString.FirstStartAutoGenSettings, + "Config/Auth file automatically generated and must be completed before continuing"), + new KeyValuePair(TranslationString.FirstStartSetupAccount, + "### Setting up new USER ACCOUNT ###"), + new KeyValuePair(TranslationString.FirstStartSetupTypePrompt, + "Please choose an account type: {0}/{1}"), + new KeyValuePair(TranslationString.FirstStartSetupTypeConfirm, + "Chosen Account Type: {0}"), + new KeyValuePair(TranslationString.FirstStartSetupTypePromptError, + "[ERROR] submitted an incorrect account type, please choose '{0}' or '{1}'"), + new KeyValuePair(TranslationString.FirstStartSetupUsernamePrompt, + "Please enter a Username"), + new KeyValuePair(TranslationString.FirstStartSetupUsernameConfirm, + "Accepted username: {0}"), + new KeyValuePair(TranslationString.FirstStartSetupPasswordPrompt, + "Please enter a Password"), + new KeyValuePair(TranslationString.FirstStartSetupPasswordConfirm, + "Accepted password: {0}"), + new KeyValuePair(TranslationString.FirstStartAccountCompleted, + "### User Account Completed ###"), + new KeyValuePair(TranslationString.FirstStartDefaultLocationPrompt, + "Would you like to setup a new Default Location? {0}/{1}"), + new KeyValuePair(TranslationString.FirstStartDefaultLocationSet, + "Default Location Applied"), + new KeyValuePair(TranslationString.FirstStartDefaultLocation, + "### Setting Default Position ###"), + new KeyValuePair(TranslationString.FirstStartSetupDefaultLocationError, + "[ERROR] Please input a correct LatLong for example: {0}"), + new KeyValuePair(TranslationString.FirstStartSetupDefaultLatLongPrompt, + "Please enter a Latitude and Longitude (Right click to paste) - Format: Value, Value"), + new KeyValuePair(TranslationString.FirstStartSetupDefaultLatLongConfirm, + "Lattitude and Longitude accepted: {0}"), + new KeyValuePair(TranslationString.SoftBanBypassed, + "Successfully bypassed!"), + new KeyValuePair(TranslationString.FirstStartSetupCompleted, + "### COMPLETED CONFIG SETUP ###"), + new KeyValuePair(TranslationString.PokedexCatchedTelegram, + "--- Pokedex catched --- \n"), + new KeyValuePair(TranslationString.PokedexPokemonCatchedTelegram, + "#{0} Name: {1} | Catched: {2} | Encountered: {3} \n"), + new KeyValuePair(TranslationString.PokedexNeededTelegram, + "--- Pokedex needed --- \n"), + new KeyValuePair(TranslationString.PokedexPokemonNeededTelegram, + "#{0}# Name: {1} \n"), + new KeyValuePair(TranslationString.LoggedInTelegram, + "You have been logged in sucessfully. Session is valid for 5 Minutes"), + new KeyValuePair(TranslationString.LoginFailedTelegram, + "Wrong Password or wrong Syntax! Use /login PASSWORD"), + new KeyValuePair(TranslationString.NotLoggedInTelegram, + "You are not logged in, use /login PASSWORD"), + new KeyValuePair(TranslationString.Proxied, + "Your IP is: {0} | Proxy IP is: {1}"), + new KeyValuePair(TranslationString.Unproxied, "Your IP is: {0}"), + new KeyValuePair(TranslationString.FixProxySettings, + "Press any key to exit so you can fix your proxy settings..."), + new KeyValuePair(TranslationString.UsageHelp, + "Invalid command arguments! \n Correct usage: \n {0}"), + new KeyValuePair(TranslationString.LoginRemainingTime, + "You are already logged in! \n Session valid for: ({0}:{1} seconds)"), + new KeyValuePair(TranslationString.HighestsPokemoHeader, + "====== {0} ======"), + new KeyValuePair(TranslationString.HighestsPokemoCell, + "# CP {0}/{1} | ({2}% {3}) | Lvl: {4} | {5}: {6} | {7}: {8} | {9}: {10} | {11}: {12}"), + new KeyValuePair(TranslationString.HumanWalkingVariant, + "Walking Speed: Has been changed, {0:n2} Km/h to {1:n2} Km/h"), + new KeyValuePair(TranslationString.AccountBanned, "Probably Permanent Ban!"), + new KeyValuePair(TranslationString.GoogleAPIWarning, + "Without a Google Api, you will have 2500 free quota limit, if you reach the maximum quota, try to change your IP. To configure \"GoogleAPIKey\", get API Key in link: https://developers.google.com/maps/documentation/directions/get-api-key"), + new KeyValuePair(TranslationString.Only10kmEggs, + "Player below level 20, saving this 10 km Egg for later"), + new KeyValuePair(TranslationString.AutoSnipeDisabled, + "Your are out of PokeBalls because of sniping pretty fast, you can reduce sniping speed by updating MinIVForAutoSnipe or SnipePokemonFilters, Auto snipe will be disabled in {0} mins"), + new KeyValuePair(TranslationString.SniperCount, "Sniper count {0}"), + new KeyValuePair(TranslationString.SnipeExceeds, + "Sniper needs to take a rest before your account is banned"), + new KeyValuePair(TranslationString.CatchExceeds, + "You are catching too fast. Your cannot catch another one until {0} seconds later"), + new KeyValuePair(TranslationString.PokeStopExceeds, + "You are visiting pokestops too fast. Your cannot visit another one until {0} seconds later"), + new KeyValuePair(TranslationString.CatchLimitReached, + "You have reached your set maximum number of Pokemon catched per 24 Hours. Catching disabled until the limits expire."), + new KeyValuePair(TranslationString.CatchTimerReached, + "You have reached your set maximum running time for Catching Pokemon per 24 Hours. Catching disabled until the limits expire."), + new KeyValuePair(TranslationString.PokestopLimitReached, + "You have reached your set maximum number of Pokestops per 24 Hours. Looting of Pokestops disabled until the limits expire."), + new KeyValuePair(TranslationString.PokestopTimerReached, + "You have reached your set maximum running time to loot Pokestops per 24 Hours. Looting of Pokestops disabled until the limits expire."), + new KeyValuePair(TranslationString.ExitDueToLimitsReached, + "We have reached both the user defined max Pokestops/max duration and caught max Pokemons/max duration. Time to take a break. Exiting."), + new KeyValuePair(TranslationString.HumanWalkSnipe, + "(HUMAN WALK) Found {0} spawning | lat: {1}, lng: {2}, dist {3:0.00}m , expired in {4:00} min {5:00} sec | Estimate : {6:00} min {7:00} sec | Allow spin: {8} | Allow catch : {9} | Speed {10:0.00}km/h"), + new KeyValuePair(TranslationString.HumanWalkSnipeUpdate, + "(HUMAN WALK) Found {0} pokemon matched with you filters. Human walking sniper won't catch em all. but try to maximun catch if possible."), + new KeyValuePair(TranslationString.HumanWalkSnipeAddedPokestop, + "(HUMAN WALK) You are {0:0.00m} away from nearest pokestop. Restart farming at this place with {1} pokestops."), + new KeyValuePair(TranslationString.HumanWalkSnipeDestinationReached, + "(HUMAN WALK) destination reached | lat: {0}, lng: {1} | wait :{2:0.00} sec"), + new KeyValuePair(TranslationString.HumanWalkSnipeNotEnoughtBalls, + "(HUMAN WALK) Not enough Pokeballs to activate catch Em-All mode. ({0})/{1}"), + new KeyValuePair(TranslationString.HumanWalkSnipePokemonEncountered, + "(HUMAN WALK) Encountered {0} | lat :{1} , Lng : {2} | removed from snipping list"), + new KeyValuePair(TranslationString.MinimumClientVersionException, + "(KILLSWITCH) We have detected a Pokemon API change. The bot emulates API version {0}, which is no longer supported. Minimum API version is now {1}."), + new KeyValuePair(TranslationString.ExitNowAfterEnterKey, + "The bot will now exit after hitting the enter key."), + new KeyValuePair(TranslationString.CaptchaShown, + "Captcha is being shown and will need to be solved."), + new KeyValuePair(TranslationString.CatchPokemonDisable, + "Too few Pokeballs. Temporarily disabling the catching of wild Pokemon for {0} min or until we have {1} balls again."), + new KeyValuePair(TranslationString.FailedSendNotification, + "Notification sending failed."), + new KeyValuePair(TranslationString.TelegramBotStarted, + "Bot has been started"), + new KeyValuePair(TranslationString.TelegramNeedChatId, + "To received notification from telgram, please initial a first message chat with bot."), + new KeyValuePair(TranslationString.BuddyPokemonUpdate, + "{0} now is your buddy!!!"), + new KeyValuePair(TranslationString.TelegramCommandAccountsDescription, + "Shows configured bot accounts"), + new KeyValuePair(TranslationString.TelegramCommandAllDescription, + "Shows your Pokemon"), + new KeyValuePair(TranslationString.TelegramCommandExitDescription, + "Exit Bot"), + new KeyValuePair(TranslationString.TelegramCommandHelpDescription, + "Shows this help"), + new KeyValuePair(TranslationString.TelegramCommandItemsDescription, + "Shows your iventory items"), + new KeyValuePair(TranslationString.TelegramCommandLogsDescription, + "Shows last 'n' log entries (defaults to {0})"), + new KeyValuePair(TranslationString.TelegramCommandPokedexDescription, + "Shows your Pokedex"), + new KeyValuePair(TranslationString.TelegramCommandProfileDescription, + "Shows your profile"), + new KeyValuePair(TranslationString.TelegramCommandRecycleDescription, + "Recycle items now"), + new KeyValuePair(TranslationString.TelegramCommandRestartDescription, + "Restart Bot"), + new KeyValuePair(TranslationString.TelegramCommandSnipeDescription, + "Perform snipe"), + new KeyValuePair(TranslationString.TelegramCommandStatusDescription, + "Shows bot runtime stats"), + new KeyValuePair(TranslationString.TelegramCommandTopDescription, + "Shows Top 'n' Pokemon (defaults to {0})"), + new KeyValuePair(TranslationString.TelegramCommandLocDescription, + "Shows Bot location"), + new KeyValuePair(TranslationString.TelegramCommandAccountsMsgHead, + ""), + new KeyValuePair(TranslationString.TelegramCommandAllMsgHead, + "{0}'s Pokemon:"), + new KeyValuePair(TranslationString.TelegramCommandExitMsgHead, + "Closing Bot... BYE BYE {0}!"), + new KeyValuePair(TranslationString.TelegramCommandHelpMsgHead, + "Help Command\nArguments in [] are optional\nArguments in <> are mandatory\n| means or"), + new KeyValuePair(TranslationString.TelegramCommandItemsMsgHead, + "{0}'s Inventory:"), + new KeyValuePair(TranslationString.TelegramCommandLogsMsgHead, + "{0}'s last {1} log entries:"), + new KeyValuePair(TranslationString.TelegramCommandPokedexMsgHead, + "{0}'s Pokedex:"), + new KeyValuePair(TranslationString.TelegramCommandProfileMsgHead, + "{0}'s Profile:"), + new KeyValuePair(TranslationString.TelegramCommandRecycleMsgHead, + "Recycling {0}'s items"), + new KeyValuePair(TranslationString.TelegramCommandRestartMsgHead, + "Restarting ... Current account is {0}"), + new KeyValuePair(TranslationString.TelegramCommandSnipeMsgHead, + "Snipe accepted for user {0}"), + new KeyValuePair(TranslationString.TelegramCommandStatusMsgHead, + "{0}'s bot status:"), + new KeyValuePair(TranslationString.TelegramCommandTopMsgHead, + "{0}'s top Pokemon:"), + new KeyValuePair(TranslationString.TelegramCommandLocMsgHead, + "{0}'s location:"), + new KeyValuePair(TranslationString.TelegramCommandProfileMsgBody, + "Account: {0}\nLvl: {1}\nPokecoins: {2}\nTotal XP: {3}\nXP until level up: {4}\nPokemon caught: {5}\nPokemon sent: {6}\nPokemon in bag: {7}\nPokemon evolved: {8}\nPokestops visited: {9}\nItems in bag: {10}\nStardust: {11}\nEggs hatched: {12}\nPokedex entries: {13}\nKM walked: {14:n0}"), + new KeyValuePair(TranslationString.TelegramCommandStatusMsgBody, + "Bot: NecroBot2 v{0}\nAccount: {1}\nRuntime: {2}\nLvl: {3}\nStardust: {15}\nPokecoins: {16}\nAdvance in: {4}h {5}m | {6} EP\nXP / h: {7:n0}\nPokemon / h: {8:n0}\nStardust / h: {9:n0}\nPokemon Sent: {10}\nPokemon Evolved: {11}\nRecycled: {12}\nPokestop limit: {13}\nCatch limit: {14}"), + }; + + [JsonProperty("PokemonStrings", + ItemTypeNameHandling = TypeNameHandling.Arrays, + ItemConverterType = typeof(KeyValuePairConverter), + ObjectCreationHandling = ObjectCreationHandling.Replace, + DefaultValueHandling = DefaultValueHandling.Populate)] + private readonly + List> _pokemonTranslationStrings = + new List>() + { + new KeyValuePair((PokemonId) 001, "Bulbasaur"), + new KeyValuePair((PokemonId) 002, "Ivysaur"), + new KeyValuePair((PokemonId) 003, "Venusaur"), + new KeyValuePair((PokemonId) 004, "Charmander"), + new KeyValuePair((PokemonId) 005, "Charmeleon"), + new KeyValuePair((PokemonId) 006, "Charizard"), + new KeyValuePair((PokemonId) 007, "Squirtle"), + new KeyValuePair((PokemonId) 008, "Wartortle"), + new KeyValuePair((PokemonId) 009, "Blastoise"), + new KeyValuePair((PokemonId) 010, "Caterpie"), + new KeyValuePair((PokemonId) 011, "Metapod"), + new KeyValuePair((PokemonId) 012, "Butterfree"), + new KeyValuePair((PokemonId) 013, "Weedle"), + new KeyValuePair((PokemonId) 014, "Kakuna"), + new KeyValuePair((PokemonId) 015, "Beedrill"), + new KeyValuePair((PokemonId) 016, "Pidgey"), + new KeyValuePair((PokemonId) 017, "Pidgeotto"), + new KeyValuePair((PokemonId) 018, "Pidgeot"), + new KeyValuePair((PokemonId) 019, "Rattata"), + new KeyValuePair((PokemonId) 020, "Raticate"), + new KeyValuePair((PokemonId) 021, "Spearow"), + new KeyValuePair((PokemonId) 022, "Fearow"), + new KeyValuePair((PokemonId) 023, "Ekans"), + new KeyValuePair((PokemonId) 024, "Arbok"), + new KeyValuePair((PokemonId) 025, "Pikachu"), + new KeyValuePair((PokemonId) 026, "Raichu"), + new KeyValuePair((PokemonId) 027, "Sandshrew"), + new KeyValuePair((PokemonId) 028, "Sandslash"), + new KeyValuePair((PokemonId) 029, "Nidoran(F)"), + new KeyValuePair((PokemonId) 030, "Nidorina"), + new KeyValuePair((PokemonId) 031, "Nidoqueen"), + new KeyValuePair((PokemonId) 032, "Nidoran(M)"), + new KeyValuePair((PokemonId) 033, "Nidorino"), + new KeyValuePair((PokemonId) 034, "Nidoking"), + new KeyValuePair((PokemonId) 035, "Clefairy"), + new KeyValuePair((PokemonId) 036, "Clefable"), + new KeyValuePair((PokemonId) 037, "Vulpix"), + new KeyValuePair((PokemonId) 038, "Ninetales"), + new KeyValuePair((PokemonId) 039, "Jigglypuff"), + new KeyValuePair((PokemonId) 040, "Wigglytuff"), + new KeyValuePair((PokemonId) 041, "Zubat"), + new KeyValuePair((PokemonId) 042, "Golbat"), + new KeyValuePair((PokemonId) 043, "Oddish"), + new KeyValuePair((PokemonId) 044, "Gloom"), + new KeyValuePair((PokemonId) 045, "Vileplume"), + new KeyValuePair((PokemonId) 046, "Paras"), + new KeyValuePair((PokemonId) 047, "Parasect"), + new KeyValuePair((PokemonId) 048, "Venonat"), + new KeyValuePair((PokemonId) 049, "Venomoth"), + new KeyValuePair((PokemonId) 050, "Diglett"), + new KeyValuePair((PokemonId) 051, "Dugtrio"), + new KeyValuePair((PokemonId) 052, "Meowth"), + new KeyValuePair((PokemonId) 053, "Persian"), + new KeyValuePair((PokemonId) 054, "Psyduck"), + new KeyValuePair((PokemonId) 055, "Golduck"), + new KeyValuePair((PokemonId) 056, "Mankey"), + new KeyValuePair((PokemonId) 057, "Primeape"), + new KeyValuePair((PokemonId) 058, "Growlithe"), + new KeyValuePair((PokemonId) 059, "Arcanine"), + new KeyValuePair((PokemonId) 060, "Poliwag"), + new KeyValuePair((PokemonId) 061, "Poliwhirl"), + new KeyValuePair((PokemonId) 062, "Poliwrath"), + new KeyValuePair((PokemonId) 063, "Abra"), + new KeyValuePair((PokemonId) 064, "Kadabra"), + new KeyValuePair((PokemonId) 065, "Alakazam"), + new KeyValuePair((PokemonId) 066, "Machop"), + new KeyValuePair((PokemonId) 067, "Machoke"), + new KeyValuePair((PokemonId) 068, "Machamp"), + new KeyValuePair((PokemonId) 069, "Bellsprout"), + new KeyValuePair((PokemonId) 070, "Weepinbell"), + new KeyValuePair((PokemonId) 071, "Victreebel"), + new KeyValuePair((PokemonId) 072, "Tentacool"), + new KeyValuePair((PokemonId) 073, "Tentacruel"), + new KeyValuePair((PokemonId) 074, "Geodude"), + new KeyValuePair((PokemonId) 075, "Graveler"), + new KeyValuePair((PokemonId) 076, "Golem"), + new KeyValuePair((PokemonId) 077, "Ponyta"), + new KeyValuePair((PokemonId) 078, "Rapidash"), + new KeyValuePair((PokemonId) 079, "Slowpoke"), + new KeyValuePair((PokemonId) 080, "Slowbro"), + new KeyValuePair((PokemonId) 081, "Magnemite"), + new KeyValuePair((PokemonId) 082, "Magneton"), + new KeyValuePair((PokemonId) 083, "Farfetchd"), + new KeyValuePair((PokemonId) 084, "Doduo"), + new KeyValuePair((PokemonId) 085, "Dodrio"), + new KeyValuePair((PokemonId) 086, "Seel"), + new KeyValuePair((PokemonId) 087, "Dewgong"), + new KeyValuePair((PokemonId) 088, "Grimer"), + new KeyValuePair((PokemonId) 089, "Muk"), + new KeyValuePair((PokemonId) 090, "Shellder"), + new KeyValuePair((PokemonId) 091, "Cloyster"), + new KeyValuePair((PokemonId) 092, "Gastly"), + new KeyValuePair((PokemonId) 093, "Haunter"), + new KeyValuePair((PokemonId) 094, "Gengar"), + new KeyValuePair((PokemonId) 095, "Onix"), + new KeyValuePair((PokemonId) 096, "Drowzee"), + new KeyValuePair((PokemonId) 097, "Hypno"), + new KeyValuePair((PokemonId) 098, "Krabby"), + new KeyValuePair((PokemonId) 099, "Kingler"), + new KeyValuePair((PokemonId) 100, "Voltorb"), + new KeyValuePair((PokemonId) 101, "Electrode"), + new KeyValuePair((PokemonId) 102, "Exeggcute"), + new KeyValuePair((PokemonId) 103, "Exeggutor"), + new KeyValuePair((PokemonId) 104, "Cubone"), + new KeyValuePair((PokemonId) 105, "Marowak"), + new KeyValuePair((PokemonId) 106, "Hitmonlee"), + new KeyValuePair((PokemonId) 107, "Hitmonchan"), + new KeyValuePair((PokemonId) 108, "Lickitung"), + new KeyValuePair((PokemonId) 109, "Koffing"), + new KeyValuePair((PokemonId) 110, "Weezing"), + new KeyValuePair((PokemonId) 111, "Rhyhorn"), + new KeyValuePair((PokemonId) 112, "Rhydon"), + new KeyValuePair((PokemonId) 113, "Chansey"), + new KeyValuePair((PokemonId) 114, "Tangela"), + new KeyValuePair((PokemonId) 115, "Kangaskhan"), + new KeyValuePair((PokemonId) 116, "Horsea"), + new KeyValuePair((PokemonId) 117, "Seadra"), + new KeyValuePair((PokemonId) 118, "Goldeen"), + new KeyValuePair((PokemonId) 119, "Seaking"), + new KeyValuePair((PokemonId) 120, "Staryu"), + new KeyValuePair((PokemonId) 121, "Starmie"), + new KeyValuePair((PokemonId) 122, "Mr. Mime"), + new KeyValuePair((PokemonId) 123, "Scyther"), + new KeyValuePair((PokemonId) 124, "Jynx"), + new KeyValuePair((PokemonId) 125, "Electabuzz"), + new KeyValuePair((PokemonId) 126, "Magmar"), + new KeyValuePair((PokemonId) 127, "Pinsir"), + new KeyValuePair((PokemonId) 128, "Tauros"), + new KeyValuePair((PokemonId) 129, "Magikarp"), + new KeyValuePair((PokemonId) 130, "Gyarados"), + new KeyValuePair((PokemonId) 131, "Lapras"), + new KeyValuePair((PokemonId) 132, "Ditto"), + new KeyValuePair((PokemonId) 133, "Eevee"), + new KeyValuePair((PokemonId) 134, "Vaporeon"), + new KeyValuePair((PokemonId) 135, "Jolteon"), + new KeyValuePair((PokemonId) 136, "Flareon"), + new KeyValuePair((PokemonId) 137, "Porygon"), + new KeyValuePair((PokemonId) 138, "Omanyte"), + new KeyValuePair((PokemonId) 139, "Omastar"), + new KeyValuePair((PokemonId) 140, "Kabuto"), + new KeyValuePair((PokemonId) 141, "Kabutops"), + new KeyValuePair((PokemonId) 142, "Aerodactyl"), + new KeyValuePair((PokemonId) 143, "Snorlax"), + new KeyValuePair((PokemonId) 144, "Articuno"), + new KeyValuePair((PokemonId) 145, "Zapdos"), + new KeyValuePair((PokemonId) 146, "Moltres"), + new KeyValuePair((PokemonId) 147, "Dratini"), + new KeyValuePair((PokemonId) 148, "Dragonair"), + new KeyValuePair((PokemonId) 149, "Dragonite"), + new KeyValuePair((PokemonId) 150, "Mewtwo"), + new KeyValuePair((PokemonId) 151, "Mew"), + new KeyValuePair((PokemonId) 152, "Chikorita"), +new KeyValuePair((PokemonId) 153, "Bayleef"), +new KeyValuePair((PokemonId) 154, "Meganium"), +new KeyValuePair((PokemonId) 155, "Cyndaquil"), +new KeyValuePair((PokemonId) 156, "Quilava"), +new KeyValuePair((PokemonId) 157, "Typhlosion"), +new KeyValuePair((PokemonId) 158, "Totodile"), +new KeyValuePair((PokemonId) 159, "Croconaw"), +new KeyValuePair((PokemonId) 160, "Feraligatr"), +new KeyValuePair((PokemonId) 161, "Sentret"), +new KeyValuePair((PokemonId) 162, "Furret"), +new KeyValuePair((PokemonId) 163, "Hoothoot"), +new KeyValuePair((PokemonId) 164, "Noctowl"), +new KeyValuePair((PokemonId) 165, "Ledyba"), +new KeyValuePair((PokemonId) 166, "Ledian"), +new KeyValuePair((PokemonId) 167, "Spinarak"), +new KeyValuePair((PokemonId) 168, "Ariados"), +new KeyValuePair((PokemonId) 169, "Crobat"), +new KeyValuePair((PokemonId) 170, "Chinchou"), +new KeyValuePair((PokemonId) 171, "Lanturn"), +new KeyValuePair((PokemonId) 172, "Pichu"), +new KeyValuePair((PokemonId) 173, "Cleffa"), +new KeyValuePair((PokemonId) 174, "Igglybuff"), +new KeyValuePair((PokemonId) 175, "Togepi"), +new KeyValuePair((PokemonId) 176, "Togetic"), +new KeyValuePair((PokemonId) 177, "Natu"), +new KeyValuePair((PokemonId) 178, "Xatu"), +new KeyValuePair((PokemonId) 179, "Mareep"), +new KeyValuePair((PokemonId) 180, "Flaaffy"), +new KeyValuePair((PokemonId) 181, "Ampharos"), +new KeyValuePair((PokemonId) 182, "Bellossom"), +new KeyValuePair((PokemonId) 183, "Marill"), +new KeyValuePair((PokemonId) 184, "Azumarill"), +new KeyValuePair((PokemonId) 185, "Sudowoodo"), +new KeyValuePair((PokemonId) 186, "Politoed"), +new KeyValuePair((PokemonId) 187, "Hoppip"), +new KeyValuePair((PokemonId) 188, "Skiploom"), +new KeyValuePair((PokemonId) 189, "Jumpluff"), +new KeyValuePair((PokemonId) 190, "Aipom"), +new KeyValuePair((PokemonId) 191, "Sunkern"), +new KeyValuePair((PokemonId) 192, "Sunflora"), +new KeyValuePair((PokemonId) 193, "Yanma"), +new KeyValuePair((PokemonId) 194, "Wooper"), +new KeyValuePair((PokemonId) 195, "Quagsire"), +new KeyValuePair((PokemonId) 196, "Espeon"), +new KeyValuePair((PokemonId) 197, "Umbreon"), +new KeyValuePair((PokemonId) 198, "Murkrow"), +new KeyValuePair((PokemonId) 199, "Slowking"), +new KeyValuePair((PokemonId) 200, "Misdreavus"), +new KeyValuePair((PokemonId) 201, "Unown"), +new KeyValuePair((PokemonId) 202, "Wobbuffet"), +new KeyValuePair((PokemonId) 203, "Girafarig"), +new KeyValuePair((PokemonId) 204, "Pineco"), +new KeyValuePair((PokemonId) 205, "Forretress"), +new KeyValuePair((PokemonId) 206, "Dunsparce"), +new KeyValuePair((PokemonId) 207, "Gligar"), +new KeyValuePair((PokemonId) 208, "Steelix"), +new KeyValuePair((PokemonId) 209, "Snubbull"), +new KeyValuePair((PokemonId) 210, "Granbull"), +new KeyValuePair((PokemonId) 211, "Qwilfish"), +new KeyValuePair((PokemonId) 212, "Scizor"), +new KeyValuePair((PokemonId) 213, "Shuckle"), +new KeyValuePair((PokemonId) 214, "Heracross"), +new KeyValuePair((PokemonId) 215, "Sneasel"), +new KeyValuePair((PokemonId) 216, "Teddiursa"), +new KeyValuePair((PokemonId) 217, "Ursaring"), +new KeyValuePair((PokemonId) 218, "Slugma"), +new KeyValuePair((PokemonId) 219, "Magcargo"), +new KeyValuePair((PokemonId) 220, "Swinub"), +new KeyValuePair((PokemonId) 221, "Piloswine"), +new KeyValuePair((PokemonId) 222, "Corsola"), +new KeyValuePair((PokemonId) 223, "Remoraid"), +new KeyValuePair((PokemonId) 224, "Octillery"), +new KeyValuePair((PokemonId) 225, "Delibird"), +new KeyValuePair((PokemonId) 226, "Mantine"), +new KeyValuePair((PokemonId) 227, "Skarmory"), +new KeyValuePair((PokemonId) 228, "Houndour"), +new KeyValuePair((PokemonId) 229, "Houndoom"), +new KeyValuePair((PokemonId) 230, "Kingdra"), +new KeyValuePair((PokemonId) 231, "Phanpy"), +new KeyValuePair((PokemonId) 232, "Donphan"), +new KeyValuePair((PokemonId) 233, "Porygon2"), +new KeyValuePair((PokemonId) 234, "Stantler"), +new KeyValuePair((PokemonId) 235, "Smeargle"), +new KeyValuePair((PokemonId) 236, "Tyrogue"), +new KeyValuePair((PokemonId) 237, "Hitmontop"), +new KeyValuePair((PokemonId) 238, "Smoochum"), +new KeyValuePair((PokemonId) 239, "Elekid"), +new KeyValuePair((PokemonId) 240, "Magby"), +new KeyValuePair((PokemonId) 241, "Miltank"), +new KeyValuePair((PokemonId) 242, "Blissey"), +new KeyValuePair((PokemonId) 243, "Raikou"), +new KeyValuePair((PokemonId) 244, "Entei"), +new KeyValuePair((PokemonId) 245, "Suicune"), +new KeyValuePair((PokemonId) 246, "Larvitar"), +new KeyValuePair((PokemonId) 247, "Pupitar"), +new KeyValuePair((PokemonId) 248, "Tyranitar"), +new KeyValuePair((PokemonId) 249, "Lugia"), +new KeyValuePair((PokemonId) 250, "Ho-Oh"), +new KeyValuePair((PokemonId) 251, "Celebi"), +new KeyValuePair((PokemonId) 252, "Treecko"), +new KeyValuePair((PokemonId) 253, "Grovyle"), +new KeyValuePair((PokemonId) 254, "Sceptile"), +new KeyValuePair((PokemonId) 255, "Torchic"), +new KeyValuePair((PokemonId) 256, "Combusken"), +new KeyValuePair((PokemonId) 257, "Blaziken"), +new KeyValuePair((PokemonId) 258, "Mudkip"), +new KeyValuePair((PokemonId) 259, "Marshtomp"), +new KeyValuePair((PokemonId) 260, "Swampert"), +new KeyValuePair((PokemonId) 261, "Poochyena"), +new KeyValuePair((PokemonId) 262, "Mightyena"), +new KeyValuePair((PokemonId) 263, "Zigzagoon"), +new KeyValuePair((PokemonId) 264, "Linoone"), +new KeyValuePair((PokemonId) 265, "Wurmple"), +new KeyValuePair((PokemonId) 266, "Silcoon"), +new KeyValuePair((PokemonId) 267, "Beautifly"), +new KeyValuePair((PokemonId) 268, "Cascoon"), +new KeyValuePair((PokemonId) 269, "Dustox"), +new KeyValuePair((PokemonId) 270, "Lotad"), +new KeyValuePair((PokemonId) 271, "Lombre"), +new KeyValuePair((PokemonId) 272, "Ludicolo"), +new KeyValuePair((PokemonId) 273, "Seedot"), +new KeyValuePair((PokemonId) 274, "Nuzleaf"), +new KeyValuePair((PokemonId) 275, "Shiftry"), +new KeyValuePair((PokemonId) 276, "Taillow"), +new KeyValuePair((PokemonId) 277, "Swellow"), +new KeyValuePair((PokemonId) 278, "Wingull"), +new KeyValuePair((PokemonId) 279, "Pelipper"), +new KeyValuePair((PokemonId) 280, "Ralts"), +new KeyValuePair((PokemonId) 281, "Kirlia"), +new KeyValuePair((PokemonId) 282, "Gardevoir"), +new KeyValuePair((PokemonId) 283, "Surskit"), +new KeyValuePair((PokemonId) 284, "Masquerain"), +new KeyValuePair((PokemonId) 285, "Shroomish"), +new KeyValuePair((PokemonId) 286, "Breloom"), +new KeyValuePair((PokemonId) 287, "Slakoth"), +new KeyValuePair((PokemonId) 288, "Vigoroth"), +new KeyValuePair((PokemonId) 289, "Slaking"), +new KeyValuePair((PokemonId) 290, "Nincada"), +new KeyValuePair((PokemonId) 291, "Ninjask"), +new KeyValuePair((PokemonId) 292, "Shedinja"), +new KeyValuePair((PokemonId) 293, "Whismur"), +new KeyValuePair((PokemonId) 294, "Loudred"), +new KeyValuePair((PokemonId) 295, "Exploud"), +new KeyValuePair((PokemonId) 296, "Makuhita"), +new KeyValuePair((PokemonId) 297, "Hariyama"), +new KeyValuePair((PokemonId) 298, "Azurill"), +new KeyValuePair((PokemonId) 299, "Nosepass"), +new KeyValuePair((PokemonId) 300, "Skitty"), +new KeyValuePair((PokemonId) 301, "Delcatty"), +new KeyValuePair((PokemonId) 302, "Sableye"), +new KeyValuePair((PokemonId) 303, "Mawile"), +new KeyValuePair((PokemonId) 304, "Aron"), +new KeyValuePair((PokemonId) 305, "Lairon"), +new KeyValuePair((PokemonId) 306, "Aggron"), +new KeyValuePair((PokemonId) 307, "Meditite"), +new KeyValuePair((PokemonId) 308, "Medicham"), +new KeyValuePair((PokemonId) 309, "Electrike"), +new KeyValuePair((PokemonId) 310, "Manectric"), +new KeyValuePair((PokemonId) 311, "Plusle"), +new KeyValuePair((PokemonId) 312, "Minun"), +new KeyValuePair((PokemonId) 313, "Volbeat"), +new KeyValuePair((PokemonId) 314, "Illumise"), +new KeyValuePair((PokemonId) 315, "Roselia"), +new KeyValuePair((PokemonId) 316, "Gulpin"), +new KeyValuePair((PokemonId) 317, "Swalot"), +new KeyValuePair((PokemonId) 318, "Carvanha"), +new KeyValuePair((PokemonId) 319, "Sharpedo"), +new KeyValuePair((PokemonId) 320, "Wailmer"), +new KeyValuePair((PokemonId) 321, "Wailord"), +new KeyValuePair((PokemonId) 322, "Numel"), +new KeyValuePair((PokemonId) 323, "Camerupt"), +new KeyValuePair((PokemonId) 324, "Torkoal"), +new KeyValuePair((PokemonId) 325, "Spoink"), +new KeyValuePair((PokemonId) 326, "Grumpig"), +new KeyValuePair((PokemonId) 327, "Spinda"), +new KeyValuePair((PokemonId) 328, "Trapinch"), +new KeyValuePair((PokemonId) 329, "Vibrava"), +new KeyValuePair((PokemonId) 330, "Flygon"), +new KeyValuePair((PokemonId) 331, "Cacnea"), +new KeyValuePair((PokemonId) 332, "Cacturne"), +new KeyValuePair((PokemonId) 333, "Swablu"), +new KeyValuePair((PokemonId) 334, "Altaria"), +new KeyValuePair((PokemonId) 335, "Zangoose"), +new KeyValuePair((PokemonId) 336, "Seviper"), +new KeyValuePair((PokemonId) 337, "Lunatone"), +new KeyValuePair((PokemonId) 338, "Solrock"), +new KeyValuePair((PokemonId) 339, "Barboach"), +new KeyValuePair((PokemonId) 340, "Whiscash"), +new KeyValuePair((PokemonId) 341, "Corphish"), +new KeyValuePair((PokemonId) 342, "Crawdaunt"), +new KeyValuePair((PokemonId) 343, "Baltoy"), +new KeyValuePair((PokemonId) 344, "Claydol"), +new KeyValuePair((PokemonId) 345, "Lileep"), +new KeyValuePair((PokemonId) 346, "Cradily"), +new KeyValuePair((PokemonId) 347, "Anorith"), +new KeyValuePair((PokemonId) 348, "Armaldo"), +new KeyValuePair((PokemonId) 349, "Feebas"), +new KeyValuePair((PokemonId) 350, "Milotic"), +new KeyValuePair((PokemonId) 351, "Castform"), +new KeyValuePair((PokemonId) 352, "Kecleon"), +new KeyValuePair((PokemonId) 353, "Shuppet"), +new KeyValuePair((PokemonId) 354, "Banette"), +new KeyValuePair((PokemonId) 355, "Duskull"), +new KeyValuePair((PokemonId) 356, "Dusclops"), +new KeyValuePair((PokemonId) 357, "Tropius"), +new KeyValuePair((PokemonId) 358, "Chimecho"), +new KeyValuePair((PokemonId) 359, "Absol"), +new KeyValuePair((PokemonId) 360, "Wynaut"), +new KeyValuePair((PokemonId) 361, "Snorunt"), +new KeyValuePair((PokemonId) 362, "Glalie"), +new KeyValuePair((PokemonId) 363, "Spheal"), +new KeyValuePair((PokemonId) 364, "Sealeo"), +new KeyValuePair((PokemonId) 365, "Walrein"), +new KeyValuePair((PokemonId) 366, "Clamperl"), +new KeyValuePair((PokemonId) 367, "Huntail"), +new KeyValuePair((PokemonId) 368, "Gorebyss"), +new KeyValuePair((PokemonId) 369, "Relicanth"), +new KeyValuePair((PokemonId) 370, "Luvdisc"), +new KeyValuePair((PokemonId) 371, "Bagon"), +new KeyValuePair((PokemonId) 372, "Shelgon"), +new KeyValuePair((PokemonId) 373, "Salamence"), +new KeyValuePair((PokemonId) 374, "Beldum"), +new KeyValuePair((PokemonId) 375, "Metang"), +new KeyValuePair((PokemonId) 376, "Metagross"), +new KeyValuePair((PokemonId) 377, "Regirock"), +new KeyValuePair((PokemonId) 378, "Regice"), +new KeyValuePair((PokemonId) 379, "Registeel"), +new KeyValuePair((PokemonId) 380, "Latias"), +new KeyValuePair((PokemonId) 381, "Latios"), +new KeyValuePair((PokemonId) 382, "Kyogre"), +new KeyValuePair((PokemonId) 383, "Groudon"), +new KeyValuePair((PokemonId) 384, "Rayquaza"), +new KeyValuePair((PokemonId) 385, "Jirachi"), +new KeyValuePair((PokemonId) 386, "Deoxys"), +new KeyValuePair((PokemonId) 387, "Turtwig"), +new KeyValuePair((PokemonId) 388, "Grotle"), +new KeyValuePair((PokemonId) 389, "Torterra"), +new KeyValuePair((PokemonId) 390, "Chimchar"), +new KeyValuePair((PokemonId) 391, "Monferno"), +new KeyValuePair((PokemonId) 392, "Infernape"), +new KeyValuePair((PokemonId) 393, "Piplup"), +new KeyValuePair((PokemonId) 394, "Prinplup"), +new KeyValuePair((PokemonId) 395, "Empoleon"), +new KeyValuePair((PokemonId) 396, "Starly"), +new KeyValuePair((PokemonId) 397, "Staravia"), +new KeyValuePair((PokemonId) 398, "Staraptor"), +new KeyValuePair((PokemonId) 399, "Bidoof"), +new KeyValuePair((PokemonId) 400, "Bibarel"), +new KeyValuePair((PokemonId) 401, "Kricketot"), +new KeyValuePair((PokemonId) 402, "Kricketune"), +new KeyValuePair((PokemonId) 403, "Shinx"), +new KeyValuePair((PokemonId) 404, "Luxio"), +new KeyValuePair((PokemonId) 405, "Luxray"), +new KeyValuePair((PokemonId) 406, "Budew"), +new KeyValuePair((PokemonId) 407, "Roserade"), +new KeyValuePair((PokemonId) 408, "Cranidos"), +new KeyValuePair((PokemonId) 409, "Rampardos"), +new KeyValuePair((PokemonId) 410, "Shieldon"), +new KeyValuePair((PokemonId) 411, "Bastiodon"), +new KeyValuePair((PokemonId) 412, "Burmy"), +new KeyValuePair((PokemonId) 413, "Wormadam"), +new KeyValuePair((PokemonId) 414, "Mothim"), +new KeyValuePair((PokemonId) 415, "Combee"), +new KeyValuePair((PokemonId) 416, "Vespiquen"), +new KeyValuePair((PokemonId) 417, "Pachirisu"), +new KeyValuePair((PokemonId) 418, "Buizel"), +new KeyValuePair((PokemonId) 419, "Floatzel"), +new KeyValuePair((PokemonId) 420, "Cherubi"), +new KeyValuePair((PokemonId) 421, "Cherrim"), +new KeyValuePair((PokemonId) 422, "Shellos"), +new KeyValuePair((PokemonId) 423, "Gastrodon"), +new KeyValuePair((PokemonId) 424, "Ambipom"), +new KeyValuePair((PokemonId) 425, "Drifloon"), +new KeyValuePair((PokemonId) 426, "Drifblim"), +new KeyValuePair((PokemonId) 427, "Buneary"), +new KeyValuePair((PokemonId) 428, "Lopunny"), +new KeyValuePair((PokemonId) 429, "Mismagius"), +new KeyValuePair((PokemonId) 430, "Honchkrow"), +new KeyValuePair((PokemonId) 431, "Glameow"), +new KeyValuePair((PokemonId) 432, "Purugly"), +new KeyValuePair((PokemonId) 433, "Chingling"), +new KeyValuePair((PokemonId) 434, "Stunky"), +new KeyValuePair((PokemonId) 435, "Skuntank"), +new KeyValuePair((PokemonId) 436, "Bronzor"), +new KeyValuePair((PokemonId) 437, "Bronzong"), +new KeyValuePair((PokemonId) 438, "Bonsly"), +new KeyValuePair((PokemonId) 439, "Mime Jr."), +new KeyValuePair((PokemonId) 440, "Happiny"), +new KeyValuePair((PokemonId) 441, "Chatot"), +new KeyValuePair((PokemonId) 442, "Spiritomb"), +new KeyValuePair((PokemonId) 443, "Gible"), +new KeyValuePair((PokemonId) 444, "Gabite"), +new KeyValuePair((PokemonId) 445, "Garchomp"), +new KeyValuePair((PokemonId) 446, "Munchlax"), +new KeyValuePair((PokemonId) 447, "Riolu"), +new KeyValuePair((PokemonId) 448, "Lucario"), +new KeyValuePair((PokemonId) 449, "Hippopotas"), +new KeyValuePair((PokemonId) 450, "Hippowdon"), +new KeyValuePair((PokemonId) 451, "Skorupi"), +new KeyValuePair((PokemonId) 452, "Drapion"), +new KeyValuePair((PokemonId) 453, "Croagunk"), +new KeyValuePair((PokemonId) 454, "Toxicroak"), +new KeyValuePair((PokemonId) 455, "Carnivine"), +new KeyValuePair((PokemonId) 456, "Finneon"), +new KeyValuePair((PokemonId) 457, "Lumineon"), +new KeyValuePair((PokemonId) 458, "Mantyke"), +new KeyValuePair((PokemonId) 459, "Snover"), +new KeyValuePair((PokemonId) 460, "Abomasnow"), +new KeyValuePair((PokemonId) 461, "Weavile"), +new KeyValuePair((PokemonId) 462, "Magnezone"), +new KeyValuePair((PokemonId) 463, "Lickilicky"), +new KeyValuePair((PokemonId) 464, "Rhyperior"), +new KeyValuePair((PokemonId) 465, "Tangrowth"), +new KeyValuePair((PokemonId) 466, "Electivire"), +new KeyValuePair((PokemonId) 467, "Magmortar"), +new KeyValuePair((PokemonId) 468, "Togekiss"), +new KeyValuePair((PokemonId) 469, "Yanmega"), +new KeyValuePair((PokemonId) 470, "Leafeon"), +new KeyValuePair((PokemonId) 471, "Glaceon"), +new KeyValuePair((PokemonId) 472, "Gliscor"), +new KeyValuePair((PokemonId) 473, "Mamoswine"), +new KeyValuePair((PokemonId) 474, "Porygon-Z"), +new KeyValuePair((PokemonId) 475, "Gallade"), +new KeyValuePair((PokemonId) 476, "Probopass"), +new KeyValuePair((PokemonId) 477, "Dusknoir"), +new KeyValuePair((PokemonId) 478, "Froslass"), +new KeyValuePair((PokemonId) 479, "Rotom"), +new KeyValuePair((PokemonId) 480, "Uxie"), +new KeyValuePair((PokemonId) 481, "Mesprit"), +new KeyValuePair((PokemonId) 482, "Azelf"), +new KeyValuePair((PokemonId) 483, "Dialga"), +new KeyValuePair((PokemonId) 484, "Palkia"), +new KeyValuePair((PokemonId) 485, "Heatran"), +new KeyValuePair((PokemonId) 486, "Regigigas"), +new KeyValuePair((PokemonId) 487, "Giratina"), +new KeyValuePair((PokemonId) 488, "Cresselia"), +new KeyValuePair((PokemonId) 489, "Phione"), +new KeyValuePair((PokemonId) 490, "Manaphy"), +new KeyValuePair((PokemonId) 491, "Darkrai"), +new KeyValuePair((PokemonId) 492, "Shaymin"), +new KeyValuePair((PokemonId) 493, "Arceus"), +new KeyValuePair((PokemonId) 494, "Victini"), +new KeyValuePair((PokemonId) 495, "Snivy"), +new KeyValuePair((PokemonId) 496, "Servine"), +new KeyValuePair((PokemonId) 497, "Serperior"), +new KeyValuePair((PokemonId) 498, "Tepig"), +new KeyValuePair((PokemonId) 499, "Pignite"), +new KeyValuePair((PokemonId) 500, "Emboar"), +new KeyValuePair((PokemonId) 501, "Oshawott"), +new KeyValuePair((PokemonId) 502, "Dewott"), +new KeyValuePair((PokemonId) 503, "Samurott"), +new KeyValuePair((PokemonId) 504, "Patrat"), +new KeyValuePair((PokemonId) 505, "Watchog"), +new KeyValuePair((PokemonId) 506, "Lillipup"), +new KeyValuePair((PokemonId) 507, "Herdier"), +new KeyValuePair((PokemonId) 508, "Stoutland"), +new KeyValuePair((PokemonId) 509, "Purrloin"), +new KeyValuePair((PokemonId) 510, "Liepard"), +new KeyValuePair((PokemonId) 511, "Pansage"), +new KeyValuePair((PokemonId) 512, "Simisage"), +new KeyValuePair((PokemonId) 513, "Pansear"), +new KeyValuePair((PokemonId) 514, "Simisear"), +new KeyValuePair((PokemonId) 515, "Panpour"), +new KeyValuePair((PokemonId) 516, "Simipour"), +new KeyValuePair((PokemonId) 517, "Munna"), +new KeyValuePair((PokemonId) 518, "Musharna"), +new KeyValuePair((PokemonId) 519, "Pidove"), +new KeyValuePair((PokemonId) 520, "Tranquill"), +new KeyValuePair((PokemonId) 521, "Unfezant"), +new KeyValuePair((PokemonId) 522, "Blitzle"), +new KeyValuePair((PokemonId) 523, "Zebstrika"), +new KeyValuePair((PokemonId) 524, "Roggenrola"), +new KeyValuePair((PokemonId) 525, "Boldore"), +new KeyValuePair((PokemonId) 526, "Gigalith"), +new KeyValuePair((PokemonId) 527, "Woobat"), +new KeyValuePair((PokemonId) 528, "Swoobat"), +new KeyValuePair((PokemonId) 529, "Drilbur"), +new KeyValuePair((PokemonId) 530, "Excadrill"), +new KeyValuePair((PokemonId) 531, "Audino"), +new KeyValuePair((PokemonId) 532, "Timburr"), +new KeyValuePair((PokemonId) 533, "Gurdurr"), +new KeyValuePair((PokemonId) 534, "Conkeldurr"), +new KeyValuePair((PokemonId) 535, "Tympole"), +new KeyValuePair((PokemonId) 536, "Palpitoad"), +new KeyValuePair((PokemonId) 537, "Seismitoad"), +new KeyValuePair((PokemonId) 538, "Throh"), +new KeyValuePair((PokemonId) 539, "Sawk"), +new KeyValuePair((PokemonId) 540, "Sewaddle"), +new KeyValuePair((PokemonId) 541, "Swadloon"), +new KeyValuePair((PokemonId) 542, "Leavanny"), +new KeyValuePair((PokemonId) 543, "Venipede"), +new KeyValuePair((PokemonId) 544, "Whirlipede"), +new KeyValuePair((PokemonId) 545, "Scolipede"), +new KeyValuePair((PokemonId) 546, "Cottonee"), +new KeyValuePair((PokemonId) 547, "Whimsicott"), +new KeyValuePair((PokemonId) 548, "Petilil"), +new KeyValuePair((PokemonId) 549, "Lilligant"), +new KeyValuePair((PokemonId) 550, "Basculin"), +new KeyValuePair((PokemonId) 551, "Sandile"), +new KeyValuePair((PokemonId) 552, "Krokorok"), +new KeyValuePair((PokemonId) 553, "Krookodile"), +new KeyValuePair((PokemonId) 554, "Darumaka"), +new KeyValuePair((PokemonId) 555, "Darmanitan"), +new KeyValuePair((PokemonId) 556, "Maractus"), +new KeyValuePair((PokemonId) 557, "Dwebble"), +new KeyValuePair((PokemonId) 558, "Crustle"), +new KeyValuePair((PokemonId) 559, "Scraggy"), +new KeyValuePair((PokemonId) 560, "Scrafty"), +new KeyValuePair((PokemonId) 561, "Sigilyph"), +new KeyValuePair((PokemonId) 562, "Yamask"), +new KeyValuePair((PokemonId) 563, "Cofagrigus"), +new KeyValuePair((PokemonId) 564, "Tirtouga"), +new KeyValuePair((PokemonId) 565, "Carracosta"), +new KeyValuePair((PokemonId) 566, "Archen"), +new KeyValuePair((PokemonId) 567, "Archeops"), +new KeyValuePair((PokemonId) 568, "Trubbish"), +new KeyValuePair((PokemonId) 569, "Garbodor"), +new KeyValuePair((PokemonId) 570, "Zorua"), +new KeyValuePair((PokemonId) 571, "Zoroark"), +new KeyValuePair((PokemonId) 572, "Minccino"), +new KeyValuePair((PokemonId) 573, "Cinccino"), +new KeyValuePair((PokemonId) 574, "Gothita"), +new KeyValuePair((PokemonId) 575, "Gothorita"), +new KeyValuePair((PokemonId) 576, "Gothitelle"), +new KeyValuePair((PokemonId) 577, "Solosis"), +new KeyValuePair((PokemonId) 578, "Duosion"), +new KeyValuePair((PokemonId) 579, "Reuniclus"), +new KeyValuePair((PokemonId) 580, "Ducklett"), +new KeyValuePair((PokemonId) 581, "Swanna"), +new KeyValuePair((PokemonId) 582, "Vanillite"), +new KeyValuePair((PokemonId) 583, "Vanillish"), +new KeyValuePair((PokemonId) 584, "Vanilluxe"), +new KeyValuePair((PokemonId) 585, "Deerling"), +new KeyValuePair((PokemonId) 586, "Sawsbuck"), +new KeyValuePair((PokemonId) 587, "Emolga"), +new KeyValuePair((PokemonId) 588, "Karrablast"), +new KeyValuePair((PokemonId) 589, "Escavalier"), +new KeyValuePair((PokemonId) 590, "Foongus"), +new KeyValuePair((PokemonId) 591, "Amoonguss"), +new KeyValuePair((PokemonId) 592, "Frillish"), +new KeyValuePair((PokemonId) 593, "Jellicent"), +new KeyValuePair((PokemonId) 594, "Alomomola"), +new KeyValuePair((PokemonId) 595, "Joltik"), +new KeyValuePair((PokemonId) 596, "Galvantula"), +new KeyValuePair((PokemonId) 597, "Ferroseed"), +new KeyValuePair((PokemonId) 598, "Ferrothorn"), +new KeyValuePair((PokemonId) 599, "Klink"), +new KeyValuePair((PokemonId) 600, "Klang"), +new KeyValuePair((PokemonId) 601, "Klinklang"), +new KeyValuePair((PokemonId) 602, "Tynamo"), +new KeyValuePair((PokemonId) 603, "Eelektrik"), +new KeyValuePair((PokemonId) 604, "Eelektross"), +new KeyValuePair((PokemonId) 605, "Elgyem"), +new KeyValuePair((PokemonId) 606, "Beheeyem"), +new KeyValuePair((PokemonId) 607, "Litwick"), +new KeyValuePair((PokemonId) 608, "Lampent"), +new KeyValuePair((PokemonId) 609, "Chandelure"), +new KeyValuePair((PokemonId) 610, "Axew"), +new KeyValuePair((PokemonId) 611, "Fraxure"), +new KeyValuePair((PokemonId) 612, "Haxorus"), +new KeyValuePair((PokemonId) 613, "Cubchoo"), +new KeyValuePair((PokemonId) 614, "Beartic"), +new KeyValuePair((PokemonId) 615, "Cryogonal"), +new KeyValuePair((PokemonId) 616, "Shelmet"), +new KeyValuePair((PokemonId) 617, "Accelgor"), +new KeyValuePair((PokemonId) 618, "Stunfisk"), +new KeyValuePair((PokemonId) 619, "Mienfoo"), +new KeyValuePair((PokemonId) 620, "Mienshao"), +new KeyValuePair((PokemonId) 621, "Druddigon"), +new KeyValuePair((PokemonId) 622, "Golett"), +new KeyValuePair((PokemonId) 623, "Golurk"), +new KeyValuePair((PokemonId) 624, "Pawniard"), +new KeyValuePair((PokemonId) 625, "Bisharp"), +new KeyValuePair((PokemonId) 626, "Bouffalant"), +new KeyValuePair((PokemonId) 627, "Rufflet"), +new KeyValuePair((PokemonId) 628, "Braviary"), +new KeyValuePair((PokemonId) 629, "Vullaby"), +new KeyValuePair((PokemonId) 630, "Mandibuzz"), +new KeyValuePair((PokemonId) 631, "Heatmor"), +new KeyValuePair((PokemonId) 632, "Durant"), +new KeyValuePair((PokemonId) 633, "Deino"), +new KeyValuePair((PokemonId) 634, "Zweilous"), +new KeyValuePair((PokemonId) 635, "Hydreigon"), +new KeyValuePair((PokemonId) 636, "Larvesta"), +new KeyValuePair((PokemonId) 637, "Volcarona"), +new KeyValuePair((PokemonId) 638, "Cobalion"), +new KeyValuePair((PokemonId) 639, "Terrakion"), +new KeyValuePair((PokemonId) 640, "Virizion"), +new KeyValuePair((PokemonId) 641, "Tornadus"), +new KeyValuePair((PokemonId) 642, "Thundurus"), +new KeyValuePair((PokemonId) 643, "Reshiram"), +new KeyValuePair((PokemonId) 644, "Zekrom"), +new KeyValuePair((PokemonId) 645, "Landorus"), +new KeyValuePair((PokemonId) 646, "Kyurem"), +new KeyValuePair((PokemonId) 647, "Keldeo"), +new KeyValuePair((PokemonId) 648, "Meloetta"), +new KeyValuePair((PokemonId) 649, "Genesect"), +new KeyValuePair((PokemonId) 650, "Chespin"), +new KeyValuePair((PokemonId) 651, "Quilladin"), +new KeyValuePair((PokemonId) 652, "Chesnaught"), +new KeyValuePair((PokemonId) 653, "Fennekin"), +new KeyValuePair((PokemonId) 654, "Braixen"), +new KeyValuePair((PokemonId) 655, "Delphox"), +new KeyValuePair((PokemonId) 656, "Froakie"), +new KeyValuePair((PokemonId) 657, "Frogadier"), +new KeyValuePair((PokemonId) 658, "Greninja"), +new KeyValuePair((PokemonId) 659, "Bunnelby"), +new KeyValuePair((PokemonId) 660, "Diggersby"), +new KeyValuePair((PokemonId) 661, "Fletchling"), +new KeyValuePair((PokemonId) 662, "Fletchinder"), +new KeyValuePair((PokemonId) 663, "Talonflame"), +new KeyValuePair((PokemonId) 664, "Scatterbug"), +new KeyValuePair((PokemonId) 665, "Spewpa"), +new KeyValuePair((PokemonId) 666, "Vivillon"), +new KeyValuePair((PokemonId) 667, "Litleo"), +new KeyValuePair((PokemonId) 668, "Pyroar"), +new KeyValuePair((PokemonId) 669, "Flabébé"), +new KeyValuePair((PokemonId) 670, "Floette"), +new KeyValuePair((PokemonId) 671, "Florges"), +new KeyValuePair((PokemonId) 672, "Skiddo"), +new KeyValuePair((PokemonId) 673, "Gogoat"), +new KeyValuePair((PokemonId) 674, "Pancham"), +new KeyValuePair((PokemonId) 675, "Pangoro"), +new KeyValuePair((PokemonId) 676, "Furfrou"), +new KeyValuePair((PokemonId) 677, "Espurr"), +new KeyValuePair((PokemonId) 678, "Meowstic"), +new KeyValuePair((PokemonId) 679, "Honedge"), +new KeyValuePair((PokemonId) 680, "Doublade"), +new KeyValuePair((PokemonId) 681, "Aegislash"), +new KeyValuePair((PokemonId) 682, "Spritzee"), +new KeyValuePair((PokemonId) 683, "Aromatisse"), +new KeyValuePair((PokemonId) 684, "Swirlix"), +new KeyValuePair((PokemonId) 685, "Slurpuff"), +new KeyValuePair((PokemonId) 686, "Inkay"), +new KeyValuePair((PokemonId) 687, "Malamar"), +new KeyValuePair((PokemonId) 688, "Binacle"), +new KeyValuePair((PokemonId) 689, "Barbaracle"), +new KeyValuePair((PokemonId) 690, "Skrelp"), +new KeyValuePair((PokemonId) 691, "Dragalge"), +new KeyValuePair((PokemonId) 692, "Clauncher"), +new KeyValuePair((PokemonId) 693, "Clawitzer"), +new KeyValuePair((PokemonId) 694, "Helioptile"), +new KeyValuePair((PokemonId) 695, "Heliolisk"), +new KeyValuePair((PokemonId) 696, "Tyrunt"), +new KeyValuePair((PokemonId) 697, "Tyrantrum"), +new KeyValuePair((PokemonId) 698, "Amaura"), +new KeyValuePair((PokemonId) 699, "Aurorus"), +new KeyValuePair((PokemonId) 700, "Sylveon"), +new KeyValuePair((PokemonId) 701, "Hawlucha"), +new KeyValuePair((PokemonId) 702, "Dedenne"), +new KeyValuePair((PokemonId) 703, "Carbink"), +new KeyValuePair((PokemonId) 704, "Goomy"), +new KeyValuePair((PokemonId) 705, "Sliggoo"), +new KeyValuePair((PokemonId) 706, "Goodra"), +new KeyValuePair((PokemonId) 707, "Klefki"), +new KeyValuePair((PokemonId) 708, "Phantump"), +new KeyValuePair((PokemonId) 709, "Trevenant"), +new KeyValuePair((PokemonId) 710, "Pumpkaboo"), +new KeyValuePair((PokemonId) 711, "Gourgeist"), +new KeyValuePair((PokemonId) 712, "Bergmite"), +new KeyValuePair((PokemonId) 713, "Avalugg"), +new KeyValuePair((PokemonId) 714, "Noibat"), +new KeyValuePair((PokemonId) 715, "Noivern"), +new KeyValuePair((PokemonId) 716, "Xerneas"), +new KeyValuePair((PokemonId) 717, "Yveltal"), +new KeyValuePair((PokemonId) 718, "Zygarde"), +new KeyValuePair((PokemonId) 719, "Diancie"), +new KeyValuePair((PokemonId) 720, "Hoopa"), +new KeyValuePair((PokemonId) 721, "Volcanion") + + }; + + [JsonProperty("PokemonMovesetStrings", + ItemTypeNameHandling = TypeNameHandling.Arrays, + ItemConverterType = typeof(KeyValuePairConverter), + ObjectCreationHandling = ObjectCreationHandling.Replace, + DefaultValueHandling = DefaultValueHandling.Populate)] + private readonly + List> _pokemonMovesetTranslationStrings = + new List>() + { + new KeyValuePair(PokemonMove.MoveUnset, "MoveUnset"), + new KeyValuePair(PokemonMove.ThunderShock, "ThunderShock"), + new KeyValuePair(PokemonMove.QuickAttack, "QuickAttack"), + new KeyValuePair(PokemonMove.Scratch, "Scratch"), + new KeyValuePair(PokemonMove.Ember, "Ember"), + new KeyValuePair(PokemonMove.VineWhip, "VineWhip"), + new KeyValuePair(PokemonMove.Tackle, "Tackle"), + new KeyValuePair(PokemonMove.RazorLeaf, "RazorLeaf"), + new KeyValuePair(PokemonMove.TakeDown, "TakeDown"), + new KeyValuePair(PokemonMove.WaterGun, "WaterGun"), + new KeyValuePair(PokemonMove.Bite, "Bite"), + new KeyValuePair(PokemonMove.Pound, "Pound"), + new KeyValuePair(PokemonMove.DoubleSlap, "DoubleSlap"), + new KeyValuePair(PokemonMove.Wrap, "Wrap"), + new KeyValuePair(PokemonMove.HyperBeam, "HyperBeam"), + new KeyValuePair(PokemonMove.Lick, "Lick"), + new KeyValuePair(PokemonMove.DarkPulse, "DarkPulse"), + new KeyValuePair(PokemonMove.Smog, "Smog"), + new KeyValuePair(PokemonMove.Sludge, "Sludge"), + new KeyValuePair(PokemonMove.MetalClaw, "MetalClaw"), + new KeyValuePair(PokemonMove.ViceGrip, "ViceGrip"), + new KeyValuePair(PokemonMove.FlameWheel, "FlameWheel"), + new KeyValuePair(PokemonMove.Megahorn, "Megahorn"), + new KeyValuePair(PokemonMove.WingAttack, "WingAttack"), + new KeyValuePair(PokemonMove.Flamethrower, "Flamethrower"), + new KeyValuePair(PokemonMove.SuckerPunch, "SuckerPunch"), + new KeyValuePair(PokemonMove.Dig, "Dig"), + new KeyValuePair(PokemonMove.LowKick, "LowKick"), + new KeyValuePair(PokemonMove.CrossChop, "CrossChop"), + new KeyValuePair(PokemonMove.PsychoCut, "PsychoCut"), + new KeyValuePair(PokemonMove.Psybeam, "Psybeam"), + new KeyValuePair(PokemonMove.Earthquake, "Earthquake"), + new KeyValuePair(PokemonMove.StoneEdge, "StoneEdge"), + new KeyValuePair(PokemonMove.IcePunch, "IcePunch"), + new KeyValuePair(PokemonMove.HeartStamp, "HeartStamp"), + new KeyValuePair(PokemonMove.Discharge, "Discharge"), + new KeyValuePair(PokemonMove.FlashCannon, "FlashCannon"), + new KeyValuePair(PokemonMove.Peck, "Peck"), + new KeyValuePair(PokemonMove.DrillPeck, "DrillPeck"), + new KeyValuePair(PokemonMove.IceBeam, "IceBeam"), + new KeyValuePair(PokemonMove.Blizzard, "Blizzard"), + new KeyValuePair(PokemonMove.AirSlash, "AirSlash"), + new KeyValuePair(PokemonMove.HeatWave, "HeatWave"), + new KeyValuePair(PokemonMove.Twineedle, "Twineedle"), + new KeyValuePair(PokemonMove.PoisonJab, "PoisonJab"), + new KeyValuePair(PokemonMove.AerialAce, "AerialAce"), + new KeyValuePair(PokemonMove.DrillRun, "DrillRun"), + new KeyValuePair(PokemonMove.PetalBlizzard, "PetalBlizzard"), + new KeyValuePair(PokemonMove.MegaDrain, "MegaDrain"), + new KeyValuePair(PokemonMove.BugBuzz, "BugBuzz"), + new KeyValuePair(PokemonMove.PoisonFang, "PoisonFang"), + new KeyValuePair(PokemonMove.NightSlash, "NightSlash"), + new KeyValuePair(PokemonMove.Slash, "Slash"), + new KeyValuePair(PokemonMove.BubbleBeam, "BubbleBeam"), + new KeyValuePair(PokemonMove.Submission, "Submission"), + new KeyValuePair(PokemonMove.KarateChop, "KarateChop"), + new KeyValuePair(PokemonMove.LowSweep, "LowSweep"), + new KeyValuePair(PokemonMove.AquaJet, "AquaJet"), + new KeyValuePair(PokemonMove.AquaTail, "AquaTail"), + new KeyValuePair(PokemonMove.SeedBomb, "SeedBomb"), + new KeyValuePair(PokemonMove.Psyshock, "Psyshock"), + new KeyValuePair(PokemonMove.RockThrow, "RockThrow"), + new KeyValuePair(PokemonMove.AncientPower, "AncientPower"), + new KeyValuePair(PokemonMove.RockTomb, "RockTomb"), + new KeyValuePair(PokemonMove.RockSlide, "RockSlide"), + new KeyValuePair(PokemonMove.PowerGem, "PowerGem"), + new KeyValuePair(PokemonMove.ShadowSneak, "ShadowSneak"), + new KeyValuePair(PokemonMove.ShadowPunch, "ShadowPunch"), + new KeyValuePair(PokemonMove.ShadowClaw, "ShadowClaw"), + new KeyValuePair(PokemonMove.OminousWind, "OminousWind"), + new KeyValuePair(PokemonMove.ShadowBall, "ShadowBall"), + new KeyValuePair(PokemonMove.BulletPunch, "BulletPunch"), + new KeyValuePair(PokemonMove.MagnetBomb, "MagnetBomb"), + new KeyValuePair(PokemonMove.SteelWing, "SteelWing"), + new KeyValuePair(PokemonMove.IronHead, "IronHead"), + new KeyValuePair(PokemonMove.ParabolicCharge, "ParabolicCharge"), + new KeyValuePair(PokemonMove.Spark, "Spark"), + new KeyValuePair(PokemonMove.ThunderPunch, "ThunderPunch"), + new KeyValuePair(PokemonMove.Thunder, "Thunder"), + new KeyValuePair(PokemonMove.Thunderbolt, "Thunderbolt"), + new KeyValuePair(PokemonMove.Twister, "Twister"), + new KeyValuePair(PokemonMove.DragonBreath, "DragonBreath"), + new KeyValuePair(PokemonMove.DragonPulse, "DragonPulse"), + new KeyValuePair(PokemonMove.DragonClaw, "DragonClaw"), + new KeyValuePair(PokemonMove.DisarmingVoice, "DisarmingVoice"), + new KeyValuePair(PokemonMove.DrainingKiss, "DrainingKiss"), + new KeyValuePair(PokemonMove.DazzlingGleam, "DazzlingGleam"), + new KeyValuePair(PokemonMove.Moonblast, "Moonblast"), + new KeyValuePair(PokemonMove.PlayRough, "PlayRough"), + new KeyValuePair(PokemonMove.CrossPoison, "CrossPoison"), + new KeyValuePair(PokemonMove.SludgeBomb, "SludgeBomb"), + new KeyValuePair(PokemonMove.SludgeWave, "SludgeWave"), + new KeyValuePair(PokemonMove.GunkShot, "GunkShot"), + new KeyValuePair(PokemonMove.MudShot, "MudShot"), + new KeyValuePair(PokemonMove.BoneClub, "BoneClub"), + new KeyValuePair(PokemonMove.Bulldoze, "Bulldoze"), + new KeyValuePair(PokemonMove.MudBomb, "MudBomb"), + new KeyValuePair(PokemonMove.FuryCutter, "FuryCutter"), + new KeyValuePair(PokemonMove.BugBite, "BugBite"), + new KeyValuePair(PokemonMove.SignalBeam, "SignalBeam"), + new KeyValuePair(PokemonMove.XScissor, "XScissor"), + new KeyValuePair(PokemonMove.FlameCharge, "FlameCharge"), + new KeyValuePair(PokemonMove.FlameBurst, "FlameBurst"), + new KeyValuePair(PokemonMove.FireBlast, "FireBlast"), + new KeyValuePair(PokemonMove.Brine, "Brine"), + new KeyValuePair(PokemonMove.WaterPulse, "WaterPulse"), + new KeyValuePair(PokemonMove.Scald, "Scald"), + new KeyValuePair(PokemonMove.HydroPump, "HydroPump"), + new KeyValuePair(PokemonMove.Psychic, "Psychic"), + new KeyValuePair(PokemonMove.Psystrike, "Psystrike"), + new KeyValuePair(PokemonMove.IceShard, "IceShard"), + new KeyValuePair(PokemonMove.IcyWind, "IcyWind"), + new KeyValuePair(PokemonMove.FrostBreath, "FrostBreath"), + new KeyValuePair(PokemonMove.Absorb, "Absorb"), + new KeyValuePair(PokemonMove.GigaDrain, "GigaDrain"), + new KeyValuePair(PokemonMove.FirePunch, "FirePunch"), + new KeyValuePair(PokemonMove.SolarBeam, "SolarBeam"), + new KeyValuePair(PokemonMove.LeafBlade, "LeafBlade"), + new KeyValuePair(PokemonMove.PowerWhip, "PowerWhip"), + new KeyValuePair(PokemonMove.Splash, "Splash"), + new KeyValuePair(PokemonMove.Acid, "Acid"), + new KeyValuePair(PokemonMove.AirCutter, "AirCutter"), + new KeyValuePair(PokemonMove.Hurricane, "Hurricane"), + new KeyValuePair(PokemonMove.BrickBreak, "BrickBreak"), + new KeyValuePair(PokemonMove.Cut, "Cut"), + new KeyValuePair(PokemonMove.Swift, "Swift"), + new KeyValuePair(PokemonMove.HornAttack, "HornAttack"), + new KeyValuePair(PokemonMove.Stomp, "Stomp"), + new KeyValuePair(PokemonMove.Headbutt, "Headbutt"), + new KeyValuePair(PokemonMove.HyperFang, "HyperFang"), + new KeyValuePair(PokemonMove.Slam, "Slam"), + new KeyValuePair(PokemonMove.BodySlam, "BodySlam"), + new KeyValuePair(PokemonMove.Rest, "Rest"), + new KeyValuePair(PokemonMove.Struggle, "Struggle"), + new KeyValuePair(PokemonMove.ScaldBlastoise, "ScaldBlastoise"), + new KeyValuePair(PokemonMove.HydroPumpBlastoise, "HydroPumpBlastoise"), + new KeyValuePair(PokemonMove.WrapGreen, "WrapGreen"), + new KeyValuePair(PokemonMove.WrapPink, "WrapPink"), + new KeyValuePair(PokemonMove.FuryCutterFast, "FuryCutterFast"), + new KeyValuePair(PokemonMove.BugBiteFast, "BugBiteFast"), + new KeyValuePair(PokemonMove.BiteFast, "BiteFast"), + new KeyValuePair(PokemonMove.SuckerPunchFast, "SuckerPunchFast"), + new KeyValuePair(PokemonMove.DragonBreathFast, "DragonBreathFast"), + new KeyValuePair(PokemonMove.ThunderShockFast, "ThunderShockFast"), + new KeyValuePair(PokemonMove.SparkFast, "SparkFast"), + new KeyValuePair(PokemonMove.LowKickFast, "LowKickFast"), + new KeyValuePair(PokemonMove.KarateChopFast, "KarateChopFast"), + new KeyValuePair(PokemonMove.EmberFast, "EmberFast"), + new KeyValuePair(PokemonMove.WingAttackFast, "WingAttackFast"), + new KeyValuePair(PokemonMove.PeckFast, "PeckFast"), + new KeyValuePair(PokemonMove.LickFast, "LickFast"), + new KeyValuePair(PokemonMove.ShadowClawFast, "ShadowClawFast"), + new KeyValuePair(PokemonMove.VineWhipFast, "VineWhipFast"), + new KeyValuePair(PokemonMove.RazorLeafFast, "RazorLeafFast"), + new KeyValuePair(PokemonMove.MudShotFast, "MudShotFast"), + new KeyValuePair(PokemonMove.IceShardFast, "IceShardFast"), + new KeyValuePair(PokemonMove.FrostBreathFast, "FrostBreathFast"), + new KeyValuePair(PokemonMove.QuickAttackFast, "QuickAttackFast"), + new KeyValuePair(PokemonMove.ScratchFast, "ScratchFast"), + new KeyValuePair(PokemonMove.TackleFast, "TackleFast"), + new KeyValuePair(PokemonMove.PoundFast, "PoundFast"), + new KeyValuePair(PokemonMove.CutFast, "CutFast"), + new KeyValuePair(PokemonMove.PoisonJabFast, "PoisonJabFast"), + new KeyValuePair(PokemonMove.AcidFast, "AcidFast"), + new KeyValuePair(PokemonMove.PsychoCutFast, "PsychoCutFast"), + new KeyValuePair(PokemonMove.RockThrowFast, "RockThrowFast"), + new KeyValuePair(PokemonMove.MetalClawFast, "MetalClawFast"), + new KeyValuePair(PokemonMove.BulletPunchFast, "BulletPunchFast"), + new KeyValuePair(PokemonMove.WaterGunFast, "WaterGunFast"), + new KeyValuePair(PokemonMove.SplashFast, "SplashFast"), + new KeyValuePair(PokemonMove.WaterGunFastBlastoise, "WaterGunFastBlastoise"), + new KeyValuePair(PokemonMove.MudSlapFast, "MudSlapFast"), + new KeyValuePair(PokemonMove.ZenHeadbuttFast, "ZenHeadbuttFast"), + new KeyValuePair(PokemonMove.ConfusionFast, "ConfusionFast"), + new KeyValuePair(PokemonMove.PoisonStingFast, "PoisonStingFast"), + new KeyValuePair(PokemonMove.BubbleFast, "BubbleFast"), + new KeyValuePair(PokemonMove.FeintAttackFast, "FeintAttackFast"), + new KeyValuePair(PokemonMove.SteelWingFast, "SteelWingFast"), + new KeyValuePair(PokemonMove.FireFangFast, "FireFangFast"), + new KeyValuePair(PokemonMove.RockSmashFast, "RockSmashFast"), + new KeyValuePair(PokemonMove.TransformFast, "TransformFast"), + new KeyValuePair(PokemonMove.CounterFast, "CounterFast"), + new KeyValuePair(PokemonMove.PowderSnowFast, "PowderSnowFast"), + new KeyValuePair(PokemonMove.CloseCombat, "CloseCombat"), + new KeyValuePair(PokemonMove.DynamicPunch, "DynamicPunch"), + new KeyValuePair(PokemonMove.FocusBlast, "FocusBlast"), + new KeyValuePair(PokemonMove.AuroraBeam, "AuroraBeam"), + new KeyValuePair(PokemonMove.ChargeBeamFast, "ChargeBeamFast"), + new KeyValuePair(PokemonMove.VoltSwitchFast, "VoltSwitchFast"), + new KeyValuePair(PokemonMove.WildCharge, "WildCharge"), + new KeyValuePair(PokemonMove.ZapCannon, "ZapCannon"), + new KeyValuePair(PokemonMove.DragonTailFast, "DragonTailFast"), + new KeyValuePair(PokemonMove.Avalanche, "Avalanche"), + new KeyValuePair(PokemonMove.AirSlashFast, "AirSlashFast"), + new KeyValuePair(PokemonMove.BraveBird, "BraveBird"), + new KeyValuePair(PokemonMove.SkyAttack, "SkyAttack"), + new KeyValuePair(PokemonMove.SandTomb, "SandTomb"), + new KeyValuePair(PokemonMove.RockBlast, "RockBlast"), + new KeyValuePair(PokemonMove.InfestationFast, "InfestationFast"), + new KeyValuePair(PokemonMove.StruggleBugFast, "StruggleBugFast"), + new KeyValuePair(PokemonMove.SilverWind, "SilverWind"), + new KeyValuePair(PokemonMove.AstonishFast, "AstonishFast"), + new KeyValuePair(PokemonMove.HexFast, "HexFast"), + new KeyValuePair(PokemonMove.NightShade, "NightShade"), + new KeyValuePair(PokemonMove.IronTailFast, "IronTailFast"), + new KeyValuePair(PokemonMove.GyroBall, "GyroBall"), + new KeyValuePair(PokemonMove.HeavySlam, "HeavySlam"), + new KeyValuePair(PokemonMove.FireSpinFast, "FireSpinFast"), + new KeyValuePair(PokemonMove.Overheat, "Overheat"), + new KeyValuePair(PokemonMove.BulletSeedFast, "BulletSeedFast"), + new KeyValuePair(PokemonMove.GrassKnot, "GrassKnot"), + new KeyValuePair(PokemonMove.EnergyBall, "EnergyBall"), + new KeyValuePair(PokemonMove.ExtrasensoryFast, "ExtrasensoryFast"), + new KeyValuePair(PokemonMove.Futuresight, "Futuresight"), + new KeyValuePair(PokemonMove.MirrorCoat, "MirrorCoat"), + new KeyValuePair(PokemonMove.Outrage, "Outrage"), + new KeyValuePair(PokemonMove.SnarlFast, "SnarlFast"), + new KeyValuePair(PokemonMove.Crunch, "Crunch"), + new KeyValuePair(PokemonMove.FoulPlay, "FoulPlay"), + new KeyValuePair(PokemonMove.HiddenPowerFast, "HiddenPowerFast") + }; + + public string GetTranslation(TranslationString translationString, params object[] data) + { + var translation = _translationStrings.FirstOrDefault(t => t.Key.Equals(translationString)).Value; + return translation != default(string) + ? string.Format(translation, data) + : $"Translation for {translationString} is missing"; + } + + public string GetTranslation(TranslationString translationString) + { + var translation = _translationStrings.FirstOrDefault(t => t.Key.Equals(translationString)).Value; + return translation != default(string) ? translation : $"Translation for {translationString} is missing"; + } + + public string GetPokemonTranslation(PokemonId id) + { + var translation = _pokemonTranslationStrings.FirstOrDefault(t => t.Key.Equals(id)).Value; + return translation != default(string) ? translation : $"Translation for pokemon {id} is missing"; + } + + public string GetPokemonMovesetTranslation(PokemonMove move) + { + var translation = _pokemonMovesetTranslationStrings.FirstOrDefault(t => t.Key.Equals(move)).Value; + return translation != default(string) ? translation : $"Translation for move {move} is missing"; + } + + public static Translation Load(ILogicSettings logicSettings) + { + return Load(logicSettings, new Translation()); + } + + public static Translation Load(ILogicSettings logicSettings, Translation translations) + { + var translationsLanguageCode = logicSettings.TranslationLanguageCode; + var translationPath = Path.Combine(logicSettings.GeneralConfigPath, "translations"); + var fullPath = Path.Combine(translationPath, "translation." + translationsLanguageCode + ".json"); + + // Load existing translationsfiles except if using default English. + // This file has to be rebuilt from default values to prevent an old/outdated + // translation being used. + if (File.Exists(fullPath) && translationsLanguageCode != "en") + { + var input = File.ReadAllText(fullPath); + + var jsonSettings = new JsonSerializerSettings(); + jsonSettings.Converters.Add(new StringEnumConverter { CamelCaseText = true }); + jsonSettings.ObjectCreationHandling = ObjectCreationHandling.Replace; + jsonSettings.DefaultValueHandling = DefaultValueHandling.Populate; + + try + { + translations = JsonConvert.DeserializeObject(input, jsonSettings); + //TODO make json to fill default values as it won't do it now + new Translation()._translationStrings.Where( + item => translations._translationStrings.All(a => a.Key != item.Key)) + .ToList() + .ForEach(translations._translationStrings.Add); + new Translation()._pokemonTranslationStrings.Where( + item => translations._pokemonTranslationStrings.All(a => a.Key != item.Key)) + .ToList() + .ForEach(translations._pokemonTranslationStrings.Add); + } + catch (JsonException ex) + { + Logger.Write($"[ERROR] Issue loading translations: {ex.ToString()}", LogLevel.Warning); + Logger.Write("[Request] Rebuild the translations folder? Y/N"); + + string strInput = Console.ReadLine().ToLower(); + + if (strInput.Equals("y")) + { + // Currently this section can only rebuild the EN translations file \\ + // This is because default values cannot be supplied from other languages \\ + Logger.Write("Loading fresh translations and continuing"); + translations = new Translation(); + translations.Save(Path.Combine(translationPath, "translation.en.json")); + } + else + { + ErrorHandler.ThrowFatalError("[ERROR] Fatal Error", 3, LogLevel.Error); + return null; + } + } + } + else + { + translations = new Translation(); + translations.Save(Path.Combine(translationPath, "translation.en.json")); + } + + return translations; + } + + public void Save(string fullPath) + { + var output = JsonConvert.SerializeObject(this, Formatting.Indented, + new StringEnumConverter { CamelCaseText = true }); + + var folder = Path.GetDirectoryName(fullPath); + if (folder != null && !Directory.Exists(folder)) + { + Directory.CreateDirectory(folder); + } + + File.WriteAllText(fullPath, output); + } + } +} diff --git a/PoGo.NecroBot.Logic/Common/UITranslation.cs b/PoGo.NecroBot.Logic/Common/UITranslation.cs new file mode 100644 index 000000000..7f3cf4936 --- /dev/null +++ b/PoGo.NecroBot.Logic/Common/UITranslation.cs @@ -0,0 +1,418 @@ +using Newtonsoft.Json; +using PoGo.NecroBot.Logic.Model.Settings; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Reflection; + +namespace PoGo.NecroBot.Logic.Common +{ + public class UITranslation + { + #region Main screen + + [Description("Accounts")] + public string AccountSetting { get; set; } + + [Description("SNIPE")] + public string SnipeText { get; set; } + + [Description("ALL BOT SNIPE")] + public string SnipeAllBotText { get; set; } + + [Description("Help")] + public string Help { get; set; } + + [Description("Show Console")] + public string ShowConsole { get; set; } + + [Description("Reset Layout")] + public string ResetLayout { get; set; } + + [Description("Switch")] + public string Switch { get; set; } + + [Description("Hide Console")] + public string HideConsole { get; set; } + + [Description("Enable Hub")] + public string EnableHub { get; set; } + + [Description("Disable Hub")] + public string DisableHub { get; set; } + + [Description("Settings")] + public string MenuSetting { get; set; } + + [Description("Theme")] + public string Theme { get; set; } + + [Description("Scheme")] + public string Scheme { get; set; } + + [Description("Enter your Command")] + public string InputCommand { get; set; } + + #endregion + #region Map + + [Description("Zoom In")] + public string ZoomIn { get; set; } + + [Description("Zoom Out")] + public string ZoomOut { get; set; } + + [Description("Clear Map")] + public string ClearMap { get; set; } + + [Description("Walk Here")] + public string WalkHere { get; set; } + + #endregion + #region snipe screen + + [Description("100% IV")] + public string TabSnipeIV100 { get; set; } + + [Description("Rare Pokemon")] + public string TabSnipeRarePokemon { get; set; } + + [Description("Others")] + public string TabSnipeOtherPokemon { get; set; } + + [Description("Not in Dex")] + public string TabSnipeNotInDexPokemon { get; set; } + + [Description("Auto Snipe List")] + public string TabSnipeAutoListPokemon { get; set; } + + [Description("Add Manual Coordinate")] + public string TabSnipeAddManualCoord { get; set; } + + [Description("Snipe??")] + public string SnipeButton { get; set; } + + [Description("FreeInput")] + public string FreeInput { get; set; } + + [Description("You can copy & paste any free text content which has pokemon name, latitude, longitude then bot will parse that content to get the snipe infomation.")] + public string FreeInputExplain { get; set; } + + [Description("Add To Snipe")] + public string AddToSnipeButtonText { get; set; } + + #endregion + #region Pokemon Inventory + [Description("TRANSFER CONFIRM?")] + public string TransferConfirmCaption { get; set; } + + [Description("Types")] + public string Types { get; set; } + + [Description("TRANSFER")] + public string TransferConfirmButton { get; set; } + + [Description("Do you want to transfer {0} IV:{1:0.00}% Lvl:{2}")] + public string TransferConfirmText { get; set; } + + [Description("Shiny")] + public string Shiny { get; set; } + + [Description("Form")] + public string Form { get; set; } + + [Description("Costume")] + public string Costume { get; set; } + + [Description("Sex")] + public string Sex { get; set; } + + [Description("Evolve Filter Setting")] + public string MenuTransferFilterText { get; set; } + + [Description("Evolve Filter Setting")] + public string MenuEvolveFilterText { get; set; } + + [Description("Snipe Filter Setting")] + public string MenuSnipeFilterText { get; set; } + + [Description("Snipe Upgrade Setting")] + public string MenuUpgradeFilterText { get; set; } + + [Description("This pokemon can be evolve to below pokemon , please select the branch you want to evolve to")] + public string EvolveConfirm { get; set; } + + [Description("Evolve Pokemon")] + public string EvolvePopupCaption { get; set; } + + [Description("Search & Filters")] + public string FilterAndSearch { get; set; } + + [Description("Pokedex")] + public string Pokedex { get; set; } + + [Description("Enter Pokemon Name")] + public string SearchPokemonName { get; set; } + + [Description("Select Pokemon %IV")] + public string SearchPokemonIV { get; set; } + + [Description("Select Pokemon Level")] + public string SearchPokemonLevel { get; set; } + + [Description("Select Pokemon CP")] + public string SearchPokemonCP { get; set; } + + [Description("Search & Select")] + public string SearchSelectAllButton { get; set; } + + [Description("Search")] + public string SearchButton { get; set; } + + [Description("Do you want to powerup this pokemon? Normal power up is do x time power up. Max power up is powerup to maximun level up to your candy, stardust and player level.")] + public string PowerUpDescription { get; set; } + + [Description("Normal Power Up")] + public string NormalPowerup { get; set; } + + [Description("Max Power Up")] + public string MaxPowerup { get; set; } + + [Description("Export")] + public string Export { get; set; } + + [Description("Verified")] + public string Verified { get; set; } + + [Description("RemainTime")] + public string RemainTime { get; set; } + + [Description("Name")] + public string PokemonName { get; set; } + + [Description("HP")] + public string HP { get; set; } + + [Description("Move1")] + public string Move1 { get; set; } + + [Description("Move2")] + public string Move2 { get; set; } + + [Description("IV")] + public string IV { get; set; } + + [Description("CP")] + public string CP { get; set; } + + [Description("Candy")] + public string Candy { get; set; } + + [Description("Level")] + public string Level { get; set; } + + [Description("Experience")] + public string ExperienceInfo { get; set; } + + [Description("Caught at")] + public string CaughtTime { get; set; } + + [Description("Location")] + public string CaughtLocation { get; set; } + + [Description("Set Buddy")] + public string SetBuddy { get; set; } + + [Description("Actions")] + public string Actions { get; set; } + + #endregion + #region Popup + + [Description("Latitude")] + public string Latitude { get; set; } + + [Description("Longitude")] + public string Longitude { get; set; } + + [Description("Distance")] + public string Distance { get; set; } + + [Description("Close")] + public string Close { get; set; } + + [Description("Walk Here")] + public string WalkToHere { get; set; } + + [Description("CP")] + public string GymDefenderCP { get; set; } + + [Description("Gym Points")] + public string GymPoints { get; set; } + + [Description("Pokestops: {0}")] + public string PokestopLimit { get; set; } + + [Description("Pokemons: {0}")] + public string CatchLimit { get; set; } + + [Description("Speed: {0:0.00} km/h")] + public string WalkSpeed { get; set; } + + [Description("Transfered: {0}")] + public string PokemonTransfered { get; set; } + + [Description("HIDE")] + public string Hide { get; set; } + + [Description("SHOW")] + public string Show { get; set; } + + [Description("Transfer filter - {0}")] + public string TransferFilterFormTitle { get; set; } + #endregion + + private Dictionary translations = new Dictionary(); + private string languageCode = "en"; + private string translationFile = @"Config\Translations\ui.{0}.json"; + public UITranslation(string language = "en") + { + languageCode = language; + + translationFile = string.Format(translationFile, language); + + Load(); + } + + public string GetTranslation(string key) + { + var prop = GetType().GetProperty(key); + if (prop != null) + { + return prop.GetValue(this).ToString(); + } + if (translations.ContainsKey(key)) + { + return translations[key]; + } + return $"{key} missing"; + } + + public void Save() + { + var type = GetType(); + foreach (var item in type.GetProperties()) + { + if (translations.ContainsKey(item.Name)) continue; + translations.Add(item.Name, item.GetValue(this).ToString()); + } + + File.WriteAllText(translationFile, JsonConvert.SerializeObject(translations, Formatting.Indented)); + } + public void Load() + { + var type = GetType(); + + var props = GetType().GetProperties(); + + foreach (var pi in props) + { + var description = pi.GetCustomAttribute(); + if (description != null) + { + pi.SetValue(this, description.Description); + } + else + pi.SetValue(this, pi.Name); + } + + if (File.Exists(translationFile)) + { + translations = JsonConvert.DeserializeObject>(File.ReadAllText(translationFile)); + + foreach (var item in translations.Keys) + { + { + var curent = type.GetProperty(item); + + if (curent != null) + { + curent.SetValue(this, translations[item]); + } + + } + } + } + //append translation for setting + Type setting = typeof(GlobalSettings); + + foreach (var item in setting.GetFields()) + { + var configAttibute = item.GetCustomAttribute(); + if (configAttibute != null) + { + var fileName = !string.IsNullOrEmpty(configAttibute.SheetName) ? configAttibute.SheetName : item.Name; + + string key = $"Setting.{item.Name}"; + var configType = item.FieldType; + + if (!translations.ContainsKey(key)) + { + translations.Add(key, !string.IsNullOrEmpty(configAttibute.Key) ? configAttibute.Key : fileName); + } + var keyDesc = $"{key}Desc"; + + if (!translations.ContainsKey(keyDesc)) + { + translations.Add(keyDesc, !string.IsNullOrEmpty(configAttibute.Description) ? configAttibute.Description : $"{key} description"); + }; + if (item.FieldType.IsGenericType && (item.FieldType.GetGenericTypeDefinition() == typeof(Dictionary<,>))) + { + Type keyType = item.FieldType.GetGenericArguments()[0]; + Type valueType = item.FieldType.GetGenericArguments()[1]; + AddResourceForType(key, valueType); + } + + if (item.FieldType.IsGenericType && (item.FieldType.GetGenericTypeDefinition() == typeof(List<>))) + { + Type keyType = item.FieldType.GetGenericArguments()[0]; + AddResourceForType(key, keyType); + } + + + AddResourceForType(key, configType); + } + + Save(); + + } + + } + + private void AddResourceForType(string key, Type configType) + { + foreach (var configItem in configType.GetProperties()) + { + var propAttibute = configItem.GetCustomAttribute(); + if (propAttibute != null) + { + string fieldValue = !string.IsNullOrEmpty(propAttibute.Key) ? propAttibute.Key : configItem.Name; + + var subKey = $"{key}.{configItem.Name}"; + var descKey = subKey + "Desc"; + + if (!translations.ContainsKey(subKey)) + { + translations.Add(subKey, string.IsNullOrEmpty(propAttibute.Key) ? fieldValue : propAttibute.Key); + } + + if (!translations.ContainsKey(descKey)) + { + translations.Add(descKey, !string.IsNullOrEmpty(propAttibute.Description) ? propAttibute.Description : $"{subKey} description"); + } + } + } + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Config/log4net.config b/PoGo.NecroBot.Logic/Config/log4net.config new file mode 100644 index 000000000..c1ebe9571 --- /dev/null +++ b/PoGo.NecroBot.Logic/Config/log4net.config @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Config/log4net.unix.config b/PoGo.NecroBot.Logic/Config/log4net.unix.config new file mode 100644 index 000000000..3b07772ac --- /dev/null +++ b/PoGo.NecroBot.Logic/Config/log4net.unix.config @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/DataDumper/Dumper.cs b/PoGo.NecroBot.Logic/DataDumper/Dumper.cs new file mode 100644 index 000000000..1626e09f9 --- /dev/null +++ b/PoGo.NecroBot.Logic/DataDumper/Dumper.cs @@ -0,0 +1,167 @@ +#region using directives + +using System; +using System.Globalization; +using System.IO; +using System.Threading.Tasks; +using OfficeOpenXml; +using PoGo.NecroBot.Logic.PoGoUtils; +using PoGo.NecroBot.Logic.State; + +#endregion + +namespace PoGo.NecroBot.Logic.DataDumper +{ + public static class Dumper + { + /// + /// Clears the specified dumpfile. + /// + /// + /// + /// File to clear/param> + public static void ClearDumpFile(ISession session, string filename, string extension = "csv") + { + var path = Path.Combine(session.LogicSettings.ProfilePath, "Dumps"); + var file = Path.Combine(path, + $"{filename}-NecroBot2 DumpFile.{extension}"); //-{DateTime.Today.ToString("yyyy-MM-dd")}-{DateTime.Now.ToString("HH")}.{extension}"); + if (!Directory.Exists(path)) Directory.CreateDirectory(path); + + // Clears all contents of a file first if overwrite is true + File.WriteAllText(file, string.Empty); + } + + /// + /// Dumps data to a file + /// + /// + /// Dumps the string data to the file + /// Filename to be used for naming the file. + /// FileExt. + public static void Dump(ISession session, string[] data, string filename, string extension = "csv") + { + string uniqueFileName = $"{filename}"; + + DumpToFile(session, data, uniqueFileName, extension); + } + + /// + /// This is used for dumping contents to a file stored in the Logs folder. + /// + /// + /// Dumps the string data to the file + /// Filename to be used for naming the file. + private static void DumpToFile(ISession session, string[] data, string filename, string extension = "csv") + { + var path = Path.Combine(session.LogicSettings.ProfilePath, "Dumps", + $"{filename}-NecroBot2 DumpFile.{extension}"); // NecroBot2-{filename}-{DateTime.Today.ToString("yyyy-MM-dd")}-{DateTime.Now.ToString("HH")}.{extension}"); + + CultureInfo culture = CultureInfo.CurrentUICulture; + string listSeparator = culture.TextInfo.ListSeparator; + + using (var dumpFile = File.AppendText(path)) + { + string strData = ""; + foreach (string str in data) + { + if (strData != "") + strData += listSeparator; + + if (str.Contains("\"")) + { + strData += string.Format("\"{0}\"", str.Replace("\"", "\"\"")); + } + else if (str.Contains(listSeparator)) + { + strData += string.Format("\"{0}\"", str); + } + else + { + strData += str; + } + } + dumpFile.WriteLine(strData); + dumpFile.Flush(); + } + } + + /// + /// Set the dumper. + /// + /// + /// + public static void SetDumper(IDumper dumper, string subPath = "") + { + } + + public static async Task SaveAsExcel(ISession session, string Filename = "") + { + await Task.Run(async () => + { + var allPokemonInBag = session.LogicSettings.PrioritizeIvOverCp + ? await session.Inventory.GetHighestsPerfect(1000).ConfigureAwait(false) + : await session.Inventory.GetHighestsCp(1000).ConfigureAwait(false); + string file = !string.IsNullOrEmpty(Filename) + ? Filename + : $"config\\{session.Settings.Username}\\allpokemon.xlsx"; + int rowNum = 1; + using (Stream stream = File.OpenWrite(file)) + using (var package = new ExcelPackage(stream)) + { + var ws = package.Workbook.Worksheets.Add("Pokemons"); + foreach (var item in allPokemonInBag) + { + var settings = await session.Inventory.GetPokemonSetting(item.PokemonId).ConfigureAwait(false); + if (rowNum == 1) + { + ws.Cells[1, 1].Value = "#"; + ws.Cells[1, 2].Value = "Pokemon"; + ws.Cells[1, 3].Value = "Display Name"; + ws.Cells[1, 4].Value = "Nickname"; + ws.Cells[1, 5].Value = "IV"; + ws.Cells[1, 6].Value = "Attack"; + ws.Cells[1, 7].Value = "Defense"; + ws.Cells[1, 8].Value = "Stamina"; + ws.Cells[1, 9].Value = "HP"; + ws.Cells[1, 10].Value = "MaxHP"; + ws.Cells[1, 11].Value = "CP"; + ws.Cells[1, 12].Value = "Level"; + ws.Cells[1, 13].Value = "Candy"; + ws.Cells[1, 14].Value = "Move1"; + ws.Cells[1, 15].Value = "Move2"; + ws.Cells[1, 16].Value = "Type"; + ws.Cells[1, 17].Value = "Shiny"; + ws.Cells[1, 18].Value = "Form"; + ws.Cells[1, 19].Value = "Costume"; + ws.Cells[1, 20].Value = "Sex"; + ws.Cells["A1:Q1"].AutoFilter = true; + ws.Cells["A1:Q1"].Style.Font.Bold = true; + } + ws.Cells[rowNum + 1, 1].Value = rowNum; + ws.Cells[rowNum + 1, 2].Value = item.PokemonId.ToString(); + ws.Cells[rowNum + 1, 3].Value = item.Nickname; + ws.Cells[rowNum + 1, 4].Value = item.OwnerName; + ws.Cells[rowNum + 1, 5].Value = Math.Round(PokemonInfo.CalculatePokemonPerfection(item), 2); + ws.Cells[rowNum + 1, 6].Value = item.IndividualAttack; + ws.Cells[rowNum + 1, 7].Value = item.IndividualDefense; + ws.Cells[rowNum + 1, 8].Value = item.IndividualStamina; + ws.Cells[rowNum + 1, 9].Value = item.Stamina; + ws.Cells[rowNum + 1, 10].Value = item.StaminaMax; + ws.Cells[rowNum + 1, 11].Value = PokemonInfo.CalculateCp(item); + ws.Cells[rowNum + 1, 12].Value = PokemonInfo.GetLevel(item); + ws.Cells[rowNum + 1, 13].Value = session.Inventory.GetCandyCount(item.PokemonId); + ws.Cells[rowNum + 1, 14].Value = item.Move1.ToString(); + ws.Cells[rowNum + 1, 15].Value = item.Move2.ToString(); + ws.Cells[rowNum + 1, 16].Value = $"{settings.Type.ToString()},{settings.Type2.ToString()}"; + ws.Cells[rowNum + 1, 17].Value = $"{(item.PokemonDisplay.Shiny ? "Yes" : "No")}"; + ws.Cells[rowNum + 1, 18].Value = $"{(item.PokemonDisplay.Form.ToString().Replace("Unown", "").Replace("Unset", "Normal"))}"; + ws.Cells[rowNum + 1, 19].Value = $"{(item.PokemonDisplay.Costume.ToString().Replace("Unset", "Regular"))}"; + ws.Cells[rowNum + 1, 20].Value = $"{(item.PokemonDisplay.Gender.ToString().Replace("Less", "Genderless"))}"; + rowNum++; + } + package.Save(); + } + }).ConfigureAwait(false); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/DataDumper/IDumper.cs b/PoGo.NecroBot.Logic/DataDumper/IDumper.cs new file mode 100644 index 000000000..4df4c9152 --- /dev/null +++ b/PoGo.NecroBot.Logic/DataDumper/IDumper.cs @@ -0,0 +1,12 @@ +namespace PoGo.NecroBot.Logic.DataDumper +{ + public interface IDumper + { + /// + /// Dump specific data. + /// + /// The data to dump. + /// File to dump to + void Dump(string[] data, string filename); + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/BotSwitchedEvent.cs b/PoGo.NecroBot.Logic/Event/BotSwitchedEvent.cs new file mode 100644 index 000000000..e18384042 --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/BotSwitchedEvent.cs @@ -0,0 +1,14 @@ +using PoGo.NecroBot.Logic.Model; + +namespace PoGo.NecroBot.Logic.Event +{ + public class BotSwitchedEvent : IEvent + { + private Account Account; + + public BotSwitchedEvent(Account nextBot) + { + Account = nextBot; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/DisplayHighestsPokemonEvent.cs b/PoGo.NecroBot.Logic/Event/DisplayHighestsPokemonEvent.cs new file mode 100644 index 000000000..196f18a2a --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/DisplayHighestsPokemonEvent.cs @@ -0,0 +1,19 @@ +#region using directives + +using System; +using System.Collections.Generic; +using POGOProtos.Data; +using POGOProtos.Enums; + +#endregion + +namespace PoGo.NecroBot.Logic.Event +{ + public class DisplayHighestsPokemonEvent : IEvent + { + //PokemonData | CP | IV | Level | MOVE1 | MOVE2 | Candy + public List> PokemonList; + + public string SortedBy; + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/EggHatchedEvent.cs b/PoGo.NecroBot.Logic/Event/EggHatchedEvent.cs new file mode 100644 index 000000000..530e7a085 --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/EggHatchedEvent.cs @@ -0,0 +1,22 @@ +#region using directives + +using POGOProtos.Enums; + +#endregion + +namespace PoGo.NecroBot.Logic.Event +{ + public class EggHatchedEvent : IEvent + { + public int Cp; + public ulong Id; + public double Level; + public int MaxCp; + public double Perfection; + public PokemonId PokemonId; + public double Dist; + public long HXP; + public int HSD; + public int HCandy; + } +} diff --git a/PoGo.NecroBot.Logic/Event/EggIncubatorStatusEvent.cs b/PoGo.NecroBot.Logic/Event/EggIncubatorStatusEvent.cs new file mode 100644 index 000000000..52e6cc5ce --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/EggIncubatorStatusEvent.cs @@ -0,0 +1,12 @@ +namespace PoGo.NecroBot.Logic.Event +{ + public class EggIncubatorStatusEvent : IEvent + { + public string IncubatorId; + public double KmRemaining; + public double KmToWalk; + public ulong PokemonId; + public bool WasAddedNow; + public double KmWalked => KmToWalk - KmRemaining; + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/EggsListEvent.cs b/PoGo.NecroBot.Logic/Event/EggsListEvent.cs new file mode 100644 index 000000000..ef5a5b90b --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/EggsListEvent.cs @@ -0,0 +1,16 @@ +#region using directives + +using System.Collections.Generic; +using POGOProtos.Inventory; + +#endregion + +namespace PoGo.NecroBot.Logic.Event +{ + public class EggsListEvent : IEvent + { + public float PlayerKmWalked { get; set; } + public List Incubators { get; set; } + public object UnusedEggs { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/EncounteredEvent.cs b/PoGo.NecroBot.Logic/Event/EncounteredEvent.cs new file mode 100644 index 000000000..204e67f7d --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/EncounteredEvent.cs @@ -0,0 +1,40 @@ +using System; +using POGOProtos.Enums; +using Pogo; + +namespace PoGo.NecroBot.Logic.Event +{ + public class EncounteredEvent : IEvent + { + public PokemonId PokemonId { get; set; } + public double Latitude { get; set; } + public double Longitude { get; set; } + public double IV { get; set; } + public int Level { get; set; } + public DateTime Expires { get; set; } + public double ExpireTimestamp { get; set; } + public string SpawnPointId { get; set; } + public string EncounterId { get; set; } + public string Move1 { get; set; } + public string Move2 { get; set; } + public bool IsRecievedFromSocket { get; set; } + public string RecieverId { get; set; } + + public Pokemon ToPokemon() + { + return new Pokemon + { + Latitude = Latitude, + Longitude = Longitude, + EncounterId = EncounterId, + SpawnPointId = SpawnPointId, + PokemonId = (int)PokemonId, + Level = Level, + Iv = IV, + Move1 = Move1, + Move2 = Move2, + ExpiredTime = ExpireTimestamp + }; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/ErrorEvent.cs b/PoGo.NecroBot.Logic/Event/ErrorEvent.cs new file mode 100644 index 000000000..529f4be35 --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/ErrorEvent.cs @@ -0,0 +1,12 @@ +namespace PoGo.NecroBot.Logic.Event +{ + public class ErrorEvent : IEvent + { + public string Message = ""; + public bool RequireExit { get; set; } + public override string ToString() + { + return Message; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/EventDispatcher.cs b/PoGo.NecroBot.Logic/Event/EventDispatcher.cs new file mode 100644 index 000000000..ebfe24bde --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/EventDispatcher.cs @@ -0,0 +1,20 @@ +namespace PoGo.NecroBot.Logic.Event +{ + public delegate void EventDelegate(IEvent evt); + + public interface IEventDispatcher + { + event EventDelegate EventReceived; + void Send(IEvent evt); + } + + public class EventDispatcher : IEventDispatcher + { + public event EventDelegate EventReceived; + + public void Send(IEvent evt) + { + EventReceived?.Invoke(evt); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/EventUsedPotion.cs b/PoGo.NecroBot.Logic/Event/EventUsedPotion.cs new file mode 100644 index 000000000..80aa4c0a0 --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/EventUsedPotion.cs @@ -0,0 +1,10 @@ +namespace PoGo.NecroBot.Logic.Event +{ + public class EventUsedPotion : IEvent + { + public string Type; + public string PokemonId; + public int PokemonCp; + public int Remaining; + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/EventUsedRevive.cs b/PoGo.NecroBot.Logic/Event/EventUsedRevive.cs new file mode 100644 index 000000000..5776da1ef --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/EventUsedRevive.cs @@ -0,0 +1,10 @@ +namespace PoGo.NecroBot.Logic.Event +{ + public class EventUsedRevive : IEvent + { + public string Type; + public string PokemonId; + public int PokemonCp; + public int Remaining; + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/EvolveCountEvent.cs b/PoGo.NecroBot.Logic/Event/EvolveCountEvent.cs new file mode 100644 index 000000000..af0c1953a --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/EvolveCountEvent.cs @@ -0,0 +1,7 @@ +namespace PoGo.NecroBot.Logic.Event +{ + public class EvolveCountEvent : IEvent + { + public int Evolves; + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/FortFailedEvent.cs b/PoGo.NecroBot.Logic/Event/FortFailedEvent.cs new file mode 100644 index 000000000..2697f1a0e --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/FortFailedEvent.cs @@ -0,0 +1,10 @@ +namespace PoGo.NecroBot.Logic.Event +{ + public class FortFailedEvent : IEvent + { + public int Max; + public string Name; + public int Try; + public bool Looted; + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/FortTargetEvent.cs b/PoGo.NecroBot.Logic/Event/FortTargetEvent.cs new file mode 100644 index 000000000..8b7eb8c95 --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/FortTargetEvent.cs @@ -0,0 +1,12 @@ +using POGOProtos.Map.Fort; + +namespace PoGo.NecroBot.Logic.Event +{ + public class FortTargetEvent : IEvent + { + public string Route; + public double Distance; + public string Name; + public FortType Type; + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/FortUsedEvent.cs b/PoGo.NecroBot.Logic/Event/FortUsedEvent.cs new file mode 100644 index 000000000..ec5088327 --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/FortUsedEvent.cs @@ -0,0 +1,24 @@ +using POGOProtos.Data; +using POGOProtos.Map.Fort; + +namespace PoGo.NecroBot.Logic.Event +{ + public class FortUsedEvent : IEvent + { + public int Exp; + public string Gems; + public string Id; + public bool InventoryFull; + public string Items; + public string Badges; + public string BonusLoot; + public string RaidTickets; + public string TeamBonusLoot; + public PokemonData PokemonDataEgg; + public double Latitude; + public double Longitude; + public double Altitude; + public string Name; + public FortData Fort; + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/GetRouteEvent.cs b/PoGo.NecroBot.Logic/Event/GetRouteEvent.cs new file mode 100644 index 000000000..2e8a51322 --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/GetRouteEvent.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using GeoCoordinatePortable; + +namespace PoGo.NecroBot.Logic.Event +{ + public class GetRouteEvent : IEvent + { + public List Points; + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/Gym/GymBattleStarted.cs b/PoGo.NecroBot.Logic/Event/Gym/GymBattleStarted.cs new file mode 100644 index 000000000..f9de491cd --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/Gym/GymBattleStarted.cs @@ -0,0 +1,7 @@ +namespace PoGo.NecroBot.Logic.Event.Gym +{ + public class GymBattleStarted : IEvent + { + public string GymName { get; internal set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/Gym/GymDeployEvent.cs b/PoGo.NecroBot.Logic/Event/Gym/GymDeployEvent.cs new file mode 100644 index 000000000..cfb837da1 --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/Gym/GymDeployEvent.cs @@ -0,0 +1,11 @@ +using POGOProtos.Enums; +using POGOProtos.Networking.Responses; + +namespace PoGo.NecroBot.Logic.Event.Gym +{ + public class GymDeployEvent : IEvent + { + public GymGetInfoResponse GymGetInfo { get; internal set; } + public PokemonId PokemonId { get; internal set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/Gym/GymDetailInfoEvent.cs b/PoGo.NecroBot.Logic/Event/Gym/GymDetailInfoEvent.cs new file mode 100644 index 000000000..10c4c51e6 --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/Gym/GymDetailInfoEvent.cs @@ -0,0 +1,11 @@ +using POGOProtos.Enums; + +namespace PoGo.NecroBot.Logic.Event.Gym +{ + public class GymDetailInfoEvent : IEvent + { + public string Name { get; internal set; } + public int Players { get; internal set; } + public TeamColor Team { get; internal set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/Gym/GymErrorUnset.cs b/PoGo.NecroBot.Logic/Event/Gym/GymErrorUnset.cs new file mode 100644 index 000000000..abb90b96a --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/Gym/GymErrorUnset.cs @@ -0,0 +1,7 @@ +namespace PoGo.NecroBot.Logic.Event.Gym +{ + public class GymErrorUnset : IEvent + { + public string GymName { get; internal set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/Gym/GymEventMessages.cs b/PoGo.NecroBot.Logic/Event/Gym/GymEventMessages.cs new file mode 100644 index 000000000..a76ee074d --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/Gym/GymEventMessages.cs @@ -0,0 +1,10 @@ +using System; + +namespace PoGo.NecroBot.Logic.Event.Gym +{ + public class GymEventMessages : IEvent + { + public string Message { get; internal set; } + public ConsoleColor consoleColor { get; internal set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/Gym/GymListEvent.cs b/PoGo.NecroBot.Logic/Event/Gym/GymListEvent.cs new file mode 100644 index 000000000..2b8dea999 --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/Gym/GymListEvent.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using POGOProtos.Map.Fort; + +namespace PoGo.NecroBot.Logic.Event.Gym +{ + public class GymListEvent : IEvent + { + public List Gyms { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/Gym/GymTeamJoinEvent.cs b/PoGo.NecroBot.Logic/Event/Gym/GymTeamJoinEvent.cs new file mode 100644 index 000000000..7937fcdf9 --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/Gym/GymTeamJoinEvent.cs @@ -0,0 +1,12 @@ +using POGOProtos.Enums; +using POGOProtos.Networking.Responses; + +namespace PoGo.NecroBot.Logic.Event.Gym +{ + public class GymTeamJoinEvent : IEvent + { + public TeamColor Team; + + public SetPlayerTeamResponse.Types.Status Status { get; internal set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/Gym/GymWalkToTargetEvent.cs b/PoGo.NecroBot.Logic/Event/Gym/GymWalkToTargetEvent.cs new file mode 100644 index 000000000..28cccb72c --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/Gym/GymWalkToTargetEvent.cs @@ -0,0 +1,10 @@ +namespace PoGo.NecroBot.Logic.Event.Gym +{ + public class GymWalkToTargetEvent : IEvent + { + public string Name { get; internal set; } + public double Distance { get; internal set; } + public double Longitude { get; internal set; } + public double Latitude { get; internal set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/HumanWalkSnipeEvent.cs b/PoGo.NecroBot.Logic/Event/HumanWalkSnipeEvent.cs new file mode 100644 index 000000000..a475be9de --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/HumanWalkSnipeEvent.cs @@ -0,0 +1,53 @@ +#region using directives + +using System.Collections.Generic; +using PoGo.NecroBot.Logic.Model.Settings; +using PoGo.NecroBot.Logic.Tasks; +using POGOProtos.Enums; +using POGOProtos.Map.Fort; + +#endregion + +namespace PoGo.NecroBot.Logic.Event +{ + public enum HumanWalkSnipeEventTypes + { + StartWalking, + DestinationReached, + PokemonScanned, + AddedSnipePokemon, + PokestopUpdated, + NotEnoughtPalls, + TargetedPokemon, + ClientRequestUpdate, + EncounterSnipePokemon, + QueueUpdated + } + + public class HumanWalkSnipeEvent : IEvent + { + public double Latitude { get; set; } + public double Longitude { get; set; } + public string Rarity { get; set; } + public double Distance { get; set; } + + public double WalkTimes { get; set; } + + public PokemonId PokemonId { get; set; } + public HumanWalkSnipeEventTypes Type { get; set; } + public double Expires { get; internal set; } + public int Estimate { get; internal set; } + public HumanWalkSnipeFilter Setting { get; internal set; } + public bool SpinPokeStop { get; set; } + public bool CatchPokemon { get; set; } + public double NearestDistance { get; internal set; } + public List Pokestops { get; internal set; } + public int CurrentBalls { get; internal set; } + public int MinBallsToSnipe { get; internal set; } + public double WalkSpeedApplied { get; internal set; } + public List Pokemons { get; internal set; } + public string UniqueId { get; internal set; } + public int PauseDuration { get; internal set; } + public bool DisplayMessage { get; internal set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/HumanWalkingEvent.cs b/PoGo.NecroBot.Logic/Event/HumanWalkingEvent.cs new file mode 100644 index 000000000..6092dbd40 --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/HumanWalkingEvent.cs @@ -0,0 +1,8 @@ +namespace PoGo.NecroBot.Logic.Event +{ + public class HumanWalkingEvent : IEvent + { + public double OldWalkingSpeed; + public double CurrentWalkingSpeed; + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/IEvent.cs b/PoGo.NecroBot.Logic/Event/IEvent.cs new file mode 100644 index 000000000..66cd0c6e4 --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/IEvent.cs @@ -0,0 +1,6 @@ +namespace PoGo.NecroBot.Logic.Event +{ + public interface IEvent + { + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/InfoEvent.cs b/PoGo.NecroBot.Logic/Event/InfoEvent.cs new file mode 100644 index 000000000..e393bd96f --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/InfoEvent.cs @@ -0,0 +1,10 @@ +namespace PoGo.NecroBot.Logic.Event +{ + public class InfoEvent: IEvent + { + public string Message { get; set; } + public InfoEvent() + { + } + } +} diff --git a/PoGo.NecroBot.Logic/Event/Inventory/FavoriteEvent.cs b/PoGo.NecroBot.Logic/Event/Inventory/FavoriteEvent.cs new file mode 100644 index 000000000..d5477b696 --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/Inventory/FavoriteEvent.cs @@ -0,0 +1,18 @@ +using POGOProtos.Data; +using POGOProtos.Networking.Responses; + +namespace PoGo.NecroBot.Logic.Event.Inventory +{ + public class FavoriteEvent : IEvent + { + public PokemonData Pokemon { get; set; } + + public FavoriteEvent(PokemonData pkm, SetFavoritePokemonResponse res) + { + Pokemon = pkm; + FavoritePokemonResponse = res; + } + + public SetFavoritePokemonResponse FavoritePokemonResponse { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/Inventory/InventoryItemUpdateEvent.cs b/PoGo.NecroBot.Logic/Event/Inventory/InventoryItemUpdateEvent.cs new file mode 100644 index 000000000..0e7c48ab0 --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/Inventory/InventoryItemUpdateEvent.cs @@ -0,0 +1,9 @@ +using POGOProtos.Inventory.Item; + +namespace PoGo.NecroBot.Logic.Event.Inventory +{ + public class InventoryItemUpdateEvent : IEvent + { + public ItemData Item { get; internal set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/Inventory/InventoryRefreshedEvent.cs b/PoGo.NecroBot.Logic/Event/Inventory/InventoryRefreshedEvent.cs new file mode 100644 index 000000000..4e6179f09 --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/Inventory/InventoryRefreshedEvent.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using POGOProtos.Data.Player; +using POGOProtos.Inventory; +using POGOProtos.Settings.Master; + +namespace PoGo.NecroBot.Logic.Event.Inventory +{ + public class InventoryRefreshedEvent : IEvent + { + public IEnumerable PlayerStats { get; set; } + + public List PokemonSettings { get; set; } + + public List Candies { get; set; } + + public InventoryRefreshedEvent(List settings, List candy) + { + Candies = candy; + PokemonSettings = settings; + } + + public InventoryRefreshedEvent(IEnumerable playerStats, + List pokemonSettings, List candy) + { + PlayerStats = playerStats; + PokemonSettings = pokemonSettings; + Candies = candy; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/Inventory/UpgradeFinishEvent.cs b/PoGo.NecroBot.Logic/Event/Inventory/UpgradeFinishEvent.cs new file mode 100644 index 000000000..7ea174205 --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/Inventory/UpgradeFinishEvent.cs @@ -0,0 +1,10 @@ +using POGOProtos.Data; + +namespace PoGo.NecroBot.Logic.Event.Inventory +{ + public class FinishUpgradeEvent : IEvent + { + public ulong PokemonId { get; set; } + public PokemonData Pokemon { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/InventoryListEvent.cs b/PoGo.NecroBot.Logic/Event/InventoryListEvent.cs new file mode 100644 index 000000000..0640e2212 --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/InventoryListEvent.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using POGOProtos.Inventory.Item; + +namespace PoGo.NecroBot.Logic.Event +{ + public class InventoryListEvent : IEvent + { + public List Items; + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/ItemRecycledEvent.cs b/PoGo.NecroBot.Logic/Event/ItemRecycledEvent.cs new file mode 100644 index 000000000..6a8d21f31 --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/ItemRecycledEvent.cs @@ -0,0 +1,14 @@ +#region using directives + +using POGOProtos.Inventory.Item; + +#endregion + +namespace PoGo.NecroBot.Logic.Event +{ + public class ItemRecycledEvent : IEvent + { + public int Count; + public ItemId Id; + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/KillSwitchEvent.cs b/PoGo.NecroBot.Logic/Event/KillSwitchEvent.cs new file mode 100644 index 000000000..dfee23b84 --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/KillSwitchEvent.cs @@ -0,0 +1,13 @@ +namespace PoGo.NecroBot.Logic.Event +{ + public class KillSwitchEvent : IEvent + { + public string Message = string.Empty; + public bool RequireStop; + + public override string ToString() + { + return Message; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/LogEvent.cs b/PoGo.NecroBot.Logic/Event/LogEvent.cs new file mode 100644 index 000000000..457dd743c --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/LogEvent.cs @@ -0,0 +1,8 @@ +namespace PoGo.NecroBot.Logic.Event +{ + public class LogEvent : IEvent + { + public string Message; + public string Color; + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/LootPokestopEvent.cs b/PoGo.NecroBot.Logic/Event/LootPokestopEvent.cs new file mode 100644 index 000000000..4c5995370 --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/LootPokestopEvent.cs @@ -0,0 +1,9 @@ +using POGOProtos.Map.Fort; + +namespace PoGo.NecroBot.Logic.Event +{ + public class LootPokestopEvent : IEvent + { + public FortData Pokestop; + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/NicknameUpdateEvent.cs b/PoGo.NecroBot.Logic/Event/NicknameUpdateEvent.cs new file mode 100644 index 000000000..c6f5a9295 --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/NicknameUpdateEvent.cs @@ -0,0 +1,7 @@ +namespace PoGo.NecroBot.Logic.Event +{ + public class NicknameUpdateEvent : IEvent + { + public string Nickname = ""; + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/NoPokeballEvent.cs b/PoGo.NecroBot.Logic/Event/NoPokeballEvent.cs new file mode 100644 index 000000000..8d3ebf8d6 --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/NoPokeballEvent.cs @@ -0,0 +1,14 @@ +#region using directives + +using POGOProtos.Enums; + +#endregion + +namespace PoGo.NecroBot.Logic.Event +{ + public class NoPokeballEvent : IEvent + { + public int Cp; + public PokemonId Id; + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/NoticeEvent.cs b/PoGo.NecroBot.Logic/Event/NoticeEvent.cs new file mode 100644 index 000000000..6d8ba3c27 --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/NoticeEvent.cs @@ -0,0 +1,12 @@ +namespace PoGo.NecroBot.Logic.Event +{ + public class NoticeEvent : IEvent + { + public string Message = ""; + + public override string ToString() + { + return Message; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/Player/BuddyUpdateEvent.cs b/PoGo.NecroBot.Logic/Event/Player/BuddyUpdateEvent.cs new file mode 100644 index 000000000..9eb491b59 --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/Player/BuddyUpdateEvent.cs @@ -0,0 +1,17 @@ +using POGOProtos.Data; + +namespace PoGo.NecroBot.Logic.Event.Player +{ + public class BuddyUpdateEvent : IEvent + { + public BuddyPokemon Buddy { get; set; } + + public PokemonData Pokemon { get; set; } + + public BuddyUpdateEvent(BuddyPokemon updatedBuddy, PokemonData pkm) + { + Buddy = updatedBuddy; + Pokemon = pkm; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/Player/LoggedEvent.cs b/PoGo.NecroBot.Logic/Event/Player/LoggedEvent.cs new file mode 100644 index 000000000..49131cdb8 --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/Player/LoggedEvent.cs @@ -0,0 +1,9 @@ +using POGOProtos.Networking.Responses; + +namespace PoGo.NecroBot.Logic.Event.Player +{ + public class LoggedEvent : IEvent + { + public GetPlayerResponse Profile { get; internal set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/Player/LoginEvent.cs b/PoGo.NecroBot.Logic/Event/Player/LoginEvent.cs new file mode 100644 index 000000000..413fa9654 --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/Player/LoginEvent.cs @@ -0,0 +1,16 @@ +using PokemonGo.RocketAPI.Enums; + +namespace PoGo.NecroBot.Logic.Event.Player +{ + public class LoginEvent : IEvent + { + public AuthType AuthType { get; set; } + public string Username { get; set; } + + public LoginEvent(AuthType authType, string v) + { + AuthType = authType; + Username = v; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/Player/TargetLocationEvent.cs b/PoGo.NecroBot.Logic/Event/Player/TargetLocationEvent.cs new file mode 100644 index 000000000..7467698a0 --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/Player/TargetLocationEvent.cs @@ -0,0 +1,16 @@ +namespace PoGo.NecroBot.Logic.Event.Player +{ + public class TargetLocationEvent : IEvent + { + public double Latitude { get; set; } + public double Longitude { get; set; } + public TargetLocationEvent(double lat, double lng) + { + + Latitude = lat; + + Longitude = lng; + + } + } +} diff --git a/PoGo.NecroBot.Logic/Event/PokeStopListEvent.cs b/PoGo.NecroBot.Logic/Event/PokeStopListEvent.cs new file mode 100644 index 000000000..63a36cee9 --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/PokeStopListEvent.cs @@ -0,0 +1,26 @@ +#region using directives + +using System.Collections.Generic; +using POGOProtos.Map.Fort; +using POGOProtos.Map.Pokemon; + +#endregion + +namespace PoGo.NecroBot.Logic.Event +{ + public class PokeStopListEvent : IEvent + { + public List Forts; + public List NearbyPokemons; + + public PokeStopListEvent(List forts) + { + Forts = forts; + } + + public PokeStopListEvent(List forts, List nearbyPokemons) : this(forts) + { + NearbyPokemons = nearbyPokemons; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/PokemonCaptureEvent.cs b/PoGo.NecroBot.Logic/Event/PokemonCaptureEvent.cs new file mode 100644 index 000000000..4011a712f --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/PokemonCaptureEvent.cs @@ -0,0 +1,46 @@ +#region using directives + +using POGOProtos.Enums; +using POGOProtos.Inventory; +using POGOProtos.Inventory.Item; +using POGOProtos.Networking.Responses; + +#endregion + +namespace PoGo.NecroBot.Logic.Event +{ + public class PokemonCaptureEvent : IEvent + { + public int Attempt; + public int BallAmount; + public string CatchType; + public int Cp; + public double Distance; + public int Exp; + public Candy Candy; + public PokemonId Id; + public ulong UniqueId; + public double Level; + public int MaxCp; + public double Perfection; + public ItemId Pokeball; + public double Probability; + public int Stardust; + public int totalStarDust; + public CatchPokemonResponse.Types.CatchStatus Status; + public CatchPokemonResponse.Types.CaptureReason CaptureReason; + public double Latitude; + public double Longitude; + public string SpawnPointId; + public ulong EncounterId; + public PokemonMove Move1; + public PokemonMove Move2; + public long Expires; + public string CatchTypeText; + public string Rarity; + public string Shiny { get; set; } + public string Form { get; internal set; } + public string Costume { get; internal set; } + public string Gender { get; internal set; } + } +} diff --git a/PoGo.NecroBot.Logic/Event/PokemonEvolveEvent.cs b/PoGo.NecroBot.Logic/Event/PokemonEvolveEvent.cs new file mode 100644 index 000000000..d1ad76d3e --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/PokemonEvolveEvent.cs @@ -0,0 +1,27 @@ +#region using directives + +using POGOProtos.Data; +using POGOProtos.Enums; +using POGOProtos.Networking.Responses; + +#endregion + +namespace PoGo.NecroBot.Logic.Event +{ + public class PokemonEvolveEvent : IEvent + { + public int Exp; + public PokemonId Id; + public ulong UniqueId; + public EvolvePokemonResponse.Types.Result Result; + public int Candy; + public double Level; + public int Cp; + public double Perfection; + + public int Sequence { get; set; } + public PokemonData EvolvedPokemon { get; set; } + public ulong OriginalId { get; set; } + public bool Cancelled { get; set; } + } +} diff --git a/PoGo.NecroBot.Logic/Event/PokemonLevelUpEvent.cs b/PoGo.NecroBot.Logic/Event/PokemonLevelUpEvent.cs new file mode 100644 index 000000000..f4c3024f6 --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/PokemonLevelUpEvent.cs @@ -0,0 +1,18 @@ +#region using directives + +using POGOProtos.Enums; + +#endregion + +namespace PoGo.NecroBot.Logic.Event +{ + public class PokemonLevelUpEvent : IEvent + { + public int Cp; + public PokemonId Id; + public ulong UniqueId; + public int PCandies; + public int PSD; + public double Lvl; + } +} diff --git a/PoGo.NecroBot.Logic/Event/PokemonListEvent.cs b/PoGo.NecroBot.Logic/Event/PokemonListEvent.cs new file mode 100644 index 000000000..c4356b31c --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/PokemonListEvent.cs @@ -0,0 +1,15 @@ +#region using directives + +using System; +using System.Collections.Generic; +using POGOProtos.Data; + +#endregion + +namespace PoGo.NecroBot.Logic.Event +{ + public class PokemonListEvent : IEvent + { + public List> PokemonList; + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/PokemonsEncounterEvent.cs b/PoGo.NecroBot.Logic/Event/PokemonsEncounterEvent.cs new file mode 100644 index 000000000..b2e768978 --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/PokemonsEncounterEvent.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using POGOProtos.Map.Pokemon; + +namespace PoGo.NecroBot.Logic.Event +{ + public class PokemonsEncounterEvent : IEvent + { + public List EncounterPokemons; + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/PokestopLimitUpdate.cs b/PoGo.NecroBot.Logic/Event/PokestopLimitUpdate.cs new file mode 100644 index 000000000..4ebbe7ecf --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/PokestopLimitUpdate.cs @@ -0,0 +1,27 @@ +namespace PoGo.NecroBot.Logic.Event +{ + public class PokestopLimitUpdate : IEvent + { + public int Limit; + public int Value; + + public PokestopLimitUpdate(int v, int pokeStopLimit) + { + Value = v; + Limit = pokeStopLimit; + } + } + + public class CatchLimitUpdate: IEvent + { + public int Limit; + public int Value; + + public CatchLimitUpdate(int v, int catchLimit) + { + Value = v; + Limit = catchLimit; + } + + } +} diff --git a/PoGo.NecroBot.Logic/Event/ProfileEvent.cs b/PoGo.NecroBot.Logic/Event/ProfileEvent.cs new file mode 100644 index 000000000..05ce51039 --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/ProfileEvent.cs @@ -0,0 +1,17 @@ +#region using directives + +using System.Collections.Generic; +using POGOProtos.Data.Player; +using POGOProtos.Networking.Responses; + +#endregion + +namespace PoGo.NecroBot.Logic.Event +{ + public class ProfileEvent : IEvent + { + public GetPlayerResponse Profile; + + public IEnumerable Stats { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/RenamePokemonEvent.cs b/PoGo.NecroBot.Logic/Event/RenamePokemonEvent.cs new file mode 100644 index 000000000..8651cfcd8 --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/RenamePokemonEvent.cs @@ -0,0 +1,16 @@ +#region using directives + +using POGOProtos.Enums; + +#endregion + +namespace PoGo.NecroBot.Logic.Event +{ + public class RenamePokemonEvent : IEvent + { + public ulong Id; + public PokemonId PokemonId; + public string OldNickname; + public string NewNickname; + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/Snipe/AllBotSnipeEvent.cs b/PoGo.NecroBot.Logic/Event/Snipe/AllBotSnipeEvent.cs new file mode 100644 index 000000000..a1bd2979b --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/Snipe/AllBotSnipeEvent.cs @@ -0,0 +1,11 @@ +namespace PoGo.NecroBot.Logic.Event.Snipe +{ + public class AllBotSnipeEvent : IEvent + { + public string EncounterId { get; set; } + public AllBotSnipeEvent(string encounterId) + { + EncounterId = encounterId; + } + } +} diff --git a/PoGo.NecroBot.Logic/Event/Snipe/AutoSnipePokemonAddedEvent.cs b/PoGo.NecroBot.Logic/Event/Snipe/AutoSnipePokemonAddedEvent.cs new file mode 100644 index 000000000..5c3f0f40e --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/Snipe/AutoSnipePokemonAddedEvent.cs @@ -0,0 +1,12 @@ +namespace PoGo.NecroBot.Logic.Event.Snipe +{ + public class AutoSnipePokemonAddedEvent : IEvent + { + public AutoSnipePokemonAddedEvent(EncounteredEvent data) + { + EncounteredEvent = data; + } + + public EncounteredEvent EncounteredEvent { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/Snipe/SnipePokemonStarted.cs b/PoGo.NecroBot.Logic/Event/Snipe/SnipePokemonStarted.cs new file mode 100644 index 000000000..62e03f87c --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/Snipe/SnipePokemonStarted.cs @@ -0,0 +1,14 @@ +using PoGo.NecroBot.Logic.Tasks; + +namespace PoGo.NecroBot.Logic.Event.Snipe +{ + public class SnipePokemonStarted : IEvent + { + public MSniperServiceTask.MSniperInfo2 Pokemon; + + public SnipePokemonStarted(MSniperServiceTask.MSniperInfo2 location) + { + Pokemon = location; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/Snipe/SnipePokemonUpdateEvent.cs b/PoGo.NecroBot.Logic/Event/Snipe/SnipePokemonUpdateEvent.cs new file mode 100644 index 000000000..070891579 --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/Snipe/SnipePokemonUpdateEvent.cs @@ -0,0 +1,29 @@ +using PoGo.NecroBot.Logic.Tasks; + +namespace PoGo.NecroBot.Logic.Event.Snipe +{ + public class SnipePokemonUpdateEvent : IEvent + { + public MSniperServiceTask.MSniperInfo2 Data + { + get; set; + } + + public string EncounterId + { + get; set; + } + + public bool IsRemoteEvent { get; set; } + public SnipePokemonUpdateEvent(string encounterId, bool isRemoteEvent = false) + { + EncounterId = encounterId; + IsRemoteEvent = IsRemoteEvent; + } + + public SnipePokemonUpdateEvent(string encounterId, bool isRemoteEvent = false, MSniperServiceTask.MSniperInfo2 find = null) : this(encounterId, isRemoteEvent) + { + Data = find; + } + } +} diff --git a/PoGo.NecroBot.Logic/Event/SnipeEvent.cs b/PoGo.NecroBot.Logic/Event/SnipeEvent.cs new file mode 100644 index 000000000..e3d3db06e --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/SnipeEvent.cs @@ -0,0 +1,29 @@ +using Pogo; +using POGOProtos.Enums; + +namespace PoGo.NecroBot.Logic.Event +{ + public class SnipeEvent : IEvent + { + public string Message = ""; + public override string ToString() + { + return Message; + } + } + public class SnipeFailedEvent : IEvent + { + public double Latitude { get; set; } + public double Longitude { get; set; } + public PokemonId PokemonId { get; set; } + public ulong EncounterId { get; set; } + + public Pokemon ToPokemon() + { + return new Pokemon + { + EncounterId = EncounterId.ToString() + }; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/SnipeModeEvent.cs b/PoGo.NecroBot.Logic/Event/SnipeModeEvent.cs new file mode 100644 index 000000000..f87ccbbcb --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/SnipeModeEvent.cs @@ -0,0 +1,7 @@ +namespace PoGo.NecroBot.Logic.Event +{ + public class SnipeModeEvent : IEvent + { + public bool Active; + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/SnipePokemonFoundEvent.cs b/PoGo.NecroBot.Logic/Event/SnipePokemonFoundEvent.cs new file mode 100644 index 000000000..b422505ad --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/SnipePokemonFoundEvent.cs @@ -0,0 +1,14 @@ +using PoGo.NecroBot.Logic.Tasks; + +namespace PoGo.NecroBot.Logic.Event +{ + public class SnipePokemonFoundEvent : IEvent + { + public SniperInfo PokemonFound { get; set; } + + public override string ToString() + { + return string.Empty;//remove this later + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/SnipeScanEvent.cs b/PoGo.NecroBot.Logic/Event/SnipeScanEvent.cs new file mode 100644 index 000000000..6e6f41a64 --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/SnipeScanEvent.cs @@ -0,0 +1,17 @@ +#region using directives + +using PoGo.NecroBot.Logic.Model.Settings; +using POGOProtos.Enums; + +#endregion + +namespace PoGo.NecroBot.Logic.Event +{ + public class SnipeScanEvent : IEvent + { + public Location Bounds { get; set; } + public PokemonId PokemonId { get; set; } + public double Iv { get; set; } + public string Source { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/TransferPokemonEvent.cs b/PoGo.NecroBot.Logic/Event/TransferPokemonEvent.cs new file mode 100644 index 000000000..c34c20950 --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/TransferPokemonEvent.cs @@ -0,0 +1,22 @@ +#region using directives + +using POGOProtos.Enums; + +#endregion + +namespace PoGo.NecroBot.Logic.Event +{ + public class TransferPokemonEvent : IEvent + { + public int BestCp; + public double BestPerfection; + public int Cp; + public PokemonId PokemonId; + public double Perfection; + public ulong Id; + public int Candy { get; internal set; } + public PokemonFamilyId FamilyId { get; internal set; } + public double Level; + public bool Slashed; + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/UI/StatusBarEvent.cs b/PoGo.NecroBot.Logic/Event/UI/StatusBarEvent.cs new file mode 100644 index 000000000..7426b1603 --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/UI/StatusBarEvent.cs @@ -0,0 +1,12 @@ +namespace PoGo.NecroBot.Logic.Event.UI +{ + public class StatusBarEvent : IEvent + { + public StatusBarEvent(string s) + { + Message = s; + } + + public string Message { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/UpdateEvent.cs b/PoGo.NecroBot.Logic/Event/UpdateEvent.cs new file mode 100644 index 000000000..6593afcea --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/UpdateEvent.cs @@ -0,0 +1,12 @@ +namespace PoGo.NecroBot.Logic.Event +{ + public class UpdateEvent : IEvent + { + public string Message = ""; + + public override string ToString() + { + return Message; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/UpdatePositionEvent.cs b/PoGo.NecroBot.Logic/Event/UpdatePositionEvent.cs new file mode 100644 index 000000000..4da2029c0 --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/UpdatePositionEvent.cs @@ -0,0 +1,9 @@ +namespace PoGo.NecroBot.Logic.Event +{ + public class UpdatePositionEvent : IEvent + { + public double Latitude; + public double Longitude; + public double Speed; + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/UpgradePokemonEvent.cs b/PoGo.NecroBot.Logic/Event/UpgradePokemonEvent.cs new file mode 100644 index 000000000..0556003f1 --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/UpgradePokemonEvent.cs @@ -0,0 +1,24 @@ +#region using directives + +using POGOProtos.Data; +using POGOProtos.Enums; + +#endregion + +namespace PoGo.NecroBot.Logic.Event +{ + public class UpgradePokemonEvent : IEvent + { + public int BestCp; + public double BestPerfection; + public int Cp; + public int Candy; + public PokemonId PokemonId; + public ulong Id; + public double Perfection; + public int USD; + public double Lvl; + + public PokemonData Pokemon { get; set; } + } +} diff --git a/PoGo.NecroBot.Logic/Event/UseBerryEvent.cs b/PoGo.NecroBot.Logic/Event/UseBerryEvent.cs new file mode 100644 index 000000000..df12aea9c --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/UseBerryEvent.cs @@ -0,0 +1,10 @@ +using POGOProtos.Inventory.Item; + +namespace PoGo.NecroBot.Logic.Event +{ + public class UseBerryEvent : IEvent + { + public ItemId BerryType; + public int Count; + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/UseLuckyEggEvent.cs b/PoGo.NecroBot.Logic/Event/UseLuckyEggEvent.cs new file mode 100644 index 000000000..d45e15fe2 --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/UseLuckyEggEvent.cs @@ -0,0 +1,7 @@ +namespace PoGo.NecroBot.Logic.Event +{ + public class UseLuckyEggEvent : IEvent + { + public int Count; + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Event/WarnEvent.cs b/PoGo.NecroBot.Logic/Event/WarnEvent.cs new file mode 100644 index 000000000..f2b71d95e --- /dev/null +++ b/PoGo.NecroBot.Logic/Event/WarnEvent.cs @@ -0,0 +1,17 @@ +namespace PoGo.NecroBot.Logic.Event +{ + public class WarnEvent : IEvent + { + public string Message = ""; + + /// + /// This event requires handler to perform input + /// + public bool RequireInput; + + public override string ToString() + { + return Message; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Exceptions/ActiveSwitchAccountManualException.cs b/PoGo.NecroBot.Logic/Exceptions/ActiveSwitchAccountManualException.cs new file mode 100644 index 000000000..d4b3b248e --- /dev/null +++ b/PoGo.NecroBot.Logic/Exceptions/ActiveSwitchAccountManualException.cs @@ -0,0 +1,15 @@ +using PoGo.NecroBot.Logic.Model; +using System; + +namespace PoGo.NecroBot.Logic.Exceptions +{ + public class ActiveSwitchAccountManualException : Exception + { + public Account RequestedAccount; + + public ActiveSwitchAccountManualException(Account requestedAccount) + { + RequestedAccount = requestedAccount; + } + } +} diff --git a/PoGo.NecroBot.Logic/Exceptions/ActiveSwitchByPokemonException.cs b/PoGo.NecroBot.Logic/Exceptions/ActiveSwitchByPokemonException.cs new file mode 100644 index 000000000..fbf4973fe --- /dev/null +++ b/PoGo.NecroBot.Logic/Exceptions/ActiveSwitchByPokemonException.cs @@ -0,0 +1,17 @@ +using System; +using PoGo.NecroBot.Logic.Event; +using POGOProtos.Enums; +using PoGo.NecroBot.Logic.Model; + +namespace PoGo.NecroBot.Logic.Exceptions +{ + public class ActiveSwitchByPokemonException : Exception + { + public double LastLatitude { get; set; } + public double LastLongitude { get; set; } + public PokemonId LastEncounterPokemonId { get; set; } + public Account Bot { get; set; } + public bool Snipe { get; set; } + public EncounteredEvent EncounterData { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Exceptions/ActiveSwitchByRuleException.cs b/PoGo.NecroBot.Logic/Exceptions/ActiveSwitchByRuleException.cs new file mode 100644 index 000000000..c51cdd48f --- /dev/null +++ b/PoGo.NecroBot.Logic/Exceptions/ActiveSwitchByRuleException.cs @@ -0,0 +1,33 @@ +using System; + +namespace PoGo.NecroBot.Logic.Exceptions +{ + public enum SwitchRules + { + Pokestop, + Pokemon, + EXP, + Runtime, + PokestopSoftban, + CatchFlee, + CatchLimitReached, + SpinPokestopReached, + EmptyMap + } + + public class ActiveSwitchByRuleException : Exception + { + public ActiveSwitchByRuleException() + { + } + + public ActiveSwitchByRuleException(SwitchRules rule, double value) + { + MatchedRule = rule; + ReachedValue = value; + } + + public SwitchRules MatchedRule { get; set; } + public double ReachedValue { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Extensions.cs b/PoGo.NecroBot.Logic/Extensions.cs new file mode 100644 index 000000000..337d1adb9 --- /dev/null +++ b/PoGo.NecroBot.Logic/Extensions.cs @@ -0,0 +1,30 @@ +using PoGo.NecroBot.Logic.Model.Settings; +using PoGo.NecroBot.Logic.PoGoUtils; +using POGOProtos.Data; +using POGOProtos.Enums; +using System.Collections.Generic; + +namespace PoGo.NecroBot.Logic +{ + public static class Extensions + { + public static double Perfection(this PokemonData pkm) + { + return PokemonInfo.CalculatePokemonPerfection(pkm); + } + public static double Level(this PokemonData pkm) + { + return PokemonInfo.GetLevel(pkm); + } + + public static double CP(this PokemonData pkm) + { + return PokemonInfo.CalculateCp(pkm); + } + + public static T GetFilter(this Dictionary source, PokemonId pid) where T:IPokemonFilter + { + return FilterUtil.GetApplyFilter(source, pid); + } + } +} diff --git a/PoGo.NecroBot.Logic/Forms/AutoUpdateForm.Designer.cs b/PoGo.NecroBot.Logic/Forms/AutoUpdateForm.Designer.cs new file mode 100644 index 000000000..859cf3333 --- /dev/null +++ b/PoGo.NecroBot.Logic/Forms/AutoUpdateForm.Designer.cs @@ -0,0 +1,216 @@ +namespace PoGo.NecroBot.Logic.Forms +{ + partial class AutoUpdateForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.label1 = new System.Windows.Forms.Label(); + this.label2 = new System.Windows.Forms.Label(); + this.panel1 = new System.Windows.Forms.Panel(); + this.lblMessage = new System.Windows.Forms.Label(); + this.btnCancel = new System.Windows.Forms.Button(); + this.btnUpdate = new System.Windows.Forms.Button(); + this.progressBar1 = new System.Windows.Forms.ProgressBar(); + this.panel2 = new System.Windows.Forms.Panel(); + this.lblLatest = new System.Windows.Forms.Label(); + this.lblCurrent = new System.Windows.Forms.Label(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.richTextBox1 = new System.Windows.Forms.RichTextBox(); + this.panel1.SuspendLayout(); + this.panel2.SuspendLayout(); + this.groupBox1.SuspendLayout(); + this.SuspendLayout(); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(16, 15); + this.label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(111, 17); + this.label1.TabIndex = 0; + this.label1.Text = "Current Version:"; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(275, 16); + this.label2.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(103, 17); + this.label2.TabIndex = 1; + this.label2.Text = "Latest Version:"; + // + // panel1 + // + this.panel1.Controls.Add(this.lblMessage); + this.panel1.Controls.Add(this.btnCancel); + this.panel1.Controls.Add(this.btnUpdate); + this.panel1.Dock = System.Windows.Forms.DockStyle.Bottom; + this.panel1.Location = new System.Drawing.Point(0, 346); + this.panel1.Margin = new System.Windows.Forms.Padding(4); + this.panel1.Name = "panel1"; + this.panel1.Size = new System.Drawing.Size(661, 91); + this.panel1.TabIndex = 2; + // + // lblMessage + // + this.lblMessage.AutoSize = true; + this.lblMessage.Enabled = false; + this.lblMessage.Location = new System.Drawing.Point(16, 20); + this.lblMessage.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.lblMessage.Name = "lblMessage"; + this.lblMessage.Size = new System.Drawing.Size(0, 17); + this.lblMessage.TabIndex = 3; + // + // btnCancel + // + this.btnCancel.Location = new System.Drawing.Point(543, 49); + this.btnCancel.Margin = new System.Windows.Forms.Padding(4); + this.btnCancel.Name = "btnCancel"; + this.btnCancel.Size = new System.Drawing.Size(113, 27); + this.btnCancel.TabIndex = 2; + this.btnCancel.Text = "Cancel"; + this.btnCancel.UseVisualStyleBackColor = true; + this.btnCancel.Click += new System.EventHandler(this.Btncancel_Click); + // + // btnUpdate + // + this.btnUpdate.Location = new System.Drawing.Point(383, 49); + this.btnUpdate.Margin = new System.Windows.Forms.Padding(4); + this.btnUpdate.Name = "btnUpdate"; + this.btnUpdate.Size = new System.Drawing.Size(152, 27); + this.btnUpdate.TabIndex = 1; + this.btnUpdate.Text = "Update"; + this.btnUpdate.UseVisualStyleBackColor = true; + this.btnUpdate.Click += new System.EventHandler(this.BtnUpdate_Click); + // + // progressBar1 + // + this.progressBar1.Location = new System.Drawing.Point(4, 7); + this.progressBar1.Margin = new System.Windows.Forms.Padding(4); + this.progressBar1.Name = "progressBar1"; + this.progressBar1.Size = new System.Drawing.Size(651, 28); + this.progressBar1.TabIndex = 0; + // + // panel2 + // + this.panel2.Controls.Add(this.lblLatest); + this.panel2.Controls.Add(this.lblCurrent); + this.panel2.Controls.Add(this.label1); + this.panel2.Controls.Add(this.label2); + this.panel2.Dock = System.Windows.Forms.DockStyle.Top; + this.panel2.Location = new System.Drawing.Point(0, 0); + this.panel2.Margin = new System.Windows.Forms.Padding(4); + this.panel2.Name = "panel2"; + this.panel2.Size = new System.Drawing.Size(661, 42); + this.panel2.TabIndex = 3; + // + // lblLatest + // + this.lblLatest.AutoSize = true; + this.lblLatest.Location = new System.Drawing.Point(392, 16); + this.lblLatest.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.lblLatest.Name = "lblLatest"; + this.lblLatest.Size = new System.Drawing.Size(59, 17); + this.lblLatest.TabIndex = 3; + this.lblLatest.Text = "v1.0.0.0"; + // + // lblCurrent + // + this.lblCurrent.AutoSize = true; + this.lblCurrent.Location = new System.Drawing.Point(128, 16); + this.lblCurrent.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.lblCurrent.Name = "lblCurrent"; + this.lblCurrent.Size = new System.Drawing.Size(59, 17); + this.lblCurrent.TabIndex = 2; + this.lblCurrent.Text = "v1.0.0.0"; + // + // groupBox1 + // + this.groupBox1.Controls.Add(this.richTextBox1); + this.groupBox1.Dock = System.Windows.Forms.DockStyle.Fill; + this.groupBox1.Location = new System.Drawing.Point(0, 42); + this.groupBox1.Margin = new System.Windows.Forms.Padding(4); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.Padding = new System.Windows.Forms.Padding(4); + this.groupBox1.Size = new System.Drawing.Size(661, 304); + this.groupBox1.TabIndex = 4; + this.groupBox1.TabStop = false; + this.groupBox1.Text = "Release Notes"; + // + // richTextBox1 + // + this.richTextBox1.Dock = System.Windows.Forms.DockStyle.Fill; + this.richTextBox1.Location = new System.Drawing.Point(4, 19); + this.richTextBox1.Margin = new System.Windows.Forms.Padding(13, 12, 13, 12); + this.richTextBox1.Name = "richTextBox1"; + this.richTextBox1.ReadOnly = true; + this.richTextBox1.Size = new System.Drawing.Size(653, 281); + this.richTextBox1.TabIndex = 0; + this.richTextBox1.Text = ""; + // + // AutoUpdateForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(661, 437); + this.Controls.Add(this.groupBox1); + this.Controls.Add(this.panel2); + this.Controls.Add(this.panel1); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow; + this.Margin = new System.Windows.Forms.Padding(4); + this.Name = "AutoUpdateForm"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; + this.Tag = "New Version Available!"; + this.Text = "New Version Available!"; + this.Load += new System.EventHandler(this.AutoUpdateForm_Load); + this.panel1.ResumeLayout(false); + this.panel1.PerformLayout(); + this.panel2.ResumeLayout(false); + this.panel2.PerformLayout(); + this.groupBox1.ResumeLayout(false); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Panel panel1; + private System.Windows.Forms.Label lblMessage; + private System.Windows.Forms.Button btnCancel; + private System.Windows.Forms.Button btnUpdate; + private System.Windows.Forms.ProgressBar progressBar1; + private System.Windows.Forms.Panel panel2; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.RichTextBox richTextBox1; + private System.Windows.Forms.Label lblLatest; + private System.Windows.Forms.Label lblCurrent; + } +} diff --git a/PoGo.NecroBot.Logic/Forms/AutoUpdateForm.cs b/PoGo.NecroBot.Logic/Forms/AutoUpdateForm.cs new file mode 100644 index 000000000..5fc5fd2a7 --- /dev/null +++ b/PoGo.NecroBot.Logic/Forms/AutoUpdateForm.cs @@ -0,0 +1,131 @@ +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.Event.UI; +using PoGo.NecroBot.Logic.Logging; +using PoGo.NecroBot.Logic.State; +using System; +using System.ComponentModel; +using System.Net; +using System.Windows.Forms; +using Markdig; +using System.Text.RegularExpressions; +using System.Web; + +namespace PoGo.NecroBot.Logic.Forms +{ + + public partial class AutoUpdateForm : Form + { + public string LatestVersion { get; set; } + public string CurrentVersion { get; set; } + public bool AutoUpdate { get; set; } + public string DownloadLink { get; set; } + public string ChangelogLink { get; set; } + public string Destination { get; set; } + public ISession Session { get; set; } + + public AutoUpdateForm() + { + InitializeComponent(); + } + + public static string StripHTML(string HTMLText, bool decode = true) + { + Regex reg = new Regex("<[^>]+>", RegexOptions.IgnoreCase); + var stripped = reg.Replace(HTMLText, ""); + return decode ? HttpUtility.HtmlDecode(stripped) : stripped; + } + + private void AutoUpdateForm_Load(object sender, EventArgs e) + { + richTextBox1.SetInnerMargins(25, 25, 25, 25); + lblCurrent.Text = $"v{CurrentVersion}"; + lblLatest.Text = $"v{LatestVersion}"; + var Client = new WebClient(); + var ChangelogRaw = Client.DownloadString(ChangelogLink); + var ChangelogFormatted = StripHTML(Markdown.ToHtml(ChangelogRaw)).Replace("Full Changelog", "").Replace("Change Log", ""); + if (ChangelogFormatted.Length > 0) + { + richTextBox1.Text = ChangelogFormatted; + } + else + { + richTextBox1.Text = "No Changelog Detected..."; + } + if (AutoUpdate) + { + btnUpdate.Enabled = false; + lblMessage.Enabled = true; + btnUpdate.Text = "Downloading..."; + StartDownload(); + } + } + + public bool DownloadFile(string url, string dest) + { + Session.EventDispatcher.Send(new UpdateEvent + { + Message = Session.Translation.GetTranslation(TranslationString.DownloadingUpdate) + }); + + using (var client = new WebClient()) + { + try + { + client.DownloadFileCompleted += Client_DownloadFileCompleted; + client.DownloadProgressChanged += Client_DownloadProgressChanged; + + client.DownloadFileAsync(new Uri(url), dest); + Logger.Write(dest, LogLevel.Info); + } + catch + { + Close(); + } + return true; + } + } + + private void Client_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e) + { + Session.EventDispatcher.Send(new UpdateEvent + { + Message = Session.Translation.GetTranslation(TranslationString.FinishedDownloadingRelease) + }); + + Invoke(new Action(() => + { + DialogResult = DialogResult.OK; + Close(); + })); + } + + private void Client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e) + { + Invoke(new Action(() => + { + lblMessage.Text = $"Updating {Application.ProductName} from v{CurrentVersion} to v{LatestVersion} ({e.ProgressPercentage}% Completed)"; + })); + } + + + public void StartDownload() + { + Session.EventDispatcher.Send(new StatusBarEvent($"Updating to v{LatestVersion}, Downloading from {DownloadLink}")); + Logger.Write(DownloadLink, LogLevel.Info); + DownloadFile(DownloadLink, Destination); + } + + private void BtnUpdate_Click(object sender, EventArgs e) + { + btnUpdate.Text = "Downloading..."; + btnUpdate.Enabled = false; + StartDownload(); + } + + private void Btncancel_Click(object sender, EventArgs e) + { + Close(); + } + } +} diff --git a/PoGo.NecroBot.Logic/Forms/AutoUpdateForm.resx b/PoGo.NecroBot.Logic/Forms/AutoUpdateForm.resx new file mode 100644 index 000000000..1af7de150 --- /dev/null +++ b/PoGo.NecroBot.Logic/Forms/AutoUpdateForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Forms/CaptchaSolveForm.Designer.cs b/PoGo.NecroBot.Logic/Forms/CaptchaSolveForm.Designer.cs new file mode 100644 index 000000000..b5546c4f9 --- /dev/null +++ b/PoGo.NecroBot.Logic/Forms/CaptchaSolveForm.Designer.cs @@ -0,0 +1,48 @@ +namespace PoGo.NecroBot.Logic.Forms +{ + partial class CaptchaSolveForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.SuspendLayout(); + // + // CaptchaSolveForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(492, 429); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow; + this.Name = "CaptchaSolveForm"; + this.Text = "Please solve captcha"; + this.Load += new System.EventHandler(this.CaptchaSolveForm_Load); + this.ResumeLayout(false); + + } + + #endregion + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Forms/CaptchaSolveForm.cs b/PoGo.NecroBot.Logic/Forms/CaptchaSolveForm.cs new file mode 100644 index 000000000..56e84d452 --- /dev/null +++ b/PoGo.NecroBot.Logic/Forms/CaptchaSolveForm.cs @@ -0,0 +1,31 @@ +using System; +using System.Windows.Forms; + +namespace PoGo.NecroBot.Logic.Forms +{ + public partial class CaptchaSolveForm : Form + { + public CaptchaSolveForm(string url) + { + captchaUrl = url; + InitializeComponent(); + } + + private string captchaUrl = ""; + + private void CaptchaSolveForm_Load(object sender, EventArgs e) + { + //this.webBrowser1.Navigate(captchaUrl); + var web = new WebBrowser() + { + Dock = DockStyle.Fill + }; + Controls.Add(web); + web.Navigate(captchaUrl); + } + + private void WebBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) + { + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Forms/CaptchaSolveForm.resx b/PoGo.NecroBot.Logic/Forms/CaptchaSolveForm.resx new file mode 100644 index 000000000..1af7de150 --- /dev/null +++ b/PoGo.NecroBot.Logic/Forms/CaptchaSolveForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Forms/EXComboBox.cs b/PoGo.NecroBot.Logic/Forms/EXComboBox.cs new file mode 100644 index 000000000..153773bd0 --- /dev/null +++ b/PoGo.NecroBot.Logic/Forms/EXComboBox.cs @@ -0,0 +1,165 @@ +using System.Windows.Forms; +using System.Drawing; +using System.Collections; + +namespace PoGo.NecroBot.Logic.Forms +{ + + class EXComboBox : ComboBox { + + private Brush _highlightbrush; //color of highlighted items + + public EXComboBox() { + _highlightbrush = SystemBrushes.Highlight; + DrawMode = DrawMode.OwnerDrawFixed; + DrawItem += new DrawItemEventHandler(This_DrawItem); + } + + public Brush MyHighlightBrush { + get {return _highlightbrush;} + set {_highlightbrush = value;} + } + + private void This_DrawItem(object sender, DrawItemEventArgs e) { + if (e.Index == -1) return; + e.DrawBackground(); + if ((e.State & DrawItemState.Selected) != 0) { + e.Graphics.FillRectangle(_highlightbrush, e.Bounds); + } + EXItem item = (EXItem)Items[e.Index]; + Rectangle bounds = e.Bounds; + int x = bounds.X + 2; + if (item.GetType() == typeof(EXImageItem)) { + EXImageItem imgitem = (EXImageItem) item; + if (imgitem.MyImage != null) { + Image img = imgitem.MyImage; + int y = bounds.Y + ((int) (bounds.Height / 2)) - ((int) (img.Height / 2)) + 1; + e.Graphics.DrawImage(img, x, y, img.Width, img.Height); + x += img.Width + 2; + } + } else if (item.GetType() == typeof(EXMultipleImagesItem)) { + EXMultipleImagesItem imgitem = (EXMultipleImagesItem) item; + if (imgitem.MyImages != null) { + for (int i = 0; i < imgitem.MyImages.Count; i++) { + Image img = (Image) imgitem.MyImages[i]; + int y = bounds.Y + ((int) (bounds.Height / 2)) - ((int) (img.Height / 2)) + 1; + e.Graphics.DrawImage(img, x, y, img.Width, img.Height); + x += img.Width + 2; + } + } + } + int fonty = bounds.Y + ((int) (bounds.Height / 2)) - ((int) (e.Font.Height / 2)); + e.Graphics.DrawString(item.Text, e.Font, new SolidBrush(e.ForeColor), x, fonty); + e.DrawFocusRectangle(); + } + + public class EXItem { + + private string _text = ""; + private string _value = ""; + + public EXItem() { + + } + + public EXItem(string text) { + _text = text; + } + + public string Text { + get {return _text;} + set {_text = value;} + } + + public string MyValue { + get {return _value;} + set {_value = value;} + } + + public override string ToString() { + return _text; + } + + } + + public class EXImageItem : EXItem { + + private Image _image; + + public EXImageItem() { + + } + + public EXImageItem(string text) { + Text = text; + } + + public EXImageItem(Image image) { + _image = image; + } + + public EXImageItem(string text, Image image) { + Text = text; + _image = image; + } + + public EXImageItem(Image image, string value) { + _image = image; + MyValue = value; + } + + public EXImageItem(string text, Image image, string value) { + Text = text; + _image = image; + MyValue = value; + } + + public Image MyImage { + get {return _image;} + set {_image = value;} + } + + } + + public class EXMultipleImagesItem : EXItem { + + private ArrayList _images; + + public EXMultipleImagesItem() { + + } + + public EXMultipleImagesItem(string text) { + Text = text; + } + + public EXMultipleImagesItem(ArrayList images) { + _images = images; + } + + public EXMultipleImagesItem(string text, ArrayList images) { + Text = text; + _images = images; + } + + public EXMultipleImagesItem(ArrayList images, string value) { + _images = images; + MyValue = value; + } + + public EXMultipleImagesItem(string text, ArrayList images, string value) { + Text = text; + _images = images; + MyValue = value; + } + + public ArrayList MyImages { + get {return _images;} + set {_images = value;} + } + + } + + } + +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Forms/EXListView.cs b/PoGo.NecroBot.Logic/Forms/EXListView.cs new file mode 100644 index 000000000..e8f5b708b --- /dev/null +++ b/PoGo.NecroBot.Logic/Forms/EXListView.cs @@ -0,0 +1,1072 @@ +using System; +using System.Windows.Forms; +using System.Drawing; +using System.Collections; +using System.Runtime.InteropServices; + +namespace PoGo.NecroBot.Logic.Forms +{ + public class EXListView : ListView + { + private ListViewItem.ListViewSubItem _clickedsubitem; //clicked ListViewSubItem + private ListViewItem _clickeditem; //clicked ListViewItem + private int _col; //index of doubleclicked ListViewSubItem + private TextBox txtbx; //the default edit control + private int _sortcol; //index of clicked ColumnHeader + private Brush _sortcolbrush; //color of items in sorted column + private Brush _highlightbrush; //color of highlighted items + private int _cpadding; //padding of the embedded controls + + private const UInt32 LVM_FIRST = 0x1000; + private const UInt32 LVM_SCROLL = (LVM_FIRST + 20); + private const int WM_HSCROLL = 0x114; + private const int WM_VSCROLL = 0x115; + private const int WM_MOUSEWHEEL = 0x020A; + private const int WM_PAINT = 0x000F; + + private struct EmbeddedControl + { + public Control MyControl; + public EXControlListViewSubItem MySubItem; + } + + private ArrayList _controls; + + [DllImport("user32.dll")] + private static extern bool SendMessage(IntPtr hWnd, UInt32 m, int wParam, int lParam); + + protected override void WndProc(ref Message m) + { + if (m.Msg == WM_PAINT) + { + foreach (EmbeddedControl c in _controls) + { + Rectangle r = c.MySubItem.Bounds; + if (r.Y > 0 && r.Y < ClientRectangle.Height) + { + c.MyControl.Visible = true; + c.MyControl.Bounds = new Rectangle(r.X + _cpadding, r.Y + _cpadding, r.Width - (2 * _cpadding), r.Height - (2 * _cpadding)); + } + else + { + c.MyControl.Visible = false; + } + } + } + switch (m.Msg) + { + case WM_HSCROLL: + case WM_VSCROLL: + case WM_MOUSEWHEEL: + Focus(); + break; + } + base.WndProc(ref m); + } + + private void ScrollMe(int x, int y) + { + SendMessage(Handle, LVM_SCROLL, x, y); + } + + public EXListView() + { + _cpadding = 4; + _controls = new ArrayList(); + _sortcol = -1; + _sortcolbrush = SystemBrushes.ControlLight; + _highlightbrush = SystemBrushes.Highlight; + OwnerDraw = true; + FullRowSelect = true; + View = View.Details; + MouseDown += new MouseEventHandler(This_MouseDown); + MouseDoubleClick += new MouseEventHandler(This_MouseDoubleClick); + DrawColumnHeader += new DrawListViewColumnHeaderEventHandler(This_DrawColumnHeader); + DrawSubItem += new DrawListViewSubItemEventHandler(This_DrawSubItem); + MouseMove += new MouseEventHandler(This_MouseMove); + ColumnClick += new ColumnClickEventHandler(This_ColumnClick); + txtbx = new TextBox() + { + Visible = false + }; + Controls.Add(txtbx); + txtbx.Leave += new EventHandler(C_Leave); + txtbx.KeyPress += new KeyPressEventHandler(Txtbx_KeyPress); + } + + public void AddControlToSubItem(Control control, EXControlListViewSubItem subitem) + { + Controls.Add(control); + subitem.MyControl = control; + EmbeddedControl ec; + ec.MyControl = control; + ec.MySubItem = subitem; + _controls.Add(ec); + } + + public void RemoveControlFromSubItem(EXControlListViewSubItem subitem) + { + Control c = subitem.MyControl; + for (int i = 0; i < _controls.Count; i++) { + if (((EmbeddedControl)_controls[i]).MySubItem == subitem) { + _controls.RemoveAt(i); + subitem.MyControl = null; + Controls.Remove(c); + c.Dispose(); + return; + } + } + } + + public int ControlPadding + { + get {return _cpadding;} + set {_cpadding = value;} + } + + public Brush MySortBrush + { + get {return _sortcolbrush;} + set {_sortcolbrush = value;} + } + + public Brush MyHighlightBrush + { + get {return _highlightbrush;} + set {_highlightbrush = value;} + } + + private void Txtbx_KeyPress(object sender, KeyPressEventArgs e) + { + if (e.KeyChar == (char) Keys.Return) { + _clickedsubitem.Text = txtbx.Text; + txtbx.Visible = false; + _clickeditem.Tag = null; + } + } + + private void C_Leave(object sender, EventArgs e) + { + Control c = (Control) sender; + _clickedsubitem.Text = c.Text; + c.Visible = false; + _clickeditem.Tag = null; + } + + private void This_MouseDown(object sender, MouseEventArgs e) + { + ListViewHitTestInfo lstvinfo = HitTest(e.X, e.Y); + ListViewItem.ListViewSubItem subitem = lstvinfo.SubItem; + if (subitem == null) return; + int subx = subitem.Bounds.Left; + if (subx < 0) + { + ScrollMe(subx, 0); + } + } + + private void This_MouseDoubleClick(object sender, MouseEventArgs e) + { + EXListViewItem lstvItem = GetItemAt(e.X, e.Y) as EXListViewItem; + if (lstvItem == null) return; + _clickeditem = lstvItem; + int x = lstvItem.Bounds.Left; + int i; + for (i = 0; i < Columns.Count; i++) + { + x = x + Columns[i].Width; + if (x > e.X) + { + x = x - Columns[i].Width; + _clickedsubitem = lstvItem.SubItems[i]; + _col = i; + break; + } + } + if (!(Columns[i] is EXColumnHeader)) return; + EXColumnHeader col = (EXColumnHeader)Columns[i]; + if (col.GetType() == typeof(EXEditableColumnHeader)) + { + EXEditableColumnHeader editcol = (EXEditableColumnHeader) col; + if (editcol.MyControl != null) + { + Control c = editcol.MyControl; + if (c.Tag != null) + { + Controls.Add(c); + c.Tag = null; + if (c is ComboBox) + { + ((ComboBox) c).SelectedValueChanged += new EventHandler(Cmbx_SelectedValueChanged); + } + c.Leave += new EventHandler(C_Leave); + } + c.Location = new Point(x, GetItemRect(Items.IndexOf(lstvItem)).Y); + c.Width = Columns[i].Width; + if (c.Width > Width) c.Width = ClientRectangle.Width; + c.Text = _clickedsubitem.Text; + c.Visible = true; + c.BringToFront(); + c.Focus(); + } + else + { + txtbx.Location = new Point(x, GetItemRect(Items.IndexOf(lstvItem)).Y); + txtbx.Width = Columns[i].Width; + if (txtbx.Width > Width) txtbx.Width = ClientRectangle.Width; + txtbx.Text = _clickedsubitem.Text; + txtbx.Visible = true; + txtbx.BringToFront(); + txtbx.Focus(); + } + } + else if (col.GetType() == typeof(EXBoolColumnHeader)) + { + EXBoolColumnHeader boolcol = (EXBoolColumnHeader) col; + if (boolcol.Editable) + { + EXBoolListViewSubItem boolsubitem = (EXBoolListViewSubItem) _clickedsubitem; + if (boolsubitem.BoolValue == true) + { + boolsubitem.BoolValue = false; + } + else + { + boolsubitem.BoolValue = true; + } + Invalidate(boolsubitem.Bounds); + } + } + } + + private void Cmbx_SelectedValueChanged(object sender, EventArgs e) + { + if (((Control) sender).Visible == false || _clickedsubitem == null) return; + if (sender.GetType() == typeof(EXComboBox)) + { + EXComboBox excmbx = (EXComboBox) sender; + object item = excmbx.SelectedItem; + //Is this an combobox item with one image? + if (item.GetType() == typeof(EXComboBox.EXImageItem)) + { + EXComboBox.EXImageItem imgitem = (EXComboBox.EXImageItem) item; + //Is the first column clicked -- in that case it's a ListViewItem + if (_col == 0) + { + if (_clickeditem.GetType() == typeof(EXImageListViewItem)) + { + ((EXImageListViewItem) _clickeditem).MyImage = imgitem.MyImage; + } + else if (_clickeditem.GetType() == typeof(EXMultipleImagesListViewItem)) + { + EXMultipleImagesListViewItem imglstvitem = (EXMultipleImagesListViewItem) _clickeditem; + imglstvitem.MyImages.Clear(); + imglstvitem.MyImages.AddRange(new object[] {imgitem.MyImage}); + } + //another column than the first one is clicked, so we have a ListViewSubItem + } + else + { + if (_clickedsubitem.GetType() == typeof(EXImageListViewSubItem)) + { + EXImageListViewSubItem imgsub = (EXImageListViewSubItem) _clickedsubitem; + imgsub.MyImage = imgitem.MyImage; + } + else if (_clickedsubitem.GetType() == typeof(EXMultipleImagesListViewSubItem)) + { + EXMultipleImagesListViewSubItem imgsub = (EXMultipleImagesListViewSubItem) _clickedsubitem; + imgsub.MyImages.Clear(); + imgsub.MyImages.Add(imgitem.MyImage); + imgsub.MyValue = imgitem.MyValue; + } + } + //or is this a combobox item with multiple images? + } + else if (item.GetType() == typeof(EXComboBox.EXMultipleImagesItem)) + { + EXComboBox.EXMultipleImagesItem imgitem = (EXComboBox.EXMultipleImagesItem) item; + if (_col == 0) + { + if (_clickeditem.GetType() == typeof(EXImageListViewItem)) + { + ((EXImageListViewItem) _clickeditem).MyImage = (Image) imgitem.MyImages[0]; + } + else if (_clickeditem.GetType() == typeof(EXMultipleImagesListViewItem)) + { + EXMultipleImagesListViewItem imglstvitem = (EXMultipleImagesListViewItem) _clickeditem; + imglstvitem.MyImages.Clear(); + imglstvitem.MyImages.AddRange(imgitem.MyImages); + } + } + else + { + if (_clickedsubitem.GetType() == typeof(EXImageListViewSubItem)) + { + EXImageListViewSubItem imgsub = (EXImageListViewSubItem) _clickedsubitem; + if (imgitem.MyImages != null) + { + imgsub.MyImage = (Image) imgitem.MyImages[0]; + } + } + else if (_clickedsubitem.GetType() == typeof(EXMultipleImagesListViewSubItem)) + { + EXMultipleImagesListViewSubItem imgsub = (EXMultipleImagesListViewSubItem) _clickedsubitem; + imgsub.MyImages.Clear(); + imgsub.MyImages.AddRange(imgitem.MyImages); + imgsub.MyValue = imgitem.MyValue; + } + } + } + } + ComboBox c = (ComboBox) sender; + _clickedsubitem.Text = c.Text; + c.Visible = false; + _clickeditem.Tag = null; + } + + private void This_MouseMove(object sender, MouseEventArgs e) + { + ListViewItem item = GetItemAt(e.X, e.Y); + if (item != null && item.Tag == null) + { + Invalidate(item.Bounds); + item.Tag = "t"; + } + } + + private void This_DrawColumnHeader(object sender, DrawListViewColumnHeaderEventArgs e) + { + e.DrawDefault = true; + } + + private void This_DrawSubItem(object sender, DrawListViewSubItemEventArgs e) + { + e.DrawBackground(); + if (e.ColumnIndex == _sortcol) + { + e.Graphics.FillRectangle(_sortcolbrush, e.Bounds); + } + if ((e.ItemState & ListViewItemStates.Selected) != 0) + { + e.Graphics.FillRectangle(_highlightbrush, e.Bounds); + } + int fonty = e.Bounds.Y + e.Bounds.Height / 2 - e.SubItem.Font.Height / 2; + int x = e.Bounds.X + 2; + if (e.ColumnIndex == 0) + { + EXListViewItem item = (EXListViewItem) e.Item; + if (item.GetType() == typeof(EXImageListViewItem)) + { + EXImageListViewItem imageitem = (EXImageListViewItem) item; + if (imageitem.MyImage != null) + { + Image img = imageitem.MyImage; + int imgy = e.Bounds.Y + e.Bounds.Height / 2 - img.Height / 2; + e.Graphics.DrawImage(img, x, imgy, img.Width, img.Height); + x += img.Width + 2; + } + } + e.Graphics.DrawString(e.SubItem.Text, e.SubItem.Font, new SolidBrush(e.SubItem.ForeColor), x, fonty); + return; + } + EXListViewSubItemAB subitem = e.SubItem as EXListViewSubItemAB; + if (subitem == null) + { + e.DrawDefault = true; + } + else + { + x = subitem.DoDraw(e, x, Columns[e.ColumnIndex] as EXColumnHeader); + e.Graphics.DrawString(e.SubItem.Text, e.SubItem.Font, new SolidBrush(e.SubItem.ForeColor), x, fonty); + } + } + + private void This_ColumnClick(object sender, ColumnClickEventArgs e) + { + if (Items.Count == 0) return; + for (int i = 0; i < Columns.Count; i++) + { + Columns[i].ImageKey = null; + } + for (int i = 0; i < Items.Count; i++) + { + Items[i].Tag = null; + } + if (e.Column != _sortcol) + { + _sortcol = e.Column; + Sorting = SortOrder.Ascending; + Columns[e.Column].ImageKey = "up"; + } + else + { + if (Sorting == SortOrder.Ascending) + { + Sorting = SortOrder.Descending; + Columns[e.Column].ImageKey = "down"; + } + else + { + Sorting = SortOrder.Ascending; + Columns[e.Column].ImageKey = "up"; + } + } + if (_sortcol == 0) + { + //ListViewItem + if (Items[0].GetType() == typeof(EXListViewItem)) + { + //sorting on text + ListViewItemSorter = new ListViewItemComparerText(e.Column, Sorting); + } + else + { + //sorting on value + ListViewItemSorter = new ListViewItemComparerValue(e.Column, Sorting); + } + } + else + { + //ListViewSubItem + if (Items[0].SubItems[_sortcol].GetType() == typeof(EXListViewSubItemAB)) + { + //sorting on text + ListViewItemSorter = new ListViewSubItemComparerText(e.Column, Sorting); + } + else + { + //sorting on value + ListViewItemSorter = new ListViewSubItemComparerValue(e.Column, Sorting); + } + } + } + + class ListViewSubItemComparerText : IComparer + { + + private int _col; + private SortOrder _order; + + public ListViewSubItemComparerText() { + _col = 0; + _order = SortOrder.Ascending; + } + + public ListViewSubItemComparerText(int col, SortOrder order) { + _col = col; + _order = order; + } + + public int Compare(object x, object y) + { +#pragma warning disable IDE0018 // Inline variable declaration - Build.Bat Error Happens if We Do + int returnVal = -1; + + string xstr = ((ListViewItem) x).SubItems[_col].Text; + string ystr = ((ListViewItem) y).SubItems[_col].Text; + + decimal dec_x; + decimal dec_y; + DateTime dat_x; + DateTime dat_y; + + if (Decimal.TryParse(xstr, out dec_x) && Decimal.TryParse(ystr, out dec_y)) + { + returnVal = Decimal.Compare(dec_x, dec_y); + } + else if (DateTime.TryParse(xstr, out dat_x) && DateTime.TryParse(ystr, out dat_y)) + { + returnVal = DateTime.Compare(dat_x, dat_y); + } + else + { + returnVal = String.Compare(xstr, ystr); + } + if (_order == SortOrder.Descending) returnVal *= -1; + return returnVal; +#pragma warning restore IDE0018 // Inline variable declaration - Build.Bat Error Happens if We Do + } + } + + class ListViewSubItemComparerValue : IComparer + { + + private int _col; + private SortOrder _order; + + public ListViewSubItemComparerValue() { + _col = 0; + _order = SortOrder.Ascending; + } + + public ListViewSubItemComparerValue(int col, SortOrder order) { + _col = col; + _order = order; + } + + public int Compare(object x, object y) + { +#pragma warning disable IDE0018 // Inline variable declaration - Build.Bat Error Happens if We Do + int returnVal = -1; + + string xstr = ((EXListViewSubItemAB) ((ListViewItem) x).SubItems[_col]).MyValue; + string ystr = ((EXListViewSubItemAB) ((ListViewItem) y).SubItems[_col]).MyValue; + + decimal dec_x; + decimal dec_y; + DateTime dat_x; + DateTime dat_y; + + if (Decimal.TryParse(xstr, out dec_x) && Decimal.TryParse(ystr, out dec_y)) + { + returnVal = Decimal.Compare(dec_x, dec_y); + } + else if (DateTime.TryParse(xstr, out dat_x) && DateTime.TryParse(ystr, out dat_y)) + { + returnVal = DateTime.Compare(dat_x, dat_y); + } + else + { + returnVal = String.Compare(xstr, ystr); + } + if (_order == SortOrder.Descending) returnVal *= -1; + return returnVal; +#pragma warning restore IDE0018 // Inline variable declaration - Build.Bat Error Happens if We Do + } + } + + class ListViewItemComparerText : IComparer + { + private int _col; + private SortOrder _order; + + public ListViewItemComparerText() { + _col = 0; + _order = SortOrder.Ascending; + } + + public ListViewItemComparerText(int col, SortOrder order) { + _col = col; + _order = order; + } + + public int Compare(object x, object y) + { +#pragma warning disable IDE0018 // Inline variable declaration - Build.Bat Error Happens if We Do + int returnVal = -1; + + string xstr = ((ListViewItem) x).Text; + string ystr = ((ListViewItem) y).Text; + + decimal dec_x; + decimal dec_y; + DateTime dat_x; + DateTime dat_y; + + if (Decimal.TryParse(xstr, out dec_x) && Decimal.TryParse(ystr, out dec_y)) + { + returnVal = Decimal.Compare(dec_x, dec_y); + } + else if (DateTime.TryParse(xstr, out dat_x) && DateTime.TryParse(ystr, out dat_y)) + { + returnVal = DateTime.Compare(dat_x, dat_y); + } + else + { + returnVal = String.Compare(xstr, ystr); + } + if (_order == SortOrder.Descending) returnVal *= -1; + return returnVal; +#pragma warning restore IDE0018 // Inline variable declaration - Build.Bat Error Happens if We Do + } + } + + class ListViewItemComparerValue : IComparer + { + private int _col; + private SortOrder _order; + + public ListViewItemComparerValue() { + _col = 0; + _order = SortOrder.Ascending; + } + + public ListViewItemComparerValue(int col, SortOrder order) { + _col = col; + _order = order; + } + + public int Compare(object x, object y) + { +#pragma warning disable IDE0018 // Inline variable declaration - Build.Bat Error Happens if We Do + int returnVal = -1; + + string xstr = ((EXListViewItem) x).MyValue; + string ystr = ((EXListViewItem) y).MyValue; + + decimal dec_x; + decimal dec_y; + DateTime dat_x; + DateTime dat_y; + + if (Decimal.TryParse(xstr, out dec_x) && Decimal.TryParse(ystr, out dec_y)) + { + returnVal = Decimal.Compare(dec_x, dec_y); + } + else if (DateTime.TryParse(xstr, out dat_x) && DateTime.TryParse(ystr, out dat_y)) + { + returnVal = DateTime.Compare(dat_x, dat_y); + } + else + { + returnVal = String.Compare(xstr, ystr); + } + if (_order == SortOrder.Descending) returnVal *= -1; + return returnVal; +#pragma warning restore IDE0018 // Inline variable declaration - Build.Bat Error Happens if We Do + } + } + } + + public class EXColumnHeader : ColumnHeader + { + public EXColumnHeader() + { + + } + + public EXColumnHeader(string text) + { + Text = text; + } + + public EXColumnHeader(string text, int width) + { + Text = text; + Width = width; + } + + } + + public class EXEditableColumnHeader : EXColumnHeader { + + private Control _control; + + public EXEditableColumnHeader() + { + + } + + public EXEditableColumnHeader(string text) + { + Text = text; + } + + public EXEditableColumnHeader(string text, int width) + { + Text = text; + Width = width; + } + + public EXEditableColumnHeader(string text, Control control) + { + Text = text; + MyControl = control; + } + + public EXEditableColumnHeader(string text, Control control, int width) + { + Text = text; + MyControl = control; + Width = width; + } + + public Control MyControl + { + get {return _control;} + set + { + _control = value; + _control.Visible = false; + _control.Tag = "not_init"; + } + } + } + + public class EXBoolColumnHeader : EXColumnHeader + { + + private Image _trueimage; + private Image _falseimage; + private bool _editable; + + public EXBoolColumnHeader() + { + Init(); + } + + public EXBoolColumnHeader(string text) + { + Init(); + Text = text; + } + + public EXBoolColumnHeader(string text, int width) + { + Init(); + Text = text; + Width = width; + } + + public EXBoolColumnHeader(string text, Image trueimage, Image falseimage) + { + Init(); + Text = text; + _trueimage = trueimage; + _falseimage = falseimage; + } + + public EXBoolColumnHeader(string text, Image trueimage, Image falseimage, int width) + { + Init(); + Text = text; + _trueimage = trueimage; + _falseimage = falseimage; + Width = width; + } + + private void Init() + { + _editable = false; + } + + public Image TrueImage + { + get {return _trueimage;} + set {_trueimage = value;} + } + + public Image FalseImage + { + get {return _falseimage;} + set {_falseimage = value;} + } + + public bool Editable + { + get {return _editable;} + set {_editable = value;} + } + } + + public abstract class EXListViewSubItemAB : ListViewItem.ListViewSubItem { + + private string _value = ""; + + public EXListViewSubItemAB() + { + + } + + public EXListViewSubItemAB(string text) + { + Text = text; + } + + public string MyValue + { + get {return _value;} + set {_value = value;} + } + + //return the new x coordinate + public abstract int DoDraw(DrawListViewSubItemEventArgs e, int x, EXColumnHeader ch); + } + + public class EXListViewSubItem : EXListViewSubItemAB + { + public EXListViewSubItem() + { + + } + + public EXListViewSubItem(string text) + { + Text = text; + } + + public override int DoDraw(DrawListViewSubItemEventArgs e, int x, EXColumnHeader ch) + { + return x; + } + } + + public class EXControlListViewSubItem : EXListViewSubItemAB + { + private Control _control; + + public EXControlListViewSubItem() + { + + } + + public Control MyControl + { + get {return _control;} + set {_control = value;} + } + + public override int DoDraw(DrawListViewSubItemEventArgs e, int x, EXColumnHeader ch) + { + return x; + } + } + + public class EXImageListViewSubItem : EXListViewSubItemAB + { + private Image _image; + + public EXImageListViewSubItem() + { + + } + + public EXImageListViewSubItem(string text) + { + Text = text; + } + + public EXImageListViewSubItem(Image image) + { + _image = image; + } + + public EXImageListViewSubItem(Image image, string value) + { + _image = image; + MyValue = value; + } + + public EXImageListViewSubItem(string text, Image image, string value) + { + Text = text; + _image = image; + MyValue = value; + } + + public Image MyImage + { + get {return _image;} + set {_image = value;} + } + + public override int DoDraw(DrawListViewSubItemEventArgs e, int x, EXColumnHeader ch) + { + if (MyImage != null) + { + Image img = MyImage; + int imgy = e.Bounds.Y + e.Bounds.Height / 2 - img.Height / 2; + e.Graphics.DrawImage(img, x, imgy, img.Width, img.Height); + x += img.Width + 2; + } + return x; + } + } + + public class EXMultipleImagesListViewSubItem : EXListViewSubItemAB + { + private ArrayList _images; + + public EXMultipleImagesListViewSubItem() + { + + } + + public EXMultipleImagesListViewSubItem(string text) + { + Text = text; + } + + public EXMultipleImagesListViewSubItem(ArrayList images) + { + _images = images; + } + + public EXMultipleImagesListViewSubItem(ArrayList images, string value) + { + _images = images; + MyValue = value; + } + + public EXMultipleImagesListViewSubItem(string text, ArrayList images, string value) + { + Text = text; + _images = images; + MyValue = value; + } + + public ArrayList MyImages + { + get {return _images;} + set {_images = value;} + } + + public override int DoDraw(DrawListViewSubItemEventArgs e, int x, EXColumnHeader ch) + { + if (MyImages != null && MyImages.Count > 0) + { + for (int i = 0; i < MyImages.Count; i++) + { + Image img = (Image)MyImages[i]; + int imgy = e.Bounds.Y + e.Bounds.Height / 2 - img.Height / 2; + e.Graphics.DrawImage(img, x, imgy, img.Width, img.Height); + x += img.Width + 2; + } + } + return x; + } + } + + public class EXBoolListViewSubItem : EXListViewSubItemAB + { + private bool _value; + + public EXBoolListViewSubItem() + { + + } + + public EXBoolListViewSubItem(bool val) + { + _value = val; + MyValue = val.ToString(); + } + + public bool BoolValue + { + get {return _value;} + set + { + _value = value; + MyValue = value.ToString(); + } + } + + public override int DoDraw(DrawListViewSubItemEventArgs e, int x, EXColumnHeader ch) + { + EXBoolColumnHeader boolcol = (EXBoolColumnHeader) ch; + Image boolimg; + if (BoolValue == true) + { + boolimg = boolcol.TrueImage; + } + else + { + boolimg = boolcol.FalseImage; + } + int imgy = e.Bounds.Y + e.Bounds.Height / 2 - boolimg.Height / 2; + e.Graphics.DrawImage(boolimg, x, imgy, boolimg.Width, boolimg.Height); + x += boolimg.Width + 2; + return x; + } + } + + public class EXListViewItem : ListViewItem + { + private string _value; + + public EXListViewItem() + { + + } + + public EXListViewItem(string text) + { + Text = text; + } + + public string MyValue + { + get {return _value;} + set {_value = value;} + } + + } + + public class EXImageListViewItem : EXListViewItem + { + private Image _image; + + public EXImageListViewItem() + { + + } + + public EXImageListViewItem(string text) + { + Text = text; + } + + public EXImageListViewItem(Image image) + { + _image = image; + } + + public EXImageListViewItem(string text, Image image) + { + _image = image; + Text = text; + } + + public EXImageListViewItem(string text, Image image, string value) + { + Text = text; + _image = image; + MyValue = value; + } + + public Image MyImage + { + get {return _image;} + set {_image = value;} + } + } + + public class EXMultipleImagesListViewItem : EXListViewItem + { + private ArrayList _images; + + public EXMultipleImagesListViewItem() + { + + } + + public EXMultipleImagesListViewItem(string text) + { + Text = text; + } + + public EXMultipleImagesListViewItem(ArrayList images) + { + _images = images; + } + + public EXMultipleImagesListViewItem(string text, ArrayList images) + { + Text = text; + _images = images; + } + + public EXMultipleImagesListViewItem(string text, ArrayList images, string value) + { + Text = text; + _images = images; + MyValue = value; + } + + public ArrayList MyImages { + get {return _images;} + set {_images = value;} + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Forms/Extentions.cs b/PoGo.NecroBot.Logic/Forms/Extentions.cs new file mode 100644 index 000000000..d50837339 --- /dev/null +++ b/PoGo.NecroBot.Logic/Forms/Extentions.cs @@ -0,0 +1,60 @@ +using System.Drawing; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace System +{ + public static class RichTextBoxExtensions + { + public static void SetInnerMargins(this TextBoxBase textBox, int left, int top, int right, int bottom) + { + var rect = textBox.GetFormattingRect(); + + var newRect = new Rectangle(left, top, rect.Width - left - right, rect.Height - top - bottom); + textBox.SetFormattingRect(newRect); + } + + [StructLayout(LayoutKind.Sequential)] + private struct RECT + { + public readonly int Left; + public readonly int Top; + public readonly int Right; + public readonly int Bottom; + + private RECT(int left, int top, int right, int bottom) + { + Left = left; + Top = top; + Right = right; + Bottom = bottom; + } + + public RECT(Rectangle r) : this(r.Left, r.Top, r.Right, r.Bottom) + { + } + } + + [DllImport(@"User32.dll", EntryPoint = @"SendMessage", CharSet = CharSet.Auto)] + private static extern int SendMessageRefRect(IntPtr hWnd, uint msg, int wParam, ref RECT rect); + + [DllImport(@"user32.dll", EntryPoint = @"SendMessage", CharSet = CharSet.Auto)] + private static extern int SendMessage(IntPtr hwnd, int wMsg, IntPtr wParam, ref Rectangle lParam); + + private const int EmGetrect = 0xB2; + private const int EmSetrect = 0xB3; + + private static void SetFormattingRect(this TextBoxBase textbox, Rectangle rect) + { + var rc = new RECT(rect); + SendMessageRefRect(textbox.Handle, EmSetrect, 0, ref rc); + } + + private static Rectangle GetFormattingRect(this TextBoxBase textbox) + { + var rect = new Rectangle(); + SendMessage(textbox.Handle, EmGetrect, (IntPtr)0, ref rect); + return rect; + } + } +} diff --git a/PoGo.NecroBot.Logic/Forms/InitialTutorialForm.Designer.cs b/PoGo.NecroBot.Logic/Forms/InitialTutorialForm.Designer.cs new file mode 100644 index 000000000..d98bd394d --- /dev/null +++ b/PoGo.NecroBot.Logic/Forms/InitialTutorialForm.Designer.cs @@ -0,0 +1,346 @@ +namespace PoGo.NecroBot.Logic.Forms +{ + partial class InitialTutorialForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(InitialTutorialForm)); + this.wizardControl1 = new AeroWizard.WizardControl(); + this.Step1 = new AeroWizard.WizardPage(); + this.rdoFemale = new System.Windows.Forms.RadioButton(); + this.rdoMale = new System.Windows.Forms.RadioButton(); + this.SavingGender_Page = new AeroWizard.WizardPage(); + this.pictureBox3 = new System.Windows.Forms.PictureBox(); + this.label2 = new System.Windows.Forms.Label(); + this.Step2 = new AeroWizard.WizardPage(); + this.rdoCharmander = new System.Windows.Forms.RadioButton(); + this.rdoSquirtle = new System.Windows.Forms.RadioButton(); + this.rdoBulbasaur = new System.Windows.Forms.RadioButton(); + this.Step2_2 = new AeroWizard.WizardPage(); + this.pictureBox2 = new System.Windows.Forms.PictureBox(); + this.label3 = new System.Windows.Forms.Label(); + this.Step3 = new AeroWizard.WizardPage(); + this.lblNameError = new System.Windows.Forms.Label(); + this.label1 = new System.Windows.Forms.Label(); + this.txtNick = new System.Windows.Forms.TextBox(); + this.Step3_3 = new AeroWizard.WizardPage(); + this.pictureBox1 = new System.Windows.Forms.PictureBox(); + this.label4 = new System.Windows.Forms.Label(); + ((System.ComponentModel.ISupportInitialize)(this.wizardControl1)).BeginInit(); + this.Step1.SuspendLayout(); + this.SavingGender_Page.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox3)).BeginInit(); + this.Step2.SuspendLayout(); + this.Step2_2.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox2)).BeginInit(); + this.Step3.SuspendLayout(); + this.Step3_3.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit(); + this.SuspendLayout(); + // + // wizardControl1 + // + this.wizardControl1.BackColor = System.Drawing.Color.White; + this.wizardControl1.Dock = System.Windows.Forms.DockStyle.Fill; + this.wizardControl1.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.wizardControl1.Location = new System.Drawing.Point(0, 0); + this.wizardControl1.Name = "wizardControl1"; + this.wizardControl1.Pages.Add(this.Step1); + this.wizardControl1.Pages.Add(this.SavingGender_Page); + this.wizardControl1.Pages.Add(this.Step2); + this.wizardControl1.Pages.Add(this.Step2_2); + this.wizardControl1.Pages.Add(this.Step3); + this.wizardControl1.Pages.Add(this.Step3_3); + this.wizardControl1.Size = new System.Drawing.Size(421, 242); + this.wizardControl1.TabIndex = 0; + this.wizardControl1.Text = "Setting"; + this.wizardControl1.Title = "Beginner Tutorial"; + // + // Step1 + // + this.Step1.Controls.Add(this.rdoFemale); + this.Step1.Controls.Add(this.rdoMale); + this.Step1.Name = "Step1"; + this.Step1.Size = new System.Drawing.Size(374, 88); + this.Step1.TabIndex = 0; + this.Step1.Text = "Select Gender"; + this.Step1.Initialize += new System.EventHandler(this.WizardPage1_Initialize); + // + // rdoFemale + // + this.rdoFemale.AutoSize = true; + this.rdoFemale.Location = new System.Drawing.Point(177, 39); + this.rdoFemale.Name = "rdoFemale"; + this.rdoFemale.Size = new System.Drawing.Size(63, 19); + this.rdoFemale.TabIndex = 1; + this.rdoFemale.Text = "Female"; + this.rdoFemale.UseVisualStyleBackColor = true; + // + // rdoMale + // + this.rdoMale.AutoSize = true; + this.rdoMale.Checked = true; + this.rdoMale.Location = new System.Drawing.Point(89, 39); + this.rdoMale.Name = "rdoMale"; + this.rdoMale.Size = new System.Drawing.Size(51, 19); + this.rdoMale.TabIndex = 0; + this.rdoMale.TabStop = true; + this.rdoMale.Text = "Male"; + this.rdoMale.UseVisualStyleBackColor = true; + // + // SavingGender_Page + // + this.SavingGender_Page.AllowBack = false; + this.SavingGender_Page.AllowNext = false; + this.SavingGender_Page.Controls.Add(this.pictureBox3); + this.SavingGender_Page.Controls.Add(this.label2); + this.SavingGender_Page.Name = "SavingGender_Page"; + this.SavingGender_Page.ShowNext = false; + this.SavingGender_Page.Size = new System.Drawing.Size(374, 88); + this.SavingGender_Page.TabIndex = 3; + this.SavingGender_Page.Text = "Working in progress"; + this.SavingGender_Page.Initialize += new System.EventHandler(this.WizardPage4_Initialize); + // + // pictureBox3 + // + this.pictureBox3.Image = global::PoGo.NecroBot.Logic.Properties.Resources.ajax_loader; + this.pictureBox3.Location = new System.Drawing.Point(98, 28); + this.pictureBox3.Name = "pictureBox3"; + this.pictureBox3.Size = new System.Drawing.Size(29, 33); + this.pictureBox3.TabIndex = 3; + this.pictureBox3.TabStop = false; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(144, 28); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(89, 15); + this.label2.TabIndex = 0; + this.label2.Text = "Please wait........"; + // + // Step2 + // + this.Step2.AllowBack = false; + this.Step2.AllowCancel = false; + this.Step2.Controls.Add(this.rdoCharmander); + this.Step2.Controls.Add(this.rdoSquirtle); + this.Step2.Controls.Add(this.rdoBulbasaur); + this.Step2.Name = "Step2"; + this.Step2.ShowCancel = false; + this.Step2.Size = new System.Drawing.Size(374, 88); + this.Step2.TabIndex = 1; + this.Step2.Text = "First Catch"; + this.Step2.Initialize += new System.EventHandler(this.WizardPage2_Initialize); + // + // rdoCharmander + // + this.rdoCharmander.AutoSize = true; + this.rdoCharmander.Location = new System.Drawing.Point(131, 34); + this.rdoCharmander.Name = "rdoCharmander"; + this.rdoCharmander.Size = new System.Drawing.Size(91, 19); + this.rdoCharmander.TabIndex = 2; + this.rdoCharmander.Text = "Charmander"; + this.rdoCharmander.UseVisualStyleBackColor = true; + // + // rdoSquirtle + // + this.rdoSquirtle.AutoSize = true; + this.rdoSquirtle.Location = new System.Drawing.Point(250, 34); + this.rdoSquirtle.Name = "rdoSquirtle"; + this.rdoSquirtle.Size = new System.Drawing.Size(61, 19); + this.rdoSquirtle.TabIndex = 1; + this.rdoSquirtle.Text = "Squirtle"; + this.rdoSquirtle.UseVisualStyleBackColor = true; + // + // rdoBulbasaur + // + this.rdoBulbasaur.AutoSize = true; + this.rdoBulbasaur.Checked = true; + this.rdoBulbasaur.Location = new System.Drawing.Point(19, 34); + this.rdoBulbasaur.Name = "rdoBulbasaur"; + this.rdoBulbasaur.Size = new System.Drawing.Size(77, 19); + this.rdoBulbasaur.TabIndex = 0; + this.rdoBulbasaur.TabStop = true; + this.rdoBulbasaur.Text = "Bulbasaur"; + this.rdoBulbasaur.UseVisualStyleBackColor = true; + // + // Step2_2 + // + this.Step2_2.AllowBack = false; + this.Step2_2.AllowNext = false; + this.Step2_2.Controls.Add(this.pictureBox2); + this.Step2_2.Controls.Add(this.label3); + this.Step2_2.Name = "Step2_2"; + this.Step2_2.Size = new System.Drawing.Size(374, 88); + this.Step2_2.TabIndex = 2; + this.Step2_2.Text = "Working in progress"; + this.Step2_2.Initialize += new System.EventHandler(this.WizardPage3_Initialize); + // + // pictureBox2 + // + this.pictureBox2.Image = global::PoGo.NecroBot.Logic.Properties.Resources.ajax_loader; + this.pictureBox2.Location = new System.Drawing.Point(93, 32); + this.pictureBox2.Name = "pictureBox2"; + this.pictureBox2.Size = new System.Drawing.Size(29, 27); + this.pictureBox2.TabIndex = 3; + this.pictureBox2.TabStop = false; + // + // label3 + // + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(142, 32); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(89, 15); + this.label3.TabIndex = 1; + this.label3.Text = "Please wait........"; + // + // Step3 + // + this.Step3.Controls.Add(this.lblNameError); + this.Step3.Controls.Add(this.label1); + this.Step3.Controls.Add(this.txtNick); + this.Step3.Name = "Step3"; + this.Step3.Size = new System.Drawing.Size(374, 88); + this.Step3.TabIndex = 4; + this.Step3.Text = "Select Nickname"; + this.Step3.Initialize += new System.EventHandler(this.WizardPage5_Initialize); + // + // lblNameError + // + this.lblNameError.AutoSize = true; + this.lblNameError.ForeColor = System.Drawing.Color.Red; + this.lblNameError.Location = new System.Drawing.Point(10, 48); + this.lblNameError.Name = "lblNameError"; + this.lblNameError.Size = new System.Drawing.Size(32, 15); + this.lblNameError.TabIndex = 2; + this.lblNameError.Text = "error"; + this.lblNameError.UseWaitCursor = true; + this.lblNameError.Visible = false; + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(10, 16); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(61, 15); + this.label1.TabIndex = 1; + this.label1.Text = "Nickname"; + this.label1.UseWaitCursor = true; + // + // txtNick + // + this.txtNick.Location = new System.Drawing.Point(77, 16); + this.txtNick.Name = "txtNick"; + this.txtNick.Size = new System.Drawing.Size(248, 23); + this.txtNick.TabIndex = 0; + // + // Step3_3 + // + this.Step3_3.AllowBack = false; + this.Step3_3.Controls.Add(this.pictureBox1); + this.Step3_3.Controls.Add(this.label4); + this.Step3_3.Name = "Step3_3"; + this.Step3_3.Size = new System.Drawing.Size(374, 88); + this.Step3_3.TabIndex = 5; + this.Step3_3.Text = "Working ‌ in progress"; + this.Step3_3.Initialize += new System.EventHandler(this.WizardPage6_Initialize); + // + // pictureBox1 + // + this.pictureBox1.Image = global::PoGo.NecroBot.Logic.Properties.Resources.ajax_loader; + this.pictureBox1.Location = new System.Drawing.Point(86, 12); + this.pictureBox1.Name = "pictureBox1"; + this.pictureBox1.Size = new System.Drawing.Size(29, 33); + this.pictureBox1.TabIndex = 2; + this.pictureBox1.TabStop = false; + // + // label4 + // + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(121, 12); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(89, 15); + this.label4.TabIndex = 1; + this.label4.Text = "Please wait........"; + // + // InitialTutorialForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(421, 242); + this.Controls.Add(this.wizardControl1); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.Name = "InitialTutorialForm"; + this.Text = "Initial Account Tutorial"; + this.TopMost = true; + ((System.ComponentModel.ISupportInitialize)(this.wizardControl1)).EndInit(); + this.Step1.ResumeLayout(false); + this.Step1.PerformLayout(); + this.SavingGender_Page.ResumeLayout(false); + this.SavingGender_Page.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox3)).EndInit(); + this.Step2.ResumeLayout(false); + this.Step2.PerformLayout(); + this.Step2_2.ResumeLayout(false); + this.Step2_2.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox2)).EndInit(); + this.Step3.ResumeLayout(false); + this.Step3.PerformLayout(); + this.Step3_3.ResumeLayout(false); + this.Step3_3.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit(); + this.ResumeLayout(false); + + } + + #endregion + + private AeroWizard.WizardControl wizardControl1; + private AeroWizard.WizardPage Step1; + private System.Windows.Forms.RadioButton rdoFemale; + private System.Windows.Forms.RadioButton rdoMale; + private AeroWizard.WizardPage Step2; + private AeroWizard.WizardPage Step2_2; + private AeroWizard.WizardPage SavingGender_Page; + private System.Windows.Forms.RadioButton rdoCharmander; + private System.Windows.Forms.RadioButton rdoSquirtle; + private System.Windows.Forms.RadioButton rdoBulbasaur; + private AeroWizard.WizardPage Step3; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.TextBox txtNick; + private AeroWizard.WizardPage Step3_3; + private System.Windows.Forms.Label lblNameError; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.PictureBox pictureBox1; + private System.Windows.Forms.PictureBox pictureBox3; + private System.Windows.Forms.PictureBox pictureBox2; + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Forms/InitialTutorialForm.cs b/PoGo.NecroBot.Logic/Forms/InitialTutorialForm.cs new file mode 100644 index 000000000..0e349cc07 --- /dev/null +++ b/PoGo.NecroBot.Logic/Forms/InitialTutorialForm.cs @@ -0,0 +1,283 @@ +using System; +using System.Threading.Tasks; +using System.Windows.Forms; +using AeroWizard; +using Google.Protobuf.Collections; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.State; +using POGOProtos.Data.Player; +using POGOProtos.Enums; +using POGOProtos.Networking.Responses; + +namespace PoGo.NecroBot.Logic.Forms +{ + public partial class InitialTutorialForm : System.Windows.Forms.Form + { + private ISession session; + private RepeatedField tutState; + CheckTosState state; + private EncounterTutorialCompleteResponse encounterTutorialCompleteResponse; + + public InitialTutorialForm() + { + InitializeComponent(); + } + + public InitialTutorialForm(CheckTosState s, RepeatedField tutState, ISession session) + { + InitializeComponent(); + state = s; + this.tutState = tutState; + this.session = session; + } + + private void Button1_Click(object sender, EventArgs e) + { + } + + private void WizardPage4_Initialize(object sender, WizardPageInitEventArgs e) + { + Task.Run(async () => + { + if (!tutState.Contains(TutorialState.AvatarSelection)) + { + int gender = rdoMale.Checked ? 0 : 1; + + var avatarRes = await session.Client.Player.SetAvatar(new PlayerAvatar() + { + Backpack = 0, + Eyes = 0, + Avatar = gender, + Hair = 0, + Hat = 0, + Pants = 0, + Shirt = 0, + Shoes = 0, + Skin = 0 + }); + + if (avatarRes.Status == SetAvatarResponse.Types.Status.AvatarAlreadySet || + avatarRes.Status == SetAvatarResponse.Types.Status.Success) + { + encounterTutorialCompleteResponse = await session.Client.Misc + .MarkTutorialComplete(new RepeatedField() + { + TutorialState.AvatarSelection + }); + + if (encounterTutorialCompleteResponse.Result == EncounterTutorialCompleteResponse.Types.Result.Success) + { + session.EventDispatcher.Send(new NoticeEvent() + { + Message = $"Selected your avatar, now you are {gender}!" + }); + + Invoke(new Action(() => + { + wizardControl1.NextPage(); + }), null); + + return true; + } + } + + Invoke(new Action(() => + { + lblNameError.Text = "Error selecting avatar gender!"; + lblNameError.Visible = true; + wizardControl1.PreviousPage(); + })); + } + return true; + }); + } + + private const int WM_NCHITTEST = 0x84; + private const int HTCLIENT = 0x1; + private const int HTCAPTION = 0x2; + + /// + /// Handling the window messages + /// + protected override void WndProc(ref Message message) + { + base.WndProc(ref message); + + if (message.Msg == WM_NCHITTEST && (int) message.Result == HTCLIENT) + message.Result = (IntPtr) HTCAPTION; + } + + private void WizardPage3_Initialize(object sender, WizardPageInitEventArgs e) + { + PokemonId firstPoke = rdoBulbasaur.Checked + ? PokemonId.Bulbasaur + : rdoCharmander.Checked + ? PokemonId.Charmander + : PokemonId.Squirtle; + + Task.Run(async () => + { + if (!tutState.Contains(TutorialState.PokemonCapture)) + { + encounterTutorialCompleteResponse = await session.Client.Encounter.EncounterTutorialComplete(firstPoke); + + if (encounterTutorialCompleteResponse.Result == EncounterTutorialCompleteResponse.Types.Result.Success) + { + session.EventDispatcher.Send(new NoticeEvent() + { + Message = $"Caught Tutorial pokemon! it's {firstPoke}!" + }); + + Invoke(new Action(() => + { + wizardControl1.NextPage(); + }), null); + } + else + { + Invoke(new Action(() => + { + lblNameError.Text = "Error catching tutorial pokemon."; + lblNameError.Visible = true; + wizardControl1.PreviousPage(); + })); + } + } + }); + } + + private void WizardPage6_Initialize(object sender, WizardPageInitEventArgs e) + { + string nickname = txtNick.Text; + ClaimCodenameResponse res = null; + + bool markTutorialComplete = false; + string errorText = null; + string warningText = null; + string infoText = null; + Task.Run(async () => + { + if (!tutState.Contains(TutorialState.NameSelection)) + { + res = await session.Client.Misc.ClaimCodename(nickname); + + switch (res.Status) + { + case ClaimCodenameResponse.Types.Status.Unset: + errorText = "Unset, somehow"; + break; + case ClaimCodenameResponse.Types.Status.Success: + infoText = $"Your name is now: {res.Codename}"; + markTutorialComplete = true; + break; + case ClaimCodenameResponse.Types.Status.CodenameNotAvailable: + errorText = $"That nickname ({nickname}) isn't available, pick another one!"; + break; + case ClaimCodenameResponse.Types.Status.CodenameNotValid: + errorText = $"That nickname ({nickname}) isn't valid, pick another one!"; + break; + case ClaimCodenameResponse.Types.Status.CurrentOwner: + warningText = $"You already own that nickname!"; + markTutorialComplete = true; + break; + case ClaimCodenameResponse.Types.Status.CodenameChangeNotAllowed: + warningText = "You can't change your nickname anymore!"; + markTutorialComplete = true; + break; + default: + errorText = "Unknown Niantic error while changing nickname."; + break; + } + if (!string.IsNullOrEmpty(infoText)) + { + session.EventDispatcher.Send(new NoticeEvent() + { + Message = infoText + }); + } + else if (!string.IsNullOrEmpty(warningText)) + { + session.EventDispatcher.Send(new WarnEvent() + { + Message = warningText + }); + } + else if (!string.IsNullOrEmpty(errorText)) + { + session.EventDispatcher.Send(new ErrorEvent() + { + Message = errorText + }); + } + + if (markTutorialComplete) + { + encounterTutorialCompleteResponse = await session.Client.Misc.MarkTutorialComplete(new RepeatedField() + { + TutorialState.NameSelection + }); + + if (encounterTutorialCompleteResponse.Result == EncounterTutorialCompleteResponse.Types.Result.Success) + { + if (!tutState.Contains(TutorialState.FirstTimeExperienceComplete)) + { + encounterTutorialCompleteResponse = await session.Client.Misc.MarkTutorialComplete(new RepeatedField() + { + TutorialState.FirstTimeExperienceComplete , TutorialState.PokemonBerry + }); + + if (encounterTutorialCompleteResponse.Result == EncounterTutorialCompleteResponse.Types.Result.Success) + { + session.EventDispatcher.Send(new NoticeEvent() + { + Message = "First time experience complete, looks like i just spinned an virtual pokestop :P" + }); + + Invoke(new Action(() => + { + DialogResult = DialogResult.OK; + Close(); + })); + + return; + } + } + } + } + + Invoke(new Action(() => + { + lblNameError.Text = errorText; + lblNameError.Visible = true; + wizardControl1.PreviousPage(); + })); + } + }); + } + + private void WizardPage1_Initialize(object sender, WizardPageInitEventArgs e) + { + if (tutState.Contains(TutorialState.AvatarSelection)) + { + wizardControl1.NextPage(Step2); + } + } + + private void WizardPage2_Initialize(object sender, WizardPageInitEventArgs e) + { + if (tutState.Contains(TutorialState.PokemonCapture)) + { + wizardControl1.NextPage(Step3); + } + } + + private void WizardPage5_Initialize(object sender, WizardPageInitEventArgs e) + { + if (tutState.Contains(TutorialState.NameSelection)) + { + DialogResult = DialogResult.OK; + Close(); + } + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Forms/InitialTutorialForm.resx b/PoGo.NecroBot.Logic/Forms/InitialTutorialForm.resx new file mode 100644 index 000000000..1b7775000 --- /dev/null +++ b/PoGo.NecroBot.Logic/Forms/InitialTutorialForm.resx @@ -0,0 +1,980 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + AAABAAEAgIAAAAEAGAAoyAAAFgAAACgAAACAAAAAAAEAAAEAGAAAAAAAAAAAABMLAAATCwAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgIAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwIEBAQAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE + BQMEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFBgUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG + BwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG + BwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAABAQEAAAABAQEAAAABAQEAAAAAAAABAQEAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAABAQEDAwMMDAwsLCxQUFB5eXmYmJi1tbXKysrc3Nzm5ubr6+vs7Ozm5ubf39/Nzc22trabm5t7e3tX + V1cxMTEPDw8DAwMCAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAEABAAAAgIBBAIDAwAKCws9PT17e3u4uLjm5ub4+Pj6+vr7+/v7+/v8/Pz8/Pz9/f39/f39 + /f39/f39/f38/Pz8/Pz8/Pz9/f37+/v8/Pz4+Pjo6Oi9vb2BgYFEREQPDw8CAgICAgICAgIBAQEAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAQIAAQMEAxo/QkqbmZnd2N35+fn8/Pz6+vr8/Pz9 + /f3+/v7+/v7+/v7+/v79/f3+/v7+/v7+/v7+/v79/f39/f39/f3+/v79/f39/f39/f39/f38/Pz7+/v5 + +fn6+vrl5eWlpaVTU1MPDw8FBQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQABAQEBAQsh + H1WstdPw9Pb49/P8/Pn8/Pz8/Pz+/v7+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v7+/v79 + /f3+/v7+/v79/f3+/v7+/v79/f39/f39/f39/f38/Pz8/Pz7+/vl5eWPj48tLS0EBAQEBAQCAgIBAQEA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG + BwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAQABAgABAQkPDUZDSI+jps/n6vj3+vn7/P37/fn9/f7+/v7+/v79/f3+/v7+ + /v79/f3+/v7+/v79/f3+/v7+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v7+/v7+/v79/f39 + /f37+/v7+/vz8/OysrJCQkIGBgYCAgIBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAgAAAAQCABIDBSwYFlpXV5K8 + w9ru+fL2+fX8+/n8/Pz9/f39/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v7+/v79/f3+/v7+/v79/f3+ + /v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f37+/v4+Pi5ublAQEAHBwcCAwIAAQAAAQAA + AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAQAAAQAAAgAAAwEAAQ8JBjoqKW+Kj7/q7vj7+vX7+/v8/Pz9/f3+/v7+/v79/f3+/v7+/v79 + /f3+/v7+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v7+/v79 + /f38/Pz6+vr19fWkpKQiIiICAwEAAQAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAQAAAAEDAQABAQYCBCMVFGFna6Tf5PL5 + +fn8/Pz+/v7+/v7+/v79/f3+/v7+/v79/f3+/v7+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+ + /v79/f3+/v7+/v79/f3+/v7+/v7+/v79/f3+/v79/f39/f37+/vm5uZub20ICQcAAQAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAACAAABAQAAAQMAARgMDE1SVZbX2+73+ff6+vr9/f3+/v7+/v7+/v7+/v79/f3+/v7+/v7+/v79 + /f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v7+/v79/f3+/v7+/v7+/v79 + /f38/fzy+fO3uboWGB8BAggAAQAAAAMAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG + BwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACBAACAwEAABYMDUZUU5Xa4u70+vb9/f39 + /f39/f39/f3+/v79/f3+/v7+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79 + /f3+/v7+/v7+/v79/f3+/v7+/v79/f3+/v79/vzz+fXk6us/NIoAAxQABQAAAAEAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AQADAQEBAAcAAg4QDE9cW6Pi6/H6+/r9/f37+/v+/v7+/v79/f3+/v7+/v7+/v79/f3+/v7+/v79/f3+ + /v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v7+/v79/f3+/v7+/v79/f3+/v79/vz3+fbn7vJU + SbANDTgABAIBAQACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwABAgAAAQABAgMBAw0TE1Z8fbbw9Pf6+/v7+/n9/f3+/v79 + /f3+/v7+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v7+/v79 + /f3+/v7+/v79/f3+/v79/vz5+vby9viPjssdF1kABAcBAQEDAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AwECAyQeImyxtdTz+vP6+vj9/fz+/v79/f3+/v7+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+ + /v79/f3+/v7+/v79/f3+/v7+/v7+/v79/f3+/v7+/v79/f3+/v79/v35/ff2+vaTkNAaFmgAAxACAwIC + AAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAggFBTZLR43j6vD39/v7/fn9/f3+/v7+/v7+/v7+/v79 + /f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v7+/v79/f3+/v7+/v79/f3+ + /v79/v34/vf2+/i7uuExM4gEBTcCAQcAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG + BwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAMAAQIAAw4WFViX + kcj0+fT5+vv9/f3+/v7+/v7+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79 + /f3+/v7+/v7+/v79/f3+/v7+/v79/f3+/v79/v32/ff1/fTs8fWTl8snKnoCBSUDAQUAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAEAAAAAAAAEBiQ9QI3m5vb4/Pf9/f3+/v7+/v7+/v7+/v79/f3+/v7+/v79/f3+ + /v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v7+/v79/f3+/v7+/v79/f3+/v79/f37/fj8/vr6 + +/np7vh/hMEkIXkDBBoAAQMAAwMDAQIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgkaGGWoq831+fn8/Pz9 + /f39/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v7+/v79 + /f3+/v7+/v79/f3+/v79/f38/fj9/vv7+/r0+vbs8u98b8YjI3oBBCgEBQUBAgMAAAEAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAwEGBThjaafx9Pv8/Pz8/Pz+/v7+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+ + /v79/f3+/v7+/v79/f3+/v7+/v7+/v79/f3+/v7+/v79/f3+/v79/v38/fj9/vv8/Pv2/fP5/PXq7vCI + hcctLIEMCj4AAwwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAIA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAQMCAxoqK4Pb4PD4+/r5+/v+/v7+/v7+/v79 + /f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v7+/v79/f3+/v7+/v79/f3+ + /v79/v35/fb3/fb5+/n1+fn1/PX4+Pnw7/Wmr906O5UEAyEAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG + BwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAgAAAwEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAYA + AREaGXezvdTz+/r1+vn9/v7+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79 + /f3+/v7+/v7+/v79/f3+/v7+/v79/f3+/v79/f36+fj1+fr1+ff1+/D7/fD8+vTy8/eapNkoKXUBAhQA + AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAECAxIGCBcDBAEBAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAABBAIBAigeG4mzuNrz+vn2+vr9/v7+/v7+/v79/f3+/v7+/v79/f3+ + /v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v7+/v79/f3+/v7+/v79/f3+/v78/fzm8u+SmbqF + i86QmdSip9Glp8+CgbksL3YEBSoCAwQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQED + AwUtLWAeHF8AAggBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBSIkHX5WU7ni6/H0+/r2 + +/r9/v7+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v7+/v79 + /f3+/v7+/v79/f3+/v79/f3w9va5veBNSr0ZG64OEoIUF14PEEkBAhsEAAUEAgAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQjIyOhodAlIoYAAhUDAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA + AQAAAQADABIcGHNtbLrZ3vP3+vb7/Pz8/Pz+/v7+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+ + /v79/f3+/v7+/v79/f3+/v7+/v7+/v79/f3+/v7+/v79/f3+/v79/v35/PL0+PXj4fiTkdY6N6UVE24E + BCcDAQoDAgECAwAABAEAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBgabm5vS1PUwKpoCAiAD + AAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAQAAAgEDAR0vNIra3PP0+fP0/fj8/Pz8/Pz+/v7+/v7+/v79 + /f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v7+/v79/f3+/v7+/v79/f3+ + /v79/v32/vb1/PT49/n09frT2fN6esY2LZIREFEABhoBAg0DAAgCAQIAAQAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG + BwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAACAgI3Nzfw8PDj6/VFPLMFBysCAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQABAQIDAypJS6Lu + 9vL5/PP4/fv9/f38/Pz+/v7+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79 + /f3+/v7+/v7+/v79/f3+/v7+/v79/f3+/v79/v35/vn4/vj6/vj8/Pb4+/vq8frFzeVtbLktKJAREUoB + AhcAAwgBAAECAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAwOysrL4+Pfu9fJaVb8HCTcCAAYAAQAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAEAAgABAQQDBDNRTa3s9vb3+vn9/Pz+/v79/f3+/v7+/v7+/v79/f3+/v7+/v79/f3+ + /v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v7+/v79/f3+/v7+/v79/f3+/v7+/v39/fz7/fv6 + /vz6/fv7/fb6+/b19vvo8Pq6wuVnY7cqJ38OD0wBARgCAgQAAAMAAQQAAwEAAAAAAAAAAQAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgJCQkL4+Pj7 + +/vz9/Vwb8MPD1gAAQIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIABQEBAgMFBTdQTq3w9vn2/Pf9/P3+/v79 + /f3+/v7+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v7+/v79 + /f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f39/f38/Pz7/Pr3/PP09/jn6/uxv+Nla7UuLIIVElQC + BCIBAwoFAA4AAQAAAQABAAEBAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAFBQWxsbH5+fn6+vvz+faho9kiG34BAgwCAQQAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEA + BQAAAQMFBTphYLD1+ff1+/f9/P3+/v79/f3+/v7+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+ + /v79/f3+/v7+/v79/f3+/v7+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v7+/v7+/v79/f38 + /fr5+/b3+vn4/fT1+PHw9fbCwup1dMMxMpEYF2UFCS8BAhIAAgYBAgABAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEyMjL09PT6+vr8/fz0+vbc4/dEPagN + CC8CAQYAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIABAEAAAgNCkh8fL/2+vL1+/f8/f3+/v79/f3+/v7+/v7+/v79 + /f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v7+/v79/f3+/v7+/v79/f3+ + /v7+/v79/f3+/v7+/v79/f39/f39/f39/f33/Pjz/vX4+/r1+/L7/Pn3+fvs9fbP1+6Mjs5MTaIuIn0Q + DTsAAQ0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG + BwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE + BASWlpb6+vr8/Pz9/f34/fr1+vmWltQjIXMBBBQAAQMAAgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICBwEDARgZGGWsq9L2 + /PT3/fX6/vv+/v79/f3+/v7+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79 + /f3+/v7+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f39/f39/f39/f3+/v79/f39/f38 + /Pz+/v77+/v7+/n4/PXy+fbo8PexteI0OXsFARwBAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVFRXn5+f8/Pz9/f39/f33/Pr4/PLn5/hgXLEUFVYBAREGBAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAQAAAQYDBCA5MIzd4vH4/fH5+/j7/vz+/v79/f3+/v7+/v7+/v79/f3+/v7+/v79/f3+ + /v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+ + /v78/v38//38//38/v3+/v79/f39/f38/Pz9/f38/Pz4+/r1/Pn2+/f3+Pbb4vY1MokBBBQAAAIAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABeXl739/f+/v79/f38 + /Pz2+/n2/fP2+/THyu4/P5gLDEQCAgYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAwkRDlB+fLrv9vX7/fX7+Pz7/fv+/v79 + /f3+/v7+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v7+/v79 + /f3+/v7+/v79/f3+/v79/f39/f39/f3+/v75/fr3/fj3/fj4/fn8/Pz8/Pz8/Pz8/Pz9/f39/f35/Pn4 + /fj5+/n4+fO5v98jIXgBAg0AAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAABAwEBAwKztLT4+fj8/f35+/r5+/j3+fP2+vD2+PTj7fOFiuAnKogBAxgAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAEAAAEAAAEBAQABBAQF + CCw7NY/T2O34+/r8+/r7+vz8/fz+/v79/f3+/v7+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+ + /v79/f3+/v7+/v79/f3+/v7+/v7+/v79/f3+/v7+/v79/f3+/v79/f78/Pr7/ff8+/36+/j4/Pb3/fX3 + +/f3+/v1+fv19/z19/r3+fv3+vv1+vn0+vny9fzt8Pl6e8ASD1MCAgcCAAEAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAUGBTx9fH5+vf5/PX0/fTv+fnb6/Ozud+Cf8JQ + TJ8qKZUSE2ABAQsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAQAAAQAAAQABAAICBAAAAQoEBCwtKnanq9fz+Pj8/Pz8/Pz8/Pz+/v7+/v79/f3+/v7+/v7+/v79 + /f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v7+/v79/f3+/v7+/v79/f3+ + /v79/f36/vf5/Pf4+vrv9/PY2++loc+Mib2AgLN+fLN6eLJqba1kaalfZKRcYaBbYJ5VWJxSU50tLW4D + BCIEBwEDAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG + BwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAwNYXFXz + +vXw+PPp8vTFyO2DgchCRKIgIHwND1YEBDkBASoAARACAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAQIBAgIABBILC0YyLoebntTt9/T5/fT9/f39 + /f39/f3+/v7+/v79/f3+/v7+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79 + /f3+/v7+/v7+/v79/f3+/v7+/v79/f3+/v79/vz3/fb4+vjt9fmdntE6NYIYE14LCUYGBTkHBTsHBTwD + BDIBAzABAi8BAiwAASgBASsBAS0AAh0ABQYCBQABAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAGBASeoZ/o7fissd5hYLcsJY0TEWYEBjUAAhgEAQoCAgICAQECAAEAAQEA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEBAQAAAQEAAQIAAgMCARQG + BTUcGXVNT6eywOTo9fX5/PT2/Pj8/f3+/v79/f39/f39/f39/f3+/v79/f39/f3+/v7+/v7+/v79/f3+ + /v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v7+/v79/f3+/v7+/v79/f3+/v78/v30/Pn2+/HS + 2fA6OIoEBTcAAw8CAgYCAQICAgICAgICAQICAAIBAAEBAAEAAQAAAQEBAAEAAwIAAgAAAAEAAQAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAECBGRlMRSWKYkJYsNDVkAAiUA + AREAAAEAAAAAAQAAAQAAAQABAAADAQECAAEBAQAAAgIAAgABAAMBAQQBAgEBAAAAAQAAAAAAAAAAAAAA + AAAAAQAAAQgBAxEAAhYCAx8NDE4fG4E6OqKIjs7d5PT1+fn7+/v7+/v8/Pz+/v7+/v77+/v4+Pj6+vr4 + +Pj4+Pj5+fn4+Pj7+/v9/f3+/v7+/v7+/v7+/v79/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v7+/v79 + /f3+/v7+/v79/f3+/v78/f31/fLz9/WDi8AWFlwAAREEAAABAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAABAQELCSoqKX0NDFIFASQCAAoBAgEBAwAAAAAAAAAAAAAAAQACAgADAgADAgACAwEBBQEAARABAhsC + Ax4AARwAARoBAQwCAwABAQIAAAAAAAAAAAAAAAYICSwcFnYiIn0zN5peXbudoNrd4vbx9/j1+/L6/fb9 + /f38/Pz6+vr6+vr09PS3t7dsbGw6OjogICAeHh43NzdlZWWsrKzw8PD6+vr8/Pz9/f39/f39/f3+/v7+ + /v79/f3+/v7+/v79/f3+/v7+/v7+/v79/f3+/v7+/v79/f39/f38/fz4/PTc4u49O5gEAyMDAgMDAAEA + AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgIBAhACAxwCBQcEBAEDAAADAAECAAEAAQAAAQAAAAEA + AAEAAQYAAA4AABYBAycJCUkYGGomJJQwMKAsK5wkJJUXGV4BAQ0DAgAAAAAAAAAAAAAAAQ8oH3h6dsm3 + uuPW4fLq8Pf3+Pb4/Pb4/vn5/Pv6+/z+/v77+/v19fWmpqYsLCwGBgYEBAQBAQEAAAAAAAABAQEDAwMG + BgYhISGampr09PT8/Pz9/f39/f3+/v7+/v79/f3+/v7+/v79/f3+/v7+/v7+/v79/f3+/v7+/v79/f39 + /f38/fzz+/aXmNAcHWQBAQ0BAgAAAAMAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG + BwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQABAQUDAQMD + AgMAAgcAAgIAAQEBAgECAQMBAQoAABMBAiUHBj4SEVsgH3cvLZVSULR/f9K0uubN1fHKz++FjeIlJogB + AhUCAwIAAQAAAgAAAAAAAxErJ4HW3PL5/PL0+/n2/fb3+/j5/Pv7/v37/Pz7+/v7+/vs7OxhYWEHBwcD + AwMBAQEEBAQCAgIBAQEBAQEDAwMFBQUCAgICAgICAgFMTEvl5uX5+fn8/Pz+/v7+/v79/f3+/v7+/v79 + /f3+/v7+/v7+/v79/f3+/v7+/v79/f39/f38/fzn8/RRUqwJCC4BAAYAAAAAAAEAAQAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAQMBBAIBAgUAAgsAAw4AAxQBASEGBDIMDVAWGWQfI3g3NJRaV7N4fMWjqt3T + 1/Tn6/jz9vjy+Pny+vjy9vp+gtEXGG4AAQ0AAQAAAQAAAwAAAAEBAhs4OIno7fn6+/X4/fv4/fv4/fv8 + /fz+/v79/f36+vrw8PBNTU0FBQUDAwMEBAQoKCiGhobKysrh4eHi4uLQ0NCRkZEzMzMDBAMAAQAHCAY0 + NDPl5eX7+/v+/v7+/v79/f3+/v7+/v79/f3+/v7+/v7+/v79/f3+/v7+/v79/f37+/v6+/vCzOIkJnwD + ARsAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgECAw8WFVQZFmsaFnQeHH0oJoo5OJZT + U611cL+Yl9S7vufa3fbs7fvy8/b2+vbx/vf1/vj3/vn3/ff0/Pjt9PRfXrwMDUoAAQUBAQEAAQAAAQAA + AAQDAilPUZfw9Pr4+vj5/vz5/vz5/vz8/v3+/v77+/v19fVpaWkDAwMDAwMLCwt8fHzt7e36+vr8/Pz9 + /f39/f36+vr7+/vz8/OPkI8NDg0BAgACAgFSUlL19fX8/Pz+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+ + /v7+/v7+/v7+/v77+/v39/qHisQTFVcAAQ0AAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAB + BAQLDRuSlL2goNaprOC6wurO1vLh5vjq7Pnu9/v0+vn4+/X6/PX4/fP4/PT5+/n4/vv5/vr5/fn5/ff1 + +/nm7PJIQqoECC0CAgIDAQUAAAAAAQAAAAcIBjxvdLDx9vf4+/r5/fv6/vz6//38/fz8/Pz4+Pe3t7cI + CAgCAgIICAicnJz4+Pj6+vrx8fGtra19fX16enqmpqbt7e339/f19fSztLIODw0CAwEDAwOdnp37+/v9 + /f39/f39/f39/f39/fz9/fz9/fz9/f39/f39/f39/f39/fz7+/nx8fhWV6kGBjgAAwQAAwAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgIYGBjd3d3m5ubl5eXm5ubm5ubm5ubm5ubj5+Xj5+Xj5+Xl + 5+Pl5uLm5+Pm5+Tm5ubm5ubm5ubj5+Pi5t7DyeQ0Kp0BBB8BAQMBAgMAAAAAAQAAAQwQEFOFibre5OXk + 5uPm5ubm5ubm5ubm5+bm5+Xh4uBAQT8DAwMEBAR0dHT39/f5+fm+vr5DQ0N+fn6ysrK1tbWHh4dAQECr + q6v39/f4+PiPj48FBQUCAwEoKSfg4d/k5OTm5ubm5ubm5uXm5+Pm5+Pm5+Pm5ubm5ubm5ubk5uXl5+Pk + 597My+cyNIkEAyEABAEAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG + BwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQHBwcHBwcG + BgYGBgYHBwcHBwcHBwcGCAYGCAYGBwYGBwYGBwYHCAYHBwYHBwcHBwcHBwcGCAYGCAgPD0YwKJkBAxQC + AQQBAgIAAAAAAQAAARAjIV4kJk8GCAsHCAYHBwcHBwcHBwcGBwYGBwYICQcCAwIBAQEcHBzo6Oj6+vrB + wcFGRkbe3t77+/v8/Pz9/f35+fno6OhSUlKrq6v5+fnx8fEwMC8AAQACAwEJCggHBwcHBwcHBwcGBwYG + BwYGBwYGBwYHBwYHBwcHBwcGBwYGCAYGCQwZF00jJ3EBAgwFAgMBAAMAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAABAAABAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAEAAgUPD1M3NZYBBQoEAAUAAgAAAgABAQABARRZWYUXGisBAwEBAQEAAAAAAAAAAAAA + AAAAAQAAAQAAAQACAgJwcHD4+Pjy8vJERETY2Nj6+vr9/f3+/v79/f37+/v4+Pjn5+c+Pj7q6ur39/eL + jIsAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAQAAAxIjIWw6PmMCAgQE + AQQAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAABAAABAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEAAQUXFVdHTIEAAwMBAAEBAAAAAgAAAAMI + BhaOjZ0EBggAAgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgKysrL5+fm+vr5wcHD5+fn+/v7+/v7+ + /v79/f39/f39/f36+vqKioqjo6P5+fnNzc0CAwEBAgAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAABAAQDCQIKCTlDQpEZFyUDAwIBAQIAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA + AwUeGlFcYX4AAQEAAAABAAAAAQAAAgI+PkJ4eHoCAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC + AgLGxsb6+vqQkJCioqL7+/v9/f3+/v79/f3+/v7+/v7+/v78/PzCwsJvb2/6+vrm5uYEBQMCAwEAAQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQccFVY2M3MGAwsCAwEAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAwEnJEZvcXsAAQEAAAABAAAAAgABBAGhpKEtLSwCAAACAAAB + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAwPHx8f7+/uOjo6lpaX7+/v+/v7+/v79/f3+/v7+/v79/f38 + /PzFxcVsbGz5+fnn6OcEBQMBAgAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAggj + HkVZWXQCAgMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG + BwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEAAQAAAAEAAAAA + AAEAAAEAAAAAAAAAAAAAAAEAAAEAAAEAAAEAAAEAAAAAAAAAAAEAAAEAAAEAAgAABANCP1Nxcm4AAQAA + AAABAAACAwEcHx63ubkFBAYCAAACAAECAAEAAAEAAAEAAAEAAAEAAgAAAAABAgACBAK0tbT6+vq1tbV2 + dnb5+fn8/Pz9/f39/f3+/v79/f39/f36+vqTk5Obm5v4+PjR0tEDBAICAgAAAQIAAAEAAAEAAAEAAQEA + AAEAAAEAAAEAAAAAAAEAAAEAAQEABAVTVlhbXmABAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAABAAAAAAADAQMGBRwLDEULDEsLDE4KC00KDEwKDEwKC0wLC08LDE8KC04KC00KC0wKC0wKC0wK + DE0KDE0KDE0JDEsMDElpZoRxcmsAAQAAAQAAAQAEBAOBgYRwcZUNDk8KDEwKC00KC04KC0wKC0wKC0wL + C00MDEkKBzECAwYABAB3e3j29vbw8PBBQUHj4+P4+Pj8/Pz9/f39/f38/Pz7+/vu7u5CQkLm5ub4+PiV + lZUDAgADAgQGBy4KDEgJC0oKDEwLDE0LDE0LDEwLDEwKC00KC00KC00KDE0ODkOXmqM1NzUAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAADAAICBAMMClMZGc8UEdsSD+ASDuASDeARDN8R + DN8RDOASDeESDeESDeESDeATDuETDuASDt0RDt0RDdwMD94ZGMx/gMN6eXYAAQAAAQAAAQAaIRrCweAw + KsARD90RDdsSDd4SDd4RDt0RDt0RDtwOEdoTEtkjHKACAxsAAwIhJCPs7ez39/ewsLBRUVHm5ub7+/v7 + +/v9/f36+vrv7+9lZWWWlpb4+Pjy8vI5OTgDAgAAAR8YGp8TEtQSD9oSDt8RDuAOD90OEN4PEN4RDd8S + Dd8SDeAVDuAqI7O8v9oSFRIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQAAAQQC + BAIFBVQIDeIDBPYBAP0BAPsBAPoBAPoBAPoCAfoCAfoBAPkBAPoBAPoBAPoBAPoBAPoCAPsCAPsAAvgK + Cel4f8t7fHsAAQAAAQAEBQOCioh3fNsNCOgDA/gBAPkCAPwCAPwBAPoBAPoBAPoAA/cCA/kZFNIKC04A + AgQDBQWEhYT29vb29vanp6dBQUGZmZnQ0NDT09OioqJISEiRkZH19fX39/efn58FBgUAAgcEBlIPEdcB + AfgBAfkCAfoBAPsAAPoAAfsAAfsCAfkBAfgBAfgFBPI2OMWlrrYTFhQHBwcDAwMAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAEABQEDAQICBD8KENkBBfQEAvgAAfoAAfwAAfsAAPsAAPsAAfsAAfwA + AfwAAfwAAfwAAfwAAfwAAfwAAPsAAvkGCel0fs+Eg4ACBQAEBQQlJifEzeIkJ9ICBPUBAvcAAPsAAfwA + AfwAAfkAAfkAAfkAA/cABfQLCOwXGJgCBBcLBgUPDxGytLP2+Pf19/bm5+aTk5NgYGBeXl6Li4ve3t73 + 9/f29/LGxcYVFxQGBgMBAxsNEZUHC+oAAfwAAPsAAfwAAfsAAPwAAPwAAfwAAfsAAfsAAfsFCO5MVtWz + vOW7vuG6wM52dXgQDg4CAwEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG + BwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEABAABAAEBAi8PEc0BBfMD + AvcAAfoAAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAfoFB+xxetGLiosBAgUN + CQulqaxpadIODOMAAfYBAvgAAf0AAf0AAfwAAfsAAfsAAfsAAvkABPYFAvYWE9IODWsDBQcKCAQPEg6V + mZTx9fD7/fv7+/v7+/v9/f36+vr8/Pz19fWpqqsYFhoEBgECAQ0IC2AREtUAA/YAAP0AAPwAAf0AAf0A + APwAAf0AAf0AAf0AAPwAAf0AAfoKC+MXD9UZEtAsKsaCicyss7MREBEAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAABAAAAAwABAQQBAh8TEbgBBfABAvkAA/kAAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0A + APwAAf0AAf0AAfsFBfFpbtCRkpMEBwVfYV2qs+cYFtgFBPYAAfYBAfkAAf0AAf0AAPwAAP0AAP0AAP0A + AfsAAfwBAvkFBfAUF8YHC0kBAw4AAQEDBgQ+QD6hoqLl5eX19fX19fXo6OiwsLBOTk4GCQUCBQIBAxED + BFIUFcMEBPQAAfwAAP0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AA/gABvIBAPwBAvgDBu4SEth+ + g8l2eXkCAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAgABAQQBAhQSEaICBfEAA/cAAvkAAf0AAPwA + Af0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAfsFBfNbXcmam54pLSvKzOI2N9AEBu4AAvoA + AfkCAPwAAf0AAf0AAP0AAP0AAP0AAP0AAP0AAP0AAvgAA/kFBu4cF8AMDVwBAhUDAwUEAwQCAwQEBQUN + DQ4NDhEGBwkEBAMFBAMBAwICAxsHB14YE8YIBu0BAvoBAvoAAfwAAPwAAf0AAf0AAPwAAf0AAf0AAfwA + AvoAAvoAAvoABPQBAvgCA/YABPMECO8tKcW4vcwEBgQBAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAA + AQAAAQwNDX8GB/IBBPUAAfsAAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAfkF + BvNJRca2trW6ucZkYswNDOUAAfkAAfsAAP0AAP0AAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAfsC + AfkLB+4ZF8oVGIMGBjgCABgAAQYBAwEDBAAABAEABQEBAgUBABUCBDQUE4IXFscKCPAAAf0AAPwAAf0A + Af0AAPwAAf0AAf0AAPwAAf0AAf0AAfoAA/cAA/gAAfsAAfoAAvoAAvoAAfoCBvMZFc25vt4KDQsDAQIA + AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAQAAAQUGCFgMD+UBAPsAAfwAAf0AAPwAAf0AAf0AAPwAAf0A + Af0AAPwAAf0AAf0AAPwAAf0AAf0AAPwEBPUtMLzV3vR9ftUPEd8ABfcAAfkAAfsAAP0AAP0AAf0AAf0A + APwAAf0AAf0AAf0AAPwAAf0AAf0AAfwBAvkDA/YDAfkHCuYXGsAXF48LEFsDBj4AAjAAAi8DBEEMCmMT + EZIWFsARCucGAvYBAvwAAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAfoAAfoCAvkEBPgCAvoA + AP0AAfsBAvgABfQUD9iwsuAhJCIDAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG + BwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQABBjEPFdAB + APsAAvoAAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAfwABPUMD9s4QNoRFtID + BfQAAvwAAvoAAfsAAP0AAP0AAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAfwAAvoCAvkAAvkAAvYC + Be8KCeoNEtsPFNMVFs0VFs8PEtYRDt4LCO0FA/MEAPoEA/gCAP0AAf0AAPwAAf0AAf0AAPwAAf0AAf0A + APwAAf0AAf0AAfwCAvoFBfASFugLDeUGAvYCAfoBAvgABPYRC+OdnNUyNjUDAQAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAACAx0OErMBAvgBBPYAAfoAAfwAAP0AAf0AAPwAAf0AAf0AAPwAAf0AAf0A + APwAAf0AAf0AAfwAAfwAAfsBAvoAAfoAAfwAAf0AAfwAAfwAAP0AAP0AAf0AAf0AAPwAAf0AAf0AAf0A + APwAAf0AAf0AAP0AAP0AAP0AAfwAAfsAAfwAAf0AAfwAAfsAAfsBAfsAAfsAAfsAAPwAAfwAAfwAAfsA + AP0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAvoAA/cFB+0lJdVWXd8VEt8DA/cCA/YABPUQ + C+eDhdBNUUwFAQABAgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAw4KDIsJBfICAfsAAfoAAfwA + AP0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0A + Af0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0A + APwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAP0A + APsDA/USDd9ye8yHiuIbFt0HBfAABfMPBe5tbM5iaWcEAgEBAQIAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAACAgUFBloTD9wEAvoAAvoAAfwAAP0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAf0A + APwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwA + Af0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0A + Af0AAPwAAf0AAf0AAPwAAf0AAf0AAfkDAvYCA/UGB+8nIMW+x+ifn+gmINYGB+8OBu9YVsl2gYQEBQEB + AQIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAQIAATMXE7sEBfMBAvgAAfsAAP0AAf0AAPwAAf0A + Af0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0A + APwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAf0A + APwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0CA/URE/AMEOcFCO4MDORW + WMLLydKTm9EpKNMSB+1IQ8KOlp4EBAMBAQQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG + BwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAQAAAhQR + EIsHB+4DAvgCAPwAAP0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwA + Af0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0A + Af0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0A + APwAAf0AAf0BAvsSEd9eYNpVVtgaGtUhF8+gqNBfZG2Xn8ZFQMJjXraZoKEEBQMAAAIAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAgAABQIGB00ND9sCAvYCAP0AAfwAAf0AAPwAAf0AAf0AAPwAAf0AAf0A + APwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAf0A + APwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwA + Af0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0BA/cGBPUYGsp4f9ert+pYT9RKUKGqrq8WHByQ + k6OHjJgeJB4CBQAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQIDAQUCAyIJEbUDCO0BAPsA + AP0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0A + Af0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0A + APwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0BB/MB + Ce8QDeYsJ7Olq8iioq6hqLNqbm8DBggDBgQECQUCBQYAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAgADAAEAAA8GCmgRD+MABPYBAfoAAfoAAfsAAP0AAP0AAPwAAf0AAf0AAPwAAf0AAf0AAf0A + APwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwA + Af0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAP0AAP0AAP0AAP0B + AvkBAvgAAPgBAPoAA/gCBe4LDOIZH9BGT9CDjdekqcuKi5QTExIGAwIAAQEAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQABAAAAAgMAAjMTELwCAfgBA/gAAvoAAfsAAP0A + AP0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0A + APwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAf0A + APwAAf0AAf0AAPwAAfwAAfwAAfwAAfwBBPYDAvYGAvQLB+sZFttJR9mJj9+jocRwboIwMDIICggCBAMB + AgACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG + BwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAMC + BAABAQsLC3YKCuoAA/cAAvoAAfsAAP0AAP0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwA + Af0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0A + Af0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAfwAAvoAAfoAAfoCBPYLC+MmJslyct+k + o9R4eX0wLC0GCAQDBQIBAgEBAwEAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAgAAAAMBAQACAgICAjwSEMUDBPgAA/YAAvoAAP0AAP0AAPwAAf0AAf0A + APwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAf0A + APwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwA + APwBAvgAAvkDA/QaFtlvbNqkqcpSVlYOEAoGAwMEAgEAAgAAAQAAAAAAAAAAAAEAAAIAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAhILC3cN + DeUBBPQBAfsBAP0AAPwAAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0A + Af0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0A + APwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAP0BAvoAA/cTFtKRlN1xcHcLDgwEBAYAAgQAAAIAAAIBAAAB + AAACAAIBAAEAAQAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAgABAQQBAS8VELQEB+sDAfkBAP0AAfwAAPwAAf0AAf0AAPwAAf0AAf0AAf0A + APwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwA + Af0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAf0AAfwCBPYjJ8W2 + u9g5NC8dIBwYHRcSFhIPEg0MDAkFBwQBAwEAAgABAwAAAgAAAgEAAwEAAAABAQEAAAABAQEAAAAAAAAB + AQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAFAQIEAwcGB2AKENoEBPMBAP0A + AfwAAfwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0A + APwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAf0A + APwAAf0AAf0AAPwAAfwCAvgAAvkPDttVXMinqNutstu3ut65vuC8weG/wuK/wtm7v9G4vMussbugpKuQ + lJqHiY94eHplZWdSUlM9Pj4qKysYGBgGBwYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG + BwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAgABAQMHARkQEI8ICO8BBfUCAfoAAfsAAfwAAP0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwA + Af0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0A + Af0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAP0AAP0AAfsAAvoLCeQQD90PENcREtQU + E9MVFdEWGc8ZG8skIcwvKMs1MMg9PMdMS8hbWctmast3edGKidaZmdyoq+C5vdqKjo0DBgEAAgAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAEAAQABAAUBAzkVEcIHBvMAAvkAAvkAAfwAAP0A + APwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAf0A + APwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwA + AP0AAP0AAfsAAvoABPUBBfUBBPQBBPUABPYABPQABvMBAvYBA/cBAvcCBPUCBfMDBu8FB+0EBu0HCOkK + CeMLCd8PD9UmJLKtt9kqMi0EBQECAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA + AgAAAw0IBl8VEtQBBvIAA/YAAfsAAP0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0A + Af0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0A + APwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAP0AAP0AAfsAAvoBAfoCAfsCAPsCAfsBAvkBAvkBAvkCAvcC + AvgCAfoBAfsBAfoBAvkBAvgAAvkBAvgCA/YBAvQACPEQC+pUW8CPmaYGBAQEAQAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAABQECAhkLDYEKDOUDA/gBA/gAAfsAAP0AAfwAAfsAAfwA + APwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwA + Af0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAP0AAP0AAfwAAfsA + AfsAAvoAAvoAAvkAAvoAAvoAAfoAAfoAAfsBAfsBAfsAAfsAAfoAAvoAAPwAAPwAAPwCAfsBA/UHBfEb + HMirtOA1OTIHBAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgEBAQUAAjIP + EaAJC+gAAvkCBPQAAfsAAfsAAfsAAfwAAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0A + APwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAf0A + APwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwA + Af0AAf0AAPwAAf0AAf0AAfsBAvgEBPMKCOpEScGvsLwOCxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG + BwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAQADAwAAAQYAAjUSFLUDBfEEA/gAA/cAAvoAAfwAAf0AAPwAAf0AAf0AAPwA + Af0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0A + Af0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0A + APwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAvoBAvkCAfoCAvkPFNKVltFeYV8B + AgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAQIAAQUAAwUDBEgQE7kEB/MA + BPYAAvkAAfwAAP0AAP0AAP0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAf0A + APwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwA + Af0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0A + Af0BAP0BAP0AAPwBAvoHBfEuNLanqL0DAgMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAABAQEBAQAAAgkDBVoPEcgGCOsAAfwCAvoAAP0AAP0AAP0AAf0AAPwAAf0AAf0AAPwAAf0A + Af0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0A + APwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAf0A + APwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAfsBBfcAAfoAA/UGBfEeHLYQETgAAAEAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgACBwAAAhQCBFQSE8UHA/YAAvsAAvsA + AfsAAP0AAP0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwA + Af0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0A + Af0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAfkABPkBAf0I + BvAWE8gICFUCAg8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAEBBAAAAwABAg0GBU8VEroKC+oAA/YAAvkAAfwAAP0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0A + APwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAf0A + APwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwA + Af0AAf0AAPwAAf0AAf0AAPwAA/cKCOgcFr4HB08EAgsBAgMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG + BwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAgEAAwkCBEURD7cHB+4CA/gAAf0AAvsAAfoA + AfwBAP0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0A + Af0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0A + APwAAf0AAf0AAf0AAPwAAf0AAP0AAP0AAP0AAP0AAfoBAvgBAfsDA/cFCuoSFbEHBU0BAQsEAgMBAQMA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIC + AQUBAwgBAzoODp0MDuQBA/oDA/cBAfoBAPwBAP0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAf0A + APwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwA + Af0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAP0AAP0AAP0AAfwABPYAA/gC + AvgKDOUQFKIBBDIBAgkAAAEAAAEAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAIDAgECAAkCAyUKDIETDdwOBfMFBPQCAfgAAfwAAPwAAf0A + Af0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0A + APwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAf0A + APwAAf0AAfsAAfoAAfsAAfsBAvcGBfEVENQLD4ABAioGAQIGAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAIAAAMBAgEBAwIB + AxQFBl4RErgMDOUEA/QAAPsAAfsAAfwAAfwAAfwAAfwAAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwA + Af0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0A + Af0AAPwAAf0AAf0AAPwAAf0AAf0AAfwAAfwAAPwAAfsAAvkAAvoCA/cKCeUTFL0JCVsBAhEAAgMAAQAC + AgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAADAQABAgIAABYBAjsNDJERD9sHA/YABPUABPkAA/cAA/cAAfsAAfwA + AP0AAf0AAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAf0A + APwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAP0AAfoAA/YBA/YABPUG + BfIREtcREJgCAzcAAQcAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG + BwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAgACAQEDAgQAAhkD + BlcQD7EICuwFBPgAAfoAA/gAAvkAAfwAAP4AAf0AAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0A + Af0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0A + APwAAPwAAPwAAfcAAfoCAfoEA/gQC+cXEbUHCVoAAhgCAgMDAgEAAAEAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAQAAAQABAQACAQACAAEAAgcBAy0JCXQVEbkNDOMCBvAAA/YAAvsAAf4AAf0AAf0AAf0A + APwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAf0AAPwAAf0AAf0AAPwA + Af0AAf0AAPwAAf0AAf0AAPwAAf0AAf0AAPwAAPsAAPwAA/UGBPAPC+EWEr8MCnAEAzAAAwkAAgADAAAC + AQEAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAEAAAABAQAAAAQAAgkBAjEJ + CXgQD7sODuUEBPYDA/oBAvoAAfsBAvkAAvoAAfsAAPwAAP0AAP0AAfwAAfsAAP0AAP0AAP0AAP0AAf0A + APwAAP0AAP0AAP0AAP0AAP0AAP0AAP0AAP0AAfwAAvkAAvsAAfwAAvsAAf0BAfwAA/gAAvoEBe8IC+gQ + EsEKDXcCAzMAAQwBAAADAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAQICAwQAARAAAi8HCWEPD6gVD+IFB/ACA/cAAvoAAvoBAfsDAPwDAPsA + AfwAAP0AAP0AAP0AAP0AAP0AAP0AAf0AAPwAAP0AAP0AAP0AAP0AAfwAAfwAAfwAAfsAAvkAAvgBAfsB + AfsCAfsDAf0CAvgECO8SDt4VE6YJCmAAAywBBA4BAQYAAAMCAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwADAQEDAAIBAQEBAQcBASQE + BU0JDX8UErIYEN0LDuIECesBAvYAA/YABPIAAvgAAPwAAfsAAvoAAfoAAfsAAf0AAPwAAfwAAfkAAfoA + AfoBAvkCAvgBAfcAAfgBAfsEAfoHBPUGCO0LDOgQENcMErIKEX8EBFAAAx0AAQsABAAABAAAAQEAAAAC + AAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG + BwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAQAAAAIAAQEABAAAAQADAAEBAwMAAgsBASoFA0wKCnoTEagRDssOD9sIDeQDB+4BBPYBBPUA + A/gAAvoAAvoBAfsBAfsBAfwAAfkBAfkBAvgDA/YFA/QFA/IIBfEJCugNDtwOEscOE5wLDHoEA0sCAigA + AQsBAgMAAgEBAQABAAECAAABAwAAAAABAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAQAAAsB + Aw8ABBoAAjEDBEkJCW0MC5EQD6sPELkODscQENQQEdoQEd4REuASEuIOEdsPEtgOEdAQEMYSEbgRD6IO + DIsKCWgDBEkAAiwAARwAABEAAAYCAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAQAAAgAAAQMBAAQDAAIDAgIAAgMAAQ8BAxcBAx4BASoCATQCAzoBAz8B + A0ECA0MDA0ECAjoCAjMBAikCAiABARQCAg4AAQcBAQQDAAIDAQABAQAAAQEAAAEAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAwAAAwAAAgEBAgIB + AgMCAAQDAQIBBQAAAgEAAQEBAgACAwEBAQEBAAECAwICAgAEAQACAwABAwABAQACAgEABAEAAwEAAQEB + AAABAAAAAAEAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEAAAEAAAEAAAEAAAAAAAAAAAAAAAAAAQAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG + BwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG + BwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG + BwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBwUEBAQAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAGBwUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== + + + \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Forms/SelectAccountForm.Designer.cs b/PoGo.NecroBot.Logic/Forms/SelectAccountForm.Designer.cs new file mode 100644 index 000000000..909cd0d02 --- /dev/null +++ b/PoGo.NecroBot.Logic/Forms/SelectAccountForm.Designer.cs @@ -0,0 +1,147 @@ +namespace PoGo.NecroBot.Logic.Forms +{ + partial class SelectAccountForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.label1 = new System.Windows.Forms.Label(); + this.lvAcc = new PoGo.NecroBot.Logic.Forms.EXListView(); + this.colType = ((PoGo.NecroBot.Logic.Forms.EXColumnHeader)(new PoGo.NecroBot.Logic.Forms.EXColumnHeader())); + this.colUsername = ((PoGo.NecroBot.Logic.Forms.EXColumnHeader)(new PoGo.NecroBot.Logic.Forms.EXColumnHeader())); + this.colRuntime = ((PoGo.NecroBot.Logic.Forms.EXColumnHeader)(new PoGo.NecroBot.Logic.Forms.EXColumnHeader())); + this.colStatus = ((PoGo.NecroBot.Logic.Forms.EXColumnHeader)(new PoGo.NecroBot.Logic.Forms.EXColumnHeader())); + this.colStart = ((PoGo.NecroBot.Logic.Forms.EXColumnHeader)(new PoGo.NecroBot.Logic.Forms.EXColumnHeader())); + this.timer1 = new System.Windows.Forms.Timer(this.components); + this.btnClose = new System.Windows.Forms.Button(); + this.SuspendLayout(); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(19, 28); + this.label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(483, 17); + this.label1.TabIndex = 0; + this.label1.Text = "PLEASE SELECT AN ACCOUNT TO START. AUTO START AFTER 30 SEC"; + // + // lvAcc + // + this.lvAcc.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { + this.colType, + this.colUsername, + this.colRuntime, + this.colStatus, + this.colStart}); + this.lvAcc.ControlPadding = 0; + this.lvAcc.FullRowSelect = true; + this.lvAcc.Location = new System.Drawing.Point(15, 69); + this.lvAcc.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); + this.lvAcc.MultiSelect = false; + this.lvAcc.Name = "lvAcc"; + this.lvAcc.OwnerDraw = true; + this.lvAcc.Size = new System.Drawing.Size(601, 242); + this.lvAcc.TabIndex = 1; + this.lvAcc.UseCompatibleStateImageBehavior = false; + this.lvAcc.View = System.Windows.Forms.View.Details; + this.lvAcc.SelectedIndexChanged += new System.EventHandler(this.ListView1_SelectedIndexChanged); + // + // colType + // + this.colType.Text = "Account Type"; + this.colType.Width = 100; + // + // colUsername + // + this.colUsername.Text = "Username"; + this.colUsername.Width = 82; + // + // colRuntime + // + this.colRuntime.Text = "Runtime"; + // + // colStatus + // + this.colStatus.Text = "Status "; + this.colStatus.Width = 91; + // + // colStart + // + this.colStart.Text = "Start"; + // + // timer1 + // + this.timer1.Enabled = true; + this.timer1.Interval = 1000; + this.timer1.Tick += new System.EventHandler(this.Timer1_Tick); + // + // btnClose + // + this.btnClose.Location = new System.Drawing.Point(517, 336); + this.btnClose.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); + this.btnClose.Name = "btnClose"; + this.btnClose.Size = new System.Drawing.Size(100, 28); + this.btnClose.TabIndex = 2; + this.btnClose.Text = "CLOSE"; + this.btnClose.UseVisualStyleBackColor = true; + this.btnClose.Click += new System.EventHandler(this.Btnclose_Click); + // + // SelectAccountForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(636, 379); + this.ControlBox = false; + this.Controls.Add(this.btnClose); + this.Controls.Add(this.lvAcc); + this.Controls.Add(this.label1); + this.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); + this.Name = "SelectAccountForm"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; + this.Text = "Startup Account"; + this.TopMost = true; + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.SelectAccountForm_FormClosing); + this.Load += new System.EventHandler(this.SelectAccountForm_Load); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label label1; + private EXListView lvAcc; + private EXColumnHeader colType; + private EXColumnHeader colUsername; + private EXColumnHeader colRuntime; + private EXColumnHeader colStatus; + private EXColumnHeader colStart; + private System.Windows.Forms.Timer timer1; + private System.Windows.Forms.Button btnClose; + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Forms/SelectAccountForm.cs b/PoGo.NecroBot.Logic/Forms/SelectAccountForm.cs new file mode 100644 index 000000000..deba9cde7 --- /dev/null +++ b/PoGo.NecroBot.Logic/Forms/SelectAccountForm.cs @@ -0,0 +1,89 @@ +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.Model; +using PokemonGo.RocketAPI.Extensions; +using System; +using System.Windows.Forms; + +namespace PoGo.NecroBot.Logic.Forms +{ + public partial class SelectAccountForm : Form + { + public Account SelectedAccount { get; set; } + public SelectAccountForm() + { + InitializeComponent(); + } + + private void ListView1_SelectedIndexChanged(object sender, EventArgs e) + { + + } + + private void SelectAccountForm_Load(object sender, EventArgs e) + { + BringToFront(); + + WindowState = FormWindowState.Minimized; + Show(); + WindowState = FormWindowState.Normal; + + lvAcc.BeginUpdate(); + using (var db = new AccountConfigContext()) + { + foreach (var item in db.Account) + { + EXListViewItem lvItem = new EXListViewItem(item.AuthType.ToString()); + lvItem.SubItems.Add(new EXControlListViewSubItem() { Text = item.Username }); + lvItem.SubItems.Add(new EXControlListViewSubItem() { Text = item.GetRuntime() }); + lvItem.SubItems.Add(new EXControlListViewSubItem() { Text = "" }); + + EXControlListViewSubItem cs = new EXControlListViewSubItem() + { + }; + Button b = new Button() + { + Text = "START", + Height = 55 + }; + b.Click += SelectBot_Click; + b.Tag = item; + lvItem.SubItems.Add(cs); + lvAcc.AddControlToSubItem(b, cs); + lvAcc.Items.Add(lvItem); + + } + } + lvAcc.EndUpdate(); + } + + private void SelectBot_Click(object sender, EventArgs e) + { + SelectedAccount = (Account)((Button)sender).Tag; + Close(); + } + + private void SelectAccountForm_FormClosing(object sender, FormClosingEventArgs e) + { + if (SelectedAccount == null) + { + var manager = TinyIoC.TinyIoCContainer.Current.Resolve(); + SelectedAccount = manager.GetMinRuntime(); + SelectedAccount.LoggedTime = DateTime.Now.ToUnixTime(); + } + } + + int countdown = 31; + private void Timer1_Tick(object sender, EventArgs e) + { + countdown--; + var translator = TinyIoC.TinyIoCContainer.Current.Resolve(); + label1.Text = translator.GetTranslation(TranslationString.MultiAccountAutoSelect, countdown); + if (countdown <= 0) Close(); + } + + private void Btnclose_Click(object sender, EventArgs e) + { + Close(); + } + } +} diff --git a/PoGo.NecroBot.Logic/Forms/SelectAccountForm.resx b/PoGo.NecroBot.Logic/Forms/SelectAccountForm.resx new file mode 100644 index 000000000..1f666f268 --- /dev/null +++ b/PoGo.NecroBot.Logic/Forms/SelectAccountForm.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Interfaces/Configuration/ILogicSettings.cs b/PoGo.NecroBot.Logic/Interfaces/Configuration/ILogicSettings.cs new file mode 100644 index 000000000..09ad5939f --- /dev/null +++ b/PoGo.NecroBot.Logic/Interfaces/Configuration/ILogicSettings.cs @@ -0,0 +1,264 @@ +#region using directives + +using System.Collections.Generic; +using PoGo.NecroBot.Logic.Model.Settings; +using POGOProtos.Enums; +using POGOProtos.Inventory.Item; + +#endregion + +namespace PoGo.NecroBot.Logic.Interfaces.Configuration +{ + public interface ILogicSettings + { + bool UseWebsocket { get; } + bool CatchPokemon { get; } + bool ByPassCatchFlee { get; } + int OutOfBallCatchBlockTime { get; } + int PokeballsToKeepForSnipe { get; } + int CatchPokemonLimit { get; } + int CatchPokemonLimitMinutes { get; } + int PokeStopLimit { get; } + int PokeStopLimitMinutes { get; } + int SnipeCountLimit { get; } + int MinIVForAutoSnipe { get; } + int SnipeRestSeconds { get; } + bool TransferWeakPokemon { get; } + bool DisableHumanWalking { get; } + bool CheckForUpdates { get; } + bool AutoUpdate { get; } + float KeepMinIvPercentage { get; } + int KeepMinCp { get; } + int KeepMinLvl { get; } + bool UseKeepMinLvl { get; } + string KeepMinOperator { get; } + bool KeepPokemonsToBeEvolved { get; } + double WalkingSpeedInKilometerPerHour { get; } + bool UseWalkingSpeedVariant { get; } + double WalkingSpeedVariant { get; } + bool ShowVariantWalking { get; } + bool RandomlyPauseAtStops { get; } + bool FastSoftBanBypass { get; } + int ByPassSpinCount { get; } + double AutoSnipeMaxDistance { get; } + bool UseTransferFilterToCatch { get; } + bool TransferDuplicatePokemon { get; } + bool TransferDuplicatePokemonOnCapture { get; } + bool UseBulkTransferPokemon { get; } + bool UseEggIncubators { get; } + bool UseLimitedEggIncubators { get; } + int UseGreatBallAboveCp { get; } + string UseBallOperator { get; } + int UseUltraBallAboveCp { get; } + int UseMasterBallAboveCp { get; } + double UseGreatBallAboveIv { get; } + double UseUltraBallAboveIv { get; } + double UseMasterBallBelowCatchProbability { get; } + double UseUltraBallBelowCatchProbability { get; } + double UseGreatBallBelowCatchProbability { get; } + bool EnableHumanizedThrows { get; } + bool EnableMissedThrows { get; } + int ThrowMissPercentage { get; } + int NiceThrowChance { get; } + int GreatThrowChance { get; } + int ExcellentThrowChance { get; } + int CurveThrowChance { get; } + double ForceGreatThrowOverIv { get; } + double ForceExcellentThrowOverIv { get; } + int ForceGreatThrowOverCp { get; } + int ForceExcellentThrowOverCp { get; } + int DelayBetweenPokemonCatch { get; } + int DelayBetweenPokemonUpgrade { get; } + bool AutomaticallyLevelUpPokemon { get; } + bool OnlyUpgradeFavorites { get; } + bool UseLevelUpList { get; } + string LevelUpByCPorIv { get; } + float UpgradePokemonCpMinimum { get; } + float UpgradePokemonIvMinimum { get; } + int DelayBetweenPlayerActions { get; } + bool UsePokemonToNotCatchFilter { get; } + bool UsePokemonToCatchLocallyListOnly { get; } + string SnipeLocationServer { get; } + int SnipeLocationServerPort { get; } + bool UseSnipeLocationServer { get; } + int KeepMinDuplicatePokemon { get; } + int KeepMaxDuplicatePokemon { get; } + bool PrioritizeIvOverCp { get; } + int AmountOfTimesToUpgradeLoop { get; } + int GetMinStarDustForLevelUp { get; } + bool UseLuckyEggConstantly { get; } + bool UseIncenseConstantly { get; } + string UpgradePokemonMinimumStatsOperator { get; } + int MaxTravelDistanceInMeters { get; } + bool StartFromLastPosition { get; } + bool UseGpxPathing { get; } + string GpxFile { get; } + #region Evolve + bool EvolvePokemonsThatMatchFilter { get; } + bool EvolveAnyPokemonAboveIv { get; } + float EvolveAnyPokemonAboveIvValue { get; } + bool TriggerEvolveAsSoonAsFilterIsMatched { get; } + bool TriggerEvolveOnEvolutionCount { get; } + int TriggerEvolveOnEvolutionCountValue { get; } + bool TriggerEvolveOnStorageUsagePercentage { get; } + double TriggerEvolveOnStorageUsagePercentageValue { get; } + bool TriggerEvolveOnStorageUsageAbsolute { get; } + int TriggerEvolveOnStorageUsageAbsoluteValue { get; } + bool TriggerEvolveIfLuckyEggIsActive { get; } + bool EvolvePreserveMinCandiesFromFilter { get; } + bool EvolveApplyLuckyEggOnEvolutionCount { get; } + int EvolveApplyLuckyEggOnEvolutionCountValue { get; } + #endregion + bool DumpPokemonStats { get; } + bool RenamePokemon { get; } + bool RenamePokemonRespectTransferRule { get; } + bool RenameOnlyAboveIv { get; } + float FavoriteMinIvPercentage { get; } + float FavoriteMinCp { get; } + int FavoriteMinLevel { get; } + string FavoriteOperator { get; } + bool AutoFavoritePokemon { get; } + bool AutoFavoriteShinyOnCatch { get; } + string RenameTemplate { get; } + int AmountOfPokemonToDisplayOnStart { get; } + string TranslationLanguageCode { get; } + string ProfilePath { get; } + string ProfileConfigPath { get; } + string GeneralConfigPath { get; } + int SchemaVersion { get; } + bool SnipeAtPokestops { get; } + bool ActivateMSniper { get; } + bool UseTelegramAPI { get; } + string TelegramAPIKey { get; } + string TelegramPassword { get; } + int MinPokeballsToSnipe { get; } + int MinPokeballsWhileSnipe { get; } + int MaxPokeballsPerPokemon { get; } + int MinDelayBetweenSnipes { get; } + bool SnipePokemonNotInPokedex { get; } + bool RandomizeRecycle { get; } + int RandomRecycleValue { get; } + + int TotalAmountOfPokeballsToKeep { get; } + int TotalAmountOfPotionsToKeep { get; } + int TotalAmountOfRevivesToKeep { get; } + int TotalAmountOfBerriesToKeep { get; } + int TotalAmountOfEvolutionToKeep { get; } + + bool UseRecyclePercentsInsteadOfTotals { get; } + int PercentOfInventoryPokeballsToKeep { get; } + int PercentOfInventoryPotionsToKeep { get; } + int PercentOfInventoryRevivesToKeep { get; } + int PercentOfInventoryBerriesToKeep { get; } + int PercentOfInventoryEvolutionToKeep { get; } + + bool DetailedCountsBeforeRecycling { get; } + bool VerboseRecycling { get; } + double RecycleInventoryAtUsagePercentage { get; } + bool UseSnipeLimit { get; } + bool UsePokeStopLimit { get; } + bool UseCatchLimit { get; } + bool UseNearActionRandom { get; } + ICollection> ItemRecycleFilter { get; } + + ICollection PokemonsToLevelUp { get; } + CatchSettings PokemonToCatchLocally { get; } + + NotificationConfig NotificationConfig { get; } + ICollection PokemonsNotToTransfer { get; } + + ICollection PokemonsNotToCatch { get; } + + ICollection PokemonToUseMasterball { get; } + + Dictionary PokemonsTransferFilter { get; } + Dictionary PokemonSnipeFilters { get; } + Dictionary PokemonEvolveFilters { get; } + Dictionary PokemonUpgradeFilters { get; } + + Dictionary BotSwitchPokemonFilters { get; } + bool StartupWelcomeDelay { get; } + bool UseGoogleWalk { get; } + double DefaultStepLength { get; } + bool UseGoogleWalkCache { get; } + string GoogleApiKey { get; } + string GoogleHeuristic { get; } + string GoogleElevationApiKey { get; } + + bool UseYoursWalk { get; } + string YoursWalkHeuristic { get; } + + bool UseMapzenWalk { get; } + string MapzenTurnByTurnApiKey { get; } + string MapzenWalkHeuristic { get; } + string MapzenElevationApiKey { get; } + + int ResumeTrack { get; } + int ResumeTrackSeg { get; } + int ResumeTrackPt { get; } + + bool EnableHumanWalkingSnipe { get; } + bool HumanWalkingSnipeDisplayList { get; } + double HumanWalkingSnipeMaxDistance { get; } + double HumanWalkingSnipeMaxEstimateTime { get; } + bool HumanWalkingSnipeTryCatchEmAll { get; } + int HumanWalkingSnipeCatchEmAllMinBalls { get; } + bool HumanWalkingSnipeCatchPokemonWhileWalking { get; } + bool HumanWalkingSnipeSpinWhileWalking { get; } + bool HumanWalkingSnipeAlwaysWalkBack { get; } + double HumanWalkingSnipeWalkbackDistanceLimit { get; } + double HumanWalkingSnipeSnipingScanOffset { get; } + bool HumanWalkingSnipeIncludeDefaultLocation { get; } + bool HumanWalkingSnipeUseSnipePokemonList { get; } + Dictionary HumanWalkSnipeFilters { get; } + bool HumanWalkingSnipeAllowSpeedUp { get; } + double HumanWalkingSnipeMaxSpeedUpSpeed { get; } + int HumanWalkingSnipeDelayTimeAtDestination { get; } + bool HumanWalkingSnipeAllowTransferWhileWalking { get; } + + bool HumanWalkingSnipeUsePokeRadar { get; } + bool HumanWalkingSnipeUseSkiplagged { get; } + bool HumanWalkingSnipeUsePokecrew { get; } + bool HumanWalkingSnipeUsePokesnipers { get; } + bool HumanWalkingSnipeUsePokeZZ { get; } + bool HumanWalkingSnipeUsePokeWatcher { get; } + bool HumanWalkingSnipeUseFastPokemap { get; } + bool HumanWalkingSnipeUsePogoLocationFeeder { get; } + + int EvolveActionDelay { get; } + int TransferActionDelay { get; } + int RecycleActionDelay { get; } + int RenamePokemonActionDelay { get; } + + GymConfig GymConfig { get; } + DataSharingConfig DataSharingConfig { get; } + MultipleBotConfig MultipleBotConfig { get; } + List Bots { get; } + CaptchaConfig CaptchaConfig { get; } + int BulkTransferStogareBuffer { get; } + int BulkTransferSize { get; } + + bool AutosnipeVerifiedOnly { get; } + int SnipePauseOnOutOfBallTime { get; } + int DefaultAutoSnipeCandy { get; } + int AutoSnipeBatchSize { get; } + GUIConfig UIConfig { get; } + string DefaultBuddyPokemon { get; } + bool AutoFinishTutorial { get; } + bool SkipFirstTimeTutorial { get; } + bool SkipCollectingLevelUpRewards { get; } + Dictionary ItemUseFilters { get; } + double UpgradePokemonLvlMinimum { get; } + int MinLevelForAutoSnipe { get; } + + bool UseHumanlikeDelays { get; } + int CatchSuccessDelay { get; } + int CatchErrorDelay { get; } + int CatchEscapeDelay { get; } + int CatchFleeDelay { get; } + int CatchMissedDelay { get; } + int BeforeCatchDelay { get; } + bool AutoWalkAI { get; } + int AutoWalkDist { get; } + } +} diff --git a/PoGo.NecroBot.Logic/Inventory.cs b/PoGo.NecroBot.Logic/Inventory.cs new file mode 100644 index 000000000..3e0c7eacf --- /dev/null +++ b/PoGo.NecroBot.Logic/Inventory.cs @@ -0,0 +1,1021 @@ +#region using directives + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Event.Inventory; +using PoGo.NecroBot.Logic.Exceptions; +using PoGo.NecroBot.Logic.Interfaces.Configuration; +using PoGo.NecroBot.Logic.Model.Settings; +using PoGo.NecroBot.Logic.PoGoUtils; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Utils; +using PokemonGo.RocketAPI; +using POGOProtos.Data; +using POGOProtos.Data.Player; +using POGOProtos.Enums; +using POGOProtos.Inventory; +using POGOProtos.Inventory.Item; +using POGOProtos.Networking.Responses; +using POGOProtos.Settings.Master; +using PokemonGo.RocketAPI.Helpers; +using TinyIoC; +using static PoGo.NecroBot.Logic.Model.Settings.FilterUtil; +using POGOProtos.Settings.Master.Pokemon; +using PoGo.NecroBot.Logic.Logging; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.Common; + +#endregion + +namespace PoGo.NecroBot.Logic +{ + public class Inventory + { + private readonly Client _client; + private readonly ILogicSettings _logicSettings; + private GetPlayerResponse _player = null; + private int _level = 0; + private IEnumerable _pokemonSettings = null; + private DateTime _lastLuckyEggTime; + private readonly List _revives = new List { ItemId.ItemRevive, ItemId.ItemMaxRevive }; + private ISession ownerSession; + + public async Task GetCandyFamily(PokemonId id) + { + var setting = (await GetPokemonSettings().ConfigureAwait(false)).FirstOrDefault(x => x.PokemonId == id); + var family = (await GetPokemonFamilies().ConfigureAwait(false)).FirstOrDefault(x => x.FamilyId == setting.FamilyId); + + if (family == null) return null; + return family; + } + + internal async Task GetPokemonSetting(PokemonId pokemonId) + { + return (await GetPokemonSettings().ConfigureAwait(false)).FirstOrDefault(p => p.PokemonId == pokemonId); + } + + public async Task GetCandyCount(PokemonId id) + { + Candy candy = await GetCandyFamily(id).ConfigureAwait(false); + if (candy != null) + return candy.Candy_; + return 0; + } + + public Inventory(ISession session, Client client, ILogicSettings logicSettings, + Action onUpdated = null) + { + ownerSession = session; + _client = client; + _logicSettings = logicSettings; + // Inventory update will be called everytime GetMapObject is called. + client.Inventory.OnInventoryUpdated += () => + { + if (onUpdated != null && _player != null) + { + onUpdated(); + } + }; + } + + private readonly List _pokeballs = new List + { + ItemId.ItemPokeBall, + ItemId.ItemGreatBall, + ItemId.ItemUltraBall, + ItemId.ItemMasterBall + }; + + private readonly List _potions = new List + { + ItemId.ItemPotion, + ItemId.ItemSuperPotion, + ItemId.ItemHyperPotion, + ItemId.ItemMaxPotion + }; + + public async Task UpdateInventoryItem(ItemId itemId) + { + var inventory = await GetCachedInventory().ConfigureAwait(false); + foreach (var item in inventory) + { + if (item.InventoryItemData != null + && item.InventoryItemData.Item != null + && item.InventoryItemData.Item.ItemId == itemId) + { + ownerSession.EventDispatcher.Send(new InventoryItemUpdateEvent() + { + Item = item.InventoryItemData.Item + }); + } + } + } + + public async Task GetLevelUpRewards(Inventory inv) + { + return await GetLevelUpRewards((await inv.GetPlayerStats().ConfigureAwait(false)).FirstOrDefault().Level).ConfigureAwait(false); + } + + public async Task> GetCachedInventory() + { + if (_player == null) + { + _player = await GetPlayerData().ConfigureAwait(false); + } + return _client.Inventory.InventoryItems.Select(kvp => kvp.Value); + } + + public async Task> GetAppliedItems() + { + var inventory = await GetCachedInventory().ConfigureAwait(false); + return inventory + .Select(i => i.InventoryItemData?.AppliedItems) + .Where(aItems => aItems?.Item != null) + .SelectMany(aItems => aItems.Item); + } + + public async Task> GetSlashedPokemonToTransfer() + { + var session = TinyIoCContainer.Current.Resolve(); + var myPokemon = await GetPokemons().ConfigureAwait(false); + var pokemonToTransfer = myPokemon.Where(p => p.IsBad); + + try + { + pokemonToTransfer = + pokemonToTransfer.Where( + p => + { + var pokemonTransferFilter = session.LogicSettings.PokemonsTransferFilter.GetFilter(p.PokemonId); + return true; + }) + .ToList(); + } + catch (ActiveSwitchByRuleException e) + { + throw e; + } + return pokemonToTransfer.OrderBy(poke => poke.Cp); + } + + public async Task> GetWeakPokemonToTransfer( + IEnumerable pokemonsNotToTransfer, Dictionary pokemonEvolveFilters, + bool keepPokemonsThatCanEvolve = false) + { + var session = TinyIoCContainer.Current.Resolve(); + var myPokemon = await GetPokemons().ConfigureAwait(false); + var pokemonToTransfer = myPokemon.Where(p => !pokemonsNotToTransfer.Contains(p.PokemonId) && CanTransferPokemon(p)); + + try + { + pokemonToTransfer = + pokemonToTransfer.Where( + p => + { + var pokemonTransferFilter = session.LogicSettings.PokemonsTransferFilter.GetFilter(p.PokemonId); + + return !pokemonTransferFilter.MovesOperator.BoolFunc( + pokemonTransferFilter.MovesOperator.ReverseBoolFunc( + pokemonTransferFilter.MovesOperator.InverseBool( + pokemonTransferFilter.Moves.Count > 0), + pokemonTransferFilter.Moves.Any(moveset => + pokemonTransferFilter.MovesOperator.ReverseBoolFunc( + pokemonTransferFilter.MovesOperator.InverseBool(moveset.Count > 0), + moveset.Intersect(new[] { p.Move1, p.Move2 }).Count() == + Math.Max(Math.Min(moveset.Count, 2), 0)))), + pokemonTransferFilter.KeepMinOperator.BoolFunc( + p.Cp >= pokemonTransferFilter.KeepMinCp, + PokemonInfo.CalculatePokemonPerfection(p) >= + pokemonTransferFilter.KeepMinIvPercentage, + pokemonTransferFilter.KeepMinOperator.ReverseBoolFunc( + pokemonTransferFilter.KeepMinOperator.InverseBool(pokemonTransferFilter + .UseKeepMinLvl), + PokemonInfo.GetLevel(p) >= pokemonTransferFilter.KeepMinLvl))); + }) + .ToList(); + } + catch (ActiveSwitchByRuleException e) + { + throw e; + } + + if (keepPokemonsThatCanEvolve) + { + var pokemonToEvolve = await session.Inventory.GetPokemonToEvolve(session.LogicSettings.PokemonEvolveFilters).ConfigureAwait(false); + + pokemonToTransfer = pokemonToTransfer.Where(pokemon => !pokemonToEvolve.Any(p => p.Id == pokemon.Id)); + } + return pokemonToTransfer.OrderBy(poke => poke.Cp); + } + + public async Task> GetDuplicatePokemonToTransfer( + IEnumerable pokemonsNotToTransfer, Dictionary pokemonEvolveFilters, + bool keepPokemonsThatCanEvolve = false, bool prioritizeIVoverCp = false + ) + { + var session = TinyIoCContainer.Current.Resolve(); + var myPokemon = await GetPokemons().ConfigureAwait(false); + var pokemonToTransfer = myPokemon.Where(p => !pokemonsNotToTransfer.Contains(p.PokemonId) && CanTransferPokemon(p)); + + try + { + pokemonToTransfer = + pokemonToTransfer.Where( + p => + { + var pokemonTransferFilter = session.LogicSettings.PokemonsTransferFilter.GetFilter(p.PokemonId); + + return !pokemonTransferFilter.MovesOperator.BoolFunc( + pokemonTransferFilter.MovesOperator.ReverseBoolFunc( + pokemonTransferFilter.MovesOperator.InverseBool( + pokemonTransferFilter.Moves.Count > 0), + pokemonTransferFilter.Moves.Any(moveset => + pokemonTransferFilter.MovesOperator.ReverseBoolFunc( + pokemonTransferFilter.MovesOperator.InverseBool(moveset.Count > 0), + moveset.Intersect(new[] { p.Move1, p.Move2 }).Count() == + Math.Max(Math.Min(moveset.Count, 2), 0)))), + pokemonTransferFilter.KeepMinOperator.BoolFunc( + p.Cp >= pokemonTransferFilter.KeepMinCp, + PokemonInfo.CalculatePokemonPerfection(p) >= + pokemonTransferFilter.KeepMinIvPercentage, + pokemonTransferFilter.KeepMinOperator.ReverseBoolFunc( + pokemonTransferFilter.KeepMinOperator.InverseBool(pokemonTransferFilter + .UseKeepMinLvl), + PokemonInfo.GetLevel(p) >= pokemonTransferFilter.KeepMinLvl))); + }) + .ToList(); + } + catch (ActiveSwitchByRuleException e) + { + throw e; + } + + var results = new List(); + var pokemonToEvolve = await GetPokemonToEvolve(pokemonEvolveFilters).ConfigureAwait(false); + + foreach (var pokemonGroupToTransfer in pokemonToTransfer.GroupBy(p => p.PokemonId).ToList()) + { + var amountToKeepInStorage = Math.Max(GetApplyFilter(session.LogicSettings.PokemonsTransferFilter, pokemonGroupToTransfer.Key).KeepMinDuplicatePokemon, 0); + var inStorage = myPokemon.Count(data => data.PokemonId == pokemonGroupToTransfer.Key); + var needToRemove = inStorage - amountToKeepInStorage; + + if (needToRemove <= 0) + continue; + + var weakPokemonCount = pokemonGroupToTransfer.Count(); + var canBeRemoved = Math.Min(needToRemove, weakPokemonCount); + + // Adjust canBeRemoved by subtracting the number of evolve pokemon we are saving. + var pokemonToEvolveForThisGroup = pokemonToEvolve.Where(p => p.PokemonId == pokemonGroupToTransfer.Key); + var numToSaveForEvolve = pokemonGroupToTransfer.Count(p => pokemonToEvolveForThisGroup.Any(p2 => p2.Id == p.Id)); + canBeRemoved -= numToSaveForEvolve; + var PokemonId = session.Translation.GetPokemonTranslation(pokemonGroupToTransfer.Key); + int candyCount = await GetCandyCount(pokemonGroupToTransfer.Key).ConfigureAwait(false); + + Logger.Write($"Saving {numToSaveForEvolve,2:#0} {PokemonId,-12} for evolve | Candies: {candyCount,4:#0} | Number to be transferred: {canBeRemoved,2:#0}", Logic.Logging.LogLevel.Info); + + if (prioritizeIVoverCp) + { + results.AddRange(pokemonGroupToTransfer + .Where(p => !pokemonToEvolveForThisGroup.Any(p2 => p2.Id == p.Id)) // Remove pokemon to evolve from the transfer list. + .OrderBy(PokemonInfo.CalculatePokemonPerfection) + .ThenBy(n => n.Cp) + .Take(canBeRemoved)); + } + else + { + results.AddRange(pokemonGroupToTransfer + .Where(p => !pokemonToEvolveForThisGroup.Any(p2 => p2.Id == p.Id)) // Remove pokemon to evolve from the transfer list. + .OrderBy(x => x.Cp) + .ThenBy(PokemonInfo.CalculatePokemonPerfection) + .Take(canBeRemoved)); + } + } + return results; + } + + public async Task> GetMaxPokemonToTransfer( + IEnumerable pokemonsNotToTransfer, bool prioritizeIVoverCp = false) + { + var session = TinyIoCContainer.Current.Resolve(); + var myPokemon = await GetPokemons().ConfigureAwait(false); + var transferrablePokemon = myPokemon.Where(p => !pokemonsNotToTransfer.Contains(p.PokemonId) && CanTransferPokemon(p)); + var results = new List(); + + foreach (var pokemonGroupToTransfer in transferrablePokemon.GroupBy(p => p.PokemonId).ToList()) + { + var amountToKeepInStorage = GetApplyFilter(session.LogicSettings.PokemonsTransferFilter, pokemonGroupToTransfer.Key).KeepMaxDuplicatePokemon; + + if (amountToKeepInStorage < 0) + continue; + + var inStorage = pokemonGroupToTransfer.Count(); + if (amountToKeepInStorage >= inStorage) + continue; + + var needToRemove = inStorage - amountToKeepInStorage; + + Logger.Write($"Max Duplicate Pokemon Allowed: {amountToKeepInStorage,2:0}. Transferring {needToRemove,2:0} {pokemonGroupToTransfer.Key} now.", Logic.Logging.LogLevel.Info); + + if (prioritizeIVoverCp) + { + results.AddRange(pokemonGroupToTransfer + .OrderBy(PokemonInfo.CalculatePokemonPerfection) + .ThenBy(n => n.Cp) + .Take(needToRemove)); + } + else + { + results.AddRange(pokemonGroupToTransfer + .OrderBy(x => x.Cp) + .ThenBy(PokemonInfo.CalculatePokemonPerfection) + .Take(needToRemove)); + } + } + return results; + } + + public class EvolutionCalculations + { + public int Transfers { get; set; } + public int Evolves { get; set; } + public int CandiesLeft { get; set; } + public int PokemonLeft { get; set; } + } + + // Calculates the number of pokemon evolutions possible given number of pokemon, candies, and candies to evolve. + // Implementation is taken from https://www.pidgeycalc.com and double-checked with calculator at https://pokeassistant.com/main/pidgeyspam + public EvolutionCalculations CalculatePokemonEvolution(int pokemonLeft, int candiesLeft, int candiesToEvolve, int candiesGainedOnEvolve) + { + int transferCandiesGained = 1; + int evolveCount = 0; + int transferCount = 0; + + // Evolutions without transferring + while (true) + { + // Not enough Pokemon or candies + if (candiesLeft / candiesToEvolve == 0 || pokemonLeft == 0) + { + break; + } + else + { + // Evolve a Pokemon + pokemonLeft--; + candiesLeft -= candiesToEvolve; + candiesLeft += candiesGainedOnEvolve; + evolveCount++; + // Break if out of Pokemon + if (pokemonLeft == 0) + { + break; + } + } + } + + // Evolutions after transferring + while (true) + { + // Not enough Pokemon or candies + if ((candiesLeft + (pokemonLeft * transferCandiesGained)) < (candiesToEvolve + transferCandiesGained) || pokemonLeft == 0) + { + break; + } + + // Keep transferring until enough candies for an evolve + while (candiesLeft < candiesToEvolve) + { + transferCount++; + pokemonLeft--; + candiesLeft += transferCandiesGained; + } + + // Evolve a Pokemon + pokemonLeft--; + candiesLeft -= candiesToEvolve; + candiesLeft += candiesGainedOnEvolve; + evolveCount++; + } + + return new EvolutionCalculations + { + Transfers = transferCount, + Evolves = evolveCount, + CandiesLeft = candiesLeft, + PokemonLeft = pokemonLeft + }; + } + + public async Task> GetEggIncubators() + { + var inventory = await GetCachedInventory().ConfigureAwait(false); + return + inventory + .Where(x => x.InventoryItemData.EggIncubators != null) + .SelectMany(i => i.InventoryItemData.EggIncubators.EggIncubator) + .Where(i => i != null); + } + + public async Task> GetEggs() + { + var inventory = await GetCachedInventory().ConfigureAwait(false); + return + inventory.Select(i => i.InventoryItemData?.PokemonData) + .Where(p => p != null && p.IsEgg); + } + + public async Task GetHighestPokemonOfTypeByCp(PokemonData pokemon) + { + var myPokemon = await GetPokemons().ConfigureAwait(false); + if (myPokemon != null) + { + var pokemons = myPokemon.ToList(); + if (pokemons != null && 0 < pokemons.Count) + { + return pokemons.Where(x => x.PokemonId == pokemon.PokemonId) + .OrderByDescending(x => x.Cp) + .FirstOrDefault(); + } + } + return null; + } + + public int UpdateStarDust(int startdust) + { + GetPlayerData().Wait(); + _player.PlayerData.Currencies[1].Amount += startdust; + + return _player.PlayerData.Currencies[1].Amount; + } + + public int GetStarDust() + { + GetPlayerData().Wait(); + return _player.PlayerData.Currencies[1].Amount; + } + + public async Task GetPlayerData() + { + if (_player == null) + { + _player = await _client.Player.GetPlayer().ConfigureAwait(false); + } + return _player; + } + + public async Task GetHighestPokemonOfTypeByIv(PokemonData pokemon) + { + var myPokemon = await GetPokemons().ConfigureAwait(false); + if (myPokemon != null) + { + var pokemons = myPokemon.ToList(); + if (pokemons != null && 0 < pokemons.Count) + { + return pokemons.Where(x => x.PokemonId == pokemon.PokemonId) + .OrderByDescending(PokemonInfo.CalculatePokemonPerfection) + .FirstOrDefault(); + } + } + return null; + } + + public async Task> GetHighestsCp(int limit) + { + var myPokemon = await GetPokemons().ConfigureAwait(false); + var pokemons = myPokemon.ToList(); + return pokemons.OrderByDescending(x => x.Cp).ThenBy(n => n.StaminaMax).Take(limit); + } + + public async Task> GetHighestCpForGym(int limit) + { + var myPokemon = await GetPokemons().ConfigureAwait(false); + // var pokemons = myPokemon.Where(i => !i.DeployedFortId.Any() && i.Stamina == i.StaminaMax); + var pokemons = myPokemon.Where(i => !i.DeployedFortId.Any()); + return pokemons.OrderByDescending(x => x.Cp).ThenBy(n => n.StaminaMax).Take(limit); + } + + public async Task> GetHighestsPerfect(int limit) + { + var myPokemon = await GetPokemons().ConfigureAwait(false); + var pokemons = myPokemon.ToList(); + return pokemons.OrderByDescending(PokemonInfo.CalculatePokemonPerfection).Take(limit); + } + + public async Task GetItemAmountByType(ItemId type) + { + var pokeballs = await GetItems().ConfigureAwait(false); + return pokeballs.FirstOrDefault(i => i.ItemId == type)?.Count ?? 0; + } + + public async Task> GetItems() + { + var inventory = await GetCachedInventory().ConfigureAwait(false); + return inventory + .Select(i => i.InventoryItemData?.Item) + .Where(p => p != null); + } + + public async Task GetTotalItemCount() + { + var myItems = (await GetItems().ConfigureAwait(false)).ToList(); + int myItemCount = 0; + foreach (var myItem in myItems) myItemCount += myItem.Count; + return myItemCount; + } + + public async Task> GetItemsToRecycle(ISession session) + { + var itemsToRecycle = new List(); + var myItems = (await GetItems().ConfigureAwait(false)).ToList(); + if (myItems == null) + return itemsToRecycle; + + var otherItemsToRecycle = myItems + .Where(x => _logicSettings.ItemRecycleFilter.Any(f => f.Key == x.ItemId && x.Count > f.Value)) + .Select( + x => + new ItemData + { + ItemId = x.ItemId, + Count = x.Count - _logicSettings.ItemRecycleFilter.FirstOrDefault(f => f.Key == x.ItemId).Value, + Unseen = x.Unseen + }); + + itemsToRecycle.AddRange(otherItemsToRecycle); + + return itemsToRecycle; + } + + public double GetPerfect(PokemonData poke) + { + var result = PokemonInfo.CalculatePokemonPerfection(poke); + return result; + } + + public async Task> GetPlayerStats() + { + var inventory = await GetCachedInventory().ConfigureAwait(false); + return inventory + .Select(i => i.InventoryItemData?.PlayerStats) + .Where(p => p != null); + } + + public async Task GetLuckyEggRemainingTime() + { + var appliedItems = await ownerSession.Inventory.GetAppliedItems().ConfigureAwait(false); + var luckyEgg = appliedItems.FirstOrDefault(i => i.ItemId == ItemId.ItemLuckyEgg); + if (luckyEgg != null) + { + // Could be old/cached + var expires = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(luckyEgg.ExpireMs); + TimeSpan duration = expires - DateTime.UtcNow; + if (duration.TotalSeconds > 0) + return duration; + } + + return new TimeSpan(0); + } + + public async Task UseLuckyEgg() + { + var inventoryContent = await ownerSession.Inventory.GetItems().ConfigureAwait(false); + var luckyEgg = inventoryContent.FirstOrDefault(p => p.ItemId == ItemId.ItemLuckyEgg); + + if (luckyEgg == null || luckyEgg.Count == 0) // We tried to use egg but we don't have any more. Just return. + { + Logger.Write(ownerSession.Translation.GetTranslation(TranslationString.NoEggsAvailable)); + return; + } + + if (_lastLuckyEggTime.AddMinutes(30).Ticks > DateTime.Now.Ticks) + { + TimeSpan duration = _lastLuckyEggTime.AddMinutes(30) - DateTime.Now; + Logger.Write(ownerSession.Translation.GetTranslation(TranslationString.UseLuckyEggActive, duration.Minutes, duration.Seconds)); + return; + } + + var responseLuckyEgg = await ownerSession.Client.Inventory.UseItemXpBoost().ConfigureAwait(false); + switch (responseLuckyEgg.Result) + { + case UseItemXpBoostResponse.Types.Result.Success: + { + _lastLuckyEggTime = DateTime.Now; + ownerSession.EventDispatcher.Send(new UseLuckyEggEvent { Count = luckyEgg.Count - 1 }); + TinyIoCContainer.Current.Resolve().DisableSwitchAccountUntil(DateTime.Now.AddMinutes(30)); + Logger.Write(ownerSession.Translation.GetTranslation(TranslationString.UsedLuckyEgg)); + } + break; + case UseItemXpBoostResponse.Types.Result.ErrorNoItemsRemaining: + { + Logger.Write(ownerSession.Translation.GetTranslation(TranslationString.NoEggsAvailable)); + } + break; + case UseItemXpBoostResponse.Types.Result.ErrorXpBoostAlreadyActive: + { + TimeSpan duration = _lastLuckyEggTime.AddMinutes(30) - DateTime.Now; + Logger.Write(ownerSession.Translation.GetTranslation(TranslationString.UseLuckyEggActive, duration.Minutes, duration.Seconds)); + } + break; + default: + Logger.Write($"Failed to use a Lucky Egg!", LogLevel.Error); + break; + } + await DelayingUtils.DelayAsync(ownerSession.LogicSettings.DelayBetweenPlayerActions, 0, ownerSession.CancellationTokenSource.Token).ConfigureAwait(false); + } + + public async Task UseIncenseConstantly() + { + var UseIncense = await _client.Inventory.UseIncense(ItemId.ItemIncenseOrdinary).ConfigureAwait(false); + return UseIncense; + } + + public async Task> GetPokeDexItems() + { + var inventory = await GetCachedInventory().ConfigureAwait(false); + + return (from items in inventory + where items.InventoryItemData?.PokedexEntry != null + select items).ToList(); + } + + public async Task> GetPokemonFamilies(int retries = 0) + { + if (retries > 3) return null; + + IEnumerable families = null; + var inventory = await GetCachedInventory().ConfigureAwait(false); + + try + { + families = from item in inventory + where item.InventoryItemData?.Candy != null + where item.InventoryItemData?.Candy.FamilyId != PokemonFamilyId.FamilyUnset + group item by item.InventoryItemData?.Candy.FamilyId + into family + select new Candy + { + FamilyId = family.First().InventoryItemData.Candy.FamilyId, + Candy_ = family.First().InventoryItemData.Candy.Candy_ + }; + } + catch (NullReferenceException) + { + await DelayingUtils.DelayAsync(3000, 3000, ownerSession.CancellationTokenSource.Token).ConfigureAwait(false); + return await GetPokemonFamilies(++retries).ConfigureAwait(false); + } + return families.ToList(); + } + + public async Task GetSinglePokemon(ulong id) + { + var inventory = await GetCachedInventory().ConfigureAwait(false); + return + inventory.Select(i => i.InventoryItemData?.PokemonData) + .FirstOrDefault(p => p != null && p.PokemonId > 0 && p.Id == id); + } + + public async Task> GetPokemons() + { + var inventory = await GetCachedInventory().ConfigureAwait(false); + return inventory + .Select(i => i.InventoryItemData?.PokemonData) + .Where(p => p != null && p.PokemonId > 0); + } + + public async Task> GetFavoritePokemons() + { + var inventory = await GetPokemons().ConfigureAwait(false); + return inventory.Where(i => i.Favorite == 1); + } + + public async Task> GetDeployedPokemons() + { + var inventory = await GetPokemons().ConfigureAwait(false); + return inventory.Where(i => !string.IsNullOrEmpty(i.DeployedFortId)); + } + + public async Task GetMoveSetting(PokemonMove move) + { + if (_client.Download.ItemTemplates == null) + await GetPokemonSettings().ConfigureAwait(false); + + var moveSettings = _client.Download.ItemTemplates.Where(x => x.MoveSettings != null) + .Select(x => x.MoveSettings) + .ToList(); + + return moveSettings.FirstOrDefault(x => x.MovementId == move); + } + + public async Task> GetPokemonSettings() + { + if (_client.Download.ItemTemplates == null) + await _client.Download.GetItemTemplates().ConfigureAwait(false); + + if (_pokemonSettings == null) + { + var moveSettings = _client.Download.ItemTemplates.Where(x => x.MoveSettings != null) + .Select(x => x.MoveSettings) + .ToList(); + + _pokemonSettings = _client.Download.ItemTemplates.Select(i => i.PokemonSettings) + .Where(p => p != null && p.FamilyId != PokemonFamilyId.FamilyUnset); + } + return _pokemonSettings; + } + + public async Task> GetMoveSettings() + { + if (_client.Download.ItemTemplates == null) + await _client.Download.GetItemTemplates().ConfigureAwait(false); + + var moveSettings = _client.Download.ItemTemplates.Where(x => x.MoveSettings != null) + .Select(x => x.MoveSettings); + + return moveSettings; + } + + public bool CanTransferPokemon(PokemonData pokemon) + { + // Can't transfer pokemon in gyms. + if (!string.IsNullOrEmpty(pokemon.DeployedFortId)) + return false; + + // Can't transfer buddy pokemon + var buddy = ownerSession.Profile.PlayerData.BuddyPokemon; + if (buddy != null && buddy.Id == pokemon.Id) + return false; + + // Can't transfer favorite + if (pokemon.Favorite == 1) + return false; + + return true; + } + + public int GetCandyToEvolve(PokemonSettings settings, EvolveFilter appliedFilter = null) + { + EvolutionBranch branch; + PokemonId evolveTo = PokemonId.Missingno; + if (appliedFilter != null && !string.IsNullOrEmpty(appliedFilter.EvolveTo) && Enum.TryParse(appliedFilter.EvolveTo, true, out evolveTo)) + { + branch = settings.EvolutionBranch.FirstOrDefault(x => x.Evolution == evolveTo); + } + else + { + branch = settings.EvolutionBranch.FirstOrDefault(x => x.EvolutionItemRequirement == ItemId.ItemUnknown); + } + + if (branch == null) + return -1; + + return branch.CandyCost; + } + + public async Task CanEvolvePokemon(PokemonData pokemon, EvolveFilter appliedFilter = null, bool checkEvolveFilterRequirements = false) + { + // Can't evolve pokemon in gyms. + if (!string.IsNullOrEmpty(pokemon.DeployedFortId)) + return false; + + IEnumerable pokemonSettings = await GetPokemonSettings().ConfigureAwait(false); + var settings = pokemonSettings.SingleOrDefault(x => x.PokemonId == pokemon.PokemonId); + + // Can't evolve pokemon that are not evolvable. + if (settings.EvolutionIds.Count == 0 && settings.EvolutionBranch.Count == 0) + return false; + + int familyCandy = await GetCandyCount(pokemon.PokemonId).ConfigureAwait(false); + + if (checkEvolveFilterRequirements + && !IsEvolveByGlobalIvFilter(pokemon) + && !IsEvolveBySpecificFilter(pokemon, settings, familyCandy, appliedFilter)) + { + return false; + } + + PokemonId evolveTo = PokemonId.Missingno; + if (appliedFilter != null && !string.IsNullOrEmpty(appliedFilter.EvolveTo) && Enum.TryParse(appliedFilter.EvolveTo, true, out evolveTo)) + { + var branch = settings.EvolutionBranch.FirstOrDefault(x => x.Evolution == evolveTo); + if (branch == null) + return false; //wrong setting, do not evolve this pokemon + + if (branch.EvolutionItemRequirement != ItemId.ItemUnknown) + { + var itemCount = (await GetItems().ConfigureAwait(false)).Count(x => x.ItemId == branch.EvolutionItemRequirement); + + if (itemCount == 0) + return false; + } + + if (familyCandy < branch.CandyCost) + return false; + } + else + { + bool canEvolve = false; + // Check requirements for all branches, if we meet the requirements for any of them then we return true. + foreach (var branch in settings.EvolutionBranch) + { + var itemCount = (await GetItems().ConfigureAwait(false)).Count(x => x.ItemId == branch.EvolutionItemRequirement); + var Candies2Evolve = branch.CandyCost; // GetCandyToEvolve(settings); + var Evolutions = familyCandy / Candies2Evolve; + + if (branch.EvolutionItemRequirement != ItemId.ItemUnknown) + { + if (itemCount == 0) + continue; // Cannot evolve so check next branch + } + + if (familyCandy < branch.CandyCost) + continue; // Cannot evolve so check next branch + + // If we got here, then we can evolve so break out of loop. + canEvolve = true; + } + return canEvolve; + } + return true; + } + + private bool IsEvolveBySpecificFilter(PokemonData pokemon, PokemonSettings settings, int familyCandy, EvolveFilter appliedFilter) + { + if (appliedFilter == null || !ownerSession.LogicSettings.EvolvePokemonsThatMatchFilter) + return false; + + if (!appliedFilter.Operator.BoolFunc( + appliedFilter.MinIV <= pokemon.Perfection(), + appliedFilter.MinLV <= pokemon.Level(), + appliedFilter.MinCP <= pokemon.CP(), + (appliedFilter.Moves == null || + appliedFilter.Moves.Count == 0 || + appliedFilter.Moves.Any(x => x[0] == pokemon.Move1 && x[1] == pokemon.Move2) + ) + )) + { + return false; + } + + int minCandiesBeforeEvolve = appliedFilter.MinCandiesBeforeEvolve; + if (minCandiesBeforeEvolve > 0) + { + if (ownerSession.LogicSettings.EvolvePreserveMinCandiesFromFilter) + { + minCandiesBeforeEvolve += GetCandyToEvolve(settings, appliedFilter); + } + if (familyCandy < minCandiesBeforeEvolve) + return false; + } + + return true; + } + + private bool IsEvolveByGlobalIvFilter(PokemonData pokemon) + { + if (ownerSession.LogicSettings.EvolveAnyPokemonAboveIv + && ownerSession.LogicSettings.EvolveAnyPokemonAboveIvValue <= pokemon.Perfection()) + return true; + return false; + } + + public async Task> GetPokemonToEvolve(Dictionary filters = null) + { + var buddy = (await GetPlayerData().ConfigureAwait(false)).PlayerData.BuddyPokemon?.Id; + + IEnumerable myPokemons = (await GetPokemons().ConfigureAwait(false)).OrderByDescending(p => p.Cp); + + List possibleEvolvePokemons = new List(); + + foreach (var pokemon in myPokemons) + { + filters.TryGetValue(pokemon.PokemonId, out var filter); + if (await CanEvolvePokemon(pokemon, filter, true).ConfigureAwait(false)) + { + possibleEvolvePokemons.Add(pokemon); + } + } + + var pokemonToEvolve = new List(); + + // Group pokemon by their PokemonId + var groupedPokemons = possibleEvolvePokemons.GroupBy(p => p.PokemonId); + + foreach (var g in groupedPokemons) + { + PokemonId pokemonId = g.Key; + filters.TryGetValue(pokemonId, out var filter); + + int candiesAvailable = await GetCandyCount(pokemonId).ConfigureAwait(false); + if (filter != null && ownerSession.LogicSettings.EvolvePreserveMinCandiesFromFilter) + { + // Do not use candies below filter defined MinCandiesBeforeEvolve + candiesAvailable -= filter.MinCandiesBeforeEvolve; + } + + PokemonSettings settings = (await GetPokemonSettings().ConfigureAwait(false)).FirstOrDefault(x => x.PokemonId == pokemonId); + int candyNeed = GetCandyToEvolve(settings, filter); + if (candyNeed == -1) + continue; // If we were unable to determine which branch to use, then skip this pokemon. + + var orderedGroup = g.OrderByDescending(p => p.Cp); + int pokemonLeft = orderedGroup.Count(); + // Calculate the number of evolutions possible (taking into account +1 candy for evolve and +1 candy for transfer) + EvolutionCalculations evolutionInfo = CalculatePokemonEvolution(pokemonLeft, candiesAvailable, candyNeed, 1); + + if (evolutionInfo.Evolves > 0) + { + // Add only the number of pokemon we can evolve. + pokemonToEvolve.AddRange(orderedGroup.Take(evolutionInfo.Evolves).ToList()); + } + } + return pokemonToEvolve; + } + + public async Task GetLevelUpRewards(int level) + { + var rewards = new LevelUpRewardsResponse(); + if (_level == 0 || level > _level) + { + _level = level; + + rewards = await _client.Player.GetLevelUpRewards(level).ConfigureAwait(false); + foreach (var item in rewards.ItemsAwarded) + { + await UpdateInventoryItem(item.ItemId).ConfigureAwait(false); + } + } + return rewards; + } + + public async Task CanUpgradePokemon(PokemonData pokemon) + { + // Can't upgrade pokemon in gyms. + if (!string.IsNullOrEmpty(pokemon.DeployedFortId)) + return false; + + var playerLevel = (await GetPlayerStats().ConfigureAwait(false)).FirstOrDefault().Level; + var pokemonLevel = PokemonInfo.GetLevel(pokemon); + + // Can't evolve unless pokemon level is lower than trainer. + if (pokemonLevel >= playerLevel + 2) + return false; + + int familyCandy = await GetCandyCount(pokemon.PokemonId).ConfigureAwait(false); + + // Can't evolve if not enough candy. + int pokemonCandyNeededAlready = PokemonCpUtils.GetCandyCostsForPowerup(pokemon.CpMultiplier + pokemon.AdditionalCpMultiplier); + if (familyCandy < pokemonCandyNeededAlready) + return false; + + // Can't evolve if not enough stardust. + var stardustToUpgrade = PokemonCpUtils.GetStardustCostsForPowerup(pokemon.CpMultiplier + pokemon.AdditionalCpMultiplier); + if (GetStarDust() < stardustToUpgrade) + return false; + + return true; + } + + public async Task> GetPokemonToUpgrade() + { + var upgradePokemon = new List(); + + if (!_logicSettings.AutomaticallyLevelUpPokemon) + return upgradePokemon; + + List upgradeablePokemons = new List(); + var pokemons = await GetPokemons().ConfigureAwait(false); + foreach (var pokemon in pokemons) + { + if (await CanUpgradePokemon(pokemon).ConfigureAwait(false)) + upgradeablePokemons.Add(pokemon); + } + + foreach (var pokemon in upgradeablePokemons) + { + var appliedFilter = _logicSettings.PokemonUpgradeFilters.GetFilter(pokemon.PokemonId); + + if ((appliedFilter.OnlyUpgradeFavorites && pokemon.Favorite == 1) || + (!appliedFilter.OnlyUpgradeFavorites && + appliedFilter.UpgradePokemonMinimumStatsOperator.BoolFunc( + pokemon.CP() >= appliedFilter.UpgradePokemonCpMinimum, + pokemon.Level() >= appliedFilter.UpgradePokemonLvlMinimum, + pokemon.Perfection() >= appliedFilter.UpgradePokemonIvMinimum, + ((appliedFilter.UpgradePokemonMinimumStatsOperator == "and" && (appliedFilter.Moves == null || appliedFilter.Moves.Count == 0)) || + (appliedFilter.Moves != null && appliedFilter.Moves.Count > 0 && appliedFilter.Moves.Any(x => x[0] == pokemon.Move1 && x[1] == pokemon.Move2)) + )))) + { + upgradePokemon.Add(pokemon); + } + } + return upgradePokemon; + } + + public async Task UpgradePokemon(ulong pokemonid) + { + return await _client.Inventory.UpgradePokemon(pokemonid).ConfigureAwait(false); + } + } +} diff --git a/PoGo.NecroBot.Logic/Localization/Localizer.cs b/PoGo.NecroBot.Logic/Localization/Localizer.cs new file mode 100644 index 000000000..04c2382f8 --- /dev/null +++ b/PoGo.NecroBot.Logic/Localization/Localizer.cs @@ -0,0 +1,27 @@ +#region using directives + +using PoGo.NecroBot.Logic.Common; + +#endregion + +namespace PoGo.NecroBot.Logic.Localization +{ + public interface ILocalizer + { + string GetFormat(TranslationString key); + string GetFormat(TranslationString key, params object[] data); + } + + public class Localizer : ILocalizer + { + public string GetFormat(TranslationString key) + { + return ""; + } + + public string GetFormat(TranslationString key, params object[] data) + { + return ""; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Logging/APILogListener.cs b/PoGo.NecroBot.Logic/Logging/APILogListener.cs new file mode 100644 index 000000000..7a581866a --- /dev/null +++ b/PoGo.NecroBot.Logic/Logging/APILogListener.cs @@ -0,0 +1,62 @@ +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.Event.UI; +using PoGo.NecroBot.Logic.State; +using PokemonGo.RocketAPI.Logging; +using System; +using TinyIoC; + +namespace PoGo.NecroBot.Logic.Logging +{ + public class APILogListener : PokemonGo.RocketAPI.ILogger + { + DateTime lastVerboseLog = DateTime.Now; + public void InboxStatusUpdate(string message, ConsoleColor color = ConsoleColor.White) + { + Logger.Write(message, LogLevel.Service, color); + } + + public void HashStatusUpdate(HashInfo info) + { + DateTime expired = Convert.ToDateTime(info.Expired).ToLocalTime(); + TimeSpan expiredTime = expired - DateTime.Now; + ISession session = TinyIoCContainer.Current.Resolve(); + if (session.Settings.DisplayVerboseLog && lastVerboseLog < DateTime.Now.AddSeconds(-60)) + { + lastVerboseLog = DateTime.Now; + Logger.Write($"(HASH SERVER) Key[{info.MaskedAPIKey}] - Last Minute: {info.Last60MinAPICalles} RPM, AVG: {info.Last60MinAPIAvgTime:0.00} MS, Fastest: {info.Fastest}, Slowest: {info.Slowest}, Available: {info.HealthyRate:0.00%}, Expires: {expired.ToString("MM/dd/yyyy")} @ {expired.ToString("HH:mm:ss tt")} ({expiredTime.Days} Days {expiredTime.Hours} Hours {expiredTime.Minutes} Minutes)", LogLevel.Info, ConsoleColor.White); + } + session.EventDispatcher.Send(new StatusBarEvent($"[{info.MaskedAPIKey}] - {info.Last60MinAPICalles} RPM, AVG: {info.Last60MinAPIAvgTime:0.00} MS, Fastest: {info.Fastest}, Slowest: {info.Slowest}, Available {info.HealthyRate:0.00%}, Expires: {expired.ToString("MM/dd/yyyy")} @ {expired.ToString("HH:mm:ss tt")} ({expiredTime.Days} Days {expiredTime.Hours} Hours {expiredTime.Minutes} Minutes)")); + } + + public void LogCritical(string message, dynamic data) + { + } + + public void LogDebug(string message) + { + Logger.Debug(message); + } + + public void LogError(string message) + { + var session = TinyIoCContainer.Current.Resolve(); + session.EventDispatcher.Send(new ErrorEvent() { Message = message }); + } + + public void LogInfo(string message) + { + var session = TinyIoCContainer.Current.Resolve(); + session.EventDispatcher.Send(new InfoEvent() { Message = message }); + } + + public void LogFlaggedInit(string message) + { + Logger.Write(message, LogLevel.Warning); + } + + public void LogErrorInit(string message) + { + Logger.Write(message, LogLevel.Error); + } + } +} diff --git a/PoGo.NecroBot.Logic/Logging/FileLogger.cs b/PoGo.NecroBot.Logic/Logging/FileLogger.cs new file mode 100644 index 000000000..335e5d1d0 --- /dev/null +++ b/PoGo.NecroBot.Logic/Logging/FileLogger.cs @@ -0,0 +1,106 @@ +#region using directives + +using System; +using System.Collections.Concurrent; +using System.IO; +using System.Text; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.State; + +#endregion + +namespace PoGo.NecroBot.Logic.Logging +{ + /// + /// The FileLogger is a simple logger which writes all logs to the Console. + /// + public class FileLogger : ILogger + { + private readonly LogLevel _maxLogLevel; + private string logPath; + private ConcurrentQueue _messageQueue = new ConcurrentQueue(); + + public void TurnOffLogBuffering() + { + // No buffering for file logger. + } + + public void SetSession(ISession session) + { + // No need for session + } + + /// + /// To create a FileLogger, we must define a maximum log level. + /// All levels above won't be logged. + /// + /// + public FileLogger(LogLevel maxLogLevel, string fileName = "", string subPath = "") + { + _maxLogLevel = maxLogLevel; + + string path = Path.Combine(Directory.GetCurrentDirectory(), subPath, "Logs"); + if (Directory.Exists(path)) + { + string[] txtList = Directory.GetFiles(path, "*.txt"); + foreach (string f in txtList) + { + File.Delete(f); + } + } + else + { + Directory.CreateDirectory(path); + } + + if (string.IsNullOrEmpty(fileName)) + fileName = $"NecroBot2-{DateTime.Today.ToString("yyyy-MM-dd")}-{DateTime.Now.ToString("HH-mm-ss")}.txt"; + + logPath = Path.Combine(path, fileName); + } + + private object ioLocker = new object(); + + /// + /// Log a specific message by LogLevel. Won't log if the LogLevel is greater than the maxLogLevel set. + /// + /// The message to log. The current time will be prepended. + /// Optional. Default . + /// Optional. Default is auotmatic + public void Write(string message, LogLevel level = LogLevel.Info, ConsoleColor color = ConsoleColor.Black) + { +#pragma warning disable IDE0018 // Inline variable declaration - Build.Bat Error Happens if We Do + // Remember to change to a font that supports your language, otherwise it'll still show as ???. + Console.OutputEncoding = Encoding.UTF8; + if (level > _maxLogLevel) + return; + + var finalMessage = Logger.GetFinalMessage(message, level, color); + LogEvent logEventToSend; + + lock (ioLocker) + { + // Add message to the queue + _messageQueue.Enqueue(new LogEvent + { + Message = finalMessage, + Color = Logger.GetHexColor(Console.ForegroundColor) + }); + + using (StreamWriter sw = File.AppendText(logPath)) + { + while (_messageQueue.TryDequeue(out logEventToSend)) + { + sw.WriteLine(finalMessage); + } + } + } +#pragma warning restore IDE0018 // Inline variable declaration - Build.Bat Error Happens if We Do + } + + public void LineSelect(int lineChar = 0, int linesUp = 1) + { + // No line select for file logger. + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Logging/ILogger.cs b/PoGo.NecroBot.Logic/Logging/ILogger.cs new file mode 100644 index 000000000..6abd85199 --- /dev/null +++ b/PoGo.NecroBot.Logic/Logging/ILogger.cs @@ -0,0 +1,29 @@ +#region using directives + +using System; +using PoGo.NecroBot.Logic.State; + +#endregion + +namespace PoGo.NecroBot.Logic.Logging +{ + public interface ILogger + { + /// + /// Set Context for a logger to be able to use translations and settings + /// + /// Context + void SetSession(ISession session); + + /// + /// Log a specific message by LogLevel. + /// + /// The message to log. + /// Optional. Default . + /// Optional. Default automatic color. + void Write(string message, LogLevel level = LogLevel.Info, ConsoleColor color = ConsoleColor.Black); + + void LineSelect(int lineChar = 0, int linesUp = 1); + void TurnOffLogBuffering(); + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Logging/Logger.cs b/PoGo.NecroBot.Logic/Logging/Logger.cs new file mode 100644 index 000000000..239bd5905 --- /dev/null +++ b/PoGo.NecroBot.Logic/Logging/Logger.cs @@ -0,0 +1,253 @@ +#region using directives + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using PoGo.NecroBot.Logic.State; + +#endregion + +namespace PoGo.NecroBot.Logic.Logging +{ + public static class Logger + { + private static List _loggers = new List(); + + private static ConcurrentQueue LogbufferList = new ConcurrentQueue(); + private static string _lastLogMessage; + + public static void TurnOffLogBuffering() + { + foreach (var logger in _loggers) + { + logger?.TurnOffLogBuffering(); + } + } + + /// + /// Add a logger. + /// + /// + public static void AddLogger(ILogger logger, string subPath = "", bool isGui = false) + { + if (!_loggers.Contains(logger)) + _loggers.Add(logger); + } + public static void Debug(string message, Exception ex = null) + { +#if DEBUG + Write(message, color: ConsoleColor.DarkRed); + if (ex != null) + { + Write(ex.Message, color: ConsoleColor.DarkRed); + } +#endif + } + /// + /// Sets Context for the loggers + /// + /// Context + public static void SetLoggerContext(ISession session) + { + LoggingStrings.SetStrings(session); + foreach (var logger in _loggers) + { + logger?.SetSession(session); + } + } + + /// + /// Log a specific message to the logger setup by . + /// + /// The message to log. + /// Optional level to log. Default . + /// Optional. Default is automatic color. + public static void Write(string message, LogLevel level = LogLevel.Info, + ConsoleColor color = ConsoleColor.Black, bool force = false) + { + if (_loggers.Count == 0 || _lastLogMessage == message) + return; + + _lastLogMessage = message; + foreach (var logger in _loggers) + logger?.Write(message, level, color); + } + + public static void LineSelect(int lineChar = 0, int linesUp = 1) + { + foreach (var logger in _loggers) + logger?.LineSelect(lineChar, linesUp); + } + + public static string GetFinalMessage(string message, LogLevel level, ConsoleColor color) + { + string finalMessage; + + switch (level) + { + case LogLevel.Error: + Console.ForegroundColor = color == ConsoleColor.Black ? ConsoleColor.Red : color; + finalMessage = $"[{DateTime.Now.ToString("HH:mm:ss")}] ({LoggingStrings.Error}) {message}"; + break; + case LogLevel.Warning: + Console.ForegroundColor = color == ConsoleColor.Black ? ConsoleColor.DarkYellow : color; + finalMessage = $"[{DateTime.Now.ToString("HH:mm:ss")}] ({LoggingStrings.Attention}) {message}"; + break; + case LogLevel.Info: + Console.ForegroundColor = color == ConsoleColor.Black ? ConsoleColor.DarkCyan : color; + finalMessage = $"[{DateTime.Now.ToString("HH:mm:ss")}] ({LoggingStrings.Info}) {message}"; + break; + case LogLevel.Pokestop: + Console.ForegroundColor = color == ConsoleColor.Black ? ConsoleColor.Cyan : color; + finalMessage = $"[{DateTime.Now.ToString("HH:mm:ss")}] ({LoggingStrings.Pokestop}) {message}"; + break; + case LogLevel.Farming: + Console.ForegroundColor = color == ConsoleColor.Black ? ConsoleColor.Magenta : color; + finalMessage = $"[{DateTime.Now.ToString("HH:mm:ss")}] ({LoggingStrings.Farming}) {message}"; + break; + case LogLevel.Sniper: + Console.ForegroundColor = color == ConsoleColor.Black ? ConsoleColor.White : color; + finalMessage = $"[{DateTime.Now.ToString("HH:mm:ss")}] ({LoggingStrings.Sniper}) {message}"; + break; + case LogLevel.Recycling: + Console.ForegroundColor = color == ConsoleColor.Black ? ConsoleColor.DarkMagenta : color; + finalMessage = $"[{DateTime.Now.ToString("HH:mm:ss")}] ({LoggingStrings.Recycling}) {message}"; + break; + case LogLevel.Caught: + Console.ForegroundColor = color == ConsoleColor.Black ? ConsoleColor.Green : color; + finalMessage = $"[{DateTime.Now.ToString("HH:mm:ss")}] ({LoggingStrings.Pkmn}) {message}"; + break; + case LogLevel.Flee: + Console.ForegroundColor = color == ConsoleColor.Black ? ConsoleColor.DarkYellow : color; + finalMessage = $"[{DateTime.Now.ToString("HH:mm:ss")}] ({LoggingStrings.Pkmn}) {message}"; + break; + case LogLevel.Transfer: + Console.ForegroundColor = color == ConsoleColor.Black ? ConsoleColor.DarkGreen : color; + finalMessage = $"[{DateTime.Now.ToString("HH:mm:ss")}] ({LoggingStrings.Transferred}) {message}"; + break; + case LogLevel.Evolve: + Console.ForegroundColor = color == ConsoleColor.Black ? ConsoleColor.DarkGreen : color; + finalMessage = $"[{DateTime.Now.ToString("HH:mm:ss")}] ({LoggingStrings.Evolved}) {message}"; + break; + case LogLevel.Berry: + Console.ForegroundColor = color == ConsoleColor.Black ? ConsoleColor.DarkYellow : color; + finalMessage = $"[{DateTime.Now.ToString("HH:mm:ss")}] ({LoggingStrings.Berry}) {message}"; + break; + case LogLevel.Egg: + Console.ForegroundColor = color == ConsoleColor.Black ? ConsoleColor.DarkYellow : color; + finalMessage = $"[{DateTime.Now.ToString("HH:mm:ss")}] ({LoggingStrings.Egg}) {message}"; + break; + case LogLevel.Debug: + Console.ForegroundColor = color == ConsoleColor.Black ? ConsoleColor.Gray : color; + finalMessage = $"[{DateTime.Now.ToString("HH:mm:ss")}] ({LoggingStrings.Debug}) {message}"; + break; + case LogLevel.Update: + Console.ForegroundColor = color == ConsoleColor.Black ? ConsoleColor.White : color; + finalMessage = $"[{DateTime.Now.ToString("HH:mm:ss")}] ({LoggingStrings.Update}) {message}"; + break; + case LogLevel.New: + Console.ForegroundColor = color == ConsoleColor.Black ? ConsoleColor.Green : color; + finalMessage = $"[{DateTime.Now.ToString("HH:mm:ss")}] ({LoggingStrings.New}) {message}"; + break; + case LogLevel.SoftBan: + Console.ForegroundColor = color == ConsoleColor.Black ? ConsoleColor.Red : color; + finalMessage = $"[{DateTime.Now.ToString("HH:mm:ss")}] ({LoggingStrings.SoftBan}) {message}"; + break; + case LogLevel.LevelUp: + Console.ForegroundColor = color == ConsoleColor.Black ? ConsoleColor.Magenta : color; + finalMessage = $"[{DateTime.Now.ToString("HH:mm:ss")}] ({LoggingStrings.LevelUp}) {message}"; + break; + case LogLevel.BotStats: + Console.ForegroundColor = color == ConsoleColor.Black ? ConsoleColor.Magenta : color; + finalMessage = $"[{DateTime.Now.ToString("HH:mm:ss")}] ({LoggingStrings.BotStats}) {message}"; + break; + case LogLevel.Gym: + Console.ForegroundColor = color == ConsoleColor.Black ? ConsoleColor.Magenta : color; + finalMessage = $"[{DateTime.Now.ToString("HH:mm:ss")}] ({LoggingStrings.Gym}) {message}"; + break; + case LogLevel.GymDisk: + Console.ForegroundColor = color == ConsoleColor.Black ? ConsoleColor.Cyan : color; + finalMessage = $"[{DateTime.Now.ToString("HH:mm:ss")}] ({LoggingStrings.GymDisk}) {message}"; + break; + case LogLevel.Service: + Console.ForegroundColor = color == ConsoleColor.Black ? ConsoleColor.White : color; + finalMessage = $"[{DateTime.Now.ToString("HH:mm:ss")}] ({LoggingStrings.Service}) {message}"; + break; + default: + Console.ForegroundColor = color == ConsoleColor.Black ? ConsoleColor.White : color; + finalMessage = $"[{DateTime.Now.ToString("HH:mm:ss")}] ({LoggingStrings.Error}) {message}"; + break; + } + return finalMessage; + } + + public static string GetHexColor(ConsoleColor color) + { + switch (color) + { + case ConsoleColor.Black: + return "#002b36"; + case ConsoleColor.Blue: + return "#268bd2"; + case ConsoleColor.Cyan: + return "#2aa198"; + case ConsoleColor.DarkBlue: + return "#000080"; + case ConsoleColor.DarkCyan: + return "#008B8B"; + case ConsoleColor.DarkGray: + return "#586e75"; + case ConsoleColor.DarkGreen: + return "#008000"; + case ConsoleColor.DarkMagenta: + return "#800080"; + case ConsoleColor.DarkRed: + return "#800000"; + case ConsoleColor.DarkYellow: + return "#808000"; + case ConsoleColor.Gray: + return "#93a1a1"; + case ConsoleColor.Green: + return "#859900"; + case ConsoleColor.Magenta: + return "#d33682"; + case ConsoleColor.Red: + return "#dc322f"; + case ConsoleColor.White: + return "#fdf6e3"; + case ConsoleColor.Yellow: + return "#b58900"; + default: + // Grey + return "#93a1a1"; + } + } + } + + public enum LogLevel + { + None = 0, + Error = 1, + Warning = 2, + Pokestop = 3, + Farming = 4, + Sniper = 5, + Recycling = 6, + Berry = 7, + Caught = 8, + Flee = 9, + Transfer = 10, + Evolve = 11, + Egg = 12, + Update = 13, + Info = 14, + New = 15, + SoftBan = 16, + LevelUp = 17, + BotStats = 18, + Gym = 19, + GymDisk = 20, + Service = 21, + Debug = 22 + } +} diff --git a/PoGo.NecroBot.Logic/Logging/LoggingStrings.cs b/PoGo.NecroBot.Logic/Logging/LoggingStrings.cs new file mode 100644 index 000000000..e0ba7f831 --- /dev/null +++ b/PoGo.NecroBot.Logic/Logging/LoggingStrings.cs @@ -0,0 +1,121 @@ +#region using directives + +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.State; + +#endregion + +namespace PoGo.NecroBot.Logic.Logging +{ + public class LoggingStrings + { + public static string Attention; + public static string Berry; + public static string Debug; + public static string Egg; + public static string Error; + public static string Evolved; + public static string Farming; + public static string Info; + public static string Pkmn; + public static string Pokestop; + public static string Recycling; + public static string Sniper; + public static string Transferred; + public static string Update; + public static string New; + public static string SoftBan; + public static string Gym; + public static string GymDisk; + public static string Service; + public static string LevelUp; + public static string BotStats; + + public static void SetStrings(ISession session) + { + Attention = + session?.Translation.GetTranslation( + TranslationString.LogEntryAttention) ?? "ATTENTION"; + + Berry = + session?.Translation.GetTranslation( + TranslationString.LogEntryBerry) ?? "BERRY"; + + Debug = + session?.Translation.GetTranslation( + TranslationString.LogEntryDebug) ?? "DEBUG"; + + Egg = + session?.Translation.GetTranslation( + TranslationString.LogEntryEgg) ?? "EGG"; + + Error = + session?.Translation.GetTranslation( + TranslationString.LogEntryError) ?? "ERROR"; + + Evolved = + session?.Translation.GetTranslation( + TranslationString.LogEntryEvolved) ?? "EVOLVED"; + + Farming = + session?.Translation.GetTranslation( + TranslationString.LogEntryFarming) ?? "FARMING"; + + Info = + session?.Translation.GetTranslation( + TranslationString.LogEntryInfo) ?? "INFO"; + + Pkmn = + session?.Translation.GetTranslation( + TranslationString.LogEntryPkmn) ?? "PKMN"; + + Pokestop = + session?.Translation.GetTranslation( + TranslationString.LogEntryPokestop) ?? "POKESTOP"; + + Recycling = + session?.Translation.GetTranslation( + TranslationString.LogEntryRecycling) ?? "RECYCLING"; + + Sniper = + session?.Translation.GetTranslation( + TranslationString.LogEntrySniper) ?? "SNIPER"; + + Transferred = + session?.Translation.GetTranslation( + TranslationString.LogEntryTransfered) ?? "TRANSFERRED"; + + Update = + session?.Translation.GetTranslation( + TranslationString.LogEntryUpdate) ?? "UPDATE"; + + New = + session?.Translation.GetTranslation( + TranslationString.LogEntryNew) ?? "NEW"; + + SoftBan = + session?.Translation.GetTranslation( + TranslationString.LogEntrySoftBan) ?? "SOFTBAN"; + + Gym = + session?.Translation.GetTranslation( + TranslationString.LogEntryGym) ?? "GYM"; + + GymDisk = + session?.Translation.GetTranslation( + TranslationString.LogEntryGymDisk) ?? "GYM DISK"; + + Service = + session?.Translation.GetTranslation( + TranslationString.LogEntryService) ?? "SERVICE"; + + LevelUp = + session?.Translation.GetTranslation( + TranslationString.LogEntryLevelUp) ?? "LEVEL UP"; + + BotStats = + session?.Translation.GetTranslation( + TranslationString.LogEntryBotStats) ?? "BOT STATS"; + } + } +} diff --git a/PoGo.NecroBot.Logic/Logging/WebSocketLogger.cs b/PoGo.NecroBot.Logic/Logging/WebSocketLogger.cs new file mode 100644 index 000000000..a94294f88 --- /dev/null +++ b/PoGo.NecroBot.Logic/Logging/WebSocketLogger.cs @@ -0,0 +1,88 @@ +#region using directives + +using System; +using System.Collections.Concurrent; +using System.Text; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.State; + +#endregion + +namespace PoGo.NecroBot.Logic.Logging +{ + /// + /// The WebSocketLogger is a simple logger which writes all logs to the Console. + /// + public class WebSocketLogger : ILogger + { + private readonly LogLevel _maxLogLevel; + private ISession _session; + private ConcurrentQueue _messageQueue = new ConcurrentQueue(); + private bool isBuffering = true; + + public void TurnOffLogBuffering() + { + if (isBuffering) + isBuffering = false; + } + + /// + /// To create a WebSocketLogger, we must define a maximum log level. + /// All levels above won't be logged. + /// + /// + public WebSocketLogger(LogLevel maxLogLevel) + { + _maxLogLevel = maxLogLevel; + } + + public void SetSession(ISession session) + { + _session = session; + } + + /// + /// Log a specific message by LogLevel. Won't log if the LogLevel is greater than the maxLogLevel set. + /// + /// The message to log. The current time will be prepended. + /// Optional. Default . + /// Optional. Default is auotmatic + public void Write(string message, LogLevel level = LogLevel.Info, ConsoleColor color = ConsoleColor.Black) + { +#pragma warning disable IDE0018 // Inline variable declaration - Build.Bat Error Happens if We Do + LogEvent logEventToSend; + // Remember to change to a font that supports your language, otherwise it'll still show as ???. + Console.OutputEncoding = Encoding.UTF8; + if (level > _maxLogLevel) + return; + + var finalMessage = Logger.GetFinalMessage(message, level, color); + + // Add message to the queue + _messageQueue.Enqueue(new LogEvent + { + Message = finalMessage, + Color = Logger.GetHexColor(Console.ForegroundColor) + }); + + // We cannot send out log events to the GUI until it has connected via websocket. So buffer all + // messages until GUI has connected. + if (_session != null && _session.EventDispatcher != null) + { + if (!isBuffering) + { + while (_messageQueue.TryDequeue(out logEventToSend)) + { + _session?.EventDispatcher?.Send(logEventToSend); + } + } + } +#pragma warning restore IDE0018 // Inline variable declaration - Build.Bat Error Happens if We Do + } + + public void LineSelect(int lineChar = 0, int linesUp = 1) + { + // No line select for file logger. + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/MKS.txt b/PoGo.NecroBot.Logic/MKS.txt new file mode 100644 index 000000000..2d1ce4332 --- /dev/null +++ b/PoGo.NecroBot.Logic/MKS.txt @@ -0,0 +1,3 @@ +MasterStatus=ENABLED; +Status; +Exit diff --git a/PoGo.NecroBot.Logic/Model/Account.cs b/PoGo.NecroBot.Logic/Model/Account.cs new file mode 100644 index 000000000..12252be6d --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Account.cs @@ -0,0 +1,96 @@ +using PoGo.NecroBot.Logic.Model.Settings; +using PokemonGo.RocketAPI.Enums; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; + +namespace PoGo.NecroBot.Logic.Model +{ + public partial class Account + { + public long Id { get; set; } + public AuthType AuthType { get; set; } + [Required] + public string Username { get; set; } + public bool AutoExitBotIfAccountFlagged { get; set; } + public double AccountLatitude { get; set; } + public double AccountLongitude { get; set; } + public bool AccountActive { get; set; } + [Required] + public string Password { get; set; } + public double? RuntimeTotal { get; set; } + public long? LastRuntimeUpdatedAt { get; set; } + public long? ReleaseBlockTime { get; set; } + public string Nickname { get; set; } + public long? LoggedTime { get; set; } + public long? Level { get; set; } + public string LastLogin { get; set; } + public long? LastLoginTimestamp { get; set; } + public long? Stardust { get; set; } + public long? CurrentXp { get; set; } + public long? PrevLevelXp { get; set; } + public long? NextLevelXp { get; set; } + public long? IsRunning { get; set; } + public ICollection PokemonTimestamp { get; set; } + public ICollection PokestopTimestamp { get; set; } + private GlobalSettings _globalSettings { get; set; } + } + + public partial class Account : INotifyPropertyChanged + { + public Account() + { + PokemonTimestamp = new HashSet(); + PokestopTimestamp = new HashSet(); + } + + public Account(AuthConfig item) + { + AuthType = item.AuthType; + Password = item.Password; + Username = item.Username; + AutoExitBotIfAccountFlagged = item.AutoExitBotIfAccountFlagged; + AccountLatitude = item.AccountLatitude; + AccountLongitude = item.AccountLongitude; + AccountActive = item.AccountActive; + } + + public event PropertyChangedEventHandler PropertyChanged; + + public void RaisePropertyChanged(string propertyName) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + public string GetRuntime() + { + if (RuntimeTotal.HasValue) + { + var seconds = (int)(RuntimeTotal.Value * 60); + var duration = new TimeSpan(0, 0, seconds); + + return duration.ToString(@"dd\:hh\:mm\:ss"); + } + + return null; + } + + public string ExperienceInfo + { + get + { + if (!CurrentXp.HasValue || !PrevLevelXp.HasValue || !NextLevelXp.HasValue) + return null; + + int percentComplete = 0; + double xp = CurrentXp.Value - PrevLevelXp.Value; + double levelXp = NextLevelXp.Value - PrevLevelXp.Value; + + if (levelXp > 0) + percentComplete = (int)Math.Floor(xp / levelXp * 100); + return $"{xp}/{levelXp} ({percentComplete}%)"; + } + } + } +} diff --git a/PoGo.NecroBot.Logic/Model/AccountConfigContext.cs b/PoGo.NecroBot.Logic/Model/AccountConfigContext.cs new file mode 100644 index 000000000..1c2d6dce6 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/AccountConfigContext.cs @@ -0,0 +1,43 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; +using System.IO; + +namespace PoGo.NecroBot.Logic.Model +{ + public partial class AccountConfigContext : DbContext + { + public virtual DbSet Account { get; set; } + public virtual DbSet PokemonTimestamp { get; set; } + public virtual DbSet PokestopTimestamp { get; set; } + + public AccountConfigContext() + { + var profilePath = Path.Combine(Directory.GetCurrentDirectory()); + var profileConfigPath = Path.Combine(profilePath, "config"); + if (!Directory.Exists(profileConfigPath)) + Directory.CreateDirectory(profileConfigPath); + Database.EnsureCreated(); + } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + var profilePath = Path.Combine(Directory.GetCurrentDirectory()); + var profileConfigPath = Path.Combine(profilePath, "config"); + var dbFile = Path.Combine(profileConfigPath, "accounts.db"); + + optionsBuilder.UseSqlite($"data source={dbFile}"); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .HasOne(p => p.Account) + .WithMany(b => b.PokemonTimestamp) + .OnDelete(DeleteBehavior.Cascade); + modelBuilder.Entity() + .HasOne(p => p.Account) + .WithMany(b => b.PokestopTimestamp) + .OnDelete(DeleteBehavior.Cascade); + } + } +} diff --git a/PoGo.NecroBot.Logic/Model/BotAccount.cs b/PoGo.NecroBot.Logic/Model/BotAccount.cs new file mode 100644 index 000000000..2dbaf4e13 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/BotAccount.cs @@ -0,0 +1,59 @@ +using PoGo.NecroBot.Logic.Model.Settings; +using System; +using System.ComponentModel; + +namespace PoGo.NecroBot.Logic.Model +{ + public class BotAccount : AuthConfig, INotifyPropertyChanged + { + public bool IsRunning { get; set; } + public BotAccount() { } + public BotAccount(AuthConfig item) + { + AuthType = item.AuthType; + Password = item.Password; + Username = item.Username; + } + + // AutoId will be automatically incremented. + public int Id { get; set; } + public string Nickname { get; set; } + public DateTime LoggedTime { get; set; } + public int Level { get; set; } + public string LastLogin { get; set; } + public long LastLoginTimestamp { get; set; } + public int Stardust { get; set; } + public long CurrentXp { get; set; } + public long PrevLevelXp { get; set; } + public long NextLevelXp { get; set; } + + public string ExperienceInfo + { + get + { + int percentComplete = 0; + double xp = CurrentXp - PrevLevelXp; + double levelXp = NextLevelXp - PrevLevelXp; + + if (levelXp > 0) + percentComplete = (int)Math.Floor(xp / levelXp * 100); + return $"{xp}/{levelXp} ({percentComplete}%)"; + } + } + + public event PropertyChangedEventHandler PropertyChanged; + + public void RaisePropertyChanged(string propertyName) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + public string GetRuntime() + { + var seconds = (int)((double)RuntimeTotal * 60); + var duration = new TimeSpan(0, 0, seconds); + + return duration.ToString(@"dd\:hh\:mm\:ss"); + } + } +} diff --git a/PoGo.NecroBot.Logic/Model/BotActions.cs b/PoGo.NecroBot.Logic/Model/BotActions.cs new file mode 100644 index 000000000..0b23de315 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/BotActions.cs @@ -0,0 +1,19 @@ +namespace PoGo.NecroBot.Logic.Model +{ + public enum BotActions + { + Idle, + Walking, + Catch, + Envolve, + Transfer, + Upgrade, + RecycleItem, + Favorite, + Eggs, + ListItems, + PokemonSettings, + GetProfile, + UpdateProfile, + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/ElevationConfigContext.cs b/PoGo.NecroBot.Logic/Model/ElevationConfigContext.cs new file mode 100644 index 000000000..8495c088d --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/ElevationConfigContext.cs @@ -0,0 +1,32 @@ +using Microsoft.EntityFrameworkCore; +using System.IO; + +namespace PoGo.NecroBot.Logic.Model +{ + public partial class ElevationConfigContext : DbContext + { + public virtual DbSet ElevationLocation { get; set; } + + public ElevationConfigContext() + { + var profilePath = Path.Combine(Directory.GetCurrentDirectory()); + var profileConfigPath = Path.Combine(profilePath, "Cache"); + if (!Directory.Exists(profileConfigPath)) + Directory.CreateDirectory(profileConfigPath); + Database.EnsureCreated(); + } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + var profilePath = Path.Combine(Directory.GetCurrentDirectory()); + var profileConfigPath = Path.Combine(profilePath, "Cache"); + var dbFile = Path.Combine(profileConfigPath, "elevations.db"); + + optionsBuilder.UseSqlite($"data source={dbFile}"); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + } + } +} diff --git a/PoGo.NecroBot.Logic/Model/ElevationLocation.cs b/PoGo.NecroBot.Logic/Model/ElevationLocation.cs new file mode 100644 index 000000000..f20040cd1 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/ElevationLocation.cs @@ -0,0 +1,88 @@ +using Google.Common.Geometry; +using PoGo.NecroBot.Logic.Service.Elevation; +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace PoGo.NecroBot.Logic.Model +{ + public class ElevationLocation + { + private static int MAX_RETRIES = 5; + private static int GEOLOCATION_PRECISION = 3; + + public long Id { get; set; } + public double Latitude { get; set; } + public double Longitude { get; set; } + public double Altitude { get; set; } + + public ElevationLocation() + { + } + + public ElevationLocation(ulong c) + { + var cellId = new S2CellId(c); + var latlng = cellId.ToLatLng(); + Init(latlng.LatDegrees, latlng.LngDegrees); + } + + public ElevationLocation(double latitude, double longitude) + { + Init(latitude, longitude); + } + + private void Init(double latitude, double longitude) + { + Latitude = Math.Round(latitude, GEOLOCATION_PRECISION); + Longitude = Math.Round(longitude, GEOLOCATION_PRECISION); + } + + public static async Task FindOrUpdateInDatabase(ElevationConfigContext db, ulong c, IElevationService service) + { + var cellId = new S2CellId(c); + var latlng = cellId.ToLatLng(); + return await FindOrUpdateInDatabase(db, latlng.LatDegrees, latlng.LngDegrees, service).ConfigureAwait(false); + } + + public static async Task FindOrUpdateInDatabase(ElevationConfigContext db, double latitude, double longitude, IElevationService service) + { + latitude = Math.Round(latitude, GEOLOCATION_PRECISION); + longitude = Math.Round(longitude, GEOLOCATION_PRECISION); + + var elevationLocation = db.ElevationLocation.FirstOrDefault(x => x.Latitude == latitude && x.Longitude == longitude); + + if (elevationLocation != null) + return elevationLocation; + + for (var i = 0; i < MAX_RETRIES; i++) + { + try + { + var altitude = await service.GetElevation(latitude, longitude).ConfigureAwait(false); + if (altitude == 0 || altitude < -100) + { + // Invalid altitude + return null; + } + + db.ElevationLocation.Add(new ElevationLocation(latitude, longitude) + { + Altitude = altitude + }); + + await db.SaveChangesAsync().ConfigureAwait(false); + + return elevationLocation; + } + catch (Exception) + { + // Just ignore exception and retry after delay + await Task.Delay(i * 100).ConfigureAwait(false); + } + } + + return null; + } + } +} diff --git a/PoGo.NecroBot.Logic/Model/GeoLocation.cs b/PoGo.NecroBot.Logic/Model/GeoLocation.cs new file mode 100644 index 000000000..6235371bc --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/GeoLocation.cs @@ -0,0 +1,166 @@ +using Geocoding.Google; +using Google.Common.Geometry; +using PokemonGo.RocketAPI.Extensions; +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace PoGo.NecroBot.Logic.Model +{ + public class GeoLocation + { + private static int GEOCODING_MAX_RETRIES = 5; + private static int GEOLOCATION_PRECISION = 3; + private static long BlacklistTimestamp = DateTime.UtcNow.ToUnixTime(); + + public long Id { get; set; } + public double Latitude { get; set; } + public double Longitude { get; set; } + public string Country { get; set; } + public string Locality { get; set; } // City/Town + public string AdminLevel1 { get; set; } // State + public string AdminLevel2 { get; set; } // County + public string AdminLevel3 { get; set; } // City/Town + + public GeoLocation() + { + } + + public GeoLocation(ulong capturedCellId) + { + var cellId = new S2CellId(capturedCellId); + var latlng = cellId.ToLatLng(); + Init(latlng.LatDegrees, latlng.LngDegrees); + } + + public GeoLocation(double latitude, double longitude) + { + Init(latitude, longitude); + } + + private void Init(double latitude, double longitude) + { + Latitude = Math.Round(latitude, GEOLOCATION_PRECISION); + Longitude = Math.Round(longitude, GEOLOCATION_PRECISION); + } + + public async Task ReverseGeocode() + { + GoogleGeocoder geocoder = new GoogleGeocoder(); + var addresses = await geocoder.ReverseGeocodeAsync(Latitude, Longitude).ConfigureAwait(false); + GoogleAddress addr = addresses.Where(a => !a.IsPartialMatch).FirstOrDefault(); + + if (addr != null) + { + if (addr[GoogleAddressType.Country] != null) + Country = addr[GoogleAddressType.Country].LongName; + if (addr[GoogleAddressType.Locality] != null) + Locality = addr[GoogleAddressType.Locality].LongName; + if (addr[GoogleAddressType.AdministrativeAreaLevel1] != null) + AdminLevel1 = addr[GoogleAddressType.AdministrativeAreaLevel1].LongName; + if (addr[GoogleAddressType.AdministrativeAreaLevel2] != null) + AdminLevel2 = addr[GoogleAddressType.AdministrativeAreaLevel2].LongName; + if (addr[GoogleAddressType.AdministrativeAreaLevel3] != null) + AdminLevel3 = addr[GoogleAddressType.AdministrativeAreaLevel3].LongName; + } + } + + public static string GetIdTokenFromLatLng(double latitude, double longitude) + { + latitude = Math.Round(latitude, GEOLOCATION_PRECISION); + longitude = Math.Round(longitude, GEOLOCATION_PRECISION); + + //return S2CellId.FromLatLng(S2LatLng.FromDegrees(latitude, longitude)).ParentForLevel(15).ToToken(); + return $"{latitude},{longitude}"; + } + + public static async Task FindOrUpdateInDatabase(ulong capturedCellId) + { + var cellId = new S2CellId(capturedCellId); + var latlng = cellId.ToLatLng(); + return await FindOrUpdateInDatabase(latlng.LatDegrees, latlng.LngDegrees).ConfigureAwait(false); + } + + public static async Task FindOrUpdateInDatabase(double latitude, double longitude) + { + if (BlacklistTimestamp > DateTime.UtcNow.ToUnixTime()) + return null; + + using (var db = new GeoLocationConfigContext()) + { + latitude = Math.Round(latitude, GEOLOCATION_PRECISION); + longitude = Math.Round(longitude, GEOLOCATION_PRECISION); + + var geoLocation = db.GeoLocation.Where(x => x.Latitude == latitude && x.Longitude == longitude).FirstOrDefault(); + + if (geoLocation != null) + return geoLocation; + + geoLocation = new GeoLocation(latitude, longitude); + + for (var i = 0; i < GEOCODING_MAX_RETRIES; i++) + { + try + { + await geoLocation.ReverseGeocode().ConfigureAwait(false); + break; + } + catch (GoogleGeocodingException e) + { + if (e.Status == GoogleStatus.OverQueryLimit) + { + BlackList(); + return null; + } + + // Just ignore exception and retry after delay + await Task.Delay(i * 100).ConfigureAwait(false); + } + catch (Exception) + { + if (i == GEOCODING_MAX_RETRIES - 1) + { + BlackList(); + return null; + } + + // Just ignore exception and retry after delay + await Task.Delay(i * 100).ConfigureAwait(false); + } + } + + // Before we store it to the database, ensure it must at least have country field set. + if (string.IsNullOrEmpty(geoLocation.Country)) + return null; + + db.GeoLocation.Add(geoLocation); + await db.SaveChangesAsync().ConfigureAwait(false); + return geoLocation; + } + } + + private static void BlackList() + { + var now = DateTime.UtcNow.ToUnixTime(); + if (BlacklistTimestamp < now) + BlacklistTimestamp = now + 60 * 1000 * 60; // 1 hour blacklist + } + + public override string ToString() + { + if (!string.IsNullOrEmpty(Country)) + { + if (!string.IsNullOrEmpty(Locality)) + return $"{Locality}, {Country}"; + if (!string.IsNullOrEmpty(AdminLevel3)) + return $"{AdminLevel3}, {Country}"; + if (!string.IsNullOrEmpty(AdminLevel2)) + return $"{AdminLevel2}, {Country}"; + if (!string.IsNullOrEmpty(AdminLevel1)) + return $"{AdminLevel1}, {Country}"; + return Country; + } + return $"{Latitude}, {Longitude}"; + } + } +} diff --git a/PoGo.NecroBot.Logic/Model/GeoLocationConfigContext.cs b/PoGo.NecroBot.Logic/Model/GeoLocationConfigContext.cs new file mode 100644 index 000000000..e123beb44 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/GeoLocationConfigContext.cs @@ -0,0 +1,32 @@ +using Microsoft.EntityFrameworkCore; +using System.IO; + +namespace PoGo.NecroBot.Logic.Model +{ + public partial class GeoLocationConfigContext : DbContext + { + public virtual DbSet GeoLocation { get; set; } + + public GeoLocationConfigContext() + { + var profilePath = Path.Combine(Directory.GetCurrentDirectory()); + var profileConfigPath = Path.Combine(profilePath, "Cache"); + if (!Directory.Exists(profileConfigPath)) + Directory.CreateDirectory(profileConfigPath); + Database.EnsureCreated(); + } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + var profilePath = Path.Combine(Directory.GetCurrentDirectory()); + var profileConfigPath = Path.Combine(profilePath, "Cache"); + var dbFile = Path.Combine(profileConfigPath, "geolocation.db"); + + optionsBuilder.UseSqlite($"data source={dbFile}"); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + } + } +} diff --git a/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/Address_Components.cs b/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/Address_Components.cs new file mode 100644 index 000000000..8a2c20ea1 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/Address_Components.cs @@ -0,0 +1,9 @@ +namespace PoGo.NecroBot.Logic.Model.Google.GoogleObjects +{ + public class Address_Components + { + public string long_name { get; set; } + public string short_name { get; set; } + public string[] types { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/Bounds.cs b/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/Bounds.cs new file mode 100644 index 000000000..279ffbb27 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/Bounds.cs @@ -0,0 +1,8 @@ +namespace PoGo.NecroBot.Logic.Model.Google.GoogleObjects +{ + public class Bounds + { + public Geo northeast { get; set; } + public Geo southwest { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/DirectionsResponse.cs b/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/DirectionsResponse.cs new file mode 100644 index 000000000..aed20f6c4 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/DirectionsResponse.cs @@ -0,0 +1,9 @@ +namespace PoGo.NecroBot.Logic.Model.Google.GoogleObjects +{ + public class DirectionsResponse + { + public GeocodedWaypoints[] geocoded_waypoints { get; set; } + public Route[] routes { get; set; } + public string status { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/DistanceMatrixResponse.cs b/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/DistanceMatrixResponse.cs new file mode 100644 index 000000000..d619749fa --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/DistanceMatrixResponse.cs @@ -0,0 +1,10 @@ +namespace PoGo.NecroBot.Logic.Model.Google.GoogleObjects +{ + public class DistanceMatrixResponse + { + public string[] destination_addresses { get; set; } + public string[] origin_addresses { get; set; } + public Row[] rows { get; set; } + public string status { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/Element.cs b/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/Element.cs new file mode 100644 index 000000000..c580ac6c1 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/Element.cs @@ -0,0 +1,10 @@ +namespace PoGo.NecroBot.Logic.Model.Google.GoogleObjects +{ + public class Element + { + public ValueText distance { get; set; } + public ValueText duration { get; set; } + public ValueText duration_in_traffic { get; set; } + public string status { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/Geo.cs b/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/Geo.cs new file mode 100644 index 000000000..d8545d04b --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/Geo.cs @@ -0,0 +1,8 @@ +namespace PoGo.NecroBot.Logic.Model.Google.GoogleObjects +{ + public class Geo + { + public float lat { get; set; } + public float lng { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/GeocodedWaypoints.cs b/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/GeocodedWaypoints.cs new file mode 100644 index 000000000..1c550bff8 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/GeocodedWaypoints.cs @@ -0,0 +1,9 @@ +namespace PoGo.NecroBot.Logic.Model.Google.GoogleObjects +{ + public class GeocodedWaypoints + { + public string geocoder_status { get; set; } + public string place_id { get; set; } + public string[] types { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/GeocodingResponse.cs b/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/GeocodingResponse.cs new file mode 100644 index 000000000..f0e197d10 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/GeocodingResponse.cs @@ -0,0 +1,8 @@ +namespace PoGo.NecroBot.Logic.Model.Google.GoogleObjects +{ + public class GeocodingResponse + { + public Result[] results { get; set; } + public string status { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/Geometry.cs b/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/Geometry.cs new file mode 100644 index 000000000..6ae42e92d --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/Geometry.cs @@ -0,0 +1,9 @@ +namespace PoGo.NecroBot.Logic.Model.Google.GoogleObjects +{ + public class Geometry + { + public Geo location { get; set; } + public string location_type { get; set; } + public Bounds viewport { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/GoogleType.cs b/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/GoogleType.cs new file mode 100644 index 000000000..c23f5f839 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/GoogleType.cs @@ -0,0 +1,8 @@ +namespace PoGo.NecroBot.Logic.Model.Google.GoogleObjects +{ + public enum GoogleType + { + DistanceMatrix = 1, + Directions = 2 + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/Leg.cs b/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/Leg.cs new file mode 100644 index 000000000..5bc046022 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/Leg.cs @@ -0,0 +1,14 @@ +namespace PoGo.NecroBot.Logic.Model.Google.GoogleObjects +{ + public class Leg + { + public ValueText distance { get; set; } + public ValueText duration { get; set; } + public string end_address { get; set; } + public Geo end_location { get; set; } + public string start_address { get; set; } + public Geo start_location { get; set; } + public Step[] steps { get; set; } + public object[] via_waypoint { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/Polyline.cs b/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/Polyline.cs new file mode 100644 index 000000000..04d0cb4ba --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/Polyline.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using GeoCoordinatePortable; + +namespace PoGo.NecroBot.Logic.Model.Google.GoogleObjects +{ + public class Polyline + { + public string points { get; set; } + + public List DecodePoly() + { + var poly = new List(); + if (string.IsNullOrEmpty(points)) + throw new ArgumentNullException("polyline"); + + char[] polylineChars = points.ToCharArray(); + int index = 0; + + int currentLat = 0; + int currentLng = 0; + int next5bits; + int sum; + int shifter; + + while (index < polylineChars.Length) + { + // calculate next latitude + sum = 0; + shifter = 0; + do + { + next5bits = (int) polylineChars[index++] - 63; + sum |= (next5bits & 31) << shifter; + shifter += 5; + } while (next5bits >= 32 && index < polylineChars.Length); + + if (index >= polylineChars.Length) + break; + + currentLat += (sum & 1) == 1 ? ~(sum >> 1) : (sum >> 1); + + //calculate next longitude + sum = 0; + shifter = 0; + do + { + next5bits = (int) polylineChars[index++] - 63; + sum |= (next5bits & 31) << shifter; + shifter += 5; + } while (next5bits >= 32 && index < polylineChars.Length); + + if (index >= polylineChars.Length && next5bits >= 32) + break; + + currentLng += (sum & 1) == 1 ? ~(sum >> 1) : (sum >> 1); + + poly.Add(new GeoCoordinate() + { + Latitude = Convert.ToDouble(currentLat) / 1E5, + Longitude = Convert.ToDouble(currentLng) / 1E5 + }); + } + + return poly; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/Result.cs b/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/Result.cs new file mode 100644 index 000000000..99ebef440 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/Result.cs @@ -0,0 +1,11 @@ +namespace PoGo.NecroBot.Logic.Model.Google.GoogleObjects +{ + public class Result + { + public Address_Components[] address_components { get; set; } + public string formatted_address { get; set; } + public Geometry geometry { get; set; } + public string place_id { get; set; } + public string[] types { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/Route.cs b/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/Route.cs new file mode 100644 index 000000000..bbfda9cc2 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/Route.cs @@ -0,0 +1,13 @@ +namespace PoGo.NecroBot.Logic.Model.Google.GoogleObjects +{ + public class Route + { + public Bounds bounds { get; set; } + public string copyrights { get; set; } + public Leg[] legs { get; set; } + public Polyline overview_polyline { get; set; } + public string summary { get; set; } + public object[] warnings { get; set; } + public object[] waypoint_order { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/Row.cs b/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/Row.cs new file mode 100644 index 000000000..577071f36 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/Row.cs @@ -0,0 +1,7 @@ +namespace PoGo.NecroBot.Logic.Model.Google.GoogleObjects +{ + public class Row + { + public Element[] elements { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/Step.cs b/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/Step.cs new file mode 100644 index 000000000..edfb3dfba --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/Step.cs @@ -0,0 +1,14 @@ +namespace PoGo.NecroBot.Logic.Model.Google.GoogleObjects +{ + public class Step + { + public ValueText distance { get; set; } + public ValueText duration { get; set; } + public Geo end_location { get; set; } + public string html_instructions { get; set; } + public Polyline polyline { get; set; } + public Geo start_location { get; set; } + public string travel_mode { get; set; } + public string maneuver { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/ValueText.cs b/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/ValueText.cs new file mode 100644 index 000000000..810f743f1 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Google/GoogleObjects/ValueText.cs @@ -0,0 +1,8 @@ +namespace PoGo.NecroBot.Logic.Model.Google.GoogleObjects +{ + public class ValueText + { + public string text { get; set; } + public int value { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Google/GoogleResult.cs b/PoGo.NecroBot.Logic/Model/Google/GoogleResult.cs new file mode 100644 index 000000000..e8c52f7fe --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Google/GoogleResult.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using PoGo.NecroBot.Logic.Model.Google.GoogleObjects; +using GeoCoordinatePortable; + +namespace PoGo.NecroBot.Logic.Model.Google +{ + public class GoogleResult + { + public DirectionsResponse Directions { get; set; } + public DateTime RequestDate { get; set; } + public GeoCoordinate Origin { get; set; } + public GeoCoordinate Destiny { get; set; } + public List Waypoints { get; set; } + public bool FromCache { get; set; } + + /// + /// Google time to reach destiny. If car, consider traffic data. + /// + /// + public float TravelTime() + { + float tempo = 0; + + foreach (var legs in Directions.routes.SelectMany(route => route.legs)) + { + tempo += legs.duration.value; + } + return tempo; + } + + + public double GetDistance() + { + float distance = 0; + + foreach (var legs in Directions.routes.SelectMany(route => route.legs)) + { + distance += legs.distance.value; + } + return distance; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Google/GoogleWalk.cs b/PoGo.NecroBot.Logic/Model/Google/GoogleWalk.cs new file mode 100644 index 000000000..6af8e4cfc --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Google/GoogleWalk.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using GeoCoordinatePortable; +using System.Globalization; +using System.Linq; + +namespace PoGo.NecroBot.Logic.Model.Google +{ + public class GoogleWalk + { + public List Waypoints { get; set; } + public double Distance { get; set; } + + public GoogleWalk(GoogleResult googleResult) + { + if (googleResult.Directions.routes == null) + throw new ArgumentException("Invalid google route."); + + var route = googleResult.Directions.routes.First(); + + Distance = googleResult.GetDistance(); + + Waypoints = new List(); + // In some cases, player are inside build + Waypoints.Add(googleResult.Origin); + + Waypoints.Add(new GeoCoordinate(route.legs.First().start_location.lat, route.legs.First().start_location.lng)); + Waypoints.AddRange(route.overview_polyline.DecodePoly()); + Waypoints.Add(new GeoCoordinate(route.legs.Last().end_location.lat, route.legs.Last().end_location.lng)); + + // In some cases, player need to get inside a build + Waypoints.Add(googleResult.Destiny); + } + + /// + /// Used for test purpose + /// + /// + public string GetTextFlyPath() => "[" + string.Join(",", Waypoints.Select(geoCoordinate => $"{{lat: {geoCoordinate.Latitude.ToString(new CultureInfo("en-US"))}, lng: {geoCoordinate.Longitude.ToString(new CultureInfo("en-US"))}}}").ToList()) + "]"; + + private GeoCoordinate _lastNextStep; + + public GeoCoordinate NextStep(GeoCoordinate actualLocation) + { + if (!Waypoints.Any()) + { + return _lastNextStep ?? (_lastNextStep = actualLocation); + } + + do + { + _lastNextStep = Waypoints.FirstOrDefault(); + Waypoints.Remove(_lastNextStep); + } while (actualLocation.GetDistanceTo(_lastNextStep) < 20 || Waypoints.Any()); + + return _lastNextStep; + } + + public static GoogleWalk Get(GoogleResult googleResult) + { + return new GoogleWalk(googleResult); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/IGeoLocation.cs b/PoGo.NecroBot.Logic/Model/IGeoLocation.cs new file mode 100644 index 000000000..5010ff639 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/IGeoLocation.cs @@ -0,0 +1,71 @@ +using POGOProtos.Data; +using POGOProtos.Map.Fort; +using POGOProtos.Networking.Responses; +using GeoCoordinatePortable; + +namespace PoGo.NecroBot.Logic.Model +{ + public interface IGeoLocation + { + double Latitude { get; set; } + double Longitude { get; set; } + double Altitude { get; set; } + string Name { get; set; } + GeoCoordinate ToGeoCoordinate(); + } + + public class FortLocation : MapLocation + { + public FortData FortData { get; set; } + public FortDetailsResponse FortInfo { get; set; } + + public FortLocation(double lat, double lng, double alt, FortData fortData, + FortDetailsResponse fortInfo) : base(lat, lng, alt) + { + FortData = fortData; + + if (fortInfo != null) + { + FortInfo = fortInfo; + Name = fortInfo.Name; + } + } + } + + public class GPXPointLocation : MapLocation + { + public GPXPointLocation(double lat, double lng, double alt) : base(lat, lng, alt) + { + } + } + + public class SnipeLocation : MapLocation + { + public SnipeLocation(double lat, double lng, double alt) : base(lat, lng, alt) + { + } + + public PokemonData Pokemon { get; set; } + } + + public class MapLocation : IGeoLocation + { + public double Latitude { get; set; } + public double Longitude { get; set; } + + public double Altitude { get; set; } + public string Name { get; set; } + + public MapLocation(double lat, double lng, double alt) + { + Latitude = lat; + Longitude = lng; + Altitude = alt; + } + + public GeoCoordinate ToGeoCoordinate() + { + return new GeoCoordinate(Latitude, Longitude, Altitude); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Mapzen/MapzenWalk.cs b/PoGo.NecroBot.Logic/Model/Mapzen/MapzenWalk.cs new file mode 100644 index 000000000..e22a50294 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Mapzen/MapzenWalk.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json.Linq; +using GeoCoordinatePortable; + +namespace PoGo.NecroBot.Logic.Model.Mapzen +{ + public class Trip + { + public string language { get; set; } + public Summary summary { get; set; } + public int status { get; set; } + public string status_message { get; set; } + public string units { get; set; } + public List legs { get; set; } + public List locations { get; set; } + } + + public class Summary + { + public double max_lon { get; set; } + public double max_lat { get; set; } + public int time { get; set; } + public double length { get; set; } + public double min_lon { get; set; } + public double min_lat { get; set; } + } + + public class Location + { + public double lon { get; set; } + public double lat { get; set; } + } + + public class Legs + { + public string shape { get; set; } + public Summary summary { get; set; } + } + + public class MapzenWalk + { + public List Waypoints { get; set; } + public double Distance { get; set; } + + public MapzenWalk(string mapzenResponse, GeoCoordinate sourceLocation, GeoCoordinate destLocation) + { + Waypoints = new List + { + // Add the source + sourceLocation + }; + JObject jsonObj = JObject.Parse(mapzenResponse); + + var trip = jsonObj["trip"]; + int status = (int) trip["status"]; + if (status == 0) + { + JObject summary = (JObject) trip["summary"]; + Distance = (double) summary["length"] * 1000; + + JArray legs = (JArray) trip["legs"]; + foreach (var leg in legs) + { + string shape = (string) leg["shape"]; + + // Decode the polyline. + Waypoints.AddRange(DecodePoly(shape)); + } + } + + // Add the destination + Waypoints.Add(destLocation); + } + + public List DecodePoly(string points, int precision = 6) + { + var poly = new List(); + if (string.IsNullOrEmpty(points)) + throw new ArgumentNullException("polyline"); + + char[] polylineChars = points.ToCharArray(); + int index = 0; + double lat = 0; + double lng = 0; + double latitudeChange = 0; + double longitudeChange = 0; + int next5bits; + int sum; + int shifter; + double factor = Math.Pow(10, precision); + + while (index < polylineChars.Length) + { + // calculate next latitude + next5bits = 0; + sum = 0; + shifter = 0; + do + { + next5bits = (int) polylineChars[index++] - 63; + sum |= (next5bits & 0x1f) << shifter; + shifter += 5; + } while (next5bits >= 0x20); + + latitudeChange = (sum & 1) == 1 ? ~(sum >> 1) : (sum >> 1); + + //calculate next longitude + next5bits = 0; + sum = 0; + shifter = 0; + do + { + next5bits = (int) polylineChars[index++] - 63; + sum |= (next5bits & 0x1f) << shifter; + shifter += 5; + } while (next5bits >= 0x20); + + longitudeChange = (sum & 1) == 1 ? ~(sum >> 1) : (sum >> 1); + + lat += latitudeChange; + lng += longitudeChange; + + poly.Add(new GeoCoordinate() + { + Latitude = lat / factor, + Longitude = lng / factor + }); + } + + return poly; + } + + public static MapzenWalk Get(string mapzenResponse, GeoCoordinate sourceLocation, GeoCoordinate destLocation) + { + return new MapzenWalk(mapzenResponse, sourceLocation, destLocation); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/PokemonGrade.cs b/PoGo.NecroBot.Logic/Model/PokemonGrade.cs new file mode 100644 index 000000000..5fb531dab --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/PokemonGrade.cs @@ -0,0 +1,14 @@ +namespace PoGo.NecroBot.Logic.Model +{ + public enum PokemonGrades + { + VeryCommon, + Common, + Popular, //Uncommon + Rare, + VeryRare, + Special, + Epic, + Legendary + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/PokemonTimestamp.cs b/PoGo.NecroBot.Logic/Model/PokemonTimestamp.cs new file mode 100644 index 000000000..3fbb34d28 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/PokemonTimestamp.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations; + +namespace PoGo.NecroBot.Logic.Model +{ + public class PokemonTimestamp + { + public int Id { get; set; } + [Required] + public long Timestamp { get; set; } + public virtual Account Account { get; set; } + } +} diff --git a/PoGo.NecroBot.Logic/Model/PokestopTimestamp.cs b/PoGo.NecroBot.Logic/Model/PokestopTimestamp.cs new file mode 100644 index 000000000..09c4eb318 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/PokestopTimestamp.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations; + +namespace PoGo.NecroBot.Logic.Model +{ + public class PokestopTimestamp + { + public int Id { get; set; } + [Required] + public long Timestamp { get; set; } + public virtual Account Account { get; set; } + } +} diff --git a/PoGo.NecroBot.Logic/Model/Settings/APIConfig.cs b/PoGo.NecroBot.Logic/Model/Settings/APIConfig.cs new file mode 100644 index 000000000..32b7336f3 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/APIConfig.cs @@ -0,0 +1,41 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; +using PokemonGo.RocketAPI; + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + [JsonObject(Title = "API Config", Description = "Set Preferred API Type to use", ItemRequired = Required.DisallowNull)] + public class APIConfig : BaseConfig + { + public APIConfig() : base() + { + } + + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + public bool UsePogoDevAPI { get; set; } + + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 2)] + public bool UseCustomAPI { get; set; } + + [DefaultValue("")] + [MinLength(0)] + [MaxLength(100)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 3)] + public string AuthAPIKey { get; set; } + + [DefaultValue(true)] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 4)] + public bool DiplayHashServerLog { get; set; } + + [DefaultValue("https://pokehash.buddyauth.com/")] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 5)] + public string UrlHashServices { get; set; } + + [DefaultValue(Constants.ApiEndPoint)] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 6)] + public string EndPoint { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Settings/AuthConfig.cs b/PoGo.NecroBot.Logic/Model/Settings/AuthConfig.cs new file mode 100644 index 000000000..80803b287 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/AuthConfig.cs @@ -0,0 +1,77 @@ +using System; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; +using PokemonGo.RocketAPI.Enums; + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + [JsonObject(Title = "Authentication Config", + Description = "Set your Authentication type (Google or Ptc) and your login information.", + ItemRequired = Required.DisallowNull)] + public class AuthConfig :BaseConfig + { + public AuthConfig() : base() { } + [DefaultValue(AuthType.Google)] + //[JsonConverter(typeof(StringEnumConverter))] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + public AuthType AuthType { get; set; } + + [DefaultValue(null)] + [MinLength(0)] + [MaxLength(64)] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 2)] + public string Username { get; set; } + + [DefaultValue(null)] + [MinLength(0)] + [MaxLength(50)] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 3)] + public string Password { get; set; } + + [DefaultValue(false)] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 4)] + public bool AutoExitBotIfAccountFlagged { get; set; } + + [DefaultValue(40.781441)] + [Range(-90, 90)] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 5)] + public double AccountLatitude { get; set; } + + [DefaultValue(-73.966586)] + [Range(-180, 180)] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 6)] + public double AccountLongitude { get; set; } + + [DefaultValue(true)] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 7)] + public bool AccountActive { get; set; } + + //TimeZone Player locale settings + [DefaultValue("US")] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 8)] + public string Country { get; set; } + + [DefaultValue("en")] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 9)] + public string Language { get; set; } + + [DefaultValue("America/New_York")] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 10)] + public string TimeZone { get; set; } + + [DefaultValue("en-us")] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 11)] + public string POSIX { get; set; } + + // Total runtime since client started + [JsonIgnore] + public double RuntimeTotal { get; set; } + + [JsonIgnore] + public DateTime LastRuntimeUpdatedAt { get; set; } + + [JsonIgnore] + public DateTime ReleaseBlockTime { get; set; } + } +} diff --git a/PoGo.NecroBot.Logic/Model/Settings/AuthSettings.cs b/PoGo.NecroBot.Logic/Model/Settings/AuthSettings.cs new file mode 100644 index 000000000..4e7a7d6f6 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/AuthSettings.cs @@ -0,0 +1,608 @@ +#region using directives + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Net; +using System.Reflection; +using System.Security.Cryptography; +using System.Text; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Schema; +using Newtonsoft.Json.Schema.Generation; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.Logging; +using PokemonGo.RocketAPI.Extensions; +using PokemonGo.RocketAPI.Helpers; +using System.Net.Http; +using static POGOProtos.Networking.Envelopes.Signature.Types; + +#endregion + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + [JsonObject(Title = "Authentication Settings", Description = "Set your authentication settings.", ItemRequired = Required.DisallowNull)] + public class AuthSettings + { + [JsonIgnore] + public static int SchemaVersionBeforeMigration { get; set; } + + [JsonIgnore] + private string _filePath; + + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Ignore, Order = 1)] + public List Bots = new List(); + + [JsonIgnore] + private AuthConfig _currentAuthConfig; + + [JsonIgnore] + public AuthConfig CurrentAuthConfig + { + get + { + if (_currentAuthConfig == null) + { + if (Bots.Count == 0) + Bots.Add(new AuthConfig()); + + _currentAuthConfig = Bots.FirstOrDefault(); + } + + return _currentAuthConfig; + } + + set + { + _currentAuthConfig = value; + } + } + + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore, Order = 2)] + public ProxyConfig ProxyConfig = new ProxyConfig(); + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore, Order = 3)] + public DeviceConfig DeviceConfig = new DeviceConfig(); + + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore, Order = 3)] + public APIConfig APIConfig = new APIConfig(); + + private JSchema _schema; + + private JSchema JsonSchema + { + get + { + if (_schema != null) + return _schema; + // JSON Schemas from .NET types + var generator = new JSchemaGenerator + { + // change contract resolver so property names are camel case + //ContractResolver = new CamelCasePropertyNamesContractResolver(), + // sets the default required state of schemas + DefaultRequired = Required.Default, + // types with no defined ID have their type name as the ID + SchemaIdGenerationHandling = SchemaIdGenerationHandling.TypeName, + // use the default order of properties. + SchemaPropertyOrderHandling = SchemaPropertyOrderHandling.Default, + // referenced schemas are inline. + SchemaLocationHandling = SchemaLocationHandling.Inline, + // all schemas can be referenced. + SchemaReferenceHandling = SchemaReferenceHandling.None + }; + // change Zone enum to generate a string property + var strEnumGen = new StringEnumGenerationProvider { CamelCaseText = true }; + generator.GenerationProviders.Add(strEnumGen); + // generate json schema + var type = typeof(AuthSettings); + try + { + var schema = generator.Generate(type); + schema.Title = type.Name; + // + _schema = schema; + } + catch (Exception) + { + } + return _schema; + } + } + + //private JObject _jsonObject; + //public JObject JsonObject + //{ + // get + // { + // if (_jsonObject == null) + // _jsonObject = JObject.FromObject(this); + + // return _jsonObject; + // } + // set + // { + // _jsonObject = value; + // } + //} + + + public AuthSettings() + { + InitializePropertyDefaultValues(this); + } + + public void InitializePropertyDefaultValues(object obj) + { + var fields = obj.GetType().GetFields(); + + foreach (var field in fields) + { + var d = field.GetCustomAttribute(); + + if (d != null) + field.SetValue(obj, d.Value); + } + } + + //public void Load(JObject jsonObj) + //{ + // try + // { + // var input = jsonObj.ToString(Formatting.None, new StringEnumConverter { CamelCaseText = true }); + // var settings = new JsonSerializerSettings(); + // settings.Converters.Add(new StringEnumConverter { CamelCaseText = true }); + // JsonConvert.PopulateObject(input, this, settings); + // Save(_filePath); + // } + // catch (JsonReaderException exception) + // { + // Logger.Write("JSON Exception: " + exception.Message, LogLevel.Error); + // } + //} + + public void Load(string configFile, string schemaFile, int schemaVersion, bool validate = false) + { + try + { + _filePath = configFile; + + if (File.Exists(_filePath)) + { + // if the file exists, load the settings + var input = File.ReadAllText(_filePath, Encoding.UTF8); + + if (validate) + { + var jsonObj = JObject.Parse(input); + + // Migrate before Validating. + MigrateSettings(schemaVersion, jsonObj, configFile, schemaFile); + + // Validate Json using JsonSchema + Logger.Write("Validating Auth.json..."); + IList errors = null; + bool valid; + try + { + valid = jsonObj.IsValid(JsonSchema, out errors); + } + catch (JSchemaException ex) + { + if (ex.Message.Contains("commercial licence") || ex.Message.Contains("free-quota")) + { + Logger.Write( + "auth.json: " + ex.Message); + valid = false; + } + else + { + throw; + } + } + if (!valid) + { + if (errors != null) + { + foreach (var error in errors) + { + Logger.Write( + "auth.json [Line: " + error.LineNumber + ", Position: " + error.LinePosition + "]: " + + error.Path + " " + + error.Message, LogLevel.Error); + } + } + + Logger.Write("Fix your auth.json and restart NecroBot or press any key to ignore and continue...", + LogLevel.Warning); + Console.ReadKey(); + } + + // Now we know it's valid so update input with the migrated version. + input = jsonObj.ToString(); + } + + var settings = new JsonSerializerSettings(); + settings.Converters.Add(new StringEnumConverter { CamelCaseText = true }); + JsonConvert.PopulateObject(input, this, settings); + } + // Do some post-load logic to determine what device info to be using - if 'custom' is set we just take what's in the file without question + if (DeviceConfig.DevicePlatform.Equals("ios", StringComparison.InvariantCultureIgnoreCase)) + { + // iOS + if (DeviceConfig.DevicePackageName.Equals("random", StringComparison.InvariantCultureIgnoreCase)) + { + var randomAppleDeviceInfo = DeviceInfoHelper.GetRandomIosDevice(); + SetDevInfoByDeviceInfo(randomAppleDeviceInfo); + + // Clearing Android Variables, as they would otherwise come back as "" instead of null + DeviceConfig.AndroidBoardName = null; + DeviceConfig.AndroidBootloader = null; + DeviceConfig.DeviceModelIdentifier = null; + DeviceConfig.FirmwareTags = null; + DeviceConfig.FirmwareFingerprint = null; + + // After generating iOS settings, automatically set the package name to "custom", so that we don't regenerate settings every time we start. + DeviceConfig.DevicePackageName = "custom"; + } + } + else + { + // We cannot emulate Android at the moment, so if we got here, then regenerate the settings with random iOS device. + /* + // Android + if (!DeviceConfig.DevicePackageName.Equals("random", StringComparison.InvariantCultureIgnoreCase) && + !DeviceConfig.DevicePackageName.Equals("custom", StringComparison.InvariantCultureIgnoreCase)) + { + // User requested a specific device package, check to see if it exists and if so, set it up - otherwise fall-back to random package + var keepDevId = DeviceConfig.DeviceId; + SetDevInfoByKey(); + DeviceConfig.DeviceId = keepDevId; + } + if (DeviceConfig.DevicePackageName.Equals("random", StringComparison.InvariantCultureIgnoreCase)) + { + // Random is set, so pick a random device package and set it up - it will get saved to disk below and re-used in subsequent sessions + var rnd = new Random(); + var rndIdx = rnd.Next(0, DeviceInfoHelper.AndroidDeviceInfoSets.Keys.Count - 1); + DeviceConfig.DevicePackageName = DeviceInfoHelper.AndroidDeviceInfoSets.Keys.ToArray()[rndIdx]; + SetDevInfoByKey(); + } + */ + DeviceConfig.DevicePlatform = "ios"; + DeviceConfig.DevicePackageName = "custom"; + + var randomAppleDeviceInfo = DeviceInfoHelper.GetRandomIosDevice(); + SetDevInfoByDeviceInfo(randomAppleDeviceInfo); + + // Clear out the android fields. + DeviceConfig.AndroidBoardName = null; + DeviceConfig.AndroidBootloader = null; + DeviceConfig.DeviceModelIdentifier = null; + DeviceConfig.FirmwareTags = null; + DeviceConfig.FirmwareFingerprint = null; + } + + if (string.IsNullOrEmpty(DeviceConfig.DeviceId) || DeviceConfig.DeviceId == "8525f5d8201f78b5") + { + // Changed to random hex as full alphabet letters could have been flagged + // iOS device ids are 16 bytes (32 chars long) + DeviceConfig.DeviceId = RandomString(32, "0123456789abcdef"); + } + + Save(_filePath); + } + catch (JsonReaderException exception) + { + if (exception.Message.Contains("Unexpected character") && exception.Message.Contains("Username")) + Logger.Write("JSON Exception: You need to properly configure your Username using quotations.", + LogLevel.Error); + else if (exception.Message.Contains("Unexpected character") && + exception.Message.Contains("Password")) + Logger.Write( + "JSON Exception: You need to properly configure your Password using quotations.", + LogLevel.Error); + else + Logger.Write("JSON Exception: " + exception.Message, LogLevel.Error); + } + } + + private static void MigrateSettings(int schemaVersion, JObject settings, string configFile, string schemaFile) + { + SchemaVersionBeforeMigration = schemaVersion; + + if (schemaVersion == UpdateConfig.CURRENT_SCHEMA_VERSION) + { + Logger.Write("Auth Configuration is up-to-date. Schema version: " + schemaVersion); + return; + } + + // Backup old config file. + long ts = DateTime.UtcNow.ToUnixTime(); // Add timestamp to avoid file conflicts + string backupPath = configFile.Replace(".json", $"-{schemaVersion}-{ts}.backup.json"); + Logger.Write($"Backing up auth.json to: {backupPath}", LogLevel.Info); + File.Copy(configFile, backupPath); + + // Add future schema migrations below. + int version; + for (version = schemaVersion; version < UpdateConfig.CURRENT_SCHEMA_VERSION; version++) + { + Logger.Write( + $"Migrating auth configuration from schema version {version} to {version + 1}", + LogLevel.Info + ); + switch (version) + { + case 3: + settings["DeviceConfig"]["AndroidBoardName"] = null; + settings["DeviceConfig"]["AndroidBootloader"] = null; + settings["DeviceConfig"]["DeviceModelIdentifier"] = null; + settings["DeviceConfig"]["FirmwareTags"] = null; + settings["DeviceConfig"]["FirmwareFingerprint"] = null; + break; + + case 19: + // Update main auth setting + if (settings["AuthConfig"] != null) + { + JObject bot = (JObject)settings["AuthConfig"]; + if ((string)bot["AuthType"] == "google") + { + if (!string.IsNullOrEmpty((string)bot["GoogleUsername"])) + bot["Username"] = bot["GoogleUsername"]; + if (!string.IsNullOrEmpty((string)bot["GooglePassword"])) + bot["Password"] = bot["GooglePassword"]; + } + else + { + if (!string.IsNullOrEmpty((string)bot["PtcUsername"])) + bot["Username"] = bot["PtcUsername"]; + if (!string.IsNullOrEmpty((string)bot["PtcPassword"])) + bot["Password"] = bot["PtcPassword"]; + } + + bot.Remove("GoogleUsername"); + bot.Remove("GooglePassword"); + bot.Remove("PtcUsername"); + bot.Remove("PtcPassword"); + } + + // Update multibot settings + if (settings["Bots"] != null) + { + foreach (JObject bot in settings["Bots"]) + { + if ((string)bot["AuthType"] == "google") + { + if (!string.IsNullOrEmpty((string)bot["GoogleUsername"])) + bot["Username"] = bot["GoogleUsername"]; + if (!string.IsNullOrEmpty((string)bot["GooglePassword"])) + bot["Password"] = bot["GooglePassword"]; + } + else + { + if (!string.IsNullOrEmpty((string)bot["PtcUsername"])) + bot["Username"] = bot["PtcUsername"]; + if (!string.IsNullOrEmpty((string)bot["PtcPassword"])) + bot["Password"] = bot["PtcPassword"]; + } + + bot.Remove("GoogleUsername"); + bot.Remove("GooglePassword"); + bot.Remove("PtcUsername"); + bot.Remove("PtcPassword"); + } + } + break; + + case 20: + if (settings["AuthConfig"] != null) + { + JObject originalBot = (JObject)settings["AuthConfig"]; + var username = (string)originalBot["Username"]; + var password = (string)originalBot["Password"]; + var authType = (string)originalBot["AuthType"]; + + if (!string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(password) && !string.IsNullOrEmpty(authType)) + { + JObject foundBot = null; + foreach (JObject bot in settings["Bots"]) + { + if ((string)bot["AuthType"] == authType && (string)bot["Username"] == username) + { + // Found + foundBot = bot; + break; + } + } + + // If not found then we need to insert it. + if (foundBot == null) + { + JObject newBot = new JObject + { + ["Username"] = username, + ["Password"] = password, + ["AuthType"] = authType + }; + ((JArray)settings["Bots"]).Insert(0, newBot); + } + } + + // Delete AuthConfig now + settings.Remove("AuthConfig"); + } + + settings.Remove("AllowMultipleBot"); + break; + + // Add more here. + } + } + } + + public void Save(string fullPath, bool validate = false) + { +#pragma warning disable IDE0018 // Inline variable declaration - Build.Bat Error Happens if We Do + var jsonSerializeSettings = new JsonSerializerSettings + { + DefaultValueHandling = DefaultValueHandling.Include, + Formatting = Formatting.Indented, + Converters = new JsonConverter[] { new StringEnumConverter { CamelCaseText = true } } + }; + var output = JsonConvert.SerializeObject(this, jsonSerializeSettings); + + var folder = Path.GetDirectoryName(fullPath); + if (folder != null && !Directory.Exists(folder)) + { + Directory.CreateDirectory(folder); + } + + File.WriteAllText(fullPath, output, Encoding.UTF8); + + //JsonSchema + File.WriteAllText(fullPath.Replace(".json", ".schema.json"), JsonSchema.ToString(), Encoding.UTF8); + + if (!validate) return; + + // validate Json using JsonSchema + Logger.Write("Validating auth.json..."); + var jsonObj = JObject.Parse(output); + IList errors; + var valid = jsonObj.IsValid(JsonSchema, out errors); + if (valid) return; + foreach (var error in errors) + { + Logger.Write( + "auth.json [Line: " + error.LineNumber + ", Position: " + error.LinePosition + "]: " + error.Path + + " " + + error.Message, LogLevel.Error); + } + Logger.Write( + "Fix auth.json and restart NecroBot or press any key to ignore and continue...", + LogLevel.Warning + ); + Console.ReadKey(); +#pragma warning restore IDE0018 // Inline variable declaration - Build.Bat Error Happens if We Do + } + + public void Save() + { + if (!string.IsNullOrEmpty(_filePath)) + { + Save(_filePath); + } + } + + public void CheckProxy(ITranslation translator) + { + string unproxiedIp; + using (HttpClient client = new HttpClient()) + { + var responseContent = client.GetAsync("https://api.ipify.org/?format=text").Result; + unproxiedIp = responseContent.Content.ReadAsStringAsync().Result; + } + + if (ProxyConfig.UseProxy) + { + var httpClientHandler = new HttpClientHandler + { + Proxy = InitProxy(), + UseProxy = true + }; + + using (HttpClient client = new HttpClient(httpClientHandler)) + { + var responseContent = client.GetAsync("https://api.ipify.org/?format=text").Result; + var proxiedIPres = responseContent.Content.ReadAsStringAsync().Result; + + var proxiedIp = proxiedIPres ?? "INVALID PROXY"; + Logger.Write(translator.GetTranslation(TranslationString.Proxied, unproxiedIp, proxiedIp), + LogLevel.Info, unproxiedIp == proxiedIp ? ConsoleColor.Red : ConsoleColor.Green); + + if (unproxiedIp != proxiedIp && proxiedIPres != null) + return; + + Logger.Write(translator.GetTranslation(TranslationString.FixProxySettings), LogLevel.Info, + ConsoleColor.Red); + Console.ReadKey(); + Environment.Exit(0); + } + } + else + { + Logger.Write(translator.GetTranslation(TranslationString.Unproxied, unproxiedIp), LogLevel.Info, + ConsoleColor.Red); + } + } + + private static string RandomString(int length, string alphabet = "abcdefghijklmnopqrstuvwxyz0123456789") + { + var outOfRange = byte.MaxValue + 1 - (byte.MaxValue + 1) % alphabet.Length; + + return string.Concat( + Enumerable + .Repeat(0, int.MaxValue) + .Select(e => RandomByte()) + .Where(randomByte => randomByte < outOfRange) + .Take(length) + .Select(randomByte => alphabet[randomByte % alphabet.Length]) + ); + } + + private static byte RandomByte() + { + using (var randomizationProvider = new RNGCryptoServiceProvider()) + { + var randomBytes = new byte[1]; + randomizationProvider.GetBytes(randomBytes); + return randomBytes.Single(); + } + } + + private void SetDevInfoByKey() + { + if (DeviceInfoHelper.AndroidDeviceInfoSets.ContainsKey(DeviceConfig.DevicePackageName)) + { + SetDevInfoByDeviceInfo(DeviceInfoHelper.AndroidDeviceInfoSets[DeviceConfig.DevicePackageName]); + } + else + { + throw new ArgumentException( + "Invalid Device Info package! Check Auth.json file and make sure a valid Device Package Name is set. For simple use set it to 'random'. If you have a custom device, then set it to 'custom'."); + } + } + + private void SetDevInfoByDeviceInfo(DeviceInfo deviceInfo) + { + DeviceConfig.AndroidBoardName = deviceInfo.AndroidBoardName; + DeviceConfig.AndroidBootloader = deviceInfo.AndroidBootloader; + DeviceConfig.DeviceBrand = deviceInfo.DeviceBrand; + DeviceConfig.DeviceId = deviceInfo.DeviceId; + DeviceConfig.DeviceModel = deviceInfo.DeviceModel; + DeviceConfig.DeviceModelBoot = deviceInfo.DeviceModelBoot; + DeviceConfig.DeviceModelIdentifier = deviceInfo.DeviceModelIdentifier; + DeviceConfig.FirmwareBrand = deviceInfo.FirmwareBrand; + DeviceConfig.FirmwareFingerprint = deviceInfo.FirmwareFingerprint; + DeviceConfig.FirmwareTags = deviceInfo.FirmwareTags; + DeviceConfig.FirmwareType = deviceInfo.FirmwareType; + DeviceConfig.HardwareManufacturer = deviceInfo.HardwareManufacturer; + DeviceConfig.HardwareModel = deviceInfo.HardwareModel; + } + + private WebProxy InitProxy() + { + if (!ProxyConfig.UseProxy) return null; + + var prox = new WebProxy(new Uri($"http://{ProxyConfig.UseProxyHost}:{ProxyConfig.UseProxyPort}"), false, + null); + + if (ProxyConfig.UseProxyAuthentication) + prox.Credentials = new NetworkCredential(ProxyConfig.UseProxyUsername, ProxyConfig.UseProxyPassword); + + return prox; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Settings/BaseConfig.cs b/PoGo.NecroBot.Logic/Model/Settings/BaseConfig.cs new file mode 100644 index 000000000..c11fb4e61 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/BaseConfig.cs @@ -0,0 +1,50 @@ +using POGOProtos.Enums; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Reflection; + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + public class BaseConfig + { + public BaseConfig() + { + try + { + PropertyInfo[] props = GetType().GetProperties(); + foreach (PropertyInfo prop in props) + { + var d = prop.GetCustomAttribute(); + + if (d != null) + { + + if (prop.PropertyType == typeof(List)) + { + var arr = d.Value.ToString().Split(new char[] { ';' }); + var list = new List(); + foreach (var pname in arr) + { + PokemonId pi = PokemonId.Missingno; + if (Enum.TryParse(pname, true, out pi)) + { + list.Add(pi); + } + } + prop.SetValue(this, list); + } + else + prop.SetValue(this, d.Value); + + } + } + } + catch (Exception ex) + { + Debug.WriteLine(ex.Message); + } + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Settings/BerryUseFilter.cs b/PoGo.NecroBot.Logic/Model/Settings/BerryUseFilter.cs new file mode 100644 index 000000000..03cdf44a8 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/BerryUseFilter.cs @@ -0,0 +1,87 @@ +using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; +using POGOProtos.Enums; +using POGOProtos.Inventory.Item; + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + [JsonObject(Description = "", ItemRequired = Required.DisallowNull)] //Dont set Title + public class ItemUseFilter : BaseConfig + { + public ItemUseFilter() + { + Pokemons = new List(); + } + + public ItemUseFilter(int minIV, int minLV, int minCP, List pokemons, string op = "or", double catchChange=0.3, int maxUse=10) + { + UseItemMinIV = minIV; + CatchProbability = catchChange; + UseItemMinLevel = minLV; + UseItemMinCP = minCP; + Pokemons = pokemons; + Operator = op; + MaxItemsUsePerPokemon = maxUse; + UseIfExceedBagRecycleFilter = true; + } + + [NecroBotConfig(Key = "Min IV", Description = "Min IV needed to use this item", Position = 2)] + [DefaultValue(95)] + [Range(0, 100)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + public int UseItemMinIV {get; set;} + + [NecroBotConfig(Key = "Min Level", Description = "Min LV needed to use this item", Position = 3)] + [DefaultValue(20)] + [Range(0, 100)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + public int UseItemMinLevel { get; set; } + + [NecroBotConfig(Key = "Min CP", Description = "Min CP needed to use this item", Position = 4)] + [DefaultValue(500)] + [Range(0, 9999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + public int UseItemMinCP { get; set; } + + [NecroBotConfig(Key = "Operator", Position = 6, Description = "The operator logic use to check for using the item")] + [DefaultValue("or")] + [EnumDataType(typeof(Operator))] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 5)] + public string Operator { get; set; } + + [NecroBotConfig(Key = "Catch Probability ", Position = 6, Description = "Catch Probability when using this Item")] + [DefaultValue(0.5)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 6)] + public double CatchProbability { get; set; } + + [NecroBotConfig(Key = "Pokemons", Position = 6, Description = "Define list of pokemon to apply these berries, empty to allow all")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 6)] + public List Pokemons { get; set; } + + [NecroBotConfig(Key = "MaxItemsUse", Position = 7, Description = "Define how many items will be used for the same pokemon")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 7)] + + public int MaxItemsUsePerPokemon { get; set; } + + + [NecroBotConfig(Key = "UseIfExceedFilter", Position = 8, Description = "If your items exceed the recycle filter, it will always use this when possible")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 7)] + [DefaultValue(true)] + public bool UseIfExceedBagRecycleFilter { get; set; } + + internal static Dictionary Default() + { + return new Dictionary + { + //use for hight catch flee + {ItemId.ItemNanabBerry, new ItemUseFilter(50, 20, 500, new List() { PokemonId.Abra, PokemonId.Dragonite, PokemonId.Venusaur, PokemonId.Blastoise, PokemonId.Charizard } , "and", 0.3 , 20) }, + //use for hight CP, low probability catch + { ItemId.ItemRazzBerry, new ItemUseFilter(90, 0, 1500, new List() { } , "and", 0.3 , 20) }, + //use for candy pokemon + { ItemId.ItemPinapBerry, new ItemUseFilter(0, 0, 0, new List() { PokemonId.Lapras, PokemonId.Snorlax, PokemonId.Chansey, PokemonId.Dratini, PokemonId.Porygon, PokemonId.Porygon2 } , "or", 1 ,10) }, + }; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Settings/CaptchaConfig.cs b/PoGo.NecroBot.Logic/Model/Settings/CaptchaConfig.cs new file mode 100644 index 000000000..e6a36cd3f --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/CaptchaConfig.cs @@ -0,0 +1,89 @@ +using System.ComponentModel; +using Newtonsoft.Json; + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + [JsonObject(Title = "Captcha Config", Description = "Setup captcha config", ItemRequired = Required.DisallowNull)] + public class CaptchaConfig : BaseConfig + { + public CaptchaConfig() : base() + { + } + + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + [NecroBotConfig(Position = 1, Description = "Display captchas on browser and allow resolving manually")] + public bool AllowManualCaptchaResolve { get; set; } + + [DefaultValue(120)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 2)] + [NecroBotConfig(Position = 2, Description = "Number of seconds bot will wait for you to resolve captcha, if after the time set and captcha havent resolved yet, the bot will continue ")] + public int ManualCaptchaTimeout { get; set; } + + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 3)] + [NecroBotConfig(Position = 3, Description = "Play an alert sound when you receive a captcha")] + public bool PlaySoundOnCaptcha { get; set; } + + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 4)] + [NecroBotConfig(Position = 4, Description = "Display captchas on top of your screen")] + public bool DisplayOnTop { get; set; } + + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 4)] + [NecroBotConfig(Position = 5, Description = "Enable Auto captcha solving with 2Captcha")] + public bool Enable2Captcha { get; set; } + + [DefaultValue(3)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 6)] + [NecroBotConfig(Position = 6, Description = "Number of times bot will try to resolve captchas automatically")] + public int AutoCaptchaRetries { get; set; } + + [DefaultValue("")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 7)] + [NecroBotConfig(Position = 7, Description = "API Key to use 2Captcha")] + public string TwoCaptchaAPIKey { get; set; } + + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 4)] + [NecroBotConfig(Position = 8, Description = "Enable Auto captcha solving with Anti-Captcha")] + public bool EnableAntiCaptcha { get; set; } + + [DefaultValue("")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 4)] + [NecroBotConfig(Position = 9, Description = "API Key to use Anti-Captcha")] + public string AntiCaptchaAPIKey { get; set; } + + [DefaultValue("")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 4)] + [NecroBotConfig(Position = 10, Description = "Proxy host to be used by captcha service")] + public string ProxyHost { get; set; } + + [DefaultValue(3128)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 4)] + [NecroBotConfig(Position = 11, Description = "Proxy port to be used by captcha service")] + public int ProxyPort { get; set; } + + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 4)] + [NecroBotConfig(Position = 12, Description = "Enable Auto captcha solving with CaptchaSolutions.com")] + public bool EnableCaptchaSolutions { get; set; } + + [DefaultValue("")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 4)] + [NecroBotConfig(Position = 13, Description = "API Key to use CaptchaSolutions")] + public string CaptchaSolutionAPIKey { get; set; } + + [DefaultValue("")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 4)] + [NecroBotConfig(Position = 14, Description = "Secret Key to use for CaptchaSolutions")] + public string CaptchaSolutionsSecretKey { get; set; } + + [DefaultValue(120)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 4)] + [NecroBotConfig(Position = 15, Description = "Timeout for auto captcha solving")] + public int AutoCaptchaTimeout { get; set; } + + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Settings/CatchConfig.cs b/PoGo.NecroBot.Logic/Model/Settings/CatchConfig.cs new file mode 100644 index 000000000..3cee19faf --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/CatchConfig.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using Newtonsoft.Json; +using POGOProtos.Enums; + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + [JsonObject(Title = "Catch Config", Description = "Set your catch settings.", ItemRequired = Required.DisallowNull)] + public class CatchConfig + { + internal static List PokemonsToIgnoreDefault() + { + return new List + { + //criteria: most common + //PokemonId.Caterpie, + //PokemonId.Weedle, + //PokemonId.Pidgey, + //PokemonId.Rattata, + //PokemonId.Spearow, + //PokemonId.Zubat, + //PokemonId.Doduo, + //criteria: regional + PokemonId.Tauros, + PokemonId.Kangaskhan, + PokemonId.MrMime, + PokemonId.Farfetchd + }; + } + + internal static List PokemonsToUseMasterballDefault() + { + return new List + { + PokemonId.Articuno, + PokemonId.Zapdos, + PokemonId.Moltres, + PokemonId.Mew, + PokemonId.Mewtwo + }; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Settings/CatchFilter.cs b/PoGo.NecroBot.Logic/Model/Settings/CatchFilter.cs new file mode 100644 index 000000000..858729fdd --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/CatchFilter.cs @@ -0,0 +1,75 @@ +using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; +using POGOProtos.Enums; + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + [JsonObject(Description = "", ItemRequired = Required.DisallowNull)] //Dont set Title + public class CatchFilter + { + public CatchFilter() + { + Moves = new List>(); + EnableCatchFilter = true; + } + + + public CatchFilter(int minIV, int minLV, int minCP, string op = "or", List> moves = null) + { + EnableCatchFilter = true; + MinIV = minIV; + Moves = moves ?? new List>(); + MinLV = minLV; + MinCP = MinCP; + Operator = op; + } + + [NecroBotConfig(IsPrimaryKey = true, Key = "Enable Catch filter", Description = "Allows bot to check for filter for catching specific pokemon(s)", Position = 1)] + [DefaultValue(false)] + [JsonIgnore] + public bool EnableCatchFilter { get; set; } + + [NecroBotConfig(Key = "Min IV", Description = "Min IV for catching pokemon", Position = 2)] + [DefaultValue(95)] + [Range(0, 100)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + public int MinIV {get; set;} + + [NecroBotConfig(Key = "Min LV", Description = "Min LV for auto catching pokemon", Position = 3)] + [DefaultValue(95)] + [Range(0, 999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + public int MinLV { get; set; } + + [NecroBotConfig(Key = "Min CP", Description = "Min CP for auto catching pokemon", Position = 4)] + [DefaultValue(10)] + [Range(0, 9999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + public int MinCP { get; set; } + + [NecroBotConfig(Key = "Moves", Description = "List of desired moves for catching pokemon", Position = 5)] + [DefaultValue(null)] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 2)] + public List> Moves { get; set; } + + [NecroBotConfig(Key = "Operator", Position = 6, Description = "The operator logic use to check for catch")] + [DefaultValue("or")] + [EnumDataType(typeof(Operator))] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 5)] + public string Operator { get; set; } + + internal static Dictionary Default() + { + return new Dictionary + { + {PokemonId.Lapras, new CatchFilter(0, 0, 0)}, + {PokemonId.Dratini, new CatchFilter(0, 0, 0)}, + {PokemonId.Dragonite, new CatchFilter(0, 0, 0)}, + {PokemonId.Snorlax, new CatchFilter(0, 0, 0)}, + {PokemonId.Zubat, new CatchFilter(100, 100, 100, "and", new List>() { })} + }; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Settings/CatchSettings.cs b/PoGo.NecroBot.Logic/Model/Settings/CatchSettings.cs new file mode 100644 index 000000000..f01d2565e --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/CatchSettings.cs @@ -0,0 +1,86 @@ +using System.Collections.Generic; +using Newtonsoft.Json; +using POGOProtos.Enums; + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + [JsonObject(Title = "Catch Settings", Description = "", ItemRequired = Required.DisallowNull)] + public class CatchSettings + { + public CatchSettings() + { + } + + public CatchSettings(List locations, List pokemon) + { + Pokemon = pokemon; + } + + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore, Order = 2)] + public List Pokemon = new List(); + + internal static CatchSettings Default() + { + return new CatchSettings + { + Pokemon = new List + { + PokemonId.Venusaur, + PokemonId.Charizard, + PokemonId.Blastoise, + PokemonId.Beedrill, + PokemonId.Raichu, + PokemonId.Sandslash, + PokemonId.Nidoking, + PokemonId.Nidoqueen, + PokemonId.Clefable, + PokemonId.Ninetales, + PokemonId.Golbat, + PokemonId.Vileplume, + PokemonId.Golduck, + PokemonId.Primeape, + PokemonId.Arcanine, + PokemonId.Poliwrath, + PokemonId.Alakazam, + PokemonId.Machamp, + PokemonId.Golem, + PokemonId.Rapidash, + PokemonId.Slowbro, + PokemonId.Farfetchd, + PokemonId.Muk, + PokemonId.Cloyster, + PokemonId.Gengar, + PokemonId.Exeggutor, + PokemonId.Marowak, + PokemonId.Hitmonchan, + PokemonId.Lickitung, + PokemonId.Rhydon, + PokemonId.Chansey, + PokemonId.Kangaskhan, + PokemonId.Starmie, + PokemonId.MrMime, + PokemonId.Scyther, + PokemonId.Magmar, + PokemonId.Electabuzz, + PokemonId.Jynx, + PokemonId.Gyarados, + PokemonId.Lapras, + PokemonId.Ditto, + PokemonId.Vaporeon, + PokemonId.Jolteon, + PokemonId.Flareon, + PokemonId.Porygon, + PokemonId.Kabutops, + PokemonId.Aerodactyl, + PokemonId.Snorlax, + PokemonId.Articuno, + PokemonId.Zapdos, + PokemonId.Moltres, + PokemonId.Dragonite, + PokemonId.Mewtwo, + PokemonId.Mew + } + }; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Settings/ClientSettings.cs b/PoGo.NecroBot.Logic/Model/Settings/ClientSettings.cs new file mode 100644 index 000000000..c149823e5 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/ClientSettings.cs @@ -0,0 +1,304 @@ +using System; +using PoGo.NecroBot.Logic.Service.Elevation; +using PoGo.NecroBot.Logic.Utils; +using PokemonGo.RocketAPI; +using PokemonGo.RocketAPI.Enums; + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + public class ClientSettings : ISettings + { + // Never spawn at the same position. + private readonly Random _rand = new Random(); + + private readonly GlobalSettings _settings; + private readonly IElevationService _elevationService; + + public ClientSettings(GlobalSettings settings, IElevationService elevationService) + { + _settings = settings; + _elevationService = elevationService; + } + + #region Auth Config Values + + public bool UseProxy + { + get { return _settings.Auth.ProxyConfig.UseProxy; } + set { _settings.Auth.ProxyConfig.UseProxy = value; } + } + + public string UseProxyHost + { + get { return _settings.Auth.ProxyConfig.UseProxyHost; } + set { _settings.Auth.ProxyConfig.UseProxyHost = value; } + } + + public string UseProxyPort + { + get { return _settings.Auth.ProxyConfig.UseProxyPort; } + set { _settings.Auth.ProxyConfig.UseProxyPort = value; } + } + + public bool UseProxyAuthentication + { + get { return _settings.Auth.ProxyConfig.UseProxyAuthentication; } + set { _settings.Auth.ProxyConfig.UseProxyAuthentication = value; } + } + + public string UseProxyUsername + { + get { return _settings.Auth.ProxyConfig.UseProxyUsername; } + set { _settings.Auth.ProxyConfig.UseProxyUsername = value; } + } + + public string UseProxyPassword + { + get { return _settings.Auth.ProxyConfig.UseProxyPassword; } + set { _settings.Auth.ProxyConfig.UseProxyPassword = value; } + } + + public string GoogleRefreshToken + { + get { return null; } + set { GoogleRefreshToken = null; } + } + + AuthType ISettings.AuthType + { + get { return _settings.Auth.CurrentAuthConfig.AuthType; } + + set { _settings.Auth.CurrentAuthConfig.AuthType = value; } + } + + string ISettings.Username + { + get { return _settings.Auth.CurrentAuthConfig.Username; } + + set { _settings.Auth.CurrentAuthConfig.Username = value; } + } + + string ISettings.Password + { + get { return _settings.Auth.CurrentAuthConfig.Password; } + + set { _settings.Auth.CurrentAuthConfig.Password = value; } + } + + bool ISettings.AutoExitBotIfAccountFlagged + { + get { return _settings.Auth.CurrentAuthConfig.AutoExitBotIfAccountFlagged; } + + set { _settings.Auth.CurrentAuthConfig.AutoExitBotIfAccountFlagged = value; } + } + + double ISettings.AccountLatitude + { + get { return _settings.Auth.CurrentAuthConfig.AccountLatitude; } + set { _settings.Auth.CurrentAuthConfig.AccountLatitude = value; } + } + + double ISettings.AccountLongitude + { + get { return _settings.Auth.CurrentAuthConfig.AccountLongitude; } + set { _settings.Auth.CurrentAuthConfig.AccountLongitude = value; } + } + + bool ISettings.AccountActive + { + get { return _settings.Auth.CurrentAuthConfig.AccountActive; } + set { _settings.Auth.CurrentAuthConfig.AccountActive = value; } + } + + string ISettings.Country + { + get { return _settings.Auth.CurrentAuthConfig.Country; } + set { _settings.Auth.CurrentAuthConfig.Country = value; } + } + + string ISettings.Language + { + get { return _settings.Auth.CurrentAuthConfig.Language; } + set { _settings.Auth.CurrentAuthConfig.Language = value; } + } + + string ISettings.TimeZone + { + get { return _settings.Auth.CurrentAuthConfig.TimeZone; } + set { _settings.Auth.CurrentAuthConfig.TimeZone = value; } + } + + string ISettings.POSIX + { + get { return _settings.Auth.CurrentAuthConfig.POSIX; } + set { _settings.Auth.CurrentAuthConfig.POSIX = value; } + } + + #endregion Auth Config Values + + #region Device Config Values + + public string DevicePlatform + { + get { return _settings.Auth.DeviceConfig.DevicePlatform; } + set { _settings.Auth.DeviceConfig.DevicePlatform = value; } + } + + string DevicePackageName + { + get { return _settings.Auth.DeviceConfig.DevicePackageName; } + set { _settings.Auth.DeviceConfig.DevicePackageName = value; } + } + + string ISettings.DeviceId + { + get { return _settings.Auth.DeviceConfig.DeviceId; } + set { _settings.Auth.DeviceConfig.DeviceId = value; } + } + + string ISettings.AndroidBoardName + { + get { return _settings.Auth.DeviceConfig.AndroidBoardName; } + set { _settings.Auth.DeviceConfig.AndroidBoardName = value; } + } + + string ISettings.AndroidBootloader + { + get { return _settings.Auth.DeviceConfig.AndroidBootloader; } + set { _settings.Auth.DeviceConfig.AndroidBootloader = value; } + } + + string ISettings.DeviceBrand + { + get { return _settings.Auth.DeviceConfig.DeviceBrand; } + set { _settings.Auth.DeviceConfig.DeviceBrand = value; } + } + + string ISettings.DeviceModel + { + get { return _settings.Auth.DeviceConfig.DeviceModel; } + set { _settings.Auth.DeviceConfig.DeviceModel = value; } + } + + string ISettings.DeviceModelIdentifier + { + get { return _settings.Auth.DeviceConfig.DeviceModelIdentifier; } + set { _settings.Auth.DeviceConfig.DeviceModelIdentifier = value; } + } + + string ISettings.DeviceModelBoot + { + get { return _settings.Auth.DeviceConfig.DeviceModelBoot; } + set { _settings.Auth.DeviceConfig.DeviceModelBoot = value; } + } + + string ISettings.HardwareManufacturer + { + get { return _settings.Auth.DeviceConfig.HardwareManufacturer; } + set { _settings.Auth.DeviceConfig.HardwareManufacturer = value; } + } + + string ISettings.HardwareModel + { + get { return _settings.Auth.DeviceConfig.HardwareModel; } + set { _settings.Auth.DeviceConfig.HardwareModel = value; } + } + + string ISettings.FirmwareBrand + { + get { return _settings.Auth.DeviceConfig.FirmwareBrand; } + set { _settings.Auth.DeviceConfig.FirmwareBrand = value; } + } + + string ISettings.FirmwareTags + { + get { return _settings.Auth.DeviceConfig.FirmwareTags; } + set { _settings.Auth.DeviceConfig.FirmwareTags = value; } + } + + string ISettings.FirmwareType + { + get { return _settings.Auth.DeviceConfig.FirmwareType; } + set { _settings.Auth.DeviceConfig.FirmwareType = value; } + } + + string ISettings.FirmwareFingerprint + { + get { return _settings.Auth.DeviceConfig.FirmwareFingerprint; } + set { _settings.Auth.DeviceConfig.FirmwareFingerprint = value; } + } + + #endregion Device Config Values + + double ISettings.DefaultLatitude + { + get + { + return _settings.LocationConfig.DefaultLatitude + _rand.NextDouble() * + ((double) _settings.LocationConfig.MaxSpawnLocationOffset / 111111); + } + + set { _settings.LocationConfig.DefaultLatitude = value; } + } + + double ISettings.DefaultLongitude + { + get + { + return _settings.LocationConfig.DefaultLongitude + + _rand.NextDouble() * + ((double) _settings.LocationConfig.MaxSpawnLocationOffset / 111111 / + Math.Cos(_settings.LocationConfig.DefaultLatitude)); + } + + set { _settings.LocationConfig.DefaultLongitude = value; } + } + + double ISettings.DefaultAltitude + { + get + { + return LocationUtils.GetElevation(_elevationService, _settings.LocationConfig.DefaultLatitude, + _settings.LocationConfig.DefaultLongitude).Result; + } + + set { } + } + + public bool UsePogoDevHashServer + { + get { return _settings.Auth.APIConfig.UsePogoDevAPI; } + set { _settings.Auth.APIConfig.UsePogoDevAPI = value; } + } + + public bool UseCustomAPI + { + get { return _settings.Auth.APIConfig.UseCustomAPI; } + set { _settings.Auth.APIConfig.UseCustomAPI = value; } + } + + public string AuthAPIKey + { + get { return _settings.Auth.APIConfig.AuthAPIKey; } + set { _settings.Auth.APIConfig.AuthAPIKey = value; } + } + + public bool DisplayVerboseLog + { + get { return _settings.Auth.APIConfig.DiplayHashServerLog; } + set { _settings.Auth.APIConfig.DiplayHashServerLog = value; } + } + + public string UrlHashServices + { + get { return _settings.Auth.APIConfig.UrlHashServices; } + set { _settings.Auth.APIConfig.UrlHashServices = value; } + } + + public string EndPoint + { + get { return _settings.Auth.APIConfig.EndPoint; } + set { _settings.Auth.APIConfig.EndPoint = value; } + } + } +} diff --git a/PoGo.NecroBot.Logic/Model/Settings/ConsoleConfig.cs b/PoGo.NecroBot.Logic/Model/Settings/ConsoleConfig.cs new file mode 100644 index 000000000..656833b10 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/ConsoleConfig.cs @@ -0,0 +1,40 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + [JsonObject( + Title = "Console Config", + Description = "Set your console settings.", + ItemRequired = Required.DisallowNull + )] + public class ConsoleConfig : BaseConfig + { + public ConsoleConfig() : base() + { + } + + [DefaultValue("en")] + [RegularExpression(@"^[a-zA-Z]{2}(-[a-zA-Z]{2})*$")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + [NecroBotConfig(SheetName = "ConsoleConfig", Position = 1, Description = "Language Transation code (ex: en = english)")] + public string TranslationLanguageCode { get; set; } + + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 2)] + [NecroBotConfig(Position = 2, Description = "If enabled, will display a welcome message on startup")] + public bool StartupWelcomeDelay { get; set; } + + [DefaultValue(2)] + [Range(0, 100)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 3)] + [NecroBotConfig(Position = 3, Description = "Amount Of Pokemon To Display On Start")] + public int AmountOfPokemonToDisplayOnStart { get; set; } + + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 4)] + [NecroBotConfig(Position = 4, Description = "Detailed Inventory Count to Display Before Recycling")] + public bool DetailedCountsBeforeRecycling { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Settings/CustomCatchConfig.cs b/PoGo.NecroBot.Logic/Model/Settings/CustomCatchConfig.cs new file mode 100644 index 000000000..18d4ae9b3 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/CustomCatchConfig.cs @@ -0,0 +1,83 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + [JsonObject(Title = "Custom Catch Config", Description = "Set your custom catch settings.", ItemRequired = Required.DisallowNull)] + public class CustomCatchConfig :BaseConfig + { + public CustomCatchConfig() : base() + { + } + + [NecroBotConfig(Description = "Allows bot to simulate throws as humanlike as possible", Position = 1)] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + public bool EnableHumanizedThrows { get; set; } + + [NecroBotConfig(Description = "Allow bot throws to miss pokemon", Position = 2)] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 2)] + public bool EnableMissedThrows { get; set; } + + [NecroBotConfig(Description = "Set percentage for how many pokemon bot can miss", Position = 3)] + [DefaultValue(25)] + [Range(0, 100)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 3)] + public int ThrowMissPercentage { get; set; } + + [NecroBotConfig(Description = "Set percentage for how many bot can throw balls with nice hits", Position = 4)] + [DefaultValue(40)] + [Range(0, 100)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 4)] + public int NiceThrowChance { get; set; } + + [NecroBotConfig(Description = "Set percentage for how many bot can throw balls with great hits", Position = 5)] + [DefaultValue(30)] + [Range(0, 100)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 5)] + public int GreatThrowChance { get; set; } + + [NecroBotConfig(Description = "Set percentage for how many bot can throw balls with excellent hits", Position = 6)] + [DefaultValue(10)] + [Range(0, 100)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 6)] + public int ExcellentThrowChance { get; set; } + + [NecroBotConfig(Description = "Set percentage for how many bot can throw balls with curve hits", Position = 7)] + [DefaultValue(90)] + [Range(0, 100)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 7)] + public int CurveThrowChance { get; set; } + + [NecroBotConfig(Description = "Forces bot to get a great throw if IV is higher than this value", Position = 8)] + [DefaultValue(90.00)] + [Range(0, 100)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 8)] + public double ForceGreatThrowOverIv { get; set; } + + [NecroBotConfig(Description = "Forces bot to get a excellent throw if IV is higher than this value", Position = 9)] + [DefaultValue(95.00)] + [Range(0, 100)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 9)] + public double ForceExcellentThrowOverIv { get; set; } + + [NecroBotConfig(Description = "Forces bot to get a great throw if CP higher than this value", Position = 10)] + [DefaultValue(1000)] + [Range(0, 9999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 10)] + public int ForceGreatThrowOverCp { get; set; } + + [NecroBotConfig(Description = "Forces bot to get an excellent throw if CP higher than this value", Position = 11)] + [DefaultValue(1500)] + [Range(0, 9999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 11)] + public int ForceExcellentThrowOverCp { get; set; } + + [NecroBotConfig(Description = "Allow bot use transfer filter to catch pokemon - ", Position = 12)] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 12)] + public bool UseTransferFilterToCatch { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Settings/DataSharingConfig.cs b/PoGo.NecroBot.Logic/Model/Settings/DataSharingConfig.cs new file mode 100644 index 000000000..9c33141c6 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/DataSharingConfig.cs @@ -0,0 +1,55 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + public class DataSharingConfig : BaseConfig + { + public DataSharingConfig() : base() + { + } + + [NecroBotConfig(Description = "ALlow bot send pokemon data to share data serice", Position = 1)] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + public bool EnableSyncData + { + get; set; + } + + //may need add support for web services/wcf/resful later. for now we use most modern web socket things. + [NecroBotConfig(Description = "Data Service Endpoint", Position = 2)] + [DefaultValue("ws://www.mypogosnipers.com/socket.io/?EIO=3&transport=websocket")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 2)] + public string DataRecieverURL { get; set; } + + [NecroBotConfig(Description = "Allows bot to auto snipe pokemon whenever it has feed sent back from server", Position = 3)] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + public bool AutoSnipe { get; set; } + + [NecroBotConfig(Description = "A unique ID you make by yourself to do a manual snipe from mypogosnipers.com. You have to make sure it is unique", Position = 4)] + [DefaultValue("")] + [MaxLength(256)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + + public string DataServiceIdentification { get; set; } + + [NecroBotConfig(Description = "The authorized access key to use snipe data", Position = 4)] + [DefaultValue("")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + public string SnipeDataAccessKey { get; set; } + + [NecroBotConfig(Description = "Enable failover data servers", Position = 5)] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + public bool EnableFailoverDataServers { get; set; } + + [NecroBotConfig(Description = "List of servers that bot will connect to when primary server is down or can't be connected to", Position = 6)] + [DefaultValue("ws://s1.mypogosnipers.com/socket.io/?EIO=3&transport=websocket;ws://s2.mypogosnipers.com/socket.io/?EIO=3&transport=websocket;ws://necrosocket.herokuapp.com/socket.io/?EIO=3&transport=websocket")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + public string FailoverDataServers { get; set; } + + } +} diff --git a/PoGo.NecroBot.Logic/Model/Settings/DeviceConfig.cs b/PoGo.NecroBot.Logic/Model/Settings/DeviceConfig.cs new file mode 100644 index 000000000..ac97e8c65 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/DeviceConfig.cs @@ -0,0 +1,155 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; +using System.IO; +using System.Text; +using System.Security.Cryptography; +using PokemonGo.RocketAPI.Helpers; + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + [JsonObject( + Title = "Device Config", + Description = "Set your device settings (set \"DevicePackageName\" to \"random\" for auto-generated device). Set \"DevicePlatform\" to \"android\" or \"ios\".", + ItemRequired = Required.DisallowNull + )] + public class DeviceConfig + { + internal enum DevicePlatformType + { + android, + ios + } + + [DefaultValue("ios")] + [EnumDataType(typeof(DevicePlatformType))] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + public string DevicePlatform = "ios"; + + [DefaultValue("custom")] + [MinLength(0)] + [MaxLength(32)] + [RegularExpression(@"[a-zA-Z0-9_\-\.\s]")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 2)] + public string DevicePackageName = "custom"; + + [DefaultValue("2d207c0f186091c04abc7ff706a985ee")] + [MinLength(16)] + [MaxLength(32)] + [RegularExpression(@"^[0-9A-Fa-f]+$")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 3)] + public string DeviceId = DeviceInfoHelper.GetRandomIosDevice().DeviceId; + + [DefaultValue(null)] + [MinLength(0)] + [MaxLength(32)] + [RegularExpression(@"[a-zA-Z0-9_\-\.\s]")] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 4)] + public string AndroidBoardName; + + [DefaultValue(null)] + [MinLength(0)] + [MaxLength(32)] + [RegularExpression(@"[a-zA-Z0-9_\-\.\s]")] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 5)] + public string AndroidBootloader; + + [DefaultValue("Apple")] + [MinLength(0)] + [MaxLength(32)] + [RegularExpression(@"[a-zA-Z0-9_\-\.\s]")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 6)] + public string DeviceBrand = "Apple"; + + [DefaultValue("iPhone")] + [MinLength(0)] + [MaxLength(32)] + [RegularExpression(@"[a-zA-Z0-9_\-\.\s]")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 7)] + public string DeviceModel = "iPhone"; + + [DefaultValue(null)] + [MinLength(0)] + [MaxLength(32)] + [RegularExpression(@"[a-zA-Z0-9_\-\.\s]")] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 8)] + public string DeviceModelIdentifier; + + [DefaultValue("iPhone9,3")] + [MinLength(0)] + [MaxLength(32)] + [RegularExpression(@"[a-zA-Z0-9_\-\.\s]")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 9)] + public string DeviceModelBoot = "iPhone9,3"; + + [DefaultValue("Apple")] + [MinLength(0)] + [MaxLength(32)] + [RegularExpression(@"[a-zA-Z0-9_\-\.\s]")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 10)] + public string HardwareManufacturer = "Apple"; + + [DefaultValue("D101AP")] + [MinLength(0)] + [MaxLength(32)] + [RegularExpression(@"[a-zA-Z0-9_\-\.\s]")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 11)] + public string HardwareModel = "D101AP"; + + [DefaultValue("iOS")] + [MinLength(0)] + [MaxLength(32)] + [RegularExpression(@"[a-zA-Z0-9_\-\.\s]")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 12)] + public string FirmwareBrand = "iOS"; + + [DefaultValue(null)] + [MinLength(0)] + [MaxLength(32)] + [RegularExpression(@"[a-zA-Z0-9_\-\.\s]")] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 13)] + public string FirmwareTags; + + [DefaultValue("11.1.0")] + [MinLength(0)] + [MaxLength(32)] + [RegularExpression(@"[a-zA-Z0-9_\-\.\s]")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 14)] + public string FirmwareType = "11.1.0"; + + [DefaultValue(null)] + [MinLength(0)] + [MaxLength(128)] + [RegularExpression(@"[[a-zA-Z0-9_\-\/\.\:]")] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 15)] + public string FirmwareFingerprint; + + [DefaultValue(false)] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 16)] + public bool UseRandomDeviceId; + + public static string GetDeviceId(string username) + { + Directory.CreateDirectory($"config\\{username}"); + string keyFile = $"config\\{username}\\device.id"; + + if (File.Exists(keyFile)) return File.ReadAllText(keyFile); + + string hashUsername = ""; + using (SHA1Managed sha1 = new SHA1Managed()) + { + var hash = sha1.ComputeHash(Encoding.UTF8.GetBytes(username + Path.GetRandomFileName())); + var sb = new StringBuilder(hash.Length * 2); + + foreach (byte b in hash) + { + sb.Append(b.ToString("X2")); + } + + hashUsername = sb.ToString(); + } + File.WriteAllText(keyFile, hashUsername); + return hashUsername; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Settings/EvolveConfig.cs b/PoGo.NecroBot.Logic/Model/Settings/EvolveConfig.cs new file mode 100644 index 000000000..7c5312ab1 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/EvolveConfig.cs @@ -0,0 +1,139 @@ +using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; +using POGOProtos.Enums; + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + [JsonObject(Title = "Evolve Config", Description = "Set your evolve settings.", ItemRequired = Required.DisallowNull)] + public class EvolveConfig : BaseConfig + { + internal static List PokemonsToEvolveDefault() + { + return new List + { + /*NOTE: keep all the end-of-line commas exept for the last one or an exception will be thrown! + criteria: 12 candies*/ + PokemonId.Caterpie, + PokemonId.Weedle, + PokemonId.Pidgey, + /*criteria: 25 candies*/ + //PokemonId.Bulbasaur, + //PokemonId.Charmander, + //PokemonId.Squirtle, + PokemonId.Rattata + //PokemonId.NidoranFemale, + //PokemonId.NidoranMale, + //PokemonId.Oddish, + //PokemonId.Poliwag, + //PokemonId.Abra, + //PokemonId.Machop, + //PokemonId.Bellsprout, + //PokemonId.Geodude, + //PokemonId.Gastly, + //PokemonId.Eevee, + //PokemonId.Dratini, + /*criteria: 50 candies commons*/ + //PokemonId.Spearow, + //PokemonId.Ekans, + //PokemonId.Zubat, + //PokemonId.Paras, + //PokemonId.Venonat, + //PokemonId.Psyduck, + //PokemonId.Slowpoke, + //PokemonId.Doduo, + //PokemonId.Drowzee, + //PokemonId.Krabby, + //PokemonId.Horsea, + //PokemonId.Goldeen, + //PokemonId.Staryu + }; + } + + public EvolveConfig() : base() + { + } + + #region Filter + [NecroBotConfig(Description = "Lets the bot evolve all pokemons with enough candy and listed in PokemonEvolveFilter", Position = 10)] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 10)] + public bool EvolvePokemonsThatMatchFilter { get; set; } + + [NecroBotConfig(Description = "Lets the bot evolve any pokemon (also if not in PokemonEvolveFilter) with enough candy and at least an IV as in EvolveAnyPokemonAboveIvValue", Position = 20)] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 20)] + public bool EvolveAnyPokemonAboveIv { get; set; } + + [NecroBotConfig(Description = "If EvolveAnyPokemonAboveIv is true, this is the IV threshold for evolving a pokemon", Position = 30)] + [DefaultValue(95)] + [Range(0, 100)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 30)] + public float EvolveAnyPokemonAboveIvValue { get; set; } + #endregion + + #region Trigger + [NecroBotConfig(Description = "A pokemon will get evolved right away if enough candy and listed in PokemonEvolveFilter. This will lead to single evolutions", Position = 40)] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 40)] + public bool TriggerAsSoonAsFilterIsMatched { get; set; } + + [NecroBotConfig(Description = "Lets the bot bulk evolve all possible evolutions if at least as many evolutions exist as specified in TriggerOnEvolutionCountValue", Position = 50)] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 50)] + public bool TriggerOnEvolutionCount { get; set; } + + [NecroBotConfig(Description = "If TriggerOnEvolutionCount is true, this is the number of evolutions needed to trigger bulk evolving", Position = 60)] + [DefaultValue(30)] + [Range(0, 999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 60)] + public int TriggerOnEvolutionCountValue { get; set; } + + [NecroBotConfig(Description = "Lets the bot bulk evolve all possible evolutions if pokemon storage usage is at least as specified in TriggerOnStorageUsagePercentageValue", Position = 70)] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 70)] + public bool TriggerOnStorageUsagePercentage { get; set; } + + [NecroBotConfig(Description = "If TriggerOnStorageUsagePercentage is true, this is the percentage threshold of storage usage to trigger bulk evolving", Position = 80)] + [DefaultValue(95)] + [Range(0, 100)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 80)] + public double TriggerOnStorageUsagePercentageValue { get; set; } + + [NecroBotConfig(Description = "Lets the bot bulk evolve all possible evolutions if pokemons in storage are at least as specified in TriggerOnStorageUsageAbsoluteValue", Position = 90)] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 90)] + public bool TriggerOnStorageUsageAbsolute { get; set; } + + [NecroBotConfig(Description = "If TriggerOnStorageUsageAbsolute is true, this is the absolute threshold of storage usage to trigger bulk evolving", Position = 100)] + [DefaultValue(240)] + [Range(0, 9999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 100)] + public int TriggerOnStorageUsageAbsoluteValue { get; set; } + + [NecroBotConfig(Description = "A pokemon will get evolved right away if enough candy, listed in PokemonEvolveFilter and a lucky egg is currently active", Position = 110)] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 110)] + public bool TriggerIfLuckyEggIsActive { get; set; } + #endregion + + #region When Evolving + [NecroBotConfig(Description = "When enabled, bot will not spend candies defined in PokemonEvolveFilter/MinCandiesBeforeEvolve, only using candies above that value", Position = 120)] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 120)] + public bool PreserveMinCandiesFromFilter { get; set; } + + [NecroBotConfig(Description = "Apply a lucky egg (if available) when bot is about to evolve in bulk as many pokemons as specified in ApplyLuckyEggOnEvolutionCountValue", Position = 130)] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 130)] + public bool ApplyLuckyEggOnEvolutionCount { get; set; } + + [NecroBotConfig(Description = "If ApplyLuckyEggOnEvolutionCount is true, this is the min number of evolutions needed to apply a lucky egg", Position = 140)] + [DefaultValue(30)] + [Range(0, 999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 140)] + public int ApplyLuckyEggOnEvolutionCountValue { get; set; } + #endregion + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Settings/EvolveFilter.cs b/PoGo.NecroBot.Logic/Model/Settings/EvolveFilter.cs new file mode 100644 index 000000000..3951164fe --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/EvolveFilter.cs @@ -0,0 +1,138 @@ +using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; +using POGOProtos.Enums; +using System; +using TinyIoC; +using PoGo.NecroBot.Logic.State; + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + [JsonObject(Description = "", ItemRequired = Required.DisallowNull)] //Dont set Title + public class EvolveFilter : BaseConfig, IPokemonFilter + { + public EvolveFilter() :base() + { + AffectToPokemons = new List(); + Moves = new List>(); + EnableEvolve = true; + Operator = "or"; + } + + + public EvolveFilter(double evolveIV, double evolveLV, double minCP, bool favorited = false,string evoOperator = "and", string evolveTo = "", List> moves = null, int minCandiesBeforeEvolve = 0) + { + Moves = new List>(); + if (moves != null) Moves = moves; + EnableEvolve = true; + MinIV = evolveIV; + MinLV = evolveLV; + EvolveTo = evolveTo; + MinCP = minCP; + Operator = evoOperator; + FavoritedOnly = favorited; + MinCandiesBeforeEvolve = minCandiesBeforeEvolve; + } + + [NecroBotConfig(IsPrimaryKey = true, Key = "Enable Envolve", Description = "Allows bot to auto-evolve pokemon", Position = 1)] + [DefaultValue(false)] + [JsonIgnore] + public bool EnableEvolve { get; set; } + + [NecroBotConfig(Key = "Evolve Min IV", Description = "Min IV allowed to auto-evolve", Position = 2)] + [DefaultValue(95)] + [Range(0, 100)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + public double MinIV { get; set; } + + [NecroBotConfig(Key = "Evolve Min LV", Description = "Min LV allowed to auto-evolve", Position = 3)] + [DefaultValue(95)] + [Range(0, 999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 2)] + public double MinLV { get; set; } + + [NecroBotConfig(Key = "Evolve Min CP", Description = "Min CP allowed to auto evolve", Position = 4)] + [DefaultValue(10)] + [Range(0, 9999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 3)] + public double MinCP { get; set; } + + [NecroBotConfig(Key = "Moves", Description = "List of desired moves for evolving", Position = 5)] + [DefaultValue(null)] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 4)] + public List> Moves { get; set; } + + [NecroBotConfig(Key = "Operator", Position = 6, Description = "The operator logic use to check for evolve")] + [DefaultValue("or")] + [EnumDataType(typeof(Operator))] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 5)] + public string Operator { get; set; } + + [NecroBotConfig(Key = "Evolve To", Position = 7, Description = "Select branch to envolve to for multiple branch pokemon like Poliwirl")] + [DefaultValue("")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 6)] + public string EvolveTo { get; set; } + + [NecroBotConfig(Key = "Affect To Pokemons", Position = 8, Description = "Sets the list of pokemon you want to use the same config for")] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 7)] + public List AffectToPokemons { get; set; } + + [JsonIgnore] + public PokemonId EvolveToPokemonId + { + get + { + PokemonId id = PokemonId.Missingno; + + if (Enum.TryParse(EvolveTo, out id)) + { + return id; + } + + return id; + } + } + + [NecroBotConfig(Key = "Evolve Favorite Only", Position = 9, Description = "If true, bot will only evolve pokemon that are favorited")] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 8)] + public bool FavoritedOnly { get; set; } + + [NecroBotConfig(Key = "Min Candies Before Evolve", Position = 10, Description = "If greater than 0, bot will not evolve right away, but instead keep transferring pokemon to save up at least min candies required by this value before evolving.")] + [DefaultValue(0)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 9)] + public int MinCandiesBeforeEvolve { get; set; } + + internal static Dictionary Default() + { + return new Dictionary + { + {PokemonId.Rattata, new EvolveFilter(0, 0, 0, false,"or") { + AffectToPokemons = new List() + { + PokemonId.Zubat, + PokemonId.Pidgey, + PokemonId.Caterpie, + PokemonId.Weedle, + } + }}, + + {PokemonId.Porygon, new EvolveFilter(100, 28, 500, false,"and",PokemonId.Porygon2.ToString())}, + {PokemonId.Gloom , new EvolveFilter(100, 28, 500, false,"and",PokemonId.Bellossom.ToString())} , + {PokemonId.Sunkern , new EvolveFilter(100, 28, 500, false,"and",PokemonId.Sunflora.ToString())} , + {PokemonId.Slowpoke, new EvolveFilter(100, 28, 500, false,"and",PokemonId.Slowking.ToString())}, + {PokemonId.Poliwhirl , new EvolveFilter(100, 28, 500, false,"and",PokemonId.Politoed.ToString())}, + {PokemonId.Seadra , new EvolveFilter(100, 28, 500, false,"and",PokemonId.Kingdra.ToString())}, + {PokemonId.Dratini, new EvolveFilter(100, 30, 800, false,"and")} + + }; + } + + public IPokemonFilter GetGlobalFilter() + { + // TODO move EvolveAnyPokemonAboveIv logic here + return null; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Settings/ExcelConfigAttribute.cs b/PoGo.NecroBot.Logic/Model/Settings/ExcelConfigAttribute.cs new file mode 100644 index 000000000..324eaadf3 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/ExcelConfigAttribute.cs @@ -0,0 +1,14 @@ +using System; + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + public class NecroBotConfigAttribute : Attribute + { + public string SheetName { get; set; } + public string Key { get; set; } + public string Description { get; set; } + public int Position { get; set; } + public bool IsPrimaryKey { get; set; } + public bool HiddenOnGui { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Settings/FilterUtil.cs b/PoGo.NecroBot.Logic/Model/Settings/FilterUtil.cs new file mode 100644 index 000000000..6564a6633 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/FilterUtil.cs @@ -0,0 +1,49 @@ +using POGOProtos.Enums; +using System; +using System.Collections.Generic; + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + public class FilterUtil + { + public static T GetApplyFilter(Dictionary source, PokemonId forPokemonId) where T : IPokemonFilter + { + if (source == null) return GetDefault(); + + try + { + if (source.ContainsKey(forPokemonId)) return source[forPokemonId]; + foreach (var item in source) + { + if (item.Value.AffectToPokemons != null && item.Value.AffectToPokemons.Contains(forPokemonId)) return item.Value; + } + return GetDefault(); + } + catch (Exception) + { + return GetDefault(); + } + } + + private static T GetDefault() where T : IPokemonFilter + { + var globalFilter = Activator.CreateInstance(); + return (T)globalFilter.GetGlobalFilter(); + } + + public static void UpdateFilterSetting(GlobalSettings globalSettings, Dictionary pokemonsTransferFilter, PokemonId id, T f) + { + if(pokemonsTransferFilter.ContainsKey(id)) + { + pokemonsTransferFilter[id] = f; + } + else + { + pokemonsTransferFilter.Add(id, f); + + } + string configFile = System.IO.Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "config\\config.json"); + globalSettings.Save(configFile); + } + } +} diff --git a/PoGo.NecroBot.Logic/Model/Settings/GUIConfig.cs b/PoGo.NecroBot.Logic/Model/Settings/GUIConfig.cs new file mode 100644 index 000000000..1698dcef2 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/GUIConfig.cs @@ -0,0 +1,30 @@ +using Newtonsoft.Json; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + [JsonObject(Title = "GUI Config", Description = "All settings related to GUI", ItemRequired = Required.DisallowNull)] + + public class GUIConfig : BaseConfig + { + public GUIConfig() : base() + { + } + + [NecroBotConfig(Description = "The number of seconds that the bot will display auto snipe data", Position = 1)] + [DefaultValue(150)] + [Range(0, 900)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + public int SnipeItemListDisplayTime { get; set; } + + [NecroBotConfig(Description = "Interval to refresh auto snipe data ", Position = 1)] + [DefaultValue(10)] + [Range(0, 30)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + public int SnipeListRefreshInterval { get; set; } + + + } + +} diff --git a/PoGo.NecroBot.Logic/Model/Settings/GlobalSettings.cs b/PoGo.NecroBot.Logic/Model/Settings/GlobalSettings.cs new file mode 100644 index 000000000..6ec07bb36 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/GlobalSettings.cs @@ -0,0 +1,1161 @@ +#region using directives + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Schema; +using Newtonsoft.Json.Schema.Generation; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.Logging; +using PoGo.NecroBot.Logic.Service.Elevation; +using PoGo.NecroBot.Logic.State; +using PokemonGo.RocketAPI.Enums; +using PokemonGo.RocketAPI.Extensions; +using POGOProtos.Enums; +using POGOProtos.Inventory.Item; + +#endregion + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + [JsonObject(Title = " Global Settings", Description = "Set your global settings.", ItemRequired = Required.DisallowNull)] + public class GlobalSettings + { + [JsonIgnore] + public AuthSettings Auth = new AuthSettings(); + [JsonIgnore] + public string GeneralConfigPath; + [JsonIgnore] + public string ProfileConfigPath; + [JsonIgnore] + public string ProfilePath; + + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + [NecroBotConfig(SheetName = "ConsoleConfig", Description = "Setting up the console output")] + public ConsoleConfig ConsoleConfig = new ConsoleConfig(); + + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + [NecroBotConfig(SheetName = "UpdateConfig", Description = "Setting up the auto checking for every time bot start up")] + public UpdateConfig UpdateConfig = new UpdateConfig(); + + [NecroBotConfig(SheetName = "WebsocketsConfig", Description = "Setting up the web socket that allow bot to communicate with Visualizer.")] + + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + public WebsocketsConfig WebsocketsConfig = new WebsocketsConfig(); + + [NecroBotConfig(SheetName = "LocationConfig", Description = "Setting up location setting for bot.")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + public LocationConfig LocationConfig = new LocationConfig(); + + [NecroBotConfig(SheetName = "TelegramConfig", Description = "Setting up Telegram API to allow control bot from Telegram .")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + public TelegramConfig TelegramConfig = new TelegramConfig(); + + [NecroBotConfig(SheetName = "GPXConfig", Description = "Setup GPS Pathing for bot.")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + public GpxConfig GPXConfig = new GpxConfig(); + + [NecroBotConfig(SheetName = "SnipeConfig", Description = "Setting up option for snipe.")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + public SnipeConfig SnipeConfig = new SnipeConfig(); + + [NecroBotConfig(SheetName = "HumanWalkSnipeConfig", Description = "Setting up option for human walk snipe.")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + public HumanWalkSnipeConfig HumanWalkSnipeConfig = new HumanWalkSnipeConfig(); + + [NecroBotConfig(SheetName = "DataSharingConfig", Description = "Setting up data socket sharing.")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + public DataSharingConfig DataSharingConfig = new DataSharingConfig(); + + [NecroBotConfig(SheetName = "PokeStopConfig", Description = "Setting up for farming pokestop.")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + public PokeStopConfig PokeStopConfig = new PokeStopConfig(); + + [NecroBotConfig(SheetName = "GymConfig", Description = "Setting up for gym and battle.")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + public GymConfig GymConfig = new GymConfig(); + + [NecroBotConfig(SheetName = "PokemonConfig", Description = "Setting up for pokemon catching, evolve, transfer, upgrade.")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + public PokemonConfig PokemonConfig = new PokemonConfig(); + + [NecroBotConfig(SheetName = "RecycleConfig", Description = "Setting up for inventory cleanup.")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + public ItemRecycleConfig RecycleConfig = new ItemRecycleConfig(); + + [NecroBotConfig(SheetName = "CustomCatchConfig", Description = "Setting up for some custom parametter for catching.")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + public CustomCatchConfig CustomCatchConfig = new CustomCatchConfig(); + + [NecroBotConfig(SheetName = "PlayerConfig", Description = "Setting up for some custom parametter for bot perfom user action.")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + public PlayerConfig PlayerConfig = new PlayerConfig(); + + [NecroBotConfig(SheetName = "SoftBanConfig", Description = "Setting up for softban resolve.")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + public SoftBanConfig SoftBanConfig = new SoftBanConfig(); + + [NecroBotConfig(SheetName = "GoogleWalkConfig", Description = "Setup parametter for google walk such as api key, account, rules..")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + public GoogleWalkConfig GoogleWalkConfig = new GoogleWalkConfig(); + + [NecroBotConfig(SheetName = "YoursWalkConfig", Description = "Setup parametter for YoursWalkConfig walk such as api key, account, rules..")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + public YoursWalkConfig YoursWalkConfig = new YoursWalkConfig(); + + [NecroBotConfig(SheetName = "MapzenWalkConfig", Description = "Setup parametter for MapzenWalkConfig walk such as api key, account, rules..")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + public MapzenWalkConfig MapzenWalkConfig = new MapzenWalkConfig(); + + [NecroBotConfig(SheetName = "ItemRecycleFilter", Description = "Set number of each item we want bot to keep when it perfom recycle for get free space")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + public List ItemRecycleFilter = Settings.ItemRecycleFilter.ItemRecycleFilterDefault(); + + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + public List PokemonsNotToTransfer = TransferConfig.PokemonsNotToTransferDefault(); + + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + public CatchSettings PokemonToCatchLocally = CatchSettings.Default(); + + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + public List PokemonsToLevelUp = LevelUpConfig.PokemonsToLevelUpDefault(); + + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + public List PokemonsToIgnore = CatchConfig.PokemonsToIgnoreDefault(); + + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + + [NecroBotConfig(SheetName = "CaptchaConfig", Description = "Captcha config to define the way you prefer to resolve captcha")] + public CaptchaConfig CaptchaConfig = new CaptchaConfig(); + + /// + /// this will auto add account to auth.json with this systax by command lin + /// -i true -t abd123{0} -s 10 -e 20 -p abc1234 + /// or -init true -template abd123{0} -start 10 -eend 20 -password abc1234 + /// above command will add 20 account to auth.json default is ptc account. -g true will turn on google account + /// + /// + /// + /// + /// + /// + public void GenerateAccount(bool isGoogle, string template, int start, int end, string password) + { + List allAcc = new List(); + for (int i = start; i < end; i++) + { + allAcc.Add(new AuthConfig() + { + AuthType = isGoogle ? AuthType.Google : AuthType.Ptc, + Username = string.Format(template, i), + Password = password + }); + } + + Auth.Bots = allAcc.ToList(); + string json = JsonConvert.SerializeObject(Auth, Formatting.Indented,new StringEnumConverter() { CamelCaseText = true }); + + File.WriteAllText("config\\auth.json", json); + if (File.Exists("accounts.db")) File.Delete("accounts.db"); + } + + [NecroBotConfig(SheetName = "PokemonsTransferFilter", Description = "Setting up pokemon filter rules")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + public Dictionary PokemonsTransferFilter = TransferFilter.TransferFilterDefault(); + + [NecroBotConfig(SheetName = "Item Use Filters", Description = "Define logic to use item when catching Pokemon")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + public Dictionary ItemUseFilters = ItemUseFilter.Default(); + + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + public List PokemonToUseMasterball = CatchConfig.PokemonsToUseMasterballDefault(); + + [NecroBotConfig(Description = "Setting up human walk snipe filter by pokemon", SheetName = "HumanWalkSnipeFilter")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + public Dictionary HumanWalkSnipeFilters = HumanWalkSnipeFilter.Default(); + + [NecroBotConfig(Description = "Setting up pokemon filter for level up", SheetName = "PokemonUpgradeFilter")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + public Dictionary PokemonUpgradeFilters = UpgradeFilter.Default(); + + [NecroBotConfig(Description = "Setting up bot to use multiple account", SheetName = "MultipleBotConfig")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + public MultipleBotConfig MultipleBotConfig = MultipleBotConfig.Default(); + + [NecroBotConfig(Description = "Setting up notifications setting", SheetName = "NotificationConfig")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + public NotificationConfig NotificationConfig = new NotificationConfig(); + + [NecroBotConfig(SheetName = "SnipePokemonFilter", Description = "Setup list pokemon for auto snipe")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + public Dictionary SnipePokemonFilter = SnipeFilter.SniperFilterDefault(); + + [NecroBotConfig(SheetName = "PokemonEvolveFilter", Description = "Setup list pokemon for auto evolve")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + public Dictionary PokemonEvolveFilter = EvolveFilter.Default(); + + [NecroBotConfig(SheetName = "CatchPokemonFilter", Description = "Setup list pokemon for catch")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + public Dictionary CatchPokemonFilter = CatchFilter.Default(); + + [NecroBotConfig(SheetName = "BotSwitchPokemonFilter", Description = "Define the filter to switch bot in multiple account mode.")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + public Dictionary BotSwitchPokemonFilters = BotSwitchPokemonFilter.Default(); + + [NecroBotConfig(SheetName = "UIConfig", Description = "Define all parametter to display data on UI.")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + public GUIConfig UIConfig = new GUIConfig(); + + [NecroBotConfig(SheetName = "HumanlikeDelays", Description = "Define the delays for humanlike behaviour when catching pokemon")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + public HumanlikeDelays HumanlikeDelays = new HumanlikeDelays(); + + public GlobalSettings() + { + InitializePropertyDefaultValues(this); + } + + public void InitializePropertyDefaultValues(object obj) + { + var fields = obj.GetType().GetFields(); + + foreach (var field in fields) + { + var d = field.GetCustomAttribute(); + + if (d != null) + field.SetValue(obj, d.Value); + } + } + + public static GlobalSettings Default => new GlobalSettings(); + + private static JSchema _schema; + + private static JSchema JsonSchema + { + get + { + if (_schema != null) + return _schema; + // JSON Schemas from .NET types + var generator = new JSchemaGenerator + { + // change contract resolver so property names are camel case + //ContractResolver = new CamelCasePropertyNamesContractResolver(), + // types with no defined ID have their type name as the ID + SchemaIdGenerationHandling = SchemaIdGenerationHandling.TypeName, + // use the default order of properties. + SchemaPropertyOrderHandling = SchemaPropertyOrderHandling.Default, + // referenced schemas are inline. + SchemaLocationHandling = SchemaLocationHandling.Inline, + // no schemas can be referenced. + SchemaReferenceHandling = SchemaReferenceHandling.None + }; + // change Zone enum to generate a string property + var strEnumGen = new StringEnumGenerationProvider { CamelCaseText = true }; + generator.GenerationProviders.Add(strEnumGen); + // generate json schema + var type = typeof(GlobalSettings); + var schema = generator.Generate(type); + schema.Title = type.Name; + // + _schema = schema; + return _schema; + } + } + + + + //private JObject _jsonObject; + //public JObject JsonObject + //{ + // get + // { + // if (_jsonObject == null) + // _jsonObject = JObject.FromObject(this); + + // return _jsonObject; + // } + // set + // { + // _jsonObject = value; + // } + //} + + + //public void Load(JObject jsonObj) + //{ + // try + // { + // var input = jsonObj.ToString(Formatting.None, new StringEnumConverter { CamelCaseText = true }); + // var settings = new JsonSerializerSettings(); + // settings.Converters.Add(new StringEnumConverter { CamelCaseText = true }); + // JsonConvert.PopulateObject(input, this, settings); + // var configFile = Path.Combine(ProfileConfigPath, "config.json"); + // this.Save(configFile); + + // } + // catch (JsonReaderException exception) + // { + // Logger.Write("JSON Exception: " + exception.Message, LogLevel.Error); + // } + //} + + public static GlobalSettings Load(string path, bool validate = false) + { + GlobalSettings settings; + + var profilePath = ""; + var profileConfigPath = ""; + var configFile = ""; + var schemaFile = ""; + + + if (Path.IsPathRooted(path)) + { + profileConfigPath = Path.GetDirectoryName(path); + configFile = path; + schemaFile = path.Replace(".json", ".schema.json"); + } + else + { + profilePath = Path.Combine(Directory.GetCurrentDirectory(), path); + profileConfigPath = Path.Combine(profilePath, "config"); + configFile = Path.Combine(profileConfigPath, "config.json"); + schemaFile = Path.Combine(profileConfigPath, "config.schema.json"); + } + var shouldExit = false; + int schemaVersionBeforeUpgrade = 0; + + if (File.Exists(configFile)) + { + try + { + //if the file exists, load the settings + string input; + var count = 0; + while (true) + { + try + { + input = File.ReadAllText(configFile, Encoding.UTF8); + if (!input.Contains("DeprecatedMoves")) + input = input.Replace("\"Moves\"", $"\"DeprecatedMoves\""); + + break; + } + catch (Exception exception) + { + if (count > 10) + { + //sometimes we have to wait close to config.json for access + Logger.Write("configFile: " + exception.Message, LogLevel.Error); + } + count++; + Thread.Sleep(1000); + } + } + + var jsonSettings = new JsonSerializerSettings(); + jsonSettings.Converters.Add(new StringEnumConverter { CamelCaseText = true }); + jsonSettings.ObjectCreationHandling = ObjectCreationHandling.Replace; + jsonSettings.DefaultValueHandling = DefaultValueHandling.Populate; + + try + { + // validate Json using JsonSchema + if (validate) + { + JObject jsonObj = JObject.Parse(input); + + // Migrate before validation. + MigrateSettings(jsonObj, configFile, schemaFile); + + // Save the original schema version since we need to pass it to AuthSettings for migration. + schemaVersionBeforeUpgrade = (int)jsonObj["UpdateConfig"]["SchemaVersion"]; + + // After migration we need to update the schema version to the latest version. + jsonObj["UpdateConfig"]["SchemaVersion"] = UpdateConfig.CURRENT_SCHEMA_VERSION; + + Logger.Write("Validating config.json..."); + IList errors = null; + bool valid; + try + { + valid = jsonObj.IsValid(JsonSchema, out errors); + } + catch (JSchemaException ex) + { + if (ex.Message.Contains("commercial licence") || ex.Message.Contains("free-quota")) + { + Logger.Write( + "config.json: " + ex.Message); + valid = false; + } + else + { + throw; + } + } + if (!valid) + { + if (errors != null) + foreach (var error in errors) + { + Logger.Write( + "config.json [Line: " + error.LineNumber + ", Position: " + error.LinePosition + + "]: " + error.Path + " " + error.Message, LogLevel.Error); + } + + Logger.Write( + "Fix config.json and restart NecroBot or press a key to ignore and continue...", + LogLevel.Warning); + Console.ReadKey(); + } + + // Now we know it's valid so update input with the migrated version. + input = jsonObj.ToString(); + } + + settings = JsonConvert.DeserializeObject(input, jsonSettings); + } + catch (JsonSerializationException exception) + { + Logger.Write("JSON Exception: " + exception.Message, LogLevel.Error); + return null; + } + catch (JsonReaderException exception) + { + Logger.Write("JSON Exception: " + exception.Message, LogLevel.Error); + return null; + } + + //This makes sure that existing config files dont get null values which lead to an exception + foreach (var filter in settings.PokemonsTransferFilter.Where(x => x.Value.KeepMinOperator == null)) + { + filter.Value.KeepMinOperator = "or"; + } + foreach (var filter in settings.PokemonsTransferFilter.Where(x => x.Value.Moves == null)) + { + filter.Value.Moves = filter.Value.DeprecatedMoves != null + ? new List> { filter.Value.DeprecatedMoves } + : filter.Value.Moves ?? new List>(); + } + foreach (var filter in settings.PokemonsTransferFilter.Where(x => x.Value.MovesOperator == null)) + { + filter.Value.MovesOperator = "or"; + } + } + catch (JsonReaderException exception) + { + Logger.Write("JSON Exception: " + exception.Message, LogLevel.Error); + return null; + } + } + else + { + settings = new GlobalSettings(); + shouldExit = true; + } + + settings.ProfilePath = profilePath; + settings.ProfileConfigPath = profileConfigPath; + settings.GeneralConfigPath = Path.Combine(Directory.GetCurrentDirectory(), "config"); + + settings.Save(configFile); + settings.Auth.Load(Path.Combine(profileConfigPath, "auth.json"), + Path.Combine(profileConfigPath, "auth.schema.json"), schemaVersionBeforeUpgrade, validate); + + return shouldExit ? null : settings; + } + + private static void MigrateSettings(JObject settings, string configFile, string schemaFile) + { + if (settings["UpdateConfig"]?["SchemaVersion"] == null) + { + // The is the first time setup for old config.json files without the SchemaVersion. + // Just set this to 0 so that we can handle the upgrade in case 0. + settings["UpdateConfig"]["SchemaVersion"] = 0; + } + + int schemaVersion = (int)settings["UpdateConfig"]["SchemaVersion"]; + if (schemaVersion == UpdateConfig.CURRENT_SCHEMA_VERSION) + { + Logger.Write("Configuration is up-to-date. Schema version: " + schemaVersion); + return; + } + + // Backup old config file. + long ts = DateTime.UtcNow.ToUnixTime(); // Add timestamp to avoid file conflicts + string backupPath = configFile.Replace(".json", $"-{schemaVersion}-{ts}.backup.json"); + Logger.Write($"Backing up config.json to: {backupPath}", LogLevel.Info); + File.Copy(configFile, backupPath); + + // Add future schema migrations below. + int version; + for (version = schemaVersion; version < UpdateConfig.CURRENT_SCHEMA_VERSION; version++) + { + Logger.Write($"Migrating configuration from schema version {version} to {version + 1}", LogLevel.Info); + switch (version) + { + case 1: + // Delete the auto complete tutorial settings. + ((JObject)settings["PlayerConfig"]).Remove("AutoCompleteTutorial"); + ((JObject)settings["PlayerConfig"]).Remove("DesiredNickname"); + ((JObject)settings["PlayerConfig"]).Remove("DesiredGender"); + ((JObject)settings["PlayerConfig"]).Remove("DesiredStarter"); + break; + + case 2: + // Remove the TransferConfigAndAuthOnUpdate setting since we always transfer now. + ((JObject)settings["UpdateConfig"]).Remove("TransferConfigAndAuthOnUpdate"); + break; + + case 6: + // Rename AustoSnipeCandy to AutoSnipeCandy + if (settings["SnipePokemonFilter"] != null) + { + foreach (var x in settings["SnipePokemonFilter"]) + { + var key = ((JProperty)(x)).Name; + var filter = ((JProperty)(x)).Value; + + if (filter["AustoSnipeCandy"] != null) + { + filter["AutoSnipeCandy"] = filter["AustoSnipeCandy"]; + ((JObject)filter).Remove("AustoSnipeCandy"); + } + } + } + break; + + case 7: + // We making the limits more conservative. + if (settings["PokemonConfig"] != null) + { + if ((int)(settings["PokemonConfig"]["CatchPokemonLimit"]) == 998) + settings["PokemonConfig"]["CatchPokemonLimit"] = 700; + if ((int)(settings["PokemonConfig"]["CatchPokemonLimitMinutes"]) == 1470) + settings["PokemonConfig"]["CatchPokemonLimitMinutes"] = 1200; + if ((int)(settings["PokeStopConfig"]["PokeStopLimit"]) == 1998) + settings["PokeStopConfig"]["PokeStopLimit"] = 1500; + if ((int)(settings["PokeStopConfig"]["PokeStopLimitMinutes"]) == 1470) + settings["PokeStopConfig"]["PokeStopLimitMinutes"] = 1200; + } + break; + case 8: + string oldTemplate = (string)settings["PokemonConfig"]["RenameTemplate"]; + settings["PokemonConfig"]["RenameTemplate"] = oldTemplate.Replace("{0}", "{Name}").Replace("{1}", "{IV}") + "_LV{Level}"; + break; + + case 9: + if (settings["PlayerConfig"]["RandomizeSettingsByPercent"] == null) + { + settings["PlayerConfig"]["RandomizeSettingsByPercent"] = 5; + } + break; + + case 10: + case 11: + if ((string)settings["PokemonConfig"]["DefaultBuddyPokemon"] == "dragonite" || + (string)settings["PokemonConfig"]["DefaultBuddyPokemon"] == null || + (string)settings["PokemonConfig"]["DefaultBuddyPokemon"] == "") + settings["PokemonConfig"]["DefaultBuddyPokemon"] = ""; + else + { + // Upper case first letter. + char[] a = ((string)settings["PokemonConfig"]["DefaultBuddyPokemon"]).ToCharArray(); + a[0] = char.ToUpper(a[0]); + settings["PokemonConfig"]["DefaultBuddyPokemon"] = new string(a); + } + break; + case 13: + settings["PokemonConfig"]["FavoriteOperator"] = "and"; + settings["PokemonConfig"]["FavoriteMinLevel"] = 0; + break; + // Add more here. + case 14: + //migrate berries setting + if (settings["PokemonConfig"]["UseBerriesMinIv"] != null && settings["ItemUseFilters"] != null) + { + settings["ItemUseFilters"]["ItemRazzBerry"]["UseItemMinIV"] = (int)settings["PokemonConfig"]["UseBerriesMinIv"]; + settings["ItemUseFilters"]["ItemRazzBerry"]["UseItemMinCP"] = settings["PokemonConfig"]["UseBerriesMinCp"]; + settings["ItemUseFilters"]["ItemRazzBerry"]["CatchProbability"] = settings["PokemonConfig"]["UseBerriesBelowCatchProbability"]; + settings["ItemUseFilters"]["ItemRazzBerry"]["Operator"] = settings["PokemonConfig"]["UseBerriesOperator"]; + settings["ItemUseFilters"]["ItemRazzBerry"]["MaxItemsUsePerPokemon"] = settings["PokemonConfig"]["MaxBerriesToUsePerPokemon"]; + //delete old + + ((JObject)settings["PokemonConfig"]).Remove("UseBerriesMinIv"); + ((JObject)settings["PokemonConfig"]).Remove("UseBerriesMinCp"); + ((JObject)settings["PokemonConfig"]).Remove("UseBerriesBelowCatchProbability"); + ((JObject)settings["PokemonConfig"]).Remove("UseBerriesOperator"); + ((JObject)settings["PokemonConfig"]).Remove("MaxBerriesToUsePerPokemon"); + } + + break; + case 15: + List existing = new List(); + foreach (var x in settings["ItemRecycleFilter"].Children()) + { + existing.Add((string)x["Key"]); + } + + List newItems = new List() { ItemId.ItemDragonScale, ItemId.ItemUpGrade, ItemId.ItemKingsRock, ItemId.ItemSunStone, ItemId.ItemMetalCoat }; + + foreach (var item in newItems) + { + if (!existing.Any(x => x.ToLower() == item.ToString().ToLower())) + { + var itemName = item.ToString(); + itemName = itemName.Replace("Item", "item"); + + var last = settings["ItemRecycleFilter"].Children().Last(); + var newFilter = JObject.Parse(@"{""Key"":""" + itemName + @""",""Value"":100}"); + + last.AddAfterSelf(newFilter); + } + } + + break; + + case 16: + if (settings["PokemonEvolveFilter"] != null && settings["PokemonsToEvolve"] != null) + { + List pokemonToEvolve = new List(); + foreach (var x in settings["PokemonsToEvolve"].Children()) + { + var pokemonName = (string)x; + pokemonName = pokemonName[0].ToString().ToUpper() + new string(pokemonName.Skip(1).ToArray()); + + if (settings["PokemonEvolveFilter"][pokemonName] == null) + { + EvolveFilter ev = new EvolveFilter(0, 0, 0); + settings["PokemonEvolveFilter"][pokemonName] = JObject.Parse(JsonConvert.SerializeObject(ev)); + } + } + } + break; + + case 17: + if (settings["PokemonEvolveFilter"] != null && settings["PokemonsToEvolve"] != null) + { + // Repeat the migration for case 16 just in case PokemonsToEvolve has beed modified. + List pokemonToEvolve = new List(); + foreach (var x in settings["PokemonsToEvolve"].Children()) + { + var pokemonName = (string)x; + pokemonName = pokemonName[0].ToString().ToUpper() + new string(pokemonName.Skip(1).ToArray()); + + if (settings["PokemonEvolveFilter"][pokemonName] == null) + { + EvolveFilter ev = new EvolveFilter(0, 0, 0); + settings["PokemonEvolveFilter"][pokemonName] = JObject.Parse(JsonConvert.SerializeObject(ev)); + } + } + } + + // Adding new MinCandiesBeforeEvolve to all filters. + if (settings["PokemonEvolveFilter"] != null) + { + foreach (var x in settings["PokemonEvolveFilter"]) + { + var filter = ((JProperty)(x)).Value; + + if (filter["MinCandiesBeforeEvolve"] == null) + { + filter["MinCandiesBeforeEvolve"] = 0; + } + } + } + + // But this time we are going to remove PokemonsToEvolve. + settings.Remove("PokemonsToEvolve"); + break; + + case 22: + if (settings["PokemonsTransferFilter"] != null) + { + foreach (var x in settings["PokemonsTransferFilter"]) + { + var key = ((JProperty)(x)).Name; + var filter = ((JProperty)(x)).Value; + + if (filter["KeepMaxDuplicatePokemon"] == null) + filter["KeepMaxDuplicatePokemon"] = 1000; + } + } + break; + + case 23: + if (settings["PokeStopConfig"] != null) + { + settings["PokeStopConfig"]["PokeStopLimit"] = 700; + Logger.Write($"PokeStopLimit changed to {settings["PokeStopConfig"]["PokeStopLimit"]}", LogLevel.Info); + } + if (settings["PokemonConfig"] != null) + { + settings["PokemonConfig"]["CatchPokemonLimit"] = 500; + Logger.Write($"CatchPokemonLimit changed to {settings["PokemonConfig"]["CatchPokemonLimit"]}", LogLevel.Info); + } + break; + } + } + } + + public void CheckProxy(ITranslation translator) + { + Auth.CheckProxy(translator); + } + + public static bool PromptForSetup(ITranslation translator) + { + bool promptForSetup = PromptForBoolean( + translator, + translator.GetTranslation(TranslationString.FirstStartPrompt, "Y", "N") + ); + if (!promptForSetup) + Logger.Write(translator.GetTranslation(TranslationString.FirstStartAutoGenSettings)); + + return promptForSetup; + } + + public static Session SetupSettings(Session session, GlobalSettings settings, + IElevationService elevationService, string configPath) + { + var newSession = SetupTranslationCode(session, elevationService, session.Translation, settings); + + SetupAccountType(newSession.Translation, settings); + SetupUserAccount(newSession.Translation, settings); + SetupConfig(newSession.Translation, settings); + SetupWalkingConfig(newSession.Translation, settings); + SetupTelegramConfig(newSession.Translation, settings); + SetupProxyConfig(newSession.Translation, settings); + SetupWebSocketConfig(newSession.Translation, settings); + SaveFiles(settings, configPath); + + Logger.Write(session.Translation.GetTranslation(TranslationString.FirstStartSetupCompleted), LogLevel.Info); + + return newSession; + } + + private static Session SetupTranslationCode(Session session, IElevationService elevationService, + ITranslation translator, GlobalSettings settings) + { + if (false == PromptForBoolean( + translator, + translator.GetTranslation(TranslationString.FirstStartLanguagePrompt, "Y", "N") + ) + ) + return session; + + string strInput = PromptForString( + translator, + translator.GetTranslation(TranslationString.FirstStartLanguageCodePrompt) + ); + + settings.ConsoleConfig.TranslationLanguageCode = strInput; + session = new Session(settings, + new ClientSettings(settings, elevationService), + new LogicSettings(settings), + elevationService + ); + translator = session.Translation; + Logger.Write(translator.GetTranslation(TranslationString.FirstStartLanguageConfirm, strInput)); + + return session; + } + + private static void SetupProxyConfig(ITranslation translator, GlobalSettings settings) + { + if (false == PromptForBoolean( + translator, + translator.GetTranslation(TranslationString.FirstStartSetupProxyPrompt, "Y", "N") + ) + ) + return; + + settings.Auth.ProxyConfig.UseProxy = true; + + string strInput = PromptForString( + translator, + translator.GetTranslation(TranslationString.FirstStartSetupProxyHostPrompt) + ); + settings.Auth.ProxyConfig.UseProxyHost = strInput; + Logger.Write(translator.GetTranslation(TranslationString.FirstStartSetupProxyHostConfirm, strInput)); + + strInput = PromptForString( + translator, + translator.GetTranslation(TranslationString.FirstStartSetupProxyPortPrompt) + ); + settings.Auth.ProxyConfig.UseProxyPort = strInput; + Logger.Write(translator.GetTranslation(TranslationString.FirstStartSetupProxyPortConfirm, strInput)); + + if (false == PromptForBoolean( + translator, + translator.GetTranslation(TranslationString.FirstStartSetupProxyAuthPrompt, "Y", "N") + ) + ) + return; + + settings.Auth.ProxyConfig.UseProxyAuthentication = true; + + strInput = PromptForString( + translator, + translator.GetTranslation(TranslationString.FirstStartSetupProxyUsernamePrompt) + ); + settings.Auth.ProxyConfig.UseProxyUsername = strInput; + Logger.Write(translator.GetTranslation(TranslationString.FirstStartSetupProxyUsernameConfirm, strInput)); + + strInput = PromptForString( + translator, + translator.GetTranslation(TranslationString.FirstStartSetupProxyPasswordPrompt) + ); + settings.Auth.ProxyConfig.UseProxyPassword = strInput; + Logger.Write(translator.GetTranslation(TranslationString.FirstStartSetupProxyPasswordConfirm, strInput)); + } + + private static void SetupWalkingConfig(ITranslation translator, GlobalSettings settings) + { + if (false == PromptForBoolean( + translator, + translator.GetTranslation(TranslationString.FirstStartSetupWalkingSpeedPrompt, "Y", "N") + ) + ) + return; + + settings.LocationConfig.WalkingSpeedInKilometerPerHour = PromptForDouble( + translator, + translator.GetTranslation(TranslationString.FirstStartSetupWalkingSpeedKmHPrompt) + ); + Logger.Write( + translator.GetTranslation(TranslationString.FirstStartSetupWalkingSpeedKmHConfirm, + settings.LocationConfig.WalkingSpeedInKilometerPerHour.ToString() + ) + ); + + settings.LocationConfig.UseWalkingSpeedVariant = PromptForBoolean( + translator, + translator.GetTranslation(TranslationString.FirstStartSetupUseWalkingSpeedVariantPrompt, "Y", "N") + ); + + settings.LocationConfig.WalkingSpeedVariant = PromptForDouble( + translator, + translator.GetTranslation(TranslationString.FirstStartSetupWalkingSpeedVariantPrompt) + ); + Logger.Write( + translator.GetTranslation( + TranslationString.FirstStartSetupWalkingSpeedVariantConfirm, + settings.LocationConfig.WalkingSpeedVariant.ToString() + ) + ); + } + + private static void SetupWebSocketConfig(ITranslation translator, GlobalSettings settings) + { + if (false == PromptForBoolean( + translator, + translator.GetTranslation(TranslationString.FirstStartSetupWebSocketPrompt, "Y", "N") + ) + ) + return; + + settings.WebsocketsConfig.UseWebsocket = true; + + settings.WebsocketsConfig.WebSocketPort = PromptForInteger( + translator, + translator.GetTranslation(TranslationString.FirstStartSetupWebSocketPortPrompt) + ); + Logger.Write( + translator.GetTranslation( + TranslationString.FirstStartSetupWebSocketPortConfirm, + settings.WebsocketsConfig.WebSocketPort.ToString() + ) + ); + } + + private static void SetupTelegramConfig(ITranslation translator, GlobalSettings settings) + { + if (false == PromptForBoolean( + translator, + translator.GetTranslation(TranslationString.FirstStartSetupTelegramPrompt, "Y", "N") + ) + ) + return; + + settings.TelegramConfig.UseTelegramAPI = true; + + string strInput = PromptForString( + translator, + translator.GetTranslation(TranslationString.FirstStartSetupTelegramCodePrompt) + ); + settings.TelegramConfig.TelegramAPIKey = strInput; + Logger.Write(translator.GetTranslation(TranslationString.FirstStartSetupTelegramCodeConfirm, strInput)); + + strInput = PromptForString( + translator, + translator.GetTranslation(TranslationString.FirstStartSetupTelegramPasswordPrompt) + ); + settings.TelegramConfig.TelegramPassword = strInput; + Logger.Write(translator.GetTranslation(TranslationString.FirstStartSetupTelegramPasswordConfirm, strInput)); + } + + private static void SetupAccountType(ITranslation translator, GlobalSettings settings) + { + Logger.Write(translator.GetTranslation(TranslationString.FirstStartSetupAccount), LogLevel.Info); + + string accountType = PromptForString( + translator, + translator.GetTranslation(TranslationString.FirstStartSetupTypePrompt, "google", "ptc"), + new string[] { "google", "ptc" }, + translator.GetTranslation(TranslationString.FirstStartSetupTypePromptError, "google", "ptc"), + false + ); + + switch (accountType) + { + case "google": + settings.Auth.CurrentAuthConfig.AuthType = AuthType.Google; + break; + case "ptc": + settings.Auth.CurrentAuthConfig.AuthType = AuthType.Ptc; + break; + } + + Logger.Write( + translator.GetTranslation(TranslationString.FirstStartSetupTypeConfirm, accountType.ToUpper() + ) + ); + } + + private static void SetupUserAccount(ITranslation translator, GlobalSettings settings) + { + Logger.Write("", LogLevel.Info); + var strInput = PromptForString( + translator, + translator.GetTranslation(TranslationString.FirstStartSetupUsernamePrompt) + ); + + settings.Auth.CurrentAuthConfig.Username = strInput; + Logger.Write(translator.GetTranslation(TranslationString.FirstStartSetupUsernameConfirm, strInput)); + + Logger.Write("", LogLevel.Info); + strInput = PromptForString( + translator, + translator.GetTranslation(TranslationString.FirstStartSetupPasswordPrompt) + ); + + settings.Auth.CurrentAuthConfig.Password = strInput; + Logger.Write(translator.GetTranslation(TranslationString.FirstStartSetupPasswordConfirm, strInput)); + + Logger.Write(translator.GetTranslation(TranslationString.FirstStartAccountCompleted), LogLevel.Info); + } + + private static void SetupConfig(ITranslation translator, GlobalSettings settings) + { + if (false == PromptForBoolean( + translator, + translator.GetTranslation( + TranslationString.FirstStartDefaultLocationPrompt, "Y", "N") + ) + ) + { + Logger.Write(translator.GetTranslation(TranslationString.FirstStartDefaultLocationSet)); + return; + } + + Logger.Write(translator.GetTranslation(TranslationString.FirstStartDefaultLocation), LogLevel.Info); + Logger.Write(translator.GetTranslation(TranslationString.FirstStartSetupDefaultLatLongPrompt)); + while (true) + { + try + { + var strInput = Console.ReadLine(); + var strSplit = strInput.Split(','); + + if (strSplit.Length > 1) + { + var dblLat = double.Parse(strSplit[0].Trim(' ')); + var dblLong = double.Parse(strSplit[1].Trim(' ')); + + settings.LocationConfig.DefaultLatitude = dblLat; + settings.LocationConfig.DefaultLongitude = dblLong; + + Logger.Write(translator.GetTranslation(TranslationString.FirstStartSetupDefaultLatLongConfirm, + $"{dblLat}, {dblLong}")); + } + else + { + Logger.Write(translator.GetTranslation(TranslationString.FirstStartSetupDefaultLocationError, + $"{settings.LocationConfig.DefaultLatitude}, {settings.LocationConfig.DefaultLongitude}", + LogLevel.Error)); + continue; + } + + break; + } + catch (FormatException) + { + Logger.Write(translator.GetTranslation(TranslationString.FirstStartSetupDefaultLocationError, + $"{settings.LocationConfig.DefaultLatitude}, {settings.LocationConfig.DefaultLongitude}", + LogLevel.Error)); + } + } + } + + public static void SaveFiles(GlobalSettings settings, string configFile) + { + settings.Save(configFile); + settings.Auth.Load( + Path.Combine(settings.ProfileConfigPath, "auth.json"), + Path.Combine(settings.ProfileConfigPath, "auth.schema.json"), + settings.UpdateConfig.SchemaVersion + ); + } + + public void Save(string fullPath, bool validate = false) + { + var output = JsonConvert.SerializeObject(this, Formatting.Indented, + new StringEnumConverter { CamelCaseText = true }); + + var folder = Path.GetDirectoryName(fullPath); + if (folder != null && !Directory.Exists(folder)) + { + Directory.CreateDirectory(folder); + } + + File.WriteAllText(fullPath, output, Encoding.UTF8); + + //JsonSchema + File.WriteAllText(fullPath.Replace(".json", ".schema.json"), JsonSchema.ToString(), Encoding.UTF8); + + if (!validate) return; + + // validate Json using JsonSchema + Logger.Write("Validating config.json..."); + var jsonObj = JObject.Parse(output); + IList errors; + var valid = jsonObj.IsValid(JsonSchema, out errors); + if (valid) return; + foreach (var error in errors) + { + Logger.Write( + "config.json [Line: " + error.LineNumber + ", Position: " + error.LinePosition + "]: " + + error.Path + " " + error.Message, LogLevel.Error); + //"Default value is '" + error.Schema.Default + "'" + } + Logger.Write("Fix config.json and restart NecroBot or press a key to ignore and continue...", + LogLevel.Warning); + Console.ReadKey(); + } + + public static bool PromptForBoolean(ITranslation translator, string initialPrompt, string errorPrompt = null) + { + while (true) + { + Logger.Write(initialPrompt, LogLevel.Info); + var strInput = Console.ReadLine().ToLower(); + + switch (strInput) + { + case "y": + return true; + case "n": + return false; + default: + if (string.IsNullOrEmpty(errorPrompt)) + errorPrompt = translator.GetTranslation(TranslationString.PromptError, "y", "n"); + Logger.Write(errorPrompt, LogLevel.Error); + continue; + } + } + } + + public static double PromptForDouble(ITranslation translator, string initialPrompt, string errorPrompt = null) + { + while (true) + { + Logger.Write(initialPrompt, LogLevel.Info); + var strInput = Console.ReadLine(); + + double doubleVal; + if (double.TryParse(strInput, out doubleVal)) + { + return doubleVal; + } + else + { + if (string.IsNullOrEmpty(errorPrompt)) + errorPrompt = translator.GetTranslation(TranslationString.PromptErrorDouble); + + Logger.Write(errorPrompt, LogLevel.Error); + } + } + } + + public static int PromptForInteger(ITranslation translator, string initialPrompt, string errorPrompt = null) + { + while (true) + { + Logger.Write(initialPrompt, LogLevel.Info); + var strInput = Console.ReadLine(); + + int intVal; + if (int.TryParse(strInput, out intVal)) + { + return intVal; + } + else + { + if (string.IsNullOrEmpty(errorPrompt)) + errorPrompt = translator.GetTranslation(TranslationString.PromptErrorInteger); + + Logger.Write(errorPrompt, LogLevel.Error); + } + } + } + + public static string PromptForString(ITranslation translator, string initialPrompt, + string[] validStrings = null, string errorPrompt = null, bool caseSensitive = true) + { + while (true) + { + Logger.Write(initialPrompt, LogLevel.Info); + // For now this just reads from the console, but in the future, we may change this to read from the GUI. + string strInput = Console.ReadLine(); + + if (!caseSensitive) + strInput = strInput.ToLower(); + + // If no valid strings to validate, then return immediately. + if (validStrings == null) + return strInput; + + // Validate string + foreach (string validString in validStrings) + { + if (String.Equals(strInput, validString, caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase)) + return strInput; + } + + // If we got here, no valid strings. + if (string.IsNullOrEmpty(errorPrompt)) + { + errorPrompt = translator.GetTranslation( + TranslationString.PromptErrorString, + string.Join(",", validStrings) + ); + } + Logger.Write(errorPrompt, LogLevel.Error); + } + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Settings/GoogleWalkConfig.cs b/PoGo.NecroBot.Logic/Model/Settings/GoogleWalkConfig.cs new file mode 100644 index 000000000..46571a751 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/GoogleWalkConfig.cs @@ -0,0 +1,65 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + /// + /// Google has some limitations for free use + /// 2,500 free requests per day, calculated as the sum of client-side and server-side queries; enable billing to access higher daily quotas, billed at $0.50 USD / 1000 additional requests, up to 100,000 requests daily. + /// With cache enabled, we can optimize the use. + /// + [JsonObject(Title = "Google Walk Config", Description = "Set your google walk settings (set \"GoogleAPIKey\" if you have a key, nowadays a single contract is $16.000,00 USD. With a key you can deactivate Cache.", ItemRequired = Required.DisallowNull)] + public class GoogleWalkConfig : BaseConfig + { + public GoogleWalkConfig() : base() + { + } + + internal enum GoogleWalkTravelModes + { + driving, + walking, + bicycling, + transit + } + + [NecroBotConfig(Description = "Use Google's API to resolve paths for destinations", Position = 1)] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + public bool UseGoogleWalk { get; set; } + + [NecroBotConfig(Description = "Set step length in met for 1 step", Position = 2)] + [DefaultValue(1.3d)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 2)] + public double DefaultStepLength { get; set; } + + [NecroBotConfig(Description = "Set heuristic for Google's Algorithm Detection - driving, walking, bicycling, transit", Position = 3)] + [DefaultValue("walking")] + [EnumDataType(typeof(GoogleWalkTravelModes))] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 3)] + //https://developers.google.com/maps/documentation/directions/intro?hl=pt-br#TravelModes + public string GoogleHeuristic { get; set; } + + [NecroBotConfig(Description = "API Key to be used to connect to Google's API Service", Position = 4)] + [DefaultValue(null)] + [MinLength(0)] + [MaxLength(64)] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 4)] + // If you have a key, nowadays a single contract is $16.000,00 USD. With a key you can deactivate Cache + public string GoogleAPIKey { get; set; } + + [NecroBotConfig(Description = "Allows bot to use cache ", Position = 5)] + [DefaultValue(true)] + [JsonProperty("Cache", Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 5)] + public bool Cache { get; set; } + + [NecroBotConfig(Description = "API key to be used to connect to Google's Elevation API Service (Not the Same as Google API Key)", Position = 6)] + [DefaultValue(null)] + [MinLength(0)] + [MaxLength(64)] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 6)] + // This can be the same as the GoogleAPIKey, but if so then you need to activate Elevation API for the key. + public string GoogleElevationAPIKey { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Settings/GpxConfig.cs b/PoGo.NecroBot.Logic/Model/Settings/GpxConfig.cs new file mode 100644 index 000000000..1e24c146e --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/GpxConfig.cs @@ -0,0 +1,26 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + [JsonObject(Title = "Gpx Config", Description = "Set your Gpx settings.", ItemRequired = Required.DisallowNull)] + public class GpxConfig : BaseConfig + { + public GpxConfig() : base() + { + } + + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + [NecroBotConfig(Description = "If this option is turned on, the bot will walk using the predifined path in GpxFile", Position = 1)] + public bool UseGpxPathing { get; set; } + + [DefaultValue("GPXPath.GPX")] + [MinLength(0)] + [MaxLength(255)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + [NecroBotConfig(Description = "The GPX file name or full path. if you not enter full path, you have to copy the file into bot directory", Position = 2)] + public string GpxFile { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Settings/GymConfig.cs b/PoGo.NecroBot.Logic/Model/Settings/GymConfig.cs new file mode 100644 index 000000000..6169e8b41 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/GymConfig.cs @@ -0,0 +1,201 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; +using System.Collections.Generic; +using POGOProtos.Enums; + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + [JsonObject(Title = "Gym Config", Description = "Setup rules for bot for gyms", ItemRequired = Required.DisallowNull)] + public class GymConfig : BaseConfig + { + public GymConfig() : base() + { + } + + internal enum TeamColor + { + Neutral, + Yellow, + Red, + Blue + } + + [NecroBotConfig(Description = "Allows bot go to a gym for berries, defense or battle with other teams.", Position = 1)] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + public bool Enable { get; set; } + + [NecroBotConfig(Description = "If Enabled, bot will select a gym to go to instead of a pokestop if gym distance is valid", Position = 2)] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + public bool PrioritizeGymOverPokestop { get; set; } + + [NecroBotConfig(Description = "Maximum distance bot is allowed to walk to a gym.", Position = 3)] + [DefaultValue(1500.0)] + [Range(0, 9999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + public double MaxDistance { get; set; } + + [NecroBotConfig(Description = "Default team color for bot to join.", Position = 4)] + [DefaultValue("Neutral")] + [EnumDataType(typeof(TeamColor))] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + public string DefaultTeam { get; set; } + + [NecroBotConfig(Description = "Max CP that pokemon will be selected for defending gyms.", Position = 5)] + [DefaultValue(4000)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + public int MaxCPToDeploy { get; set; } + + [NecroBotConfig(Description = "Max LV of pokemon that can be put into a gym.", Position = 6)] + [DefaultValue(40)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + public int MaxLevelToDeploy { get; set; } + + [NecroBotConfig(Description = "Enables attacking raids", Position = 7)] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + public bool EnableAttackRaid { get; set; } + + [NecroBotConfig(Description = "Use random pokemon for gym.", Position = 8)] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + public bool UseRandomPokemon { get; set; } + + [NecroBotConfig(Description = "Enables attacking gyms", Position = 9)] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + public bool EnableAttackGym { get; set; } + + [NecroBotConfig(Description = "Enables deploy pokemon", Position = 10)] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + public bool EnableDeployPokemon { get; set; } + + [NecroBotConfig(Description = "Heal defenders before applying to gym", Position = 11)] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + public bool HealDefendersBeforeApplyToGym { get; set; } + + [NecroBotConfig(Description = "Enables gym berries", Position = 12)] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + public bool EnableGymBerries { get; set; } + + [NecroBotConfig(Description = "Min CP pokemon to use in attacking gyms", Position = 13)] + [DefaultValue(1000)] + [Range(1, 3500)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + public int MinCpToUseInAttack { get; set; } + + [NecroBotConfig(Description = "But not less than defender's percent", Position = 14)] + [DefaultValue(0.75)] + [Range(0.01, 1)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + public double ButNotLessThanDefenderPercent { get; set; } + + [NecroBotConfig(Description = "Use Pokemon to attack only by their CP", Position = 15)] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + public bool UsePokemonToAttackOnlyByCp { get; set; } + + [NecroBotConfig(Description = "Do not use dodge in gyms", Position = 16)] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + public bool UseDodge { get; set; } + + [NecroBotConfig(Description = "Minimum revive potions to use gym module", Position = 17)] + [DefaultValue(5)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + public int MinRevivePotions { get; set; } + + [NecroBotConfig(Description = "Prioritize Gym with free slot", Position = 18)] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + public bool PrioritizeGymWithFreeSlot { get; set; } + + [NecroBotConfig(Description = "Save Max Revives", Position = 19)] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + public bool SaveMaxRevives { get; set; } + + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + [NecroBotConfig(Description = "List of Defenders to use for Gyms", Position = 20)] + public List Defenders { get; set; } = TeamMemberConfig.GetDefaultDefenders(); + + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + [NecroBotConfig(Description = "List of Attackers to use for Gyms", Position = 21)] + public List Attackers { get; set; } = TeamMemberConfig.GetDefaultAttackers(); + } + + [JsonObject(Description = "", ItemRequired = Required.DisallowNull)] + public class TeamMemberConfig : BaseConfig + { + public TeamMemberConfig() : base() + { + } + + [NecroBotConfig(Description = "Pokemon to use in Gyms", Position = 1)] + [JsonProperty(Required = Required.Always, DefaultValueHandling = DefaultValueHandling.Populate)] + public PokemonId Pokemon { get; set; } + + [NecroBotConfig(Description = "Min CP to use in a team", Position = 2)] + [Range(1, 5000)] + [DefaultValue(null)] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate)] + public int? MinCP { get; set; } + + [NecroBotConfig(Description = "Max CP to use in a team", Position = 3)] + [Range(1, 5000)] + [DefaultValue(null)] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate)] + public int? MaxCP { get; set; } + + [NecroBotConfig(Description = "Priority", Position = 4)] + [Range(1, 100)] + [DefaultValue(5)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + public int Priority { get; set; } + + [NecroBotConfig(Key = "Moves", Description = "List of Moves to use in Gyms", Position = 5)] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 5)] + [DefaultValue(null)] + public List Moves { get; set; } + + internal static List GetDefaultDefenders() + { + return new List() + { + { new TeamMemberConfig() { Pokemon=PokemonId.Lapras, MinCP=1000 } }, + { new TeamMemberConfig() { Pokemon=PokemonId.Snorlax, MinCP=1000 } }, + { new TeamMemberConfig() { Pokemon=PokemonId.Vaporeon, MinCP=2000, Moves = new List() { new PokemonMove[2] { PokemonMove.MoveUnset, PokemonMove.HydroPump } } } }, + { new TeamMemberConfig() { Pokemon=PokemonId.Dragonite, MinCP=2000 } }, + { new TeamMemberConfig() { Pokemon=PokemonId.Charizard, MinCP=2000 } }, + { new TeamMemberConfig() { Pokemon=PokemonId.Alakazam, MinCP=2000 } } + }; + } + + internal static List GetDefaultAttackers() + { + return new List() + { + { new TeamMemberConfig() { Pokemon=PokemonId.Dragonite, MinCP=2500 } }, + { new TeamMemberConfig() { Pokemon=PokemonId.Vaporeon, MinCP=2000 } }, + { new TeamMemberConfig() { Pokemon=PokemonId.Gyarados, MinCP=2000 } }, + { new TeamMemberConfig() { Pokemon=PokemonId.Snorlax, MinCP=2000 } }, + { new TeamMemberConfig() { Pokemon=PokemonId.Charizard, MinCP=2000 } }, + { new TeamMemberConfig() { Pokemon=PokemonId.Lapras, MinCP=2000 } } + }; + } + + internal bool IsMoveMatch(PokemonMove move1, PokemonMove move2) + { + if(Moves!=null && Moves.Count > 0) + { + return Moves.Find(f => (f[0] == move1 || f[0] == PokemonMove.MoveUnset) && (f[1] == move2 || f[1] == PokemonMove.MoveUnset)) != null; + } + return true; + } + } +} diff --git a/PoGo.NecroBot.Logic/Model/Settings/HumanWalkSnipeConfig.cs b/PoGo.NecroBot.Logic/Model/Settings/HumanWalkSnipeConfig.cs new file mode 100644 index 000000000..5ced8d70e --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/HumanWalkSnipeConfig.cs @@ -0,0 +1,146 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + [JsonObject(Title = "Human Walk Snipe Config", Description = "This feature allow bot pull data from pokemap site, if pokemon match with your config. bot will walk to pokemon's location to catch him.", ItemRequired = Required.DisallowNull)] + public class HumanWalkSnipeConfig :BaseConfig + { + public HumanWalkSnipeConfig() : base() + { + } + + [NecroBotConfig(Position = 1, Description = "Allow bot using human walk sniper feature")] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + public bool Enable { get; set; } + + [NecroBotConfig(Position = 2, Description = "Display list pokemon snipeable in console window")] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + public bool DisplayPokemonList { get; set; } + + [NecroBotConfig(Position = 3, Description = "Max distance that you want bot travel for snipe")] + [DefaultValue(1500.0)] + [Range(0, 9999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + public double MaxDistance { get; set; } + + [NecroBotConfig(Position = 4, Description = "Max walking time you want bot travel to snipe")] + [DefaultValue(900.0)] + [Range(0, 9999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + public double MaxEstimateTime { get; set; } + + [NecroBotConfig(Position = 5, Description = "Minimun ball available in bag for catch em all mode. this mean continuously snipping if pokemon available.")] + [DefaultValue(50)] + [Range(0, 999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + public int CatchEmAllMinBalls { get; set; } + + [NecroBotConfig(Position = 6, Description = "Try to catch em all - confused")] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + public bool TryCatchEmAll { get; set; } + + [NecroBotConfig(Position = 7, Description = "Allow catch pokemon when walking to target - overwrite by pokemon filter")] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + public bool CatchPokemonWhileWalking { get; set; } + + [NecroBotConfig(Position = 8, Description = "Allow farm pokestop when walking to target - overwrite by pokemon filter")] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + public bool SpinWhileWalking { get; set; } + + [NecroBotConfig(Position = 9, Description = "Set to make bot return to farm zone define in MaxTravelDistance in location config")] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + public bool AlwaysWalkback { get; set; } + + [NecroBotConfig(Position = 10, Description = "The area the bot looking for pokemon from data service.")] + [DefaultValue(0.025)] + [Range(0, 1)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + public double SnipingScanOffset { get; set; } + + [NecroBotConfig(Position = 11, Description = "The max distance bot will always walk back regardless AlwaysWalkback")] + [DefaultValue(300.0)] + [Range(0, 9999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + public double WalkbackDistanceLimit { get; set; } + + [NecroBotConfig(Position = 12, Description = "Turn it on will always looking for pokemon at default location no matter what how far from current location")] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + public bool IncludeDefaultLocation { get; set; } + + [NecroBotConfig(Position = 13, Description = "Use list pokemon pokemon to snipe")] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + public bool UseSnipePokemonList { get; set; } + + [NecroBotConfig(Position = 14, Description = "The maximun speed up that bot travel when snipe - overwrite by pokemon setting")] + [DefaultValue(60.0)] + [Range(0, 999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + public double MaxSpeedUpSpeed { get; set; } + + [NecroBotConfig(Position = 15, Description = "Allow bot speed up for snipe with the max speed defined above - overwrite by pokemon setting")] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + public bool AllowSpeedUp { get; set; } + + [NecroBotConfig(Position = 16, Description = "Milisecond delay time at destination before looking for pokemon - overwrite by pokemon setting")] + [DefaultValue(10000)] + [Range(0, 999999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + public int DelayTimeAtDestination { get; set; } // 10 sec + + [NecroBotConfig(Position = 17, Description = "Datasource from pokeradar.info")] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + public bool UsePokeRadar { get; set; } + + [NecroBotConfig(Position = 18, Description = "Datasource from UseSkiplagged.info")] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + public bool UseSkiplagged { get; set; } + + [NecroBotConfig(Position = 19, Description = "Datasource from pokekcrew")] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + public bool UsePokecrew { get; set; } + + [NecroBotConfig(Position = 20, Description = "Datasource from pokesnipers")] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + public bool UsePokesnipers { get; set; } + + [NecroBotConfig(Position = 21, Description = "Datasource from pokezz.info")] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + public bool UsePokeZZ { get; set; } + + [NecroBotConfig(Position = 22, Description = "Datasource from pokewatcher")] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + public bool UsePokeWatcher { get; set; } + + [NecroBotConfig(Position = 23, Description = "Datasource from FPM")] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + public bool UseFastPokemap { get; set; } + + [NecroBotConfig(Position = 24, Description = "Datasource from location feeder")] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + public bool UsePogoLocationFeeder { get; set; } + + [NecroBotConfig(Position = 25, Description = "Allow bot transfer while working to target - overriteable")] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate)] + public bool AllowTransferWhileWalking { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Settings/HumanWalkSnipeFilter.cs b/PoGo.NecroBot.Logic/Model/Settings/HumanWalkSnipeFilter.cs new file mode 100644 index 000000000..d42f8246a --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/HumanWalkSnipeFilter.cs @@ -0,0 +1,135 @@ +using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; +using POGOProtos.Enums; + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + [JsonObject(Description = "", ItemRequired = Required.DisallowNull)] //Dont set Title + public class HumanWalkSnipeFilter + { + public HumanWalkSnipeFilter() + { + } + + [JsonIgnore] + [NecroBotConfig(IsPrimaryKey = true, Key = "Allow Snipe", Position = 1, Description = "Allows bot to snipe this pokemon")] + public bool AllowSnipe { get; set; } + + [DefaultValue(300.0)] + [Range(0, 9999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + [NecroBotConfig(Key = "MaxDistance" , Description = "Max distance that bot is allowed to walk to snipe this pokemon", Position = 2)] + public double MaxDistance { get; set; } + + [NecroBotConfig(Key = "Priority", Description = "Priority for this pokemon compared to others", Position = 3)] + [DefaultValue(1)] + [Range(0, 151)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 2)] + public int Priority { get; set; } + + [NecroBotConfig(Key = "Max Walk Times", Description = "Max walk time for bot to catch this pokemon compared to others", Position = 4)] + [DefaultValue(200.0)] + [Range(0, 9999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 3)] + public double MaxWalkTimes { get; set; } + + [NecroBotConfig(Key = "Allow Catch", Description = "Allows bot to catch pokemon while it walking to snipe target", Position = 5)] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 4)] + public bool CatchPokemonWhileWalking { get; set; } + + [NecroBotConfig(Key = "Allow Spin", Description = "Allows bot spin pokestops while it's walking to snipe target", Position = 6)] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 5)] + public bool SpinPokestopWhileWalking { get; set; } + + [NecroBotConfig(Key = "Max Speed", Description = "The max speed up to apply while walking to this pokemon", Position = 7)] + [DefaultValue(60.0)] + [Range(0, 999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 6)] + public double MaxSpeedUpSpeed { get; set; } + + [NecroBotConfig(Key = "Allow SpeedUP", Description = "Allows bot to speed up to catch this pokemon", Position = 8)] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 7)] + public bool AllowSpeedUp { get; set; } + + [NecroBotConfig(Key = "Delay at Dest", Description = "Sets delay time at destination", Position = 9)] + [DefaultValue(10000)] + [Range(0, 999999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 8)] + public int DelayTimeAtDestination { get; set; } + + [NecroBotConfig(Key = "Allow Transfer", Description = "Allows bot to transfer pokemon while it's walking to target", Position = 10)] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 9)] + public bool AllowTransferWhileWalking { get; set; } + + public HumanWalkSnipeFilter(double maxDistance, double maxWalkTimes, int priority, bool catchPokemon, + bool spinPokestop, bool allowSpeedUp, double speedUpSpeed, int delay = 10, bool allowtransfer = false) + { + AllowSnipe = true; + MaxDistance = maxDistance; + MaxWalkTimes = maxWalkTimes; + Priority = priority; + CatchPokemonWhileWalking = catchPokemon; + SpinPokestopWhileWalking = spinPokestop; + AllowSpeedUp = allowSpeedUp; + MaxSpeedUpSpeed = speedUpSpeed; + DelayTimeAtDestination = delay; + AllowTransferWhileWalking = allowtransfer; + } + + internal static Dictionary Default() + { + return new Dictionary + { + //http://fraghero.com/heres-the-full-pokemon-go-pokemon-list-most-common-to-the-rarest/ + + {PokemonId.Magikarp, new HumanWalkSnipeFilter(300, 200, 10, true, true, false, 60.0)}, + {PokemonId.Eevee, new HumanWalkSnipeFilter(500, 200, 10, true, true, false, 60.0)}, + {PokemonId.Electabuzz, new HumanWalkSnipeFilter(1500, 700, 2, true, true, false, 60.0)}, + {PokemonId.Dragonite, new HumanWalkSnipeFilter(3000, 900, 1, false, false, false, 60.0)}, + {PokemonId.Dragonair, new HumanWalkSnipeFilter(3000, 900, 1, false, false, false, 60.0)}, + {PokemonId.Dratini, new HumanWalkSnipeFilter(2000, 900, 1, false, false, false, 60.0)}, + {PokemonId.Charizard, new HumanWalkSnipeFilter(3000, 900, 1, false, false, false, 60.0)}, + {PokemonId.Snorlax, new HumanWalkSnipeFilter(3000, 900, 1, false, false, false, 60.0)}, + {PokemonId.Lapras, new HumanWalkSnipeFilter(3000, 900, 1, false, false, false, 60.0)}, + {PokemonId.Exeggutor, new HumanWalkSnipeFilter(1500, 600, 1, false, true, false, 60.0)}, + {PokemonId.Vaporeon, new HumanWalkSnipeFilter(1800, 800, 2, false, false, false, 60.0)}, + {PokemonId.Lickitung, new HumanWalkSnipeFilter(1800, 800, 2, false, false, false, 60.0)}, + {PokemonId.Flareon, new HumanWalkSnipeFilter(1800, 800, 2, false, false, false, 60.0)}, + {PokemonId.Scyther, new HumanWalkSnipeFilter(1000, 800, 3, false, false, false, 60.0)}, + {PokemonId.Beedrill, new HumanWalkSnipeFilter(1000, 800, 3, false, false, false, 60.0)}, + {PokemonId.Chansey, new HumanWalkSnipeFilter(1500, 800, 2, false, false, false, 60.0)}, + {PokemonId.Hitmonlee, new HumanWalkSnipeFilter(1500, 800, 2, false, false, false, 60.0)}, + {PokemonId.Machamp, new HumanWalkSnipeFilter(1500, 800, 2, false, false, false, 60.0)}, + {PokemonId.Ninetales, new HumanWalkSnipeFilter(1500, 800, 2, false, false, false, 60.0)}, + {PokemonId.Jolteon, new HumanWalkSnipeFilter(1200, 800, 2, false, false, false, 60.0)}, + {PokemonId.Poliwhirl, new HumanWalkSnipeFilter(1200, 800, 2, false, false, false, 60.0)}, + {PokemonId.Rapidash, new HumanWalkSnipeFilter(1500, 800, 2, false, false, false, 60.0)}, + {PokemonId.Cloyster, new HumanWalkSnipeFilter(1200, 800, 2, false, false, false, 60.0)}, + {PokemonId.Dodrio, new HumanWalkSnipeFilter(1200, 800, 2, false, false, false, 60.0)}, + {PokemonId.Clefable, new HumanWalkSnipeFilter(1000, 800, 3, false, false, false, 60.0)}, + {PokemonId.Golbat, new HumanWalkSnipeFilter(200, 800, 6, true, true, false, 60.0)}, + {PokemonId.Jynx, new HumanWalkSnipeFilter(1200, 800, 4, true, true, false, 60.0)}, + {PokemonId.Rhydon, new HumanWalkSnipeFilter(1200, 800, 4, true, true, false, 60.0)}, + {PokemonId.Kangaskhan, new HumanWalkSnipeFilter(800, 800, 4, true, true, false, 60.0)}, + {PokemonId.Wigglytuff, new HumanWalkSnipeFilter(1250, 800, 4, true, true, false, 60.0)}, + {PokemonId.Gyarados, new HumanWalkSnipeFilter(1800, 800, 2, false, false, false, 60.0)}, + {PokemonId.Dewgong, new HumanWalkSnipeFilter(1800, 800, 2, false, false, false, 60.0)}, + {PokemonId.Blastoise, new HumanWalkSnipeFilter(3000, 900, 1, false, false, true, 60.0)}, + {PokemonId.Venusaur, new HumanWalkSnipeFilter(3000, 900, 1, false, false, true, 60.0)}, + {PokemonId.Bulbasaur, new HumanWalkSnipeFilter(500, 300, 3, true, true, false, 60.0)}, + {PokemonId.Charmander, new HumanWalkSnipeFilter(500, 300, 3, true, true, false, 60.0)}, + {PokemonId.Squirtle, new HumanWalkSnipeFilter(500, 300, 3, true, true, false, 60.0)}, + {PokemonId.Omanyte, new HumanWalkSnipeFilter(1500, 300, 3, true, true, false, 60.0)}, + {PokemonId.Marowak, new HumanWalkSnipeFilter(1500, 300, 3, true, true, false, 60.0)}, + {PokemonId.Venomoth, new HumanWalkSnipeFilter(1500, 300, 3, true, true, false, 60.0)}, + {PokemonId.Vileplume, new HumanWalkSnipeFilter(1500, 300, 3, true, true, false, 60.0)} + }; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Settings/HumanlikeDelays.cs b/PoGo.NecroBot.Logic/Model/Settings/HumanlikeDelays.cs new file mode 100644 index 000000000..e7c3df1f8 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/HumanlikeDelays.cs @@ -0,0 +1,53 @@ +using Newtonsoft.Json; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +namespace PoGo.NecroBot.Logic.Model.Settings +{ + public class HumanlikeDelays : BaseConfig + { + public HumanlikeDelays() : base() + { + } + + [NecroBotConfig(Description = "Enables the usage of Human-Like Delays", Position = 1)] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + public bool UseHumanlikeDelays { get; set; } + + [NecroBotConfig(Description = "Sets delay time when a catch is successfull", Position = 2)] + [DefaultValue(13000)] + [Range(0, 999999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 2)] + public int CatchSuccessDelay { get; set; } + + [NecroBotConfig(Description = "Sets delay time when a catch is error", Position = 3)] + [DefaultValue(1000)] + [Range(0, 999999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 3)] + public int CatchErrorDelay { get; set; } + + [NecroBotConfig(Description = "Sets delay time when a catch escapes", Position = 4)] + [DefaultValue(3500)] + [Range(0, 999999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 4)] + public int CatchEscapeDelay { get; set; } + + [NecroBotConfig(Description = "Sets delay time when a catch flees", Position = 5)] + [DefaultValue(2000)] + [Range(0, 999999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 5)] + public int CatchFleeDelay { get; set; } + + [NecroBotConfig(Description = "Sets delay time when a catch is missed", Position = 6)] + [DefaultValue(1000)] + [Range(0, 999999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 6)] + public int CatchMissedDelay { get; set; } + + [NecroBotConfig(Description = "Sets delay time when throwing the ball before catch", Position = 7)] + [DefaultValue(1500)] + [Range(0, 999999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 7)] + public int BeforeCatchDelay { get; set; } + } +} diff --git a/PoGo.NecroBot.Logic/Model/Settings/IPokemonFilter.cs b/PoGo.NecroBot.Logic/Model/Settings/IPokemonFilter.cs new file mode 100644 index 000000000..fc953ea30 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/IPokemonFilter.cs @@ -0,0 +1,11 @@ +using POGOProtos.Enums; +using System.Collections.Generic; + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + public interface IPokemonFilter + { + List AffectToPokemons { get; set; } + IPokemonFilter GetGlobalFilter(); + } +} diff --git a/PoGo.NecroBot.Logic/Model/Settings/ItemRecycleConfig.cs b/PoGo.NecroBot.Logic/Model/Settings/ItemRecycleConfig.cs new file mode 100644 index 000000000..76d3da829 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/ItemRecycleConfig.cs @@ -0,0 +1,103 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + [JsonObject(Title = "Recycle Config", Description = "Set your recycle settings.", ItemRequired = Required.DisallowNull)] + public class ItemRecycleConfig : BaseConfig + { + public ItemRecycleConfig() : base() + { + } + + [NecroBotConfig(Description = "Allows bot to display lists of items to be recycled", Position = 1)] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + public bool VerboseRecycling { get; set; } + + [NecroBotConfig(Description = "Specify percentage of inventory full to start recycle", Position = 2)] + [DefaultValue(80)] + [Range(0, 100)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 2)] + public double RecycleInventoryAtUsagePercentage { get; set; } + + [NecroBotConfig(Description = "Turn on randomizing recycled items", Position = 3)] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 3)] + public bool RandomizeRecycle; + + [NecroBotConfig(Description = "Number of randomized items to be recycled", Position = 4)] + [DefaultValue(3)] + [Range(0, 100)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 4)] + public int RandomRecycleValue { get; set; } + + /*Amounts*/ + [NecroBotConfig(Description = "How many pokeballs (normal, great, ultra) to be kept ", Position = 5)] + [DefaultValue(100)] + [Range(0, 999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 5)] + public int TotalAmountOfPokeballsToKeep { get; set; } + + [NecroBotConfig(Description = "How many potions (normal, hyper, ultra, max) to be kept ", Position = 6)] + [DefaultValue(75)] + [Range(0, 999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 6)] + public int TotalAmountOfPotionsToKeep { get; set; } + + [NecroBotConfig(Description = "How many revives (normal, max) to be kept ", Position = 7)] + [DefaultValue(75)] + [Range(0, 999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 7)] + public int TotalAmountOfRevivesToKeep { get; set; } + + [NecroBotConfig(Description = "How many berries to be kept ", Position = 8)] + [DefaultValue(45)] + [Range(0, 999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 8)] + public int TotalAmountOfBerriesToKeep { get; set; } + + [NecroBotConfig(Description = "How many Evolution to be kept ", Position = 9)] + [DefaultValue(25)] + [Range(0, 999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 9)] + public int TotalAmountOfEvolutionToKeep { get; set; } + + /* Percents */ + [NecroBotConfig(Description = "Use recycle percents instead of totals (for example PercentOfInventoryPokeballsToKeep instead of TotalAmountOfPokeballsToKeep) ", Position = 10)] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 10)] + public bool UseRecyclePercentsInsteadOfTotals { get; set; } + + [NecroBotConfig(Description = "How many pokeballs (normal, great, ultra) to be kept as a percent of inventory ", Position = 11)] + [DefaultValue(35)] + [Range(0, 100)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 11)] + public int PercentOfInventoryPokeballsToKeep { get; set; } + + [NecroBotConfig(Description = "How many potions (normal, hyper, ultra, max) to be kept as a percent of inventory ", Position = 12)] + [DefaultValue(35)] + [Range(0, 100)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 12)] + public int PercentOfInventoryPotionsToKeep { get; set; } + + [NecroBotConfig(Description = "How many revives (normal, max) to be kept as a percent of inventory ", Position = 13)] + [DefaultValue(20)] + [Range(0, 100)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 13)] + public int PercentOfInventoryRevivesToKeep { get; set; } + + [NecroBotConfig(Description = "How many berries to be kept as a percent of inventory ", Position = 14)] + [DefaultValue(10)] + [Range(0, 100)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 14)] + public int PercentOfInventoryBerriesToKeep { get; set; } + + [NecroBotConfig(Description = "How many evolution to be kept as a percent of inventory ", Position = 15)] + [DefaultValue(10)] + [Range(0, 100)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 15)] + public int PercentOfInventoryEvolutionToKeep { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Settings/ItemRecycleFilter.cs b/PoGo.NecroBot.Logic/Model/Settings/ItemRecycleFilter.cs new file mode 100644 index 000000000..13da7072f --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/ItemRecycleFilter.cs @@ -0,0 +1,72 @@ +using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; +using POGOProtos.Inventory.Item; + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + [JsonObject(Title = "Item Recycle Filter", Description = "", ItemRequired = Required.DisallowNull)] + public class ItemRecycleFilter :BaseConfig + { + public ItemRecycleFilter() :base() + { + } + + public ItemRecycleFilter(ItemId key, int value) + { + Key = key; + Value = value; + } + + [NecroBotConfig(Description ="Item Name")] + [DefaultValue(ItemId.ItemUnknown)] + [JsonProperty(Required = Required.Always, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + public ItemId Key { get; set; } + + [NecroBotConfig(Description = "Item Amount to keep")] + [DefaultValue(0)] + [Range(0, 999)] + [JsonProperty(Required = Required.Always, DefaultValueHandling = DefaultValueHandling.Populate, Order = 2)] + public int Value { get; set; } + + internal static List ItemRecycleFilterDefault() + { + return new List + { + new ItemRecycleFilter(ItemId.ItemUnknown, 0), + new ItemRecycleFilter(ItemId.ItemLuckyEgg, 200), + new ItemRecycleFilter(ItemId.ItemIncenseOrdinary, 100), + new ItemRecycleFilter(ItemId.ItemIncenseSpicy, 100), + new ItemRecycleFilter(ItemId.ItemIncenseCool, 100), + new ItemRecycleFilter(ItemId.ItemIncenseFloral, 100), + new ItemRecycleFilter(ItemId.ItemTroyDisk, 100), + new ItemRecycleFilter(ItemId.ItemXAttack, 100), + new ItemRecycleFilter(ItemId.ItemXDefense, 100), + new ItemRecycleFilter(ItemId.ItemXMiracle, 100), + new ItemRecycleFilter(ItemId.ItemSpecialCamera, 100), + new ItemRecycleFilter(ItemId.ItemIncubatorBasicUnlimited, 100), + new ItemRecycleFilter(ItemId.ItemIncubatorBasic, 100), + new ItemRecycleFilter(ItemId.ItemPokemonStorageUpgrade, 100), + new ItemRecycleFilter(ItemId.ItemItemStorageUpgrade, 100), + new ItemRecycleFilter(ItemId.ItemPokeBall, 50), + new ItemRecycleFilter(ItemId.ItemRevive, 10), + new ItemRecycleFilter(ItemId.ItemPotion, 10), + new ItemRecycleFilter(ItemId.ItemHyperPotion, 10), + new ItemRecycleFilter(ItemId.ItemGreatBall, 100), + new ItemRecycleFilter(ItemId.ItemBlukBerry, 30), + new ItemRecycleFilter(ItemId.ItemNanabBerry, 30), + new ItemRecycleFilter(ItemId.ItemWeparBerry, 30), + new ItemRecycleFilter(ItemId.ItemPinapBerry, 30), + new ItemRecycleFilter(ItemId.ItemGoldenNanabBerry, 30), + new ItemRecycleFilter(ItemId.ItemGoldenPinapBerry, 30), + new ItemRecycleFilter(ItemId.ItemGoldenRazzBerry, 30), + new ItemRecycleFilter(ItemId.ItemDragonScale, 10), + new ItemRecycleFilter(ItemId.ItemKingsRock, 10), + new ItemRecycleFilter(ItemId.ItemSunStone, 10), + new ItemRecycleFilter(ItemId.ItemMetalCoat, 10), + new ItemRecycleFilter(ItemId.ItemUpGrade, 10) + }; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Settings/LevelUpConfig.cs b/PoGo.NecroBot.Logic/Model/Settings/LevelUpConfig.cs new file mode 100644 index 000000000..cfd694821 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/LevelUpConfig.cs @@ -0,0 +1,54 @@ +using System.Collections.Generic; +using Newtonsoft.Json; +using POGOProtos.Enums; + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + [JsonObject(Title = "LevelUp Config", Description = "Set your levelup settings.", ItemRequired = Required.DisallowNull)] + public class LevelUpConfig + { + internal static List PokemonsToLevelUpDefault() + { + return new List + { + //criteria: from SS Tier to A Tier + Regional Exclusive + PokemonId.Venusaur, + PokemonId.Charizard, + PokemonId.Blastoise, + //PokemonId.Nidoqueen, + //PokemonId.Nidoking, + PokemonId.Clefable, + //PokemonId.Vileplume, + //PokemonId.Golduck, + //PokemonId.Arcanine, + //PokemonId.Poliwrath, + //PokemonId.Machamp, + //PokemonId.Victreebel, + //PokemonId.Golem, + //PokemonId.Slowbro, + //PokemonId.Farfetchd, + PokemonId.Muk, + //PokemonId.Exeggutor, + //PokemonId.Lickitung, + PokemonId.Chansey, + //PokemonId.Kangaskhan, + //PokemonId.MrMime, + //PokemonId.Tauros, + PokemonId.Gyarados, + //PokemonId.Lapras, + PokemonId.Ditto, + //PokemonId.Vaporeon, + //PokemonId.Jolteon, + //PokemonId.Flareon, + //PokemonId.Porygon, + PokemonId.Snorlax, + PokemonId.Articuno, + PokemonId.Zapdos, + PokemonId.Moltres, + PokemonId.Dragonite, + PokemonId.Mewtwo, + PokemonId.Mew + }; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Settings/Location.cs b/PoGo.NecroBot.Logic/Model/Settings/Location.cs new file mode 100644 index 000000000..7dee28e0c --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/Location.cs @@ -0,0 +1,27 @@ +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + [JsonObject(Title = "Location", Description = "", ItemRequired = Required.DisallowNull)] + public class Location + { + public Location() + { + } + + public Location(double latitude, double longitude) + { + Latitude = latitude; + Longitude = longitude; + } + + [Range(-90, 90)] + [JsonProperty(Required = Required.Always, DefaultValueHandling = DefaultValueHandling.Ignore, Order = 1)] + public double Latitude { get; set; } + + [Range(-180, 180)] + [JsonProperty(Required = Required.Always, DefaultValueHandling = DefaultValueHandling.Ignore, Order = 2)] + public double Longitude { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Settings/LocationConfig.cs b/PoGo.NecroBot.Logic/Model/Settings/LocationConfig.cs new file mode 100644 index 000000000..b25d605c0 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/LocationConfig.cs @@ -0,0 +1,82 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + [JsonObject(Title = "Location Config", Description = "Set your location settings.", ItemRequired = Required.DisallowNull)] + public class LocationConfig : BaseConfig + { + public LocationConfig() : base() + { + } + + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + [NecroBotConfig(Description = "When enabled, bot will teleport instead of walking. this is not recommended.", Position = 1)] + public bool DisableHumanWalking { get; set; } + + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 2)] + [NecroBotConfig(Description = "When enabled, bot will start from the last known location instead of default location", Position = 2)] + public bool StartFromLastPosition { get; set; } + + [DefaultValue(40.785092)] + [Range(-90, 90)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 3)] + [NecroBotConfig(Description = "Default Latitude that bot will start playing at.", Position = 3)] + public double DefaultLatitude { get; set; } + + [DefaultValue(-73.968286)] + [Range(-180, 180)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 4)] + [NecroBotConfig(Description = "Default Longitude that bot will start playing at.", Position = 4)] + public double DefaultLongitude { get; set; } + + [DefaultValue(24.98)] + [Range(0, 999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 5)] + [NecroBotConfig(Description = "The walking speed that applies to bot for moving between pokestops.", Position = 5)] + public double WalkingSpeedInKilometerPerHour { get; set; } + + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 6)] + [NecroBotConfig(Description = "Turn this option on to add random speed variation into Walking Speed.", Position = 6)] + public bool UseWalkingSpeedVariant { get; set; } + + [DefaultValue(1.2)] + [Range(0, 999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 7)] + [NecroBotConfig(Description = "The random speed add/minus into walking speed when UseWalkingSpeedVariant is set to true", Position = 7)] + public double WalkingSpeedVariant { get; set; } + + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 8)] + [NecroBotConfig(Description = "Display variant speed change in console window ", Position = 8)] + public bool ShowVariantWalking { get; set; } + + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 9)] + [NecroBotConfig(Description = "When Enabled, bot will stop at a pokestop randomly, making him more humanlike", Position = 9)] + public bool RandomlyPauseAtStops { get; set; } + + [DefaultValue(10)] + [Range(0, 999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 10)] + [NecroBotConfig(Description = "Set random offset change when bot starts from default location", Position = 10)] + public int MaxSpawnLocationOffset { get; set; } + + [DefaultValue(1000)] + [Range(0, 9999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 11)] + [NecroBotConfig(Description = "The radius distance for bot to travel from default location. Notice that this may be changed depending on other configs, as bot may walk out of that radius", Position = 11)] + public int MaxTravelDistanceInMeters { get; set; } + + [JsonIgnore] + public int ResumeTrack = 0; + [JsonIgnore] + public int ResumeTrackSeg = 0; + [JsonIgnore] + public int ResumeTrackPt = 0; + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Settings/LogicSettings.cs b/PoGo.NecroBot.Logic/Model/Settings/LogicSettings.cs new file mode 100644 index 000000000..4310053a7 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/LogicSettings.cs @@ -0,0 +1,304 @@ +#region using directives + +using System.Collections.Generic; +using System.Linq; +using PoGo.NecroBot.Logic.Interfaces.Configuration; +using POGOProtos.Enums; +using POGOProtos.Inventory.Item; +using System; + +#endregion + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + public class LogicSettings : ILogicSettings + { + private readonly GlobalSettings _settings; + private Random _rand; + + public LogicSettings(GlobalSettings settings) + { + _settings = settings; + _rand = new Random(); + } + + public double GenRandom(double min, double max) + { + return _rand.NextDouble() * (max - min) + min; + } + + public int GenRandom(int val) + { + double min = val - (_settings.PlayerConfig.RandomizeSettingsByPercent / 100.0 * val); + int newVal = (int)Math.Floor(GenRandom(min, val)); + if (newVal < 0) + newVal = 0; + return newVal; + } + + public double GenRandom(double val) + { + double min = val - (_settings.PlayerConfig.RandomizeSettingsByPercent / 100.0 * val); + double newVal = GenRandom(min, val); + if (newVal < 0) + newVal = 0; + return newVal; + } + + public float GenRandom(float val) + { + double min = val - (_settings.PlayerConfig.RandomizeSettingsByPercent / 100.0 * val); + double newVal = GenRandom(min, val); + if (newVal < 0) + newVal = 0; + return (float)newVal; + } + + public string ProfilePath => _settings.ProfilePath; + public string ProfileConfigPath => _settings.ProfileConfigPath; + public string GeneralConfigPath => _settings.GeneralConfigPath; + public int SchemaVersion => _settings.UpdateConfig.SchemaVersion; + public bool CheckForUpdates => _settings.UpdateConfig.CheckForUpdates; + public bool AutoUpdate => _settings.UpdateConfig.AutoUpdate; + public bool UseWebsocket => _settings.WebsocketsConfig.UseWebsocket; + public bool CatchPokemon => _settings.PokemonConfig.CatchPokemon; + public int OutOfBallCatchBlockTime => GenRandom(_settings.PokemonConfig.OutOfBallCatchBlockTime); + public string SnipeLocationServer => _settings.SnipeConfig.SnipeLocationServer; + public int SnipeLocationServerPort => _settings.SnipeConfig.SnipeLocationServerPort; + + public bool UseSnipeLocationServer => _settings.SnipeConfig.UseSnipeLocationServer; + public int PokeballsToKeepForSnipe => GenRandom(_settings.PokemonConfig.PokeballToKeepForSnipe); + public double AutoSnipeMaxDistance => GenRandom(_settings.SnipeConfig.AutoSnipeMaxDistance); + public int AutoSnipeBatchSize => _settings.SnipeConfig.AutoSnipeBatchSize; + public int CatchPokemonLimit => GenRandom(_settings.PokemonConfig.CatchPokemonLimit); + public int CatchPokemonLimitMinutes => GenRandom(_settings.PokemonConfig.CatchPokemonLimitMinutes); + public int PokeStopLimit => GenRandom(_settings.PokeStopConfig.PokeStopLimit); + public int PokeStopLimitMinutes => GenRandom(_settings.PokeStopConfig.PokeStopLimitMinutes); + public int SnipeCountLimit => GenRandom(_settings.SnipeConfig.SnipeCountLimit); + public int SnipeRestSeconds => GenRandom(_settings.SnipeConfig.SnipeRestSeconds); + public bool TransferWeakPokemon => _settings.PokemonConfig.TransferWeakPokemon; + public bool DisableHumanWalking => _settings.LocationConfig.DisableHumanWalking; + public float KeepMinIvPercentage => _settings.PokemonConfig.KeepMinIvPercentage; + public string KeepMinOperator => _settings.PokemonConfig.KeepMinOperator; + public int KeepMinCp => _settings.PokemonConfig.KeepMinCp; + public int KeepMinLvl => _settings.PokemonConfig.KeepMinLvl; + public bool UseKeepMinLvl => _settings.PokemonConfig.UseKeepMinLvl; + public bool KeepPokemonsToBeEvolved => _settings.PokemonConfig.KeepPokemonsToBeEvolved; + public bool AutomaticallyLevelUpPokemon => _settings.PokemonConfig.AutomaticallyLevelUpPokemon; + public bool OnlyUpgradeFavorites => _settings.PokemonConfig.OnlyUpgradeFavorites; + public double UpgradePokemonLvlMinimum => _settings.PokemonConfig.UpgradePokemonLvlMinimum; + public bool UseLevelUpList => _settings.PokemonConfig.UseLevelUpList; + public int AmountOfTimesToUpgradeLoop => _settings.PokemonConfig.AmountOfTimesToUpgradeLoop; + public string LevelUpByCPorIv => _settings.PokemonConfig.LevelUpByCPorIv; + public int GetMinStarDustForLevelUp => _settings.PokemonConfig.GetMinStarDustForLevelUp; + public bool UseLuckyEggConstantly => _settings.PokemonConfig.UseLuckyEggConstantly; + public bool UseIncenseConstantly => _settings.PokemonConfig.UseIncenseConstantly; + public string UseBallOperator => _settings.PokemonConfig.UseBallOperator.ToString(); + public float UpgradePokemonIvMinimum => _settings.PokemonConfig.UpgradePokemonIvMinimum; + public float UpgradePokemonCpMinimum => _settings.PokemonConfig.UpgradePokemonCpMinimum; + public string UpgradePokemonMinimumStatsOperator => _settings.PokemonConfig.UpgradePokemonMinimumStatsOperator; + public double WalkingSpeedInKilometerPerHour => _settings.LocationConfig.WalkingSpeedInKilometerPerHour; + public bool UseWalkingSpeedVariant => _settings.LocationConfig.UseWalkingSpeedVariant; + public double WalkingSpeedVariant => _settings.LocationConfig.WalkingSpeedVariant; + public bool ShowVariantWalking => _settings.LocationConfig.ShowVariantWalking; + public bool FastSoftBanBypass => _settings.SoftBanConfig.FastSoftBanBypass; + public int ByPassSpinCount => _settings.SoftBanConfig.ByPassSpinCount; + public bool ByPassCatchFlee => _settings.PokemonConfig.ByPassCatchFlee; + public bool TransferDuplicatePokemon => _settings.PokemonConfig.TransferDuplicatePokemon; + public bool TransferDuplicatePokemonOnCapture => _settings.PokemonConfig.TransferDuplicatePokemonOnCapture; + public bool UseBulkTransferPokemon => _settings.PokemonConfig.UseBulkTransferPokemon; + public string DefaultBuddyPokemon => _settings.PokemonConfig.DefaultBuddyPokemon; + public bool AutoFinishTutorial => _settings.PlayerConfig.AutoFinishTutorial; + public bool SkipFirstTimeTutorial => _settings.PlayerConfig.SkipFirstTimeTutorial; + public bool SkipCollectingLevelUpRewards => _settings.PlayerConfig.SkipCollectingLevelUpRewards; + public int BulkTransferSize => _settings.PokemonConfig.BulkTransferSize; + public int BulkTransferStogareBuffer => _settings.PokemonConfig.BulkTransferStogareBuffer; + public bool AutoWalkAI => _settings.PlayerConfig.AutoWalkAI; + public int AutoWalkDist => _settings.PlayerConfig.AutoWalkDist; + + public bool UseEggIncubators => _settings.PokemonConfig.UseEggIncubators; + public bool UseLimitedEggIncubators => _settings.PokemonConfig.UseLimitedEggIncubators; + public int UseGreatBallAboveCp => GenRandom(_settings.PokemonConfig.UseGreatBallAboveCp); + public int UseUltraBallAboveCp => GenRandom(_settings.PokemonConfig.UseUltraBallAboveCp); + public int UseMasterBallAboveCp => GenRandom(_settings.PokemonConfig.UseMasterBallAboveCp); + public double UseGreatBallAboveIv => GenRandom(_settings.PokemonConfig.UseGreatBallAboveIv); + public double UseUltraBallAboveIv => GenRandom(_settings.PokemonConfig.UseUltraBallAboveIv); + public double UseMasterBallBelowCatchProbability => GenRandom(_settings.PokemonConfig.UseMasterBallBelowCatchProbability); + public double UseUltraBallBelowCatchProbability => GenRandom(_settings.PokemonConfig.UseUltraBallBelowCatchProbability); + public double UseGreatBallBelowCatchProbability => GenRandom(_settings.PokemonConfig.UseGreatBallBelowCatchProbability); + public bool EnableHumanizedThrows => _settings.CustomCatchConfig.EnableHumanizedThrows; + public bool EnableMissedThrows => _settings.CustomCatchConfig.EnableMissedThrows; + public int ThrowMissPercentage => _settings.CustomCatchConfig.ThrowMissPercentage; + public int NiceThrowChance => GenRandom(_settings.CustomCatchConfig.NiceThrowChance); + public int GreatThrowChance => GenRandom(_settings.CustomCatchConfig.GreatThrowChance); + public int ExcellentThrowChance => GenRandom(_settings.CustomCatchConfig.ExcellentThrowChance); + public int CurveThrowChance => GenRandom(_settings.CustomCatchConfig.CurveThrowChance); + public double ForceGreatThrowOverIv => GenRandom(_settings.CustomCatchConfig.ForceGreatThrowOverIv); + public double ForceExcellentThrowOverIv => GenRandom(_settings.CustomCatchConfig.ForceExcellentThrowOverIv); + public int ForceGreatThrowOverCp => GenRandom(_settings.CustomCatchConfig.ForceGreatThrowOverCp); + public int ForceExcellentThrowOverCp => GenRandom(_settings.CustomCatchConfig.ForceExcellentThrowOverCp); + public int DelayBetweenPokemonUpgrade => GenRandom(_settings.PokemonConfig.DelayBetweenPokemonUpgrade); + public int DelayBetweenPokemonCatch => GenRandom(_settings.PokemonConfig.DelayBetweenPokemonCatch); + + public int DelayBetweenPlayerActions => GenRandom(_settings.PlayerConfig.DelayBetweenPlayerActions); + public int EvolveActionDelay => GenRandom(_settings.PlayerConfig.EvolveActionDelay); + public int TransferActionDelay => GenRandom(_settings.PlayerConfig.TransferActionDelay); + public int RecycleActionDelay => GenRandom(_settings.PlayerConfig.RecycleActionDelay); + public int RenamePokemonActionDelay => GenRandom(_settings.PlayerConfig.RenamePokemonActionDelay); + public bool UseNearActionRandom => _settings.PlayerConfig.UseNearActionRandom; + public bool UsePokemonToNotCatchFilter => _settings.PokemonConfig.UsePokemonToNotCatchFilter; + public bool UsePokemonToCatchLocallyListOnly => _settings.PokemonConfig.UsePokemonToCatchLocallyListOnly; + public CatchSettings PokemonToCatchLocally => _settings.PokemonToCatchLocally; + public int KeepMinDuplicatePokemon => _settings.PokemonConfig.KeepMinDuplicatePokemon; + public int KeepMaxDuplicatePokemon => _settings.PokemonConfig.KeepMaxDuplicatePokemon; + public bool PrioritizeIvOverCp => _settings.PokemonConfig.PrioritizeIvOverCp; + public int MaxTravelDistanceInMeters => GenRandom(_settings.LocationConfig.MaxTravelDistanceInMeters); + public bool StartFromLastPosition => _settings.LocationConfig.StartFromLastPosition; + public string GpxFile => _settings.GPXConfig.GpxFile; + public bool UseGpxPathing => _settings.GPXConfig.UseGpxPathing; + public bool RenamePokemon => _settings.PokemonConfig.RenamePokemon; + public bool RenamePokemonRespectTransferRule => _settings.PokemonConfig.RenamePokemonRespectTransferRule; + public bool RenameOnlyAboveIv => _settings.PokemonConfig.RenameOnlyAboveIv; + public float FavoriteMinIvPercentage => _settings.PokemonConfig.FavoriteMinIvPercentage; + public float FavoriteMinCp => _settings.PokemonConfig.FavoriteMinCp; + public int FavoriteMinLevel => _settings.PokemonConfig.FavoriteMinLevel; + public string FavoriteOperator => _settings.PokemonConfig.FavoriteOperator.ToString(); + + public bool AutoFavoritePokemon => _settings.PokemonConfig.AutoFavoritePokemon; + public bool AutoFavoriteShinyOnCatch => _settings.PokemonConfig.AutoFavoriteShinyOnCatch; + public string RenameTemplate => _settings.PokemonConfig.RenameTemplate; + public int AmountOfPokemonToDisplayOnStart => _settings.ConsoleConfig.AmountOfPokemonToDisplayOnStart; + public bool DumpPokemonStats => _settings.PokemonConfig.DumpPokemonStats; + public string TranslationLanguageCode => _settings.ConsoleConfig.TranslationLanguageCode; + public bool DetailedCountsBeforeRecycling => _settings.ConsoleConfig.DetailedCountsBeforeRecycling; + public bool VerboseRecycling => _settings.RecycleConfig.VerboseRecycling; + public double RecycleInventoryAtUsagePercentage => GenRandom(_settings.RecycleConfig.RecycleInventoryAtUsagePercentage); + public Dictionary ItemUseFilters => _settings.ItemUseFilters; + + public ICollection> ItemRecycleFilter => _settings.ItemRecycleFilter.Select(itemRecycleFilter => new KeyValuePair(itemRecycleFilter.Key, itemRecycleFilter.Value)).ToList(); + public ICollection PokemonsToLevelUp => _settings.PokemonsToLevelUp; + public ICollection PokemonsNotToTransfer => _settings.PokemonsNotToTransfer; + public ICollection PokemonsNotToCatch => _settings.PokemonsToIgnore; + + public ICollection PokemonToUseMasterball => _settings.PokemonToUseMasterball; + public Dictionary PokemonsTransferFilter => _settings.PokemonsTransferFilter; + public Dictionary PokemonUpgradeFilters => _settings.PokemonUpgradeFilters; + public Dictionary PokemonSnipeFilters => _settings.SnipePokemonFilter; + public Dictionary PokemonEvolveFilters => _settings.PokemonEvolveFilter; + public bool StartupWelcomeDelay => _settings.ConsoleConfig.StartupWelcomeDelay; + + public bool UseGoogleWalk => _settings.GoogleWalkConfig.UseGoogleWalk; + public double DefaultStepLength => _settings.GoogleWalkConfig.DefaultStepLength; + public bool UseGoogleWalkCache => _settings.GoogleWalkConfig.Cache; + public string GoogleApiKey => _settings.GoogleWalkConfig.GoogleAPIKey; + public string GoogleHeuristic => _settings.GoogleWalkConfig.GoogleHeuristic; + public string GoogleElevationApiKey => _settings.GoogleWalkConfig.GoogleElevationAPIKey; + + public bool UseYoursWalk => _settings.YoursWalkConfig.UseYoursWalk; + public string YoursWalkHeuristic => _settings.YoursWalkConfig.YoursWalkHeuristic; + + public bool UseMapzenWalk => _settings.MapzenWalkConfig.UseMapzenWalk; + public string MapzenTurnByTurnApiKey => _settings.MapzenWalkConfig.MapzenTurnByTurnApiKey; + public string MapzenWalkHeuristic => _settings.MapzenWalkConfig.MapzenWalkHeuristic; + public string MapzenElevationApiKey => _settings.MapzenWalkConfig.MapzenElevationApiKey; + public bool SnipeAtPokestops => _settings.SnipeConfig.SnipeAtPokestops; + public bool ActivateMSniper => _settings.SnipeConfig.ActivateMSniper; + public bool UseTelegramAPI => _settings.TelegramConfig.UseTelegramAPI; + public string TelegramAPIKey => _settings.TelegramConfig.TelegramAPIKey; + public string TelegramPassword => _settings.TelegramConfig.TelegramPassword; + public int MinPokeballsToSnipe => GenRandom(_settings.SnipeConfig.MinPokeballsToSnipe); + public int MinPokeballsWhileSnipe => GenRandom(_settings.SnipeConfig.MinPokeballsWhileSnipe); + public int MaxPokeballsPerPokemon => GenRandom(_settings.PokemonConfig.MaxPokeballsPerPokemon); + public bool RandomlyPauseAtStops => _settings.LocationConfig.RandomlyPauseAtStops; + public int MinDelayBetweenSnipes => GenRandom(_settings.SnipeConfig.MinDelayBetweenSnipes); + public bool SnipePokemonNotInPokedex => _settings.SnipeConfig.SnipePokemonNotInPokedex; + public bool RandomizeRecycle => _settings.RecycleConfig.RandomizeRecycle; + public int RandomRecycleValue => _settings.RecycleConfig.RandomRecycleValue; + public int TotalAmountOfPokeballsToKeep => GenRandom(_settings.RecycleConfig.TotalAmountOfPokeballsToKeep); + public int TotalAmountOfPotionsToKeep => GenRandom(_settings.RecycleConfig.TotalAmountOfPotionsToKeep); + public int TotalAmountOfRevivesToKeep => GenRandom(_settings.RecycleConfig.TotalAmountOfRevivesToKeep); + public int TotalAmountOfBerriesToKeep => GenRandom(_settings.RecycleConfig.TotalAmountOfBerriesToKeep); + public int TotalAmountOfEvolutionToKeep => GenRandom(_settings.RecycleConfig.TotalAmountOfEvolutionToKeep); + + public bool UseRecyclePercentsInsteadOfTotals => _settings.RecycleConfig.UseRecyclePercentsInsteadOfTotals; + public int PercentOfInventoryPokeballsToKeep => GenRandom(_settings.RecycleConfig.PercentOfInventoryPokeballsToKeep); + public int PercentOfInventoryPotionsToKeep => GenRandom(_settings.RecycleConfig.PercentOfInventoryPotionsToKeep); + public int PercentOfInventoryRevivesToKeep => GenRandom(_settings.RecycleConfig.PercentOfInventoryRevivesToKeep); + public int PercentOfInventoryBerriesToKeep => GenRandom(_settings.RecycleConfig.PercentOfInventoryBerriesToKeep); + public int PercentOfInventoryEvolutionToKeep => GenRandom(_settings.RecycleConfig.PercentOfInventoryEvolutionToKeep); + + public bool UseSnipeLimit => _settings.SnipeConfig.UseSnipeLimit; + public bool UsePokeStopLimit => _settings.PokeStopConfig.UsePokeStopLimit; + public bool UseCatchLimit => _settings.PokemonConfig.UseCatchLimit; + public int ResumeTrack => _settings.LocationConfig.ResumeTrack; + public int ResumeTrackSeg => _settings.LocationConfig.ResumeTrackSeg; + public int ResumeTrackPt => _settings.LocationConfig.ResumeTrackPt; + public bool EnableHumanWalkingSnipe => _settings.HumanWalkSnipeConfig.Enable; + public bool HumanWalkingSnipeDisplayList => _settings.HumanWalkSnipeConfig.DisplayPokemonList; + public double HumanWalkingSnipeMaxDistance => GenRandom(_settings.HumanWalkSnipeConfig.MaxDistance); + public double HumanWalkingSnipeMaxEstimateTime => GenRandom(_settings.HumanWalkSnipeConfig.MaxEstimateTime); + public bool HumanWalkingSnipeTryCatchEmAll => _settings.HumanWalkSnipeConfig.TryCatchEmAll; + public int HumanWalkingSnipeCatchEmAllMinBalls => GenRandom(_settings.HumanWalkSnipeConfig.CatchEmAllMinBalls); + public bool HumanWalkingSnipeCatchPokemonWhileWalking => _settings.HumanWalkSnipeConfig.CatchPokemonWhileWalking; + public bool HumanWalkingSnipeSpinWhileWalking => _settings.HumanWalkSnipeConfig.SpinWhileWalking; + public bool HumanWalkingSnipeAlwaysWalkBack => _settings.HumanWalkSnipeConfig.AlwaysWalkback; + public double HumanWalkingSnipeWalkbackDistanceLimit => GenRandom(_settings.HumanWalkSnipeConfig.WalkbackDistanceLimit); + public double HumanWalkingSnipeSnipingScanOffset => GenRandom(_settings.HumanWalkSnipeConfig.SnipingScanOffset); + public bool HumanWalkingSnipeIncludeDefaultLocation => _settings.HumanWalkSnipeConfig.IncludeDefaultLocation; + public bool HumanWalkingSnipeUseSnipePokemonList => _settings.HumanWalkSnipeConfig.UseSnipePokemonList; + public Dictionary HumanWalkSnipeFilters => _settings.HumanWalkSnipeFilters; + public bool HumanWalkingSnipeAllowSpeedUp => _settings.HumanWalkSnipeConfig.AllowSpeedUp; + public double HumanWalkingSnipeMaxSpeedUpSpeed => _settings.HumanWalkSnipeConfig.MaxSpeedUpSpeed; + public int HumanWalkingSnipeDelayTimeAtDestination => GenRandom(_settings.HumanWalkSnipeConfig.DelayTimeAtDestination); + public bool HumanWalkingSnipeUsePokeRadar => _settings.HumanWalkSnipeConfig.UsePokeRadar; + public bool HumanWalkingSnipeUseSkiplagged => _settings.HumanWalkSnipeConfig.UseSkiplagged; + public bool HumanWalkingSnipeUsePokecrew => _settings.HumanWalkSnipeConfig.UsePokecrew; + public bool HumanWalkingSnipeUsePokesnipers => _settings.HumanWalkSnipeConfig.UsePokesnipers; + public bool HumanWalkingSnipeUsePokeZZ => _settings.HumanWalkSnipeConfig.UsePokeZZ; + public bool HumanWalkingSnipeUsePokeWatcher => _settings.HumanWalkSnipeConfig.UsePokeWatcher; + public bool HumanWalkingSnipeUseFastPokemap => _settings.HumanWalkSnipeConfig.UseFastPokemap; + public bool HumanWalkingSnipeUsePogoLocationFeeder => _settings.HumanWalkSnipeConfig.UsePogoLocationFeeder; + public bool HumanWalkingSnipeAllowTransferWhileWalking => _settings.HumanWalkSnipeConfig.AllowTransferWhileWalking; + public GymConfig GymConfig => _settings.GymConfig; + + public DataSharingConfig DataSharingConfig => _settings.DataSharingConfig; + public int SnipePauseOnOutOfBallTime => GenRandom(_settings.SnipeConfig.SnipePauseOnOutOfBallTime); + + public bool UseTransferFilterToCatch => _settings.CustomCatchConfig.UseTransferFilterToCatch; + public MultipleBotConfig MultipleBotConfig => _settings.MultipleBotConfig; + public List Bots => _settings.Auth.Bots; + public int MinIVForAutoSnipe => _settings.SnipeConfig.MinIVForAutoSnipe; + public bool AutosnipeVerifiedOnly => _settings.SnipeConfig.AutosnipeVerifiedOnly; + public int DefaultAutoSnipeCandy => _settings.SnipeConfig.DefaultAutoSnipeCandy; + public Dictionary BotSwitchPokemonFilters => _settings.BotSwitchPokemonFilters; + public NotificationConfig NotificationConfig => _settings.NotificationConfig; + public CaptchaConfig CaptchaConfig => _settings.CaptchaConfig; + public GUIConfig UIConfig => _settings.UIConfig; + + public int MinLevelForAutoSnipe => _settings.SnipeConfig.MinLevelForAutoSnipe; + + public bool UseHumanlikeDelays => _settings.HumanlikeDelays.UseHumanlikeDelays; + public int CatchSuccessDelay => _settings.HumanlikeDelays.CatchSuccessDelay; + public int CatchErrorDelay => _settings.HumanlikeDelays.CatchErrorDelay; + public int CatchEscapeDelay => _settings.HumanlikeDelays.CatchEscapeDelay; + public int CatchFleeDelay => _settings.HumanlikeDelays.CatchFleeDelay; + public int CatchMissedDelay => _settings.HumanlikeDelays.CatchMissedDelay; + public int BeforeCatchDelay => _settings.HumanlikeDelays.BeforeCatchDelay; + #region Evolve + public bool EvolvePokemonsThatMatchFilter => _settings.PokemonConfig.EvolveConfig.EvolvePokemonsThatMatchFilter; + public bool EvolveAnyPokemonAboveIv => _settings.PokemonConfig.EvolveConfig.EvolveAnyPokemonAboveIv; + public float EvolveAnyPokemonAboveIvValue => _settings.PokemonConfig.EvolveConfig.EvolveAnyPokemonAboveIvValue; + public bool TriggerEvolveAsSoonAsFilterIsMatched => _settings.PokemonConfig.EvolveConfig.TriggerAsSoonAsFilterIsMatched; + public bool TriggerEvolveOnEvolutionCount => _settings.PokemonConfig.EvolveConfig.TriggerOnEvolutionCount; + public int TriggerEvolveOnEvolutionCountValue => _settings.PokemonConfig.EvolveConfig.TriggerOnEvolutionCountValue; + public bool TriggerEvolveOnStorageUsagePercentage => _settings.PokemonConfig.EvolveConfig.TriggerOnStorageUsagePercentage; + public double TriggerEvolveOnStorageUsagePercentageValue => GenRandom(_settings.PokemonConfig.EvolveConfig.TriggerOnStorageUsagePercentageValue); + public bool TriggerEvolveOnStorageUsageAbsolute => _settings.PokemonConfig.EvolveConfig.TriggerOnStorageUsageAbsolute; + public int TriggerEvolveOnStorageUsageAbsoluteValue => GenRandom(_settings.PokemonConfig.EvolveConfig.TriggerOnStorageUsageAbsoluteValue); + public bool TriggerEvolveIfLuckyEggIsActive => _settings.PokemonConfig.EvolveConfig.TriggerIfLuckyEggIsActive; + public bool EvolvePreserveMinCandiesFromFilter => _settings.PokemonConfig.EvolveConfig.PreserveMinCandiesFromFilter; + public bool EvolveApplyLuckyEggOnEvolutionCount => _settings.PokemonConfig.EvolveConfig.ApplyLuckyEggOnEvolutionCount; + public int EvolveApplyLuckyEggOnEvolutionCountValue => _settings.PokemonConfig.EvolveConfig.ApplyLuckyEggOnEvolutionCountValue; + #endregion + } +} diff --git a/PoGo.NecroBot.Logic/Model/Settings/MapzenWalkConfig.cs b/PoGo.NecroBot.Logic/Model/Settings/MapzenWalkConfig.cs new file mode 100644 index 000000000..68b8d8dac --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/MapzenWalkConfig.cs @@ -0,0 +1,42 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + [JsonObject(Title = "MapzenWalk Config", Description = "Set your MapzenWalk settings.", ItemRequired = Required.DisallowNull)] + public class MapzenWalkConfig : BaseConfig + { + public MapzenWalkConfig() : base() + { + } + + internal enum MapzenWalkTravelModes + { + auto, + bicycle, + pedestrian + } + + [NecroBotConfig(Description = "Allows bot to use Mapzen API to resolve path", Position = 1)] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + public bool UseMapzenWalk { get; set; } + + [NecroBotConfig(Description = "API Key used to connect to Mapzen Services", Position = 2)] + [DefaultValue(null)] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 2)] + public string MapzenTurnByTurnApiKey { get; set; } + + [NecroBotConfig(Description = "Set the heuristic to find routes", Position = 3)] + [DefaultValue("bicycle")] + [EnumDataType(typeof(MapzenWalkTravelModes))] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 3)] + public string MapzenWalkHeuristic { get; set; } + + [NecroBotConfig(Description = "API Key used to connect to Mapzen API Elevation Services", Position = 4)] + [DefaultValue(null)] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 4)] + public string MapzenElevationApiKey { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Settings/MultipleBotConfig.cs b/PoGo.NecroBot.Logic/Model/Settings/MultipleBotConfig.cs new file mode 100644 index 000000000..be2c973c8 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/MultipleBotConfig.cs @@ -0,0 +1,201 @@ +using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; +using PoGo.NecroBot.Logic.Interfaces.Configuration; +using POGOProtos.Enums; + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + public class BotSwitchPokemonFilter : BaseConfig, IPokemonFilter + { + [JsonIgnore] + [NecroBotConfig(IsPrimaryKey = true, Key = "Allow Switch", Description = "Allows bot to use invidual filters for switching accounts", Position = 1)] + public bool AllowBotSwitch { get; set; } + + [NecroBotConfig(Key = "Min IV", Description = "When this pokemon has a IV > this value, the bot will switch accounts", Position = 2)] + [Range(0, 100)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + public int IV { get; set; } + + [NecroBotConfig(Key = "Min LV", Description = "When this pokemon has a LV > this value, the bot will switch accounts", Position = 3)] + [Range(0, 999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 2)] + public int LV { get; set; } + + [NecroBotConfig(Key = "Move", Description = "When a wild pokemon has the move match, the bot will switch accounts to catch", Position = 4)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 3)] + public List> Moves { get; set; } + + + [NecroBotConfig(Key = "Remain times", Description = "Number of seconds since pokemon disappeared", Position = 5)] + [Range(0, 900)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + + public int RemainTimes { get; set; } + + [NecroBotConfig(Key = "Operator", Description = "The operator to check", Position = 6)] + [EnumDataType(typeof(Operator))] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + public string Operator { get; set; } + + + [NecroBotConfig(Key = "Affect to Pokemons", Description = "List of same pokemon to apply to this filter", Position = 6)] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + + public List AffectToPokemons { get; set; } + + public BotSwitchPokemonFilter() + { + Moves = new List>(); + AffectToPokemons = new List(); + } + + public BotSwitchPokemonFilter(int iv, int lv, int remain) + { + AffectToPokemons = new List(); + + Operator = "or"; + Moves = new List>(); + IV = iv; + LV = lv; + RemainTimes = remain; + } + + public static Dictionary Default() + { + return new Dictionary() + { + {PokemonId.Lickitung, new BotSwitchPokemonFilter(30, 0, 60)}, + {PokemonId.Dragonite, new BotSwitchPokemonFilter(10, 0, 60)}, + {PokemonId.Lapras, new BotSwitchPokemonFilter(10, 0, 60)}, + {PokemonId.Exeggutor, new BotSwitchPokemonFilter(10, 0, 60)}, + {PokemonId.Magmar, new BotSwitchPokemonFilter(70, 0, 60)}, + {PokemonId.Arcanine, new BotSwitchPokemonFilter(10, 0, 60)}, + {PokemonId.Beedrill, new BotSwitchPokemonFilter(10, 0, 60)}, + {PokemonId.Blastoise, new BotSwitchPokemonFilter(10, 0, 60)}, + {PokemonId.Charizard, new BotSwitchPokemonFilter(10, 0, 60)}, + {PokemonId.Venusaur, new BotSwitchPokemonFilter(10, 100, 60)}, + {PokemonId.Vileplume, new BotSwitchPokemonFilter(10, 0, 60)}, + {PokemonId.Vaporeon, new BotSwitchPokemonFilter(10, 0, 60)}, + {PokemonId.Dragonair, new BotSwitchPokemonFilter(70, 0, 60)}, + {PokemonId.Dratini, new BotSwitchPokemonFilter(90, 100, 60)}, + {PokemonId.Snorlax, new BotSwitchPokemonFilter(30, 0, 60)}, + {PokemonId.Kangaskhan, new BotSwitchPokemonFilter(80, 0, 60)}, + {PokemonId.Ninetales, new BotSwitchPokemonFilter(10, 0, 60)}, + {PokemonId.Electabuzz, new BotSwitchPokemonFilter(10, 0, 60)}, + {PokemonId.Magikarp, new BotSwitchPokemonFilter(95, 0, 60)}, + }; + } + + public IPokemonFilter GetGlobalFilter() + { + //var session = TinyIoCContainer.Current.Resolve(); + + return null; + } + } + + [JsonObject(Title = "Multiple Bot Config", Description = "Use this to setup the conditions when we switch to next bot account", ItemRequired = Required.DisallowNull)] + public class MultipleBotConfig : BaseConfig + { + public MultipleBotConfig() : base() + { + } + + [NecroBotConfig (Description = "Bot will switch to new account after this many minutes ", Position = 1)] + [DefaultValue(55)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + public int RuntimeSwitch { get; set; } + + [NecroBotConfig(Description = "Add +-this or anything between to the RuntimeSwitch", Position = 1)] + [DefaultValue(10)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + public int RuntimeSwitchRandomTime { get; set; } + + [NecroBotConfig(Description = "This many minutes to block this bot when reaching the daily limit ", Position = 1)] + [DefaultValue(15)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + + public int OnLimitPauseTimes { get; set; } + + [NecroBotConfig(Description = "Allows bot to switch account when encountering a rare pokemon that you've definied in the list", Position = 2)] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 2)] + public bool OnRarePokemon { get; set; } + + [NecroBotConfig(Description = "Allows bot to switch account when encountering a pokemon IV higher than this value", Position = 3)] + [DefaultValue(90.0)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 3)] + public double MinIVToSwitch { get; set; } + + [NecroBotConfig(Description = "Bot will switch to a new account after collecting this much EXP in a session ", Position = 4)] + [DefaultValue(25000)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 3)] + public int EXPSwitch { get; set; } + + [NecroBotConfig(Description = "Bot will switch to a new account after this many pokestops are farmed", Position = 5)] + [DefaultValue(500)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 4)] + public int PokestopSwitch { get; set; } + + [NecroBotConfig(Description = "Bot will switch to a new account after this many pokemon are caught ", Position = 6)] + [DefaultValue(200)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 5)] + public int PokemonSwitch { get; set; } + + [NecroBotConfig(Description = "Bot will switch to a new account after this many pokemon are caught in 1 hours - not being used atm ", Position = 7)] + [DefaultValue(100)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 6)] + public int PokemonPerHourSwitch { get; set; } //only apply if runtime > 1h. + + [NecroBotConfig(Description = "Tell bot to start at default location", Position = 8)] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 7)] + public bool StartFromDefaultLocation { get; set; } //only apply if runtime > 1h. + + [NecroBotConfig(Description = "How many times pokestop softban can triger bot switch, 0 means it doesn't switch", Position = 9)] + [DefaultValue(5)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 7)] + [Range(0, 100)] + public int PokestopSoftbanCount { get; set; } //only apply if runtime > 1h. + + + [NecroBotConfig(Description = "Displays bot list (include ran time) on switch", Position = 10)] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 9)] + public bool DisplayList { get; set; } + + [NecroBotConfig(Description = "Bot will display a list of accounts that you have setup in auth.json then ask you to select which account you want to start with.", Position = 11)] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 9)] + public bool SelectAccountOnStartUp { get; set; } + + [NecroBotConfig(Description = "Number of continuously catch flees before switching account", Position = 12)] + [DefaultValue(5)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 12)] + public int CatchFleeCount{ get; set; } + + [NecroBotConfig(Description = "Switch account on meeting catch limit", Position = 13)] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 13)] + public bool SwitchOnCatchLimit { get; set; } + + + [NecroBotConfig(Description = "Switch account on meeting pokestop limit", Position = 14)] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 14)] + public bool SwitchOnPokestopLimit { get; set; } + + + public static MultipleBotConfig Default() + { + return new MultipleBotConfig(); + } + + public static bool IsMultiBotActive(ILogicSettings logicSettings, MultiAccountManager manager) + { + return manager.AllowMultipleBot() && logicSettings.Bots != null && logicSettings.Bots.Count >= 1; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Settings/NotificationConfig.cs b/PoGo.NecroBot.Logic/Model/Settings/NotificationConfig.cs new file mode 100644 index 000000000..3ac7fe680 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/NotificationConfig.cs @@ -0,0 +1,42 @@ +using System.ComponentModel; +using Newtonsoft.Json; + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + public class NotificationConfig : BaseConfig + { + public NotificationConfig() : base() + { + } + + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + [NecroBotConfig(Position = 1, Description = "Enable Pushbullet Notifications")] + public bool EnablePushBulletNotification { get; set; } + + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 2)] + [NecroBotConfig(Position = 2, Description = "Enable Email Notifications")] + public bool EnableEmailNotification { get; set; } + + [DefaultValue("")] + [JsonProperty(Required = Required.AllowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 3)] + [NecroBotConfig(Position = 3, Description = "API Key to connect to pushbullet (Go to pushbullet.com to get a key)")] + public string PushBulletApiKey { get; set; } + + [DefaultValue("")] + [JsonProperty(Required = Required.AllowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 4)] + [NecroBotConfig(Position = 4, Description = "Gmail Address to send emails to")] + public string GmailUsername { get; set; } + + [DefaultValue("")] + [JsonProperty(Required = Required.AllowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 5)] + [NecroBotConfig(Position = 5, Description = "Gmail Password")] + public string GmailPassword { get; set; } + + [DefaultValue("")] + [JsonProperty(Required = Required.AllowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 6)] + [NecroBotConfig(Position = 6, Description = "List of email addresses allowed to recieve notificaitons")] + public string Recipients { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Settings/PlayerConfig.cs b/PoGo.NecroBot.Logic/Model/Settings/PlayerConfig.cs new file mode 100644 index 000000000..a477a4ce1 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/PlayerConfig.cs @@ -0,0 +1,84 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + [JsonObject(Title = "Player Config", Description = "Set your player settings.", ItemRequired = Required.DisallowNull)] + public class PlayerConfig :BaseConfig + { + public PlayerConfig() : base() + { + } + + [DefaultValue(4000)] + [Range(0, 999999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + public int DelayBetweenPlayerActions { get; set; } + + [NecroBotConfig(Description = "Sets delay time for evolve actions", Position = 2)] + [DefaultValue(20000)] + [Range(0, 999999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 2)] + public int EvolveActionDelay { get; set; } + + [NecroBotConfig(Description = "Sets delay time for transfer actions", Position = 3)] + [DefaultValue(5000)] + [Range(0, 999999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 3)] + public int TransferActionDelay { get; set; } + + [NecroBotConfig(Description = "Sets delay time for recycling items", Position = 4)] + [DefaultValue(1000)] + [Range(0, 999999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 4)] + public int RecycleActionDelay { get; set; } + + [NecroBotConfig(Description = "Sets delay time for renaming pokemon actions", Position = 5)] + [DefaultValue(2000)] + [Range(0, 999999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 5)] + public int RenamePokemonActionDelay { get; set; } + + [NecroBotConfig(Description = "Sets delay time for random actions", Position = 6)] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 6)] + public bool UseNearActionRandom { get; set; } + + [NecroBotConfig(Description = "Randomize numeric settings by percent.", Position = 7)] + [DefaultValue(5)] + [Range(0, 100)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 7)] + public int RandomizeSettingsByPercent { get; set; } + + [NecroBotConfig(Description = "Auto Complete first time experience tutorial (Bot will try to use your ptc username or firstpart of email as username)", Position = 8)] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 8)] + public bool AutoFinishTutorial { get; set; } + + [NecroBotConfig(Description = "Skip first time experience tutorial", Position = 9)] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 9)] + public bool SkipFirstTimeTutorial { get; set; } + + [NecroBotConfig(Description = "Skip collecting level up rewards", Position = 10)] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 10)] + public bool SkipCollectingLevelUpRewards { get; set; } + + [NecroBotConfig(Description = "Sets the AutoWalkAI", Position = 11)] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 11)] + public bool AutoWalkAI { get; set; } + + [NecroBotConfig(Description = "Sets the AutoWalkAI Distance(Default = 250m)", Position = 12)] + [DefaultValue(250)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 12)] + public int AutoWalkDist { get; set; } + + [NecroBotConfig(Description = "Sets the LoadPokeStopsTimer interval(10 - 60 sec)", Position = 13)] + [DefaultValue(30)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 13)] + public int PokeStopsTimer { get; set; } + } +} diff --git a/PoGo.NecroBot.Logic/Model/Settings/PokeStopConfig.cs b/PoGo.NecroBot.Logic/Model/Settings/PokeStopConfig.cs new file mode 100644 index 000000000..fa89ab3e5 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/PokeStopConfig.cs @@ -0,0 +1,31 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + [JsonObject(Title = "Poke Stop Config", Description = "Set your poke stop settings.", ItemRequired = Required.DisallowNull)] + public class PokeStopConfig : BaseConfig + { + public PokeStopConfig() : base() + { + } + + [NecroBotConfig(Description = "Allows bot to check for pokestop daily limit - PokeStopLimit per PokeStopLimitMinutes", Position = 1)] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + public bool UsePokeStopLimit { get; set; } + + [NecroBotConfig(Description = "Max number of pokestops bot is allowed to farm a day", Position = 2)] + [DefaultValue(700)] + [Range(0, 9999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 2)] + public int PokeStopLimit {get; set; } + + [NecroBotConfig(Description = "Time duration apply for the limit above in minutes", Position = 3)] + [DefaultValue(60 * 22 + 30)] + [Range(0, 9999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 3)] + public int PokeStopLimitMinutes { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Settings/PokemonConfig.cs b/PoGo.NecroBot.Logic/Model/Settings/PokemonConfig.cs new file mode 100644 index 000000000..70c6949fb --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/PokemonConfig.cs @@ -0,0 +1,419 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + [JsonObject(Title = "Pokemon Config", Description = "Set your pokemon settings.", ItemRequired = Required.DisallowNull)] + public class PokemonConfig :BaseConfig + { + public PokemonConfig() : base() + { + } + + public enum Operator + { + or, + and + } + + internal enum CpIv + { + cp, + iv + } + + [NecroBotConfig(Description = "Allows bot to Catch Pokemon", Position = 100)] + /*Catch*/ + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 100)] + public bool CatchPokemon { get; set; } + + [NecroBotConfig(Description = "Delay time between time catching pokemon", Position = 200)] + [DefaultValue(2000)] + [Range(0, 99999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 200)] + public int DelayBetweenPokemonCatch { get; set; } + + /*CatchLimit*/ + [NecroBotConfig(Description = "Check for daily limit catch rate - CatchPokemonLimit per CatchPokemonLimitMinutes", Position = 30)] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 30)] + public bool UseCatchLimit { get; set; } + + [NecroBotConfig(Description = "Number of pokemon allowed for catch duration", Position = 40)] + [DefaultValue(500)] + [Range(0, 9999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 40)] + public int CatchPokemonLimit { get; set; } + + [NecroBotConfig(Description = "Catch duration applied for catch limit & number", Position = 50)] + [DefaultValue(60 * 22 + 30)] + [Range(0, 9999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 50)] + public int CatchPokemonLimitMinutes { get; set; } + + /*Incense*/ + [NecroBotConfig(Description = "Allows bot to use Incense", Position = 60)] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 60)] + public bool UseIncenseConstantly; + + /*Egg*/ + [NecroBotConfig(Description = "Allows bot to put an egg in an Incubator for hatching", Position = 70)] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 70)] + public bool UseEggIncubators { get; set; } + + [NecroBotConfig(Description = "When Enabled, bot will only put 10km egg into a non-infinity incubator", Position = 80)] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 80)] + public bool UseLimitedEggIncubators { get; set; } + + [NecroBotConfig(Description = "When Enabled, bot will always use a lucky egg when they are available in bag", Position = 90)] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 90)] + public bool UseLuckyEggConstantly; + + /*Berries*/ + //[NecroBotConfig(Description = "Specify min CP will be use berries when catch", Position = 120)] + //[DefaultValue(1000)] + //[Range(0, 9999)] + //[JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 120)] + //public int UseBerriesMinCp { get; set; } + + //[NecroBotConfig(Description = "Specify min IV will be use berries when catch", Position = 130)] + //[DefaultValue(90)] + //[Range(0, 100)] + //[JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 130)] + //public float UseBerriesMinIv { get; set; } + + //[NecroBotConfig(Description = "Specify max catch chance will be use berries when catch", Position = 140)] + //[DefaultValue(0.20)] + //[Range(0, 1)] + //[JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 140)] + //public double UseBerriesBelowCatchProbability { get; set; } + + //[NecroBotConfig(Description = "The operator logic for berry use", Position = 150)] + //[DefaultValue("or")] + //[EnumDataType(typeof(Operator))] + //[JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 150)] + //public string UseBerriesOperator { get; set; } + + //[NecroBotConfig(Description = "Number of berries can be used for 1 pokemon", Position = 160)] + //[DefaultValue(30)] + //[Range(0, 999)] + //[JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 160)] + //public int MaxBerriesToUsePerPokemon { get; set; } + + /*Transfer*/ + [NecroBotConfig(Description = "Allows bot to transfer weak/low cp pokemon", Position = 170)] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 170)] + public bool TransferWeakPokemon; + + [NecroBotConfig(Description = "Allows bot to transfer all duplicate pokemon", Position = 180)] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 180)] + public bool TransferDuplicatePokemon { get; set; } + + [NecroBotConfig(Description = "Allows bot to transfer duplicated pokemon right after catch", Position = 190)] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 190)] + public bool TransferDuplicatePokemonOnCapture { get; set; } + + /*Rename*/ + [NecroBotConfig(Description = "Allows bot to rename pokemon after catch", Position = 200)] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 200)] + public bool RenamePokemon; + + [NecroBotConfig(Description = "Set Min IV for rename, bot will only rename pokemon that has IV higher then this value", Position = 210)] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 210)] + public bool RenameOnlyAboveIv; + + [NecroBotConfig(Description = "The template for pokemon rename", Position = 220)] + [DefaultValue("{Name}_{IV}_Lv{Level}")] + [MinLength(0)] + [MaxLength(32)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 220)] + public string RenameTemplate { get; set; } + + /*Favorite*/ + [NecroBotConfig(Description = "Set min IV for auto favoriting pokemon", Position = 230)] + [DefaultValue(95)] + [Range(0, 100)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 230)] + public float FavoriteMinIvPercentage { get; set; } + + + [NecroBotConfig(Description = "Allows bot to auto favorite pokemon after catch", Position = 240)] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 240)] + public bool AutoFavoritePokemon; + + [NecroBotConfig(Description = "Allows bot to auto favorite any shiny pokemon on catch", Position = 250)] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 250)] + public bool AutoFavoriteShinyOnCatch; + + /*PokeBalls*/ + [NecroBotConfig(Description = "Number of balls that will be used to catch a pokemon", Position = 260)] + [DefaultValue(6)] + [Range(0, 999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 260)] + public int MaxPokeballsPerPokemon { get; set; } + + [NecroBotConfig(Description = "Min CP for using Great Ball instead of PokeBall", Position = 270)] + [DefaultValue(1000)] + [Range(0, 9999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 270)] + public int UseGreatBallAboveCp { get; set; } + + [NecroBotConfig(Description = "Min CP for using Ultra Ball instead of Great Ball", Position = 280)] + [DefaultValue(1250)] + [Range(0, 9999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 280)] + public int UseUltraBallAboveCp { get; set; } + + [NecroBotConfig(Description = "Min CP for using Master Ball instead of Ultra Ball", Position = 290)] + [DefaultValue(1500)] + [Range(0, 9999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 290)] + public int UseMasterBallAboveCp { get; set; } + + [NecroBotConfig(Description = "Min IV for using Great Ball instead of PokeBall", Position = 300)] + [DefaultValue(85.0)] + [Range(0, 101)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 300)] + public double UseGreatBallAboveIv { get; set; } + + [NecroBotConfig(Description = "Min CP for using Ultra Ball instead of Great Ball", Position = 310)] + [DefaultValue(95.0)] + [Range(0, 101)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 310)] + public double UseUltraBallAboveIv { get; set; } + + [NecroBotConfig(Description = "Min catch probability for using Ultra Ball instead of PokeBall", Position = 320)] + [DefaultValue(0.2)] + [Range(0, 1)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 320)] + public double UseGreatBallBelowCatchProbability { get; set; } + + [NecroBotConfig(Description = "Min catch probability for using Ultra Ball instead of Great Ball", Position = 330)] + [DefaultValue(0.1)] + [Range(0, 1)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 330)] + public double UseUltraBallBelowCatchProbability { get; set; } + + [NecroBotConfig(Description = "Min catch probability for using Master Ball instead of Ultra Ball", Position = 340)] + [DefaultValue(0.05)] + [Range(0, 999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 340)] + public double UseMasterBallBelowCatchProbability { get; set; } + + /*PoweUp*/ + [NecroBotConfig(Description = "Allows bot to power up Pokemon ", Position = 350)] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 350)] + public bool AutomaticallyLevelUpPokemon; + + [NecroBotConfig(Description = "Only allow the bot to upgrade favorited pokemon", Position = 360)] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 360)] + public bool OnlyUpgradeFavorites { get; set; } + + [NecroBotConfig(Description = "Use level up on this list of pokemon", Position = 370)] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 370)] + public bool UseLevelUpList { get; set; } + + [NecroBotConfig(Description = "Number of times to upgrade a Pokemon", Position = 380)] + [DefaultValue(5)] + [Range(0, 99)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 380)] + public int AmountOfTimesToUpgradeLoop { get; set; } + + [NecroBotConfig(Description = "Min stardust to keep for auto power up", Position = 390)] + [DefaultValue(5000)] + [Range(0, 999999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 390)] + public int GetMinStarDustForLevelUp { get; set; } + + [NecroBotConfig(Description = "Select pokemon to powerup by IV or CP", Position = 400)] + [DefaultValue("iv")] + [EnumDataType(typeof(CpIv))] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 400)] + public string LevelUpByCPorIv { get; set; } + + [NecroBotConfig(Description = "Min CP for pokemon upgrade", Position = 410)] + [DefaultValue(1000)] + [Range(0, 9999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 410)] + public float UpgradePokemonCpMinimum { get; set; } + + [NecroBotConfig(Description = "Min IV for pokemon upgrade", Position = 420)] + [DefaultValue(95)] + [Range(0, 100)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 420)] + public float UpgradePokemonIvMinimum { get; set; } + + [NecroBotConfig(Description = "Logic operator for selecting pokemon for upgrade", Position = 430)] + [DefaultValue("and")] + [EnumDataType(typeof(Operator))] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 430)] + public string UpgradePokemonMinimumStatsOperator { get; set; } + + /*Keep*/ + [NecroBotConfig(Description = "Allows bot to keep pokemon for evolving if configured in EvolveConfig and appropriate candy is available", Position = 490)] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 490)] + public bool KeepPokemonsToBeEvolved { get; set; } + + [NecroBotConfig(Description = "Specify min CP to not transfer pokemon", Position = 500)] + [DefaultValue(1250)] + [Range(0, 9999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 500)] + public int KeepMinCp { get; set; } + + [NecroBotConfig(Description = "Specify min IV to not transfer pokemon", Position = 510)] + [DefaultValue(90)] + [Range(0, 101)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 510)] + public float KeepMinIvPercentage { get; set; } + + [NecroBotConfig(Description = "Specify min LV to not transfer pokemon", Position = 520)] + [DefaultValue(6)] + [Range(0, 100)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 520)] + public int KeepMinLvl { get; set; } + + [NecroBotConfig(Description = "Logic operator for keep pokemon check", Position = 530)] + [DefaultValue("or")] + [EnumDataType(typeof(Operator))] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 530)] + public string KeepMinOperator { get; set; } + + [NecroBotConfig(Description = "Tell bot to check level before transfer", Position = 540)] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 540)] + public bool UseKeepMinLvl; + + [NecroBotConfig(Description = "Keep pokemon has higher IV then CP to not transfer pokemon", Position = 550)] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 550)] + public bool PrioritizeIvOverCp { get; set; } + + [NecroBotConfig(Description = "Min number of duplicated pokemon to keep", Position = 560)] + [DefaultValue(1)] + [Range(0, 999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 560)] + public int KeepMinDuplicatePokemon { get; set; } + + [NecroBotConfig(Description = "Max number of duplicated pokemon to keep", Position = 570)] + [DefaultValue(1000)] + [Range(0, 100000)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 570)] + public int KeepMaxDuplicatePokemon { get; set; } + + /*NotCatch*/ + [NecroBotConfig(Description = "Use the list pokemon not catch filter", Position = 580)] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 580)] + public bool UsePokemonToNotCatchFilter { get; set; } + + [NecroBotConfig(Description = "Use the Pokemon To Catch Local List", Position = 590)] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 590)] + public bool UsePokemonToCatchLocallyListOnly { get; set; } + + /*Dump Stats*/ + [NecroBotConfig(Description = "Allows bot to dump list pokemon to csv file", Position = 600)] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 600)] + public bool DumpPokemonStats; + + [DefaultValue(10000)] + [NecroBotConfig(Description = "Delay time between pokemon upgrades", Position = 610)] + [Range(0, 99999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 610)] + public int DelayBetweenPokemonUpgrade { get; set; } + + [DefaultValue(5)] + [NecroBotConfig(Description = "Temporarily disable catching pokemon for certain minutes if bot runs out of balls", Position = 620)] + [Range(0, 120)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 620)] + public int OutOfBallCatchBlockTime { get; set; } + + [DefaultValue(50)] + [NecroBotConfig(Description = "Number of balls you want to save for snipe or manual play - it means if total balls is less than this value, catch pokemon will be deactivated", Position = 630)] + [Range(0, 999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 630)] + public int PokeballToKeepForSnipe { get; set; } + + [DefaultValue(true)] + [NecroBotConfig(Description = "Transfer multiple pokemon at once - this will increase bot speed and reduce api call", Position = 640)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 640)] + public bool UseBulkTransferPokemon { get; set; } + + [DefaultValue(10)] + [NecroBotConfig(Description = "Bot will transfer pokemons only when MaxStogare < pokemon + buffer", Position = 650)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 650)] + public int BulkTransferStogareBuffer { get; set; } + + [DefaultValue(100)] + [NecroBotConfig(Description = "Maximun number of pokemon in a transfer", Position = 660)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 660)] + [Range(1,100)] + public int BulkTransferSize { get; set; } + + [DefaultValue(Operator.or)] + [NecroBotConfig(Description = "Use ball operator between IV and CP ", Position = 670)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 670)] + public Operator UseBallOperator { get; set; } + + + /*Favorite CP*/ + [NecroBotConfig(Description = "Set min CP for auto favoriting pokemon", Position = 680)] + [DefaultValue(0)] + [Range(0, 9999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 680)] + public float FavoriteMinCp { get; set; } + + [NecroBotConfig(Description = "Set Buddy pokemon", Position = 690)] + [DefaultValue("")] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 690)] + public string DefaultBuddyPokemon { get; set; } + + [NecroBotConfig(Description = "Min level to use favoriting", Position = 700)] + [DefaultValue(0)] + [Range(0,100)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 700)] + public int FavoriteMinLevel { get; set; } + + [NecroBotConfig(Description = "The logic operator to check compbo IV, CP, Level to favorite pokemon", Position = 710)] + [DefaultValue(Operator.and)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 710)] + public Operator FavoriteOperator { get; set; } + + [NecroBotConfig(Description = "If Enabled, bot will only rename pokemon not meeting transfer settings, otherwise, the bot will rename all pokemon in bag", Position = 720)] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 720)] + public bool RenamePokemonRespectTransferRule { get; set; } + + [NecroBotConfig(Description = "Minimum pokemon level to upgrade", Position = 730)] + [DefaultValue(30)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 730)] + public double UpgradePokemonLvlMinimum { get; set; } + + [NecroBotConfig(Description = "Allows bot to bypass catchflee - not recommended to use this feature", Position = 790)] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 790)] + public bool ByPassCatchFlee{ get; set; } + + [NecroBotConfig(SheetName = "EvolveConfig", Description = "Setting up for pokemon evolving", Position = 800)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore, Order = 800)] + public EvolveConfig EvolveConfig = new EvolveConfig(); + } +} diff --git a/PoGo.NecroBot.Logic/Model/Settings/ProxyConfig.cs b/PoGo.NecroBot.Logic/Model/Settings/ProxyConfig.cs new file mode 100644 index 000000000..28ac9121d --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/ProxyConfig.cs @@ -0,0 +1,42 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + [JsonObject(Title = "Proxy Config", Description = "Set your proxy settings.", ItemRequired = Required.DisallowNull)] + public class ProxyConfig + { + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + public bool UseProxy; + + [DefaultValue(null)] + [MinLength(0)] + [MaxLength(32)] + //[RegularExpression(@"^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$")] //Ip Only + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 2)] + public string UseProxyHost; + + [DefaultValue(null)] + [RegularExpression(@"^([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$")] //Valid Port + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 3)] + public string UseProxyPort; + + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 4)] + public bool UseProxyAuthentication; + + [DefaultValue(null)] + [MinLength(0)] + [MaxLength(32)] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 5)] + public string UseProxyUsername; + + [DefaultValue(null)] + [MinLength(0)] + [MaxLength(32)] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 6)] + public string UseProxyPassword; + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Settings/SnipeConfig.cs b/PoGo.NecroBot.Logic/Model/Settings/SnipeConfig.cs new file mode 100644 index 000000000..1c61a987c --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/SnipeConfig.cs @@ -0,0 +1,133 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + [JsonObject(Title = "Snipe Config", Description = "Set your snipe settings.", ItemRequired = Required.DisallowNull)] + public class SnipeConfig : BaseConfig + { + [NecroBotConfig(Description = "Tell bot to use location service, detail at - https://github.com/5andr0/PogoLocationFeeder", Position = 1)] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + public bool UseSnipeLocationServer { get; set; } + + [NecroBotConfig(Description = "IP Address or server name of location server, usually this is localhost or 127.0.0.1", Position = 2)] + [DefaultValue("localhost")] + [MinLength(0)] + [MaxLength(32)] + //[RegularExpression(@"^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$")] //Ip Only + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 2)] + public string SnipeLocationServer { get; set; } + + [NecroBotConfig(Description = "Port number of location server. ", Position = 3)] + [DefaultValue(16969)] + [Range(1, 65535)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 3)] + public int SnipeLocationServerPort { get; set; } + + [NecroBotConfig(Description = "Number of balls in inventory to get sniper function to work. ", Position = 9)] + [DefaultValue(20)] + [Range(0, 999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 9)] + public int MinPokeballsToSnipe { get; set; } + + [NecroBotConfig(Description = "Min balls allowed to exist sniper", Position = 10)] + [DefaultValue(0)] + [Range(0, 999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 10)] + public int MinPokeballsWhileSnipe { get; set; } + + [NecroBotConfig(Description = "Delay time between 2 snipes.", Position = 11)] + [DefaultValue(60000)] + [Range(0, 999999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 11)] + public int MinDelayBetweenSnipes { get; set; } + + [NecroBotConfig(Description = "The area bot try to scan for target pokemon.", Position = 12)] + [DefaultValue(0.005)] + [Range(0, 1)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 12)] + public double SnipingScanOffset { get; set; } + + [NecroBotConfig(Description = "That setting will make bot snipe when it reaches every pokestop.", Position = 13)] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 13)] + public bool SnipeAtPokestops { get; set; } + + [DefaultValue(false)] + [NecroBotConfig(Description = "Turn this on to ignore pokemon with unknown IV from data source", Position = 14)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 14)] + public bool SnipeIgnoreUnknownIv { get; set; } + + [NecroBotConfig(Description = "Bot will transfer pokemon for sniping if the sniping pokemon is of a higher IV than this.", Position = 15)] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 15)] + public bool UseTransferIvForSnipe { get; set; } + + [NecroBotConfig(Description = "Turn this on it for bot only by priority to snipe pokemon not in pokedex.", Position = 16)] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 16)] + public bool SnipePokemonNotInPokedex { get; set; } + + /*SnipeLimit*/ + [NecroBotConfig(Description = "Turn this on to limit the speed for sniping.", Position = 17)] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 17)] + public bool UseSnipeLimit { get; set; } + + [NecroBotConfig(Description = "Delay time between 2 snipes", Position = 18)] + [DefaultValue(10 * 60)] + [Range(0, 9999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 18)] + public int SnipeRestSeconds { get; set; } + + [NecroBotConfig(Description = "Limits number of snipe in hour.", Position = 19)] + [DefaultValue(39)] + [Range(0, 999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 19)] + public int SnipeCountLimit { get; set; } + + [NecroBotConfig(Description = "Allow MSniper feature with bot.", Position = 20)] + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 20)] + public bool ActivateMSniper = true; + + [NecroBotConfig(Description = "Min IV that the bot will automatically snipe pokemon", Position = 21)] + [DefaultValue(100)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 21)] + public int MinIVForAutoSnipe { get; set; } + + [NecroBotConfig(Description = "Min Level that the bot will automatically snipe pokemon", Position = 22)] + [DefaultValue(0)] + [Range(0,100)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 22)] + public int MinLevelForAutoSnipe { get; set; } + + [NecroBotConfig(Description = "Only auto snipe pokemon that have been verified (overwriteable by invidual pokemon)", Position = 23)] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 23)] + public bool AutosnipeVerifiedOnly { get; set; } + + [NecroBotConfig(Description = "Set the amount of candy you want bot to auto snipe if it has less candy than this value.", Position = 24)] + [DefaultValue(0)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 24)] + public int DefaultAutoSnipeCandy { get; set; } + + [NecroBotConfig(Description = "Total time in minutes bot will ignore auto snipe when out of pokeballs", Position = 25)] + [DefaultValue(5)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 25)] + public int SnipePauseOnOutOfBallTime { get; set; } + + [NecroBotConfig(Description = "Max distance in km that will allow bot to auto snipe.", Position = 26)] + [DefaultValue(0)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 26)] + public double AutoSnipeMaxDistance { get; set; } + + [NecroBotConfig(Description = "Number of auto snipe on a pokemon in a row that bot pickup for snipe", Position = 27)] + [DefaultValue(10)] + [Range(1,1000)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 27)] + public int AutoSnipeBatchSize { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Settings/SnipeFIlter.cs b/PoGo.NecroBot.Logic/Model/Settings/SnipeFIlter.cs new file mode 100644 index 000000000..562fd49dc --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/SnipeFIlter.cs @@ -0,0 +1,157 @@ +using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; +using POGOProtos.Enums; +using System.Linq; +using TinyIoC; +using PoGo.NecroBot.Logic.State; + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + [JsonObject(Description = "", ItemRequired = Required.DisallowNull)] //Dont set Title + public class SnipeFilter : BaseConfig, IPokemonFilter + { + public SnipeFilter() : base() + { + Priority = 5; + Moves = new List>(); + AffectToPokemons = new List(); + } + + public SnipeFilter(int snipeMinIV, List> moves = null) : base() + { + + AffectToPokemons = new List(); + Operator = "or"; + SnipeIV = snipeMinIV; + Moves = moves; + VerifiedOnly = false; + Priority = 5; + } + + [JsonIgnore] + [NecroBotConfig(IsPrimaryKey = true,Key = "Enable Snipe", Description = "Enables bot Snipe filter, if not it will use global setting", Position = 1)] + [DefaultValue(false)] + public bool EnableSnipe { get; set; } + + [NecroBotConfig(Key = "Snipe Min IV", Description = "Min Pokemon IV for auto sniping", Position = 2)] + [DefaultValue(90)] + [Range(0, 101)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + public int SnipeIV {get; set;} + + [NecroBotConfig(Key = "Moves", Description = "Defines list of moves that you want snipe", Position = 3)] + [DefaultValue(null)] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 2)] + public List> Moves { get; set; } + + [NecroBotConfig(Key = "Operator", Description = "Operator logic check between move and IV", Position = 4)] + [EnumDataType(typeof(Operator))] + [DefaultValue("or")] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 4)] + public string Operator { get; set; } + + [NecroBotConfig(Key = "Verified Only", Description = "Only catch pokemon that have been verified", Position = 5)] + [DefaultValue(false)] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 5)] + public bool VerifiedOnly { get; set; } + + [NecroBotConfig(Key = "Auto Snipe Priority", Description = "Set autosnipe priority", Position = 6)] + [DefaultValue(5)] + [Range(1,10)] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 6)] + public int Priority { get; set; } + + [NecroBotConfig(Key = "Auto Snipe Candy", Description = "Set number of candy you want bot to snipe for this pokemon", Position = 7)] + [DefaultValue(2000)] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 7)] + public int AutoSnipeCandy { get; set; } + + [NecroBotConfig(Key = "Snipe Level", Description = "Min level to snipe, level are using and logic with IV and move and only activate for verify data", Position = 8)] + [DefaultValue(0)] + [Range(0,100)] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 8)] + public int Level { get; set; } + + [NecroBotConfig(Key = "AllowMultiAccountSnipe", Description = "Allows bot to change account to snipe this pokemon", Position = 9)] + [DefaultValue(false)] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 9)] + public bool AllowMultiAccountSnipe { get; set; } + + [NecroBotConfig(Key = "Affect To Pokemon", Description = "Defines list of pokemon using this filter too", Position = 9)] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 9)] + public List AffectToPokemons { get; set; } + internal static Dictionary SniperFilterDefault() + { + return new Dictionary + { + {PokemonId.Lapras, new SnipeFilter(0, new List>() { })}, + {PokemonId.Dragonite, new SnipeFilter(0, new List>() { })}, + {PokemonId.Snorlax, new SnipeFilter(0, new List>() { })}, + {PokemonId.Dratini, new SnipeFilter(0, new List>() { })}, + {PokemonId.Rhyhorn, new SnipeFilter(0, new List>() { })}, + {PokemonId.Abra, new SnipeFilter(0, new List>() { })} + }; + } + + public bool IsMatch(double iv, PokemonMove move1, PokemonMove move2, int level, bool verified ) + { + var filter = this; + //if not verified and undetermine move. If not verify, level won't apply + if (((verified && filter.Level <= level) || !filter.VerifiedOnly) && + filter.SnipeIV <= iv && + move1 == PokemonMove.MoveUnset && move2 == PokemonMove.MoveUnset && + (filter.Moves == null || filter.Moves.Count == 0)) + { + return true; + } + + //need refactore this to better + if (((verified && filter.Level <= level) || !verified) && + (string.IsNullOrEmpty(filter.Operator) || filter.Operator == "or") && + (filter.SnipeIV <= iv + || (filter.Moves != null + && filter.Moves.Count > 0 + && filter.Moves.Any(x => x[0] == move1 && x[1] == move2)) + )) + + { + return true; + } + + if (((verified && filter.Level <= level) || !verified) && + filter.Operator == "and" && + filter.SnipeIV <= iv && + ( + (filter.Moves == null || filter.Moves.Count ==0) || + filter.Moves.Any(x => x[0] == move1 && x[1] == move2) + ) + + ) + { + return true; + } + + return false; + + + } + + public IPokemonFilter GetGlobalFilter() + { + var session = TinyIoCContainer.Current.Resolve(); + + var _setting = session.LogicSettings; + return new SnipeFilter(_setting.MinIVForAutoSnipe) + { + SnipeIV = _setting.MinIVForAutoSnipe, + Operator = "and", + Priority = 5, + AutoSnipeCandy = _setting.DefaultAutoSnipeCandy, + Level = _setting.MinLevelForAutoSnipe, + VerifiedOnly = _setting.AutosnipeVerifiedOnly + }; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Settings/SoftBanConfig.cs b/PoGo.NecroBot.Logic/Model/Settings/SoftBanConfig.cs new file mode 100644 index 000000000..72f2ae134 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/SoftBanConfig.cs @@ -0,0 +1,23 @@ +using System.ComponentModel; +using Newtonsoft.Json; + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + [JsonObject(Title = "Soft Ban Config", Description = "Set your soft ban settings.", ItemRequired = Required.DisallowNull)] + public class SoftBanConfig : BaseConfig + { + public SoftBanConfig() : base() + { + } + + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + [NecroBotConfig(Description = "Allows bot to resolve softbans automatically", Position = 1)] + public bool FastSoftBanBypass { get; set; } + + [DefaultValue(1)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 2)] + [NecroBotConfig(Description = "Bypass pokestop spin count.", Position = 2)] + public int ByPassSpinCount { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Settings/TelegramConfig.cs b/PoGo.NecroBot.Logic/Model/Settings/TelegramConfig.cs new file mode 100644 index 000000000..d7477b344 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/TelegramConfig.cs @@ -0,0 +1,33 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + [JsonObject(Title = "Telegram Messaging Client", Description = "Configure to use with Telegram Messaging.", ItemRequired = Required.DisallowNull)] + public class TelegramConfig : BaseConfig + { + public TelegramConfig() : base() + { + } + + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + [NecroBotConfig (Description = "Allows control of bot from Telegram commands", Position = 1)] + public bool UseTelegramAPI { get; set; } + + [DefaultValue(null)] + [MinLength(0)] + [MaxLength(64)] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 2)] + [NecroBotConfig(Description = "Telegram API Key that's required for communication", Position = 2)] + public string TelegramAPIKey { get; set; } + + [DefaultValue(null)] + [MinLength(0)] + [MaxLength(32)] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 3)] + [NecroBotConfig(Description = "Telegram password to connect", Position = 3)] + public string TelegramPassword { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Settings/TransferConfig.cs b/PoGo.NecroBot.Logic/Model/Settings/TransferConfig.cs new file mode 100644 index 000000000..2360fca45 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/TransferConfig.cs @@ -0,0 +1,54 @@ +using System.Collections.Generic; +using Newtonsoft.Json; +using POGOProtos.Enums; + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + [JsonObject(Title = "Transfer Config", Description = "Set your transfer settings.", ItemRequired = Required.DisallowNull)] + public class TransferConfig + { + internal static List PokemonsNotToTransferDefault() + { + return new List + { + //criteria: from SS Tier to A Tier + Regional Exclusive + PokemonId.Venusaur, + PokemonId.Charizard, + PokemonId.Blastoise, + //PokemonId.Nidoqueen, + //PokemonId.Nidoking, + PokemonId.Clefable, + //PokemonId.Vileplume, + //PokemonId.Golduck, + //PokemonId.Arcanine, + //PokemonId.Poliwrath, + //PokemonId.Machamp, + //PokemonId.Victreebel, + //PokemonId.Golem, + //PokemonId.Slowbro, + //PokemonId.Farfetchd, + PokemonId.Muk, + //PokemonId.Exeggutor, + //PokemonId.Lickitung, + PokemonId.Chansey, + //PokemonId.Kangaskhan, + //PokemonId.MrMime, + //PokemonId.Tauros, + PokemonId.Gyarados, + PokemonId.Lapras, + PokemonId.Ditto, + PokemonId.Vaporeon, + PokemonId.Jolteon, + PokemonId.Flareon, + //PokemonId.Porygon, + PokemonId.Snorlax, + PokemonId.Articuno, + PokemonId.Zapdos, + PokemonId.Moltres, + PokemonId.Dragonite, + PokemonId.Mewtwo, + PokemonId.Mew + }; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Settings/TransferFilter.cs b/PoGo.NecroBot.Logic/Model/Settings/TransferFilter.cs new file mode 100644 index 000000000..b693077c1 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/TransferFilter.cs @@ -0,0 +1,169 @@ +using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; +using POGOProtos.Enums; +using TinyIoC; +using PoGo.NecroBot.Logic.State; + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + public enum Operator + { + or, + and + } + + [JsonObject(Description = "", ItemRequired = Required.DisallowNull)] //Dont set Title + public class TransferFilter : BaseConfig, IPokemonFilter + { + public TransferFilter() + { + AffectToPokemons = new List(); + MovesOperator = "and"; + KeepMinOperator = Operator.or.ToString(); + Moves = new List>(); + DeprecatedMoves = new List(); + } + + public TransferFilter(int keepMinCp, int keepMinLvl, bool useKeepMinLvl, float keepMinIvPercentage, + string keepMinOperator, int keepMinDuplicatePokemon, int keepMaxDuplicatePokemon, + List> moves = null, List deprecatedMoves = null, string movesOperator = "or", + bool catchOnlyPokemonMeetTransferCriteria = false) + { + AffectToPokemons = new List(); + DoNotTransfer = false; + AllowTransfer = true; + KeepMinCp = keepMinCp; + KeepMinLvl = keepMinLvl; + UseKeepMinLvl = useKeepMinLvl; + KeepMinIvPercentage = keepMinIvPercentage; + KeepMinDuplicatePokemon = keepMinDuplicatePokemon; + KeepMaxDuplicatePokemon = keepMaxDuplicatePokemon; + KeepMinOperator = keepMinOperator; + Moves = (moves == null && deprecatedMoves != null) + ? new List> {deprecatedMoves} + : moves ?? new List>(); + MovesOperator = movesOperator; + CatchOnlyPokemonMeetTransferCriteria = catchOnlyPokemonMeetTransferCriteria; + } + + [JsonIgnore] + [NecroBotConfig(HiddenOnGui = true,IsPrimaryKey = true, Key = "Allow Transfer", Position = 1, Description = "If Enabled, bot will transfer this type of pokemon when matched with filter condition.")] + public bool AllowTransfer { get; set; } + + [JsonIgnore] + [NecroBotConfig(Key = "Do Not Transfer", Position = 2, Description = "If Enabled, Bot won't transfer this pokemon. If Not, Bot will use other parameters to check.")] + public bool DoNotTransfer { get; set; } + + [NecroBotConfig(Key = "KeepMinCp", Position = 3 , Description = "Pokemon with CP lower than this value will be transfered")] + [DefaultValue(1250)] + [Range(0, 9999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + public int KeepMinCp { get; set; } + + [NecroBotConfig (Key = "KeepMinIvPercentage", Position = 4, Description = "Pokemon with IV lower than this value will be transfered")] + [DefaultValue(90)] + [Range(0, 101)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 2)] + public float KeepMinIvPercentage { get; set; } + + [NecroBotConfig(Key = "KeepMinLvl", Position = 5, Description = "Pokemon with LV lower than this value will be transfered")] + [DefaultValue(6)] + [Range(0, 999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 3)] + public int KeepMinLvl { get; set; } + + [NecroBotConfig(Key = "UseKeepMinLvl", Position = 6, Description = "Use Min Level for transfer")] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 4)] + public bool UseKeepMinLvl { get; set; } + + [NecroBotConfig(Key = "KeepMinOperator", Position = 7, Description ="The operator logic use to check for transfer ")] + [DefaultValue("or")] + [EnumDataType(typeof(Operator))] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 5)] + public string KeepMinOperator { get; set; } + + [NecroBotConfig(Key = "KeepMinDuplicatePokemon", Position = 8, Description = "Min number of duplication pokemon to keep")] + [DefaultValue(1)] + [Range(0, 999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 6)] + public int KeepMinDuplicatePokemon { get; set; } + + [NecroBotConfig(Key = "KeepMaxDuplicatePokemon", Position = 9, Description = "Max number of duplication pokemon to keep")] + [DefaultValue(1000)] + [Range(0, 100000)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 7)] + public int KeepMaxDuplicatePokemon { get; set; } + + [NecroBotConfig(Key = "Moves", Position = 10, Description = "Defined unwanted moves, and pokemon that have this move will be transfered")] + [DefaultValue(null)] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 8)] + public List> Moves { get; set; } + + [DefaultValue(null)] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 9)] + public List DeprecatedMoves { get; set; } + + [NecroBotConfig(Key = "MovesOperator", Position = 11)] + [DefaultValue("and")] + [EnumDataType(typeof(Operator))] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 10)] + public string MovesOperator { get; set; } + + [NecroBotConfig(Key = "CatchOnlyPokemonMeetTransferCriteria", Position = 12, Description ="Turn on this option to allow bot to catch only good pokemon with not meet transfer condition.")] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 11)] + public bool CatchOnlyPokemonMeetTransferCriteria { get; set; } + + [NecroBotConfig(Key = "AffectToPokemons", Position = 13, Description = "Define the list of pokemon which this setting will affect")] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 12)] + public List AffectToPokemons { get; set; } + + internal static Dictionary TransferFilterDefault() + { + return new Dictionary + { + //criteria: based on NY Central Park and Tokyo variety + sniping optimization + {PokemonId.Golduck, new TransferFilter(1800, 6, false, 95, "or", 1, 1000, new List>() { new List() { PokemonMove.WaterGunFast,PokemonMove.HydroPump }},null,"and")}, + {PokemonId.Aerodactyl, new TransferFilter(1250, 6, false, 80, "or", 1, 1000, new List>() { new List() { PokemonMove.BiteFast,PokemonMove.HyperBeam }},null,"and")}, + {PokemonId.Venusaur, new TransferFilter(1800, 6, false, 95, "or", 1, 1000, new List>() { new List() { PokemonMove.VineWhipFast,PokemonMove.SolarBeam }},null,"and")}, + {PokemonId.Farfetchd, new TransferFilter(1250, 6, false, 80, "or", 1, 1000)}, + {PokemonId.Krabby, new TransferFilter(1250, 6, false, 95, "or", 1, 1000)}, + {PokemonId.Kangaskhan, new TransferFilter(1500, 6, false, 60, "or", 1, 1000)}, + {PokemonId.Horsea, new TransferFilter(1250, 6, false, 95, "or", 1, 1000)}, + {PokemonId.Staryu, new TransferFilter(1250, 6, false, 95, "or", 1, 1000)}, + {PokemonId.MrMime, new TransferFilter(1250, 6, false, 40, "or", 1, 1000)}, + {PokemonId.Scyther, new TransferFilter(1800, 6, false, 80, "or", 1, 1000)}, + {PokemonId.Jynx, new TransferFilter(1250, 6, false, 95, "or", 1, 1000)}, + {PokemonId.Charizard, new TransferFilter(1250, 6, false, 80, "or", 1, 1000, new List>() { new List() { PokemonMove.WingAttackFast,PokemonMove.FireBlast }},null,"and")}, + {PokemonId.Electabuzz, new TransferFilter(1250, 6, false, 80, "or", 1, 1000, new List>() { new List() { PokemonMove.ThunderShockFast,PokemonMove.Thunder }},null,"and")}, + {PokemonId.Magmar, new TransferFilter(1500, 6, false, 80, "or", 1, 1000)}, + {PokemonId.Pinsir, new TransferFilter(1800, 6, false, 95, "or", 1, 1000, new List>() { new List() { PokemonMove.RockSmashFast,PokemonMove.XScissor }},null,"and")}, + {PokemonId.Tauros, new TransferFilter(1250, 6, false, 90, "or", 1, 1000)}, + {PokemonId.Magikarp, new TransferFilter(200, 6, false, 95, "or", 1, 1000)}, + {PokemonId.Exeggutor, new TransferFilter(1800, 6, false, 90, "or", 1, 1000, new List>() { new List() { PokemonMove.ZenHeadbuttFast,PokemonMove.SolarBeam }},null,"and")}, + {PokemonId.Gyarados, new TransferFilter(1250, 6, false, 90, "or", 1, 1000, new List>() { new List() { PokemonMove.DragonBreath,PokemonMove.HydroPump }},null,"and")}, + {PokemonId.Lapras, new TransferFilter(1800, 6, false, 80, "or", 1, 1000, new List>() { new List() { PokemonMove.FrostBreathFast,PokemonMove.Blizzard }},null,"and")}, + {PokemonId.Eevee, new TransferFilter(1250, 6, false, 95, "or", 1, 1000)}, + {PokemonId.Vaporeon, new TransferFilter(1500, 6, false, 90, "or", 1, 1000, new List>() { new List() { PokemonMove.WaterGun,PokemonMove.HydroPump }},null,"and")}, + {PokemonId.Jolteon, new TransferFilter(1500, 6, false, 90, "or", 1, 1000)}, + {PokemonId.Flareon, new TransferFilter(1500, 6, false, 90, "or", 1, 1000, new List>() { new List() { PokemonMove.Ember,PokemonMove.FireBlast }},null,"and")}, + {PokemonId.Porygon, new TransferFilter(1250, 6, false, 60, "or", 1, 1000)}, + {PokemonId.Arcanine, new TransferFilter(1800, 6, false, 80, "or", 1, 1000, new List>() { new List() { PokemonMove.FireFangFast,PokemonMove.FireBlast }},null,"and")}, + {PokemonId.Snorlax, new TransferFilter(2600, 6, false, 90, "or", 1, 1000, new List>() { new List() { PokemonMove.ZenHeadbuttFast,PokemonMove.HyperBeam }},null,"and")}, + {PokemonId.Dragonite, new TransferFilter(2600, 6, false, 90, "or", 1, 1000, new List>() { new List() { PokemonMove.DragonBreath,PokemonMove.DragonClaw }},null,"and")}, + }; + } + + public IPokemonFilter GetGlobalFilter() + { + var session = TinyIoCContainer.Current.Resolve(); + var _logicSettings = session.LogicSettings; + return new TransferFilter(_logicSettings.KeepMinCp, _logicSettings.KeepMinLvl, _logicSettings.UseKeepMinLvl, + _logicSettings.KeepMinIvPercentage, + _logicSettings.KeepMinOperator, _logicSettings.KeepMinDuplicatePokemon, _logicSettings.KeepMaxDuplicatePokemon); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Settings/UpdateConfig.cs b/PoGo.NecroBot.Logic/Model/Settings/UpdateConfig.cs new file mode 100644 index 000000000..05d9083fb --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/UpdateConfig.cs @@ -0,0 +1,30 @@ +using System.ComponentModel; +using Newtonsoft.Json; + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + [JsonObject(Title = "Update Config", Description = "Set your update settings.", ItemRequired = Required.DisallowNull)] + public class UpdateConfig : BaseConfig + { + public UpdateConfig() : base() + { + } + + public const int CURRENT_SCHEMA_VERSION = 33; + + [DefaultValue(CURRENT_SCHEMA_VERSION)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + [NecroBotConfig(Description = "Allows bot to automatically check for latest version, and it will display message on console if an update is available.", Position = 1)] + public int SchemaVersion { get; set; } + + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 2)] + [NecroBotConfig(Description = "Allows bot to automatically update to latest version", Position = 2)] + public bool CheckForUpdates { get; set; } + + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 3)] + [NecroBotConfig(Description = "Transfer existing config when bot updates", Position = 3)] + public bool AutoUpdate { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Settings/UpgradeFilter.cs b/PoGo.NecroBot.Logic/Model/Settings/UpgradeFilter.cs new file mode 100644 index 000000000..74ba85091 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/UpgradeFilter.cs @@ -0,0 +1,110 @@ +#region using directives + +using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; +using POGOProtos.Enums; +using TinyIoC; +using PoGo.NecroBot.Logic.State; + +#endregion + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + public class UpgradeFilter : BaseConfig, IPokemonFilter + { + internal enum Operator + { + or, + and + } + + internal enum CPorIv + { + cp, + iv + } + + public UpgradeFilter(): base() + { + Moves = new List>(); + AffectToPokemons = new List(); + } + + public UpgradeFilter(double minLevel, double upgradePokemonCpMinimum, double upgradePokemonIvMinimum, + string upgradePokemonMinimumStatsOperator, bool onlyUpgradeFavorites) + { + AffectToPokemons = new List(); + Moves = new List>(); + UpgradePokemonLvlMinimum = minLevel; + UpgradePokemonCpMinimum = upgradePokemonCpMinimum; + UpgradePokemonIvMinimum = upgradePokemonIvMinimum; + UpgradePokemonMinimumStatsOperator = upgradePokemonMinimumStatsOperator; + OnlyUpgradeFavorites = onlyUpgradeFavorites; + AllowTransfer = true; + } + + [JsonIgnore] + [NecroBotConfig(IsPrimaryKey = true, Key = "Allow Upgrade", Position = 1, Description = "If enabled, will allow custom filter for level up")] + public bool AllowTransfer { get; set; } + + [NecroBotConfig(Key = "Min Level To Upgrade", Position = 2, Description ="Min Level to upgrade")] + [Range(0,100)] + [DefaultValue(30)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 2)] + public double UpgradePokemonLvlMinimum { get; set; } + + [NecroBotConfig(Key = "Min Upgrade CP", Position = 3, Description = "Upgrade by IV or CP")] + [DefaultValue(2250)] + [Range(0, 9999)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 2)] + public double UpgradePokemonCpMinimum { get; set; } + + [NecroBotConfig(Key = "Min Upgrade IV", Position = 4, Description = "Define Min IV to upgrade")] + [DefaultValue(100)] + [Range(0, 101)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 3)] + public double UpgradePokemonIvMinimum { get; set; } + + [NecroBotConfig(Key = "Operator", Position = 5, Description = "Operator logic to check pokemon for upgrade")] + [DefaultValue("or")] + [EnumDataType(typeof(Operator))] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 4)] + public string UpgradePokemonMinimumStatsOperator { get; set; } + + [NecroBotConfig(Key = "Only Farovite", Position = 6, Description = "If Enabled, Bot will only upgrade pokemon that are favorited only, and it will ignore all those condition check.")] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 5)] + public bool OnlyUpgradeFavorites { get; set; } + + [NecroBotConfig(Key = "Move Set", Position = 6, Description = "Only update if move set match with pair in the list")] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 6)] + public List> Moves { get; set; } = new List>(); + + [NecroBotConfig(Key = "Affect To Pokemons", Position = 7, Description = "Define list of pokemon that this upgrade filter will also be applied to")] + [JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate, Order = 6)] + + public List AffectToPokemons { get; set; } + + internal static Dictionary Default() + { + return new Dictionary + { + {PokemonId.Dratini, new UpgradeFilter(100, 600, 99, "or", false)} + }; + } + + public IPokemonFilter GetGlobalFilter() + { + var session = TinyIoCContainer.Current.Resolve(); + var _logicSettings = session.LogicSettings; + return new UpgradeFilter(_logicSettings.UpgradePokemonLvlMinimum, _logicSettings.UpgradePokemonCpMinimum, + _logicSettings.UpgradePokemonIvMinimum, _logicSettings.UpgradePokemonMinimumStatsOperator, + _logicSettings.OnlyUpgradeFavorites); + + + + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Settings/WebsocketsConfig.cs b/PoGo.NecroBot.Logic/Model/Settings/WebsocketsConfig.cs new file mode 100644 index 000000000..d0468e360 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/WebsocketsConfig.cs @@ -0,0 +1,25 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + [JsonObject(Title = "Websockets Config", Description = "Set your websockets settings.", ItemRequired = Required.DisallowNull)] + public class WebsocketsConfig : BaseConfig + { + public WebsocketsConfig() : base() + { + } + + [DefaultValue(true)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + [NecroBotConfig(Description = "Allow Visualizer to connect to bot using web socket", Position = 1)] + public bool UseWebsocket { get; set; } + + [DefaultValue(14251)] + [Range(1, 65535)] + [NecroBotConfig(Description = "Specify web socket port. you need to set this port/neighbor port into visualizer", Position = 2)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 2)] + public int WebSocketPort { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Settings/YoursWalkConfig.cs b/PoGo.NecroBot.Logic/Model/Settings/YoursWalkConfig.cs new file mode 100644 index 000000000..6776e4770 --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Settings/YoursWalkConfig.cs @@ -0,0 +1,38 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; + +namespace PoGo.NecroBot.Logic.Model.Settings +{ + [JsonObject(Title = "YoursWalk Config", Description = "Set your yourswalk settings.", ItemRequired = Required.DisallowNull)] + public class YoursWalkConfig : BaseConfig + { + internal enum YoursWalkTravelModes + { + motorcar, + hgv, + goods, + psv, + bicycle, + cycleroute, + foot, + moped, + mofa + } + + public YoursWalkConfig() : base() + { + } + + [NecroBotConfig(Description = "Use your walk api to resolve path for bot moving", Position = 1)] + [DefaultValue(false)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 1)] + public bool UseYoursWalk { get; set; } + + [NecroBotConfig(Description = "Set heuricstic for moving: motorcar, bicycle, foot..", Position = 2)] + [DefaultValue("bicycle")] + [EnumDataType(typeof(YoursWalkTravelModes))] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Populate, Order = 2)] + public string YoursWalkHeuristic { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Model/Yours/YoursWalk.cs b/PoGo.NecroBot.Logic/Model/Yours/YoursWalk.cs new file mode 100644 index 000000000..9125edf3a --- /dev/null +++ b/PoGo.NecroBot.Logic/Model/Yours/YoursWalk.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; +using Newtonsoft.Json; +using GeoCoordinatePortable; + +namespace PoGo.NecroBot.Logic.Model.Yours +{ + public class RoutingResponse + { + public string type { get; set; } + public Crs crs { get; set; } + public List> coordinates { get; set; } + public Properties2 properties { get; set; } + } + + public class Properties + { + public string name { get; set; } + } + + public class Crs + { + public string type { get; set; } + public Properties properties { get; set; } + } + + public class Properties2 + { + public string distance { get; set; } + public string description { get; set; } + public string traveltime { get; set; } + } + + public class YoursWalk + { + public List Waypoints { get; set; } + public double Distance { get; set; } + + public YoursWalk(string yoursResponse) + { + RoutingResponse yoursResponseParsed = JsonConvert.DeserializeObject(yoursResponse); + + Distance = double.Parse(yoursResponseParsed.properties.distance) * 1000; + + Waypoints = new List(); + foreach (List coordinate in yoursResponseParsed.coordinates) + { + Waypoints.Add(new GeoCoordinate(coordinate.ToArray()[1], coordinate.ToArray()[0])); + } + } + + public static YoursWalk Get(string yoursResponse) + { + return new YoursWalk(yoursResponse); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/MultiAccountManager.cs b/PoGo.NecroBot.Logic/MultiAccountManager.cs new file mode 100644 index 000000000..74dfc076d --- /dev/null +++ b/PoGo.NecroBot.Logic/MultiAccountManager.cs @@ -0,0 +1,447 @@ +using Microsoft.EntityFrameworkCore.ChangeTracking; +using PoGo.NecroBot.Logic.Exceptions; +using PoGo.NecroBot.Logic.Forms; +using PoGo.NecroBot.Logic.Logging; +using PoGo.NecroBot.Logic.Model; +using PoGo.NecroBot.Logic.Model.Settings; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Tasks; +using PoGo.NecroBot.Logic.Utils; +using PokemonGo.RocketAPI; +using PokemonGo.RocketAPI.Enums; +using PokemonGo.RocketAPI.Extensions; +using PokemonGo.RocketAPI.Util; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using TinyIoC; + +namespace PoGo.NecroBot.Logic +{ + public class MultiAccountManager + { + private AccountConfigContext _context = new AccountConfigContext(); + private const string ACCOUNT_DB_NAME = "accounts.db"; + public object Settings { get; private set; } + private GlobalSettings _globalSettings { get; set; } + Client Client { get; set; } + Account account { get; set; } + + public MultiAccountManager(GlobalSettings globalSettings, List accounts) + { + _globalSettings = globalSettings; + MigrateDatabase(); + SyncDatabase(accounts); + } + + public MultiAccountManager() + { + } + + private LocalView _localAccounts; + + public LocalView Accounts + { + get + { + if (_localAccounts != null) + return _localAccounts; + + if (_context.Account.Count() > 0) + { + foreach (var item in _context.Account.OrderBy(p => p.Id)) + { + item.IsRunning = 0; + item.LastRuntimeUpdatedAt = null; + item.RuntimeTotal = 0; + } + _context.SaveChanges(); + } + _localAccounts = _context.Account.Local; + return _localAccounts; + } + } + + public AccountConfigContext GetDbContext() + { + return _context; + } + + public List AccountsReadOnly + { + get + { + return _context.Account.ToList(); + } + } + + public Account GetCurrentAccount() + { + var session = TinyIoCContainer.Current.Resolve(); + return _context.Account.FirstOrDefault(a => session.Settings.Username == a.Username && session.Settings.AuthType == a.AuthType); + } + + public void SwitchAccounts(Account newAccount) + { + if (newAccount == null) + return; + + var runningAccount = GetCurrentAccount(); + if (runningAccount != null) + { + runningAccount.IsRunning = 0; + var now = DateTime.Now; + + if (runningAccount.LastRuntimeUpdatedAt.HasValue) + runningAccount.RuntimeTotal += (now - TimeUtil.GetDateTimeFromMilliseconds(runningAccount.LastRuntimeUpdatedAt.Value)).TotalMinutes; + runningAccount.LastRuntimeUpdatedAt = now.ToUnixTime(); + UpdateLocalAccount(runningAccount); + } + + newAccount.IsRunning = 1; + newAccount.LoggedTime = DateTime.Now.ToUnixTime(); + newAccount.LastRuntimeUpdatedAt = newAccount.LoggedTime; + UpdateLocalAccount(newAccount); + + // Update current auth config with new account. + _globalSettings.Auth.CurrentAuthConfig.AuthType = (AuthType)newAccount.AuthType; + _globalSettings.Auth.CurrentAuthConfig.Username = newAccount.Username; + _globalSettings.Auth.CurrentAuthConfig.Password = newAccount.Password; + _globalSettings.Auth.CurrentAuthConfig.AutoExitBotIfAccountFlagged = newAccount.AutoExitBotIfAccountFlagged; + _globalSettings.Auth.CurrentAuthConfig.AccountLatitude = newAccount.AccountLatitude; + _globalSettings.Auth.CurrentAuthConfig.AccountLongitude = newAccount.AccountLongitude; + _globalSettings.Auth.CurrentAuthConfig.AccountActive = newAccount.AccountActive; + + string body = ""; + foreach (var item in Accounts) + { + body = body + $"{item.Username} - {item.GetRuntime()}\r\n"; + } + } + + public void BlockCurrentBot(int expired = 60) + { + var currentAccount = GetCurrentAccount(); + + if (currentAccount != null) + { + currentAccount.ReleaseBlockTime = DateTime.Now.AddMinutes(expired).ToUnixTime(); + UpdateLocalAccount(currentAccount); + } + } + + private void LoadDataFromDB() + { + if (_context.Account.Count() > 0) + { + foreach (var item in _context.Account.OrderBy(p => p.Id)) + { + item.IsRunning = 0; + item.RuntimeTotal = 0; + UpdateLocalAccount(item); + } + } + } + + private void MigrateDatabase() + { + if (AuthSettings.SchemaVersionBeforeMigration == UpdateConfig.CURRENT_SCHEMA_VERSION) + return; + + int schemaVersion = AuthSettings.SchemaVersionBeforeMigration; + + // Add future schema migrations below. + int version; + for (version = schemaVersion; version < UpdateConfig.CURRENT_SCHEMA_VERSION; version++) + { + Logging.Logger.Write($"Migrating {ACCOUNT_DB_NAME} from schema version {version} to {version + 1}", LogLevel.Info); + switch (version) + { + case 19: + // Just delete the accounts.db so it gets regenerated from scratch. + File.Delete(ACCOUNT_DB_NAME); + break; + case 25: + File.Delete(ACCOUNT_DB_NAME); + break; + } + } + } + + private void SyncDatabase(List authConfigs) + { + if (authConfigs.Count() == 0) + return; + + // Add new accounts and update existing accounts. + foreach (var authConfig in authConfigs) + { + if (string.IsNullOrEmpty(authConfig.Username) || string.IsNullOrEmpty(authConfig.Password)) + continue; + + var existing = _context.Account.FirstOrDefault(x => x.Username == authConfig.Username && x.AuthType == authConfig.AuthType); + + if (existing == null) + { + try + { + Account newAcc = new Account(authConfig); + _context.Account.Add(newAcc); + _context.SaveChanges(); + } + catch(Exception) + { + Logic.Logging.Logger.Write($"Error while saving data into {ACCOUNT_DB_NAME}, please delete {ACCOUNT_DB_NAME} and restart bot to have it fully work in order"); + } + } + else + { + // Update credentials in database using values from json. + existing.Username = authConfig.Username; + existing.Password = authConfig.Password; + existing.AutoExitBotIfAccountFlagged = authConfig.AutoExitBotIfAccountFlagged; + existing.AccountLatitude = authConfig.AccountLatitude; + existing.AccountLongitude = authConfig.AccountLongitude; + existing.AccountActive = authConfig.AccountActive; + _context.SaveChanges(); + } + } + + // Remove accounts that are not in the auth.json but in the database. + List accountsToRemove = new List(); + foreach (var item in _context.Account) + { + var existing = authConfigs.FirstOrDefault(x => x.Username == item.Username && x.AuthType == item.AuthType); + if (existing == null) + { + accountsToRemove.Add(item); + } + } + + foreach (var item in accountsToRemove) + { + _context.Account.Remove(item); + } + _context.SaveChanges(); + } + + internal Account GetMinRuntime(bool ignoreBlockCheck = false) + { + if (_context.Account.Count() == 0) + return null; + + if (ignoreBlockCheck) + return _context.Account.OrderByDescending(x => x.Level).ThenByDescending(x => x.CurrentXp).Where(a => a.AccountActive == true).LastOrDefault(); + else + return _context.Account.OrderByDescending(x => x.Level).ThenByDescending(x => x.CurrentXp).ThenBy(x => x.RuntimeTotal).Where(x => x != null && x.ReleaseBlockTime.HasValue && x.ReleaseBlockTime < DateTime.Now.ToUnixTime()).LastOrDefault(); + } + + public bool AllowMultipleBot() + { + return _context.Account.Count() > 1; + } + + public Account GetStartUpAccount() + { + ISession session = TinyIoCContainer.Current.Resolve(); + Account startupAccount = null; + + if (!AllowMultipleBot()) + { + startupAccount = _context.Account.Last(); + } + else + { + startupAccount = GetMinRuntime(true); + } + + if (AllowMultipleBot() + && session.LogicSettings.MultipleBotConfig.SelectAccountOnStartUp) + { + SelectAccountForm f = new SelectAccountForm(); + f.ShowDialog(); + startupAccount = f.SelectedAccount; + } + return startupAccount; + } + + private DateTime disableSwitchTime = DateTime.MinValue; + internal void DisableSwitchAccountUntil(DateTime untilTime) + { + if (disableSwitchTime < untilTime) disableSwitchTime = untilTime; + } + + public bool AllowSwitch() + { + return disableSwitchTime < DateTime.Now; + } + + public Account GetSwitchableAccount(Account bot = null, bool pauseIfNoSwitchableAccount = true) + { + ISession session = TinyIoCContainer.Current.Resolve(); + + var currentAccount = GetCurrentAccount(); + + // If the bot to switch to is the same as the current bot then just return. + if (bot == currentAccount) + return bot; + + if (bot != null) + return bot; + + if (_context.Account.Count() > 0) + { + var runnableAccount = _context.Account.OrderByDescending(p => p.Level).ThenByDescending(p => p.CurrentXp).Where(a => a.AccountActive == true).LastOrDefault(); //_context.Account.OrderByDescending(x => x.RuntimeTotal).ThenByDescending(p => p.Level).ThenByDescending(p => p.CurrentXp).LastOrDefault(p => p != currentAccount && p.AccountActive == true); + + if (runnableAccount != null) + return runnableAccount; + } + + if (!pauseIfNoSwitchableAccount) + return null; + + // If we got here all accounts blocked so pause and retry. + var pauseTime = session.LogicSettings.MultipleBotConfig.OnLimitPauseTimes; + + Logic.Logging.Logger.Write($"All accounts are blocked. None of your accounts are available to switch to, so bot will sleep for {pauseTime} minutes until next account is available to run."); + if (session.LogicSettings.NotificationConfig.EnablePushBulletNotification) + PushNotificationClient.SendNotification(session, "All accounts are blocked.", $"None of your accounts are available to switch to, so bot will sleep for {pauseTime} minutes until next account is available to run.", true).ConfigureAwait(false); + + Task.Delay(pauseTime * 60 * 1000).Wait(); + return GetSwitchableAccount(); + } + + private bool switchAccountRequest = false; + public void SwitchAccountTo(Account account) + { + requestedAccount = account; + switchAccountRequest = true; + } + + public void ThrowIfSwitchAccountRequested() + { + if (switchAccountRequest && requestedAccount != null && (!requestedAccount.IsRunning.HasValue || requestedAccount.IsRunning.Value == 0)) + { + switchAccountRequest = false; + throw new ActiveSwitchAccountManualException(requestedAccount); + } + } + + private Account requestedAccount = null; + public void UpdateLocalAccount(Account current, bool save = true) + { + var localAccount = Accounts.Where(a => a.Id == current.Id).FirstOrDefault(); + if (localAccount != null) + { + localAccount.Nickname = current.Nickname; + localAccount.RaisePropertyChanged("Nickname"); + localAccount.RuntimeTotal = current.RuntimeTotal; + localAccount.RaisePropertyChanged("RuntimeTotal"); + localAccount.IsRunning = current.IsRunning; + localAccount.RaisePropertyChanged("IsRunning"); + localAccount.Level = current.Level; + localAccount.RaisePropertyChanged("Level"); + localAccount.LastLogin = current.LastLogin; + localAccount.RaisePropertyChanged("LastLogin"); + localAccount.LastLoginTimestamp = current.LastLoginTimestamp; + localAccount.RaisePropertyChanged("LastLoginTimestamp"); + localAccount.Level = current.Level; + localAccount.RaisePropertyChanged("Level"); + localAccount.Stardust = current.Stardust; + localAccount.RaisePropertyChanged("Stardust"); + localAccount.CurrentXp = current.CurrentXp; + localAccount.RaisePropertyChanged("CurrentXp"); + localAccount.NextLevelXp = current.NextLevelXp; + localAccount.RaisePropertyChanged("NextLevelXp"); + localAccount.PrevLevelXp = current.PrevLevelXp; + localAccount.RaisePropertyChanged("PrevLevelXp"); + localAccount.RaisePropertyChanged("ExperienceInfo"); + + localAccount.AccountLatitude = string.IsNullOrEmpty(current.AccountLatitude.ToString()) ? Client.CurrentLatitude : current.AccountLatitude; + localAccount.AccountLongitude = string.IsNullOrEmpty(current.AccountLongitude.ToString()) ? Client.CurrentLongitude : current.AccountLongitude; + localAccount.AccountActive = current.AccountActive; + + if (save) + _context.SaveChanges(); + } + } + + public void DumpAccountList() + { + var userL = 0; + var maxL = 0; + var user = ""; + + foreach (var item in Accounts) + { + user = string.IsNullOrEmpty(item.Nickname) ? item.Username : item.Nickname; + userL = user.Length; + if (userL > maxL) + { + maxL = userL; + } + } + + int acnt = 0; + + foreach (var item in Accounts.OrderByDescending(p => p.Level).ThenByDescending(p => p.CurrentXp)) + { + user = string.IsNullOrEmpty(item.Nickname) ? item.Username : item.Nickname; + acnt = acnt + 1; + + if (item.Level > 0) + { + if (item.AccountActive) + Logger.Write($"{acnt,2}) {user.PadRight(maxL)} | Lvl: {item.Level,2:#0} | XP: {item.CurrentXp,8:0} ({(int)((double)item.CurrentXp.Value / (double)item.NextLevelXp.Value * 100),2:#0}%) | SD: {item.Stardust,8:0} | Runtime: {item.RuntimeTotal:00:00}", LogLevel.BotStats); + else + Logger.Write($"{acnt,2}) {user.PadRight(maxL)} | Lvl: {item.Level,2:#0} | XP: {item.CurrentXp,8:0} ({(int)((double)item.CurrentXp.Value / (double)item.NextLevelXp.Value * 100),2:#0}%) | SD: {item.Stardust,8:0} | Runtime: {item.RuntimeTotal:00:00}", LogLevel.BotStats, ConsoleColor.Red); + } + else + { + if (item.AccountActive) + Logger.Write($"{acnt,2}) {user.PadRight(maxL)} | Lvl: ?? | XP: 0 ( 0%) | SD: 0 | Runtime: {item.RuntimeTotal:00:00}", LogLevel.BotStats, ConsoleColor.Yellow); + else + Logger.Write($"{acnt,2}) {user.PadRight(maxL)} | Lvl: ?? | XP: 0 ( 0%) | SD: 0 | Runtime: {item.RuntimeTotal:00:00}", LogLevel.BotStats, ConsoleColor.Red); + } + } + } + + public Account FindAvailableAccountForPokemonSwitch(string encounterId) + { + ISession session = TinyIoCContainer.Current.Resolve(); + + //set current + Account switchableAccount = GetSwitchableAccount(null, false); // Don't pause if no switchable account is available. + if (switchableAccount != null) + { + if (session.Cache.GetCacheItem(CatchPokemonTask.GetUsernameEncounterCacheKey(switchableAccount.Username, encounterId)) == null) + { + // Don't edit the running account until we actually switch. Just return the pending account. + return switchableAccount; + } + } + return null; + } + + internal void DirtyEventHandle(Statistics stat) + { + var account = GetCurrentAccount(); + if (account == null) + return; + + account.Level = stat.StatsExport.Level; + account.Stardust = stat.TotalStardust; + account.CurrentXp = stat.StatsExport.CurrentXp - stat.StatsExport.LevelXp; + account.NextLevelXp = stat.StatsExport.LevelupXp - stat.StatsExport.LevelXp; + account.PrevLevelXp = stat.StatsExport.PreviousXp - stat.StatsExport.LevelXp; + var now = DateTime.Now; + if (account.LastRuntimeUpdatedAt.HasValue) + account.RuntimeTotal += (now - TimeUtil.GetDateTimeFromMilliseconds(account.LastRuntimeUpdatedAt.Value)).TotalMinutes; + account.LastRuntimeUpdatedAt = now.ToUnixTime(); + + UpdateLocalAccount(account); + } + } +} diff --git a/PoGo.NecroBot.Logic/Navigation.cs b/PoGo.NecroBot.Logic/Navigation.cs new file mode 100644 index 000000000..db90cdf57 --- /dev/null +++ b/PoGo.NecroBot.Logic/Navigation.cs @@ -0,0 +1,249 @@ +#region using directives + +using GeoCoordinatePortable; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.Interfaces.Configuration; +using PoGo.NecroBot.Logic.Model; +using PoGo.NecroBot.Logic.Model.Settings; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Strategies.Walk; +using PoGo.NecroBot.Logic.Utils; +using PokemonGo.RocketAPI; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +#endregion + +namespace PoGo.NecroBot.Logic +{ + public delegate void UpdatePositionDelegate(ISession session, double lat, double lng, double speed); + + public class Navigation + { + public IWalkStrategy WalkStrategy { get; set; } + private readonly Client _client; + private Random WalkingRandom = new Random(); + private List WalkStrategyQueue { get; set; } + + public Dictionary WalkStrategyBlackList = new Dictionary(); + + private bool _GoogleWalk, _MapZenWalk, _YoursWalk, _AutoWalkAI; + private double distance; + //private int speedChangeFactor = 1; + private int _AutoWalkDist; + + public Navigation(Client client, ILogicSettings logicSettings) + { + _client = client; + + InitializeWalkStrategies(logicSettings); + WalkStrategy = GetStrategy(logicSettings); + } + + public double VariantRandom(ISession session, double currentSpeed) + { + /* + * this changes as bug into BaseWalkStrategy + * + double variantSpeed = session.LogicSettings.WalkingSpeedVariant; + if (variantSpeed == 0.0) + return currentSpeed; + + double baseSpeed = session.LogicSettings.WalkingSpeedInKilometerPerHour; + // Between -1.0 and 1.0 the current deviation from baseSpeed + double currentVariantFactor = (currentSpeed - baseSpeed) / variantSpeed; + + // The more speed is changing towards limit, the more it is likely that speed change direction changes + if (WalkingRandom.Next(1, 10) > 8 + || (currentVariantFactor * speedChangeFactor > 0.0 + && WalkingRandom.NextDouble() + Math.Abs(currentVariantFactor) > 1.50)) + // Change from slow down to speed up or vice versa + speedChangeFactor *= -1; + + // This is the max. delta for each speed change + double newSpeed = currentSpeed + WalkingRandom.NextDouble() * variantSpeed * speedChangeFactor; + + var max = baseSpeed + variantSpeed; + var min = baseSpeed - variantSpeed; + + if (newSpeed > max) + newSpeed -= newSpeed - max; + if (newSpeed < min) + newSpeed += min - newSpeed; + + if (Math.Round(newSpeed, 2) != Math.Round(currentSpeed, 2)) + { + session.EventDispatcher.Send(new HumanWalkingEvent + { + OldWalkingSpeed = currentSpeed, + CurrentWalkingSpeed = newSpeed + }); + } + return newSpeed; + */ + if (WalkingRandom.Next(1, 10) > 5) + { + if (WalkingRandom.Next(1, 10) > 5) + { + var randomicSpeed = currentSpeed; + var max = session.LogicSettings.WalkingSpeedInKilometerPerHour + + session.LogicSettings.WalkingSpeedVariant; + randomicSpeed += WalkingRandom.NextDouble() * (0.02 - 0.001) + 0.001; + + if (randomicSpeed > max) + randomicSpeed = max; + + if (Math.Round(randomicSpeed, 2) != Math.Round(currentSpeed, 2)) + { + session.EventDispatcher.Send(new HumanWalkingEvent + { + OldWalkingSpeed = currentSpeed, + CurrentWalkingSpeed = randomicSpeed + }); + } + return randomicSpeed; + } + else + { + var randomicSpeed = currentSpeed; + var min = session.LogicSettings.WalkingSpeedInKilometerPerHour - + session.LogicSettings.WalkingSpeedVariant; + randomicSpeed -= WalkingRandom.NextDouble() * (0.02 - 0.001) + 0.001; + + if (randomicSpeed < min) + randomicSpeed = min; + + if (Math.Round(randomicSpeed, 2) != Math.Round(currentSpeed, 2)) + { + session.EventDispatcher.Send(new HumanWalkingEvent + { + OldWalkingSpeed = currentSpeed, + CurrentWalkingSpeed = randomicSpeed + }); + } + return randomicSpeed; + } + } + return currentSpeed; + } + + public async Task Move(IGeoLocation targetLocation, + Func functionExecutedWhileWalking, + ISession session, + CancellationToken cancellationToken, double customWalkingSpeed = 0.0) + { + distance = LocationUtils.CalculateDistanceInMeters(session.Client.CurrentLatitude, session.Client.CurrentLongitude, + targetLocation.Latitude, targetLocation.Longitude); + + cancellationToken.ThrowIfCancellationRequested(); + TinyIoC.TinyIoCContainer.Current.Resolve().ThrowIfSwitchAccountRequested(); + + // If the stretegies become bigger, create a factory for easy management + + //Logging.Logger.Write($"Navigation - Walking speed {customWalkingSpeed}"); + InitializeWalkStrategies(session.LogicSettings); + WalkStrategy = GetStrategy(session.LogicSettings); + await WalkStrategy.Walk(targetLocation, functionExecutedWhileWalking, session, cancellationToken, customWalkingSpeed).ConfigureAwait(false); + } + + private void InitializeWalkStrategies(ILogicSettings logicSettings) + { + //AutoWalkAI code + _AutoWalkAI = logicSettings.AutoWalkAI; + _AutoWalkDist = logicSettings.AutoWalkDist; + + if (_AutoWalkAI) + { + _YoursWalk = false; _GoogleWalk = false; _MapZenWalk = false; + + if (distance >= _AutoWalkDist) + { + if (logicSettings.GoogleApiKey != "") + { + Logging.Logger.Write($"Distance to travel is > {_AutoWalkDist}m, using 'Google Walk'", Logging.LogLevel.Info, ConsoleColor.DarkYellow); + _GoogleWalk = true; + } + else if (logicSettings.MapzenTurnByTurnApiKey != "") + { + Logging.Logger.Write($"Distance to travel is > {_AutoWalkDist}m, using 'Mapzen Walk'", Logging.LogLevel.Info, ConsoleColor.DarkYellow); + _MapZenWalk = true; + } + else if (logicSettings.UseYoursWalk) + { + Logging.Logger.Write($"Distance to travel is > {_AutoWalkDist}m, using 'Yours Walk'", Logging.LogLevel.Info, ConsoleColor.DarkYellow); + _YoursWalk = true; + } + else + { + Logging.Logger.Write($"No Base walk strategy enabled, using 'NecroBot Walk'", Logging.LogLevel.Info, ConsoleColor.DarkYellow); + } + } + else if (distance > 15) + { + Logging.Logger.Write($"Distance to travel is < {_AutoWalkDist}m, using 'NecroBot Walk'", Logging.LogLevel.Info, ConsoleColor.DarkYellow); + } + } + else + { + //No AutoWalkAI strategy enabled, using 'NecroBot Config defaults' + _GoogleWalk = logicSettings.UseGoogleWalk; + _MapZenWalk = logicSettings.UseMapzenWalk; + _YoursWalk = logicSettings.UseYoursWalk; + } + + WalkStrategyQueue = new List(); + + //Maybe change configuration for a Navigation Type. + if (logicSettings.DisableHumanWalking) + WalkStrategyQueue.Add(new FlyStrategy(_client)); + + if (logicSettings.UseGpxPathing) + WalkStrategyQueue.Add(new HumanPathWalkingStrategy(_client)); + + if (_GoogleWalk) + WalkStrategyQueue.Add(new GoogleStrategy(_client)); + + if (_MapZenWalk) + WalkStrategyQueue.Add(new MapzenNavigationStrategy(_client)); + + if (_YoursWalk) + WalkStrategyQueue.Add(new YoursNavigationStrategy(_client)); + + // This is the NecroBot Walk default + WalkStrategyQueue.Add(new HumanStrategy(_client)); + } + + public bool IsWalkingStrategyBlacklisted(Type strategy) + { + if (!WalkStrategyBlackList.ContainsKey(strategy)) + return false; + + DateTime now = DateTime.Now; + DateTime blacklistExpiresAt = WalkStrategyBlackList[strategy]; + if (blacklistExpiresAt < now) + { + // Blacklist expired + WalkStrategyBlackList.Remove(strategy); + return false; + } + else + { + return true; + } + } + + public void BlacklistStrategy(Type strategy) + { + // Black list for 1 hour. + WalkStrategyBlackList[strategy] = DateTime.Now.AddHours(1); + } + + public IWalkStrategy GetStrategy(ILogicSettings logicSettings) + { + return WalkStrategyQueue.First(q => !IsWalkingStrategyBlacklisted(q.GetType())); + } + } +} diff --git a/PoGo.NecroBot.Logic/PoGo.NecroBot.Logic.csproj b/PoGo.NecroBot.Logic/PoGo.NecroBot.Logic.csproj new file mode 100644 index 000000000..b9b13d80d --- /dev/null +++ b/PoGo.NecroBot.Logic/PoGo.NecroBot.Logic.csproj @@ -0,0 +1,719 @@ + + + + + + Debug + AnyCPU + {0739E40D-C589-4AEB-93E5-EE8CD6773C60} + Library + Properties + PoGo.NecroBot.Logic + PoGo.NecroBot.Logic + v4.6.2 + 512 + + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + true + bin\x86\Debug\ + DEBUG;TRACE + full + x86 + prompt + MinimumRecommendedRules.ruleset + + + bin\x86\Release\ + TRACE + true + none + x86 + prompt + MinimumRecommendedRules.ruleset + + + x64 + bin\x64\Debug\ + TRACE;DEBUG + full + + + x64 + bin\x64\Release\ + TRACE + true + + + + $(SolutionDir)\packages\AeroWizard.2.1.10\lib\net452\AeroWizard.dll + + + $(SolutionDir)\packages\Portable.BouncyCastle.1.8.1.3\lib\net40\BouncyCastle.Crypto.dll + + + $(SolutionDir)\packages\CloudFlareUtilities.0.4.0-alpha\lib\portable45-net45+win8+wpa81\CloudFlareUtilities.dll + + + $(SolutionDir)\packages\EngineIoClientDotNet.1.0.3\lib\net45\EngineIoClientDotNet.dll + + + $(SolutionDir)packages\EPPlus.4.1.1\lib\net40\EPPlus.dll + + + $(SolutionDir)\packages\Geocoding.net.3.6.1\lib\net40\Geocoding.dll + + + $(SolutionDir)\packages\GeoCoordinate.NetStandard1.1.0.1\lib\netstandard1.1\GeoCoordinate.NetStandard1.dll + + + $(SolutionDir)\packages\Google.Api.CommonProtos.1.1.0\lib\net45\Google.Api.CommonProtos.dll + + + $(SolutionDir)\packages\Google.Api.Gax.2.2.1\lib\net45\Google.Api.Gax.dll + + + False + $(SolutionDir)\packages\Google.Apis.Analytics.v3.1.0.2\lib\net45\Google.Apis.Analytics.v3.dll + + + $(SolutionDir)\packages\Google.Protobuf.3.5.0\lib\net45\Google.Protobuf.dll + + + $(SolutionDir)\packages\GPSOAuthSharp.NetStandard1.1.0.1\lib\netstandard1.1\GPSOAuthSharp.NetStandard1.dll + + + $(SolutionDir)\packages\log4net.2.0.8\lib\net45-full\log4net.dll + + + $(SolutionDir)\packages\Markdig.0.14.8\lib\net40\Markdig.dll + + + $(SolutionDir)\packages\Microsoft.AspNet.SignalR.Client.2.2.2\lib\net45\Microsoft.AspNet.SignalR.Client.dll + + + $(SolutionDir)\packages\Microsoft.Data.Sqlite.1.1.0\lib\net451\Microsoft.Data.Sqlite.dll + + + $(SolutionDir)\packages\Microsoft.EntityFrameworkCore.1.1.2\lib\net451\Microsoft.EntityFrameworkCore.dll + + + $(SolutionDir)\packages\Microsoft.EntityFrameworkCore.Relational.1.1.2\lib\net451\Microsoft.EntityFrameworkCore.Relational.dll + + + $(SolutionDir)\packages\Microsoft.EntityFrameworkCore.Sqlite.1.1.2\lib\net451\Microsoft.EntityFrameworkCore.Sqlite.dll + + + $(SolutionDir)\packages\Microsoft.Extensions.Caching.Abstractions.1.1.2\lib\netstandard1.0\Microsoft.Extensions.Caching.Abstractions.dll + + + $(SolutionDir)\packages\Microsoft.Extensions.Caching.Memory.1.1.2\lib\net451\Microsoft.Extensions.Caching.Memory.dll + + + $(SolutionDir)\packages\Microsoft.Extensions.DependencyInjection.1.1.1\lib\netstandard1.1\Microsoft.Extensions.DependencyInjection.dll + + + $(SolutionDir)\packages\Microsoft.Extensions.DependencyInjection.Abstractions.1.1.1\lib\netstandard1.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll + + + $(SolutionDir)\packages\Microsoft.Extensions.Logging.1.1.2\lib\netstandard1.1\Microsoft.Extensions.Logging.dll + + + $(SolutionDir)\packages\Microsoft.Extensions.Logging.Abstractions.1.1.2\lib\netstandard1.1\Microsoft.Extensions.Logging.Abstractions.dll + + + $(SolutionDir)\packages\Microsoft.Extensions.Options.1.1.2\lib\netstandard1.0\Microsoft.Extensions.Options.dll + + + $(SolutionDir)\packages\Microsoft.Extensions.Primitives.1.1.1\lib\netstandard1.0\Microsoft.Extensions.Primitives.dll + + + $(SolutionDir)\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll + + + $(SolutionDir)\packages\Newtonsoft.Json.Schema.3.0.4\lib\net45\Newtonsoft.Json.Schema.dll + + + $(SolutionDir)\packages\POGOProtos.Core.2.20.0\lib\net45\POGOProtos.Core.dll + + + $(SolutionDir)\packages\Remotion.Linq.2.1.2\lib\net45\Remotion.Linq.dll + + + $(SolutionDir)\packages\S2Geometry.1.0.3\lib\portable-net45+wp8+win8\S2Geometry.dll + True + + + $(SolutionDir)\packages\SocketIoClientDotNet.1.0.3\lib\net45\SocketIoClientDotNet.dll + + + $(SolutionDir)\packages\SuperSocket.1.6.6.1\lib\net45\SuperSocket.Common.dll + + + $(SolutionDir)\packages\SuperSocket.1.6.6.1\lib\net45\SuperSocket.Facility.dll + + + $(SolutionDir)\packages\SuperSocket.1.6.6.1\lib\net45\SuperSocket.SocketBase.dll + + + $(SolutionDir)\packages\SuperSocket.Engine.1.6.6.1\lib\net45\SuperSocket.SocketEngine.dll + + + $(SolutionDir)\packages\SuperSocket.Engine.1.6.6.1\lib\net45\SuperSocket.SocketService.exe + + + $(SolutionDir)\packages\SuperSocket.WebSocket.1.6.6.1\lib\net45\SuperSocket.WebSocket.dll + + + True + + + $(SolutionDir)\packages\System.Collections.Immutable.1.4.0\lib\netstandard2.0\System.Collections.Immutable.dll + + + + + True + + + + $(SolutionDir)\packages\System.Diagnostics.DiagnosticSource.4.4.1\lib\net46\System.Diagnostics.DiagnosticSource.dll + + + + $(SolutionDir)\packages\System.Interactive.Async.3.1.1\lib\net46\System.Interactive.Async.dll + + + True + + + $(SolutionDir)\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll + True + + + + + $(SolutionDir)\packages\System.Runtime.CompilerServices.Unsafe.4.4.0\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll + + + + $(SolutionDir)\packages\System.Security.Cryptography.Algorithms.4.3.1\lib\net461\System.Security.Cryptography.Algorithms.dll + True + + + $(SolutionDir)\packages\System.Security.Cryptography.X509Certificates.4.3.2\lib\net461\System.Security.Cryptography.X509Certificates.dll + True + + + + + + True + + + + $(SolutionDir)\packages\Telegram.Bot.13.2.1\lib\netstandard1.1\Telegram.Bot.dll + + + $(SolutionDir)\packages\Selenium.WebDriver.3.7.0\lib\net45\WebDriver.dll + + + $(SolutionDir)\packages\Selenium.Support.3.7.0\lib\net45\WebDriver.Support.dll + + + $(SolutionDir)\packages\WebSocketSharp.1.0.3-rc11\lib\websocket-sharp.dll + + + $(SolutionDir)\packages\WebSocket4Net.0.15.0\lib\net45\WebSocket4Net.dll + + + $(SolutionDir)\packages\Zlib.Portable.Signed.1.11.0\lib\portable-net4+sl5+wp8+win8+wpa81+MonoTouch+MonoAndroid\Zlib.Portable.dll + + + $(SolutionDir)\packages\Google.Apis.Core.1.30.0\lib\net45\Google.Apis.Core.dll + + + $(SolutionDir)\packages\Google.Apis.1.30.0\lib\net45\Google.Apis.dll + + + $(SolutionDir)\packages\Google.Apis.1.30.0\lib\net45\Google.Apis.PlatformServices.dll + + + $(SolutionDir)\packages\Google.Apis.Auth.1.30.0\lib\net45\Google.Apis.Auth.dll + + + $(SolutionDir)\packages\Google.Apis.Auth.1.30.0\lib\net45\Google.Apis.Auth.PlatformServices.dll + + + + $(SolutionDir)\packages\SuperSocket.ClientEngine.Core.0.8.0.14\lib\net45\SuperSocket.ClientEngine.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Form + + + AutoUpdateForm.cs + + + Form + + + CaptchaSolveForm.cs + + + Component + + + Component + + + + Form + + + InitialTutorialForm.cs + + + Form + + + SelectAccountForm.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + Resources.resx + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + HumanWalkSnipeTask.cs + + + HumanWalkSnipeTask.cs + + + HumanWalkSnipeTask.cs + + + HumanWalkSnipeTask.cs + + + HumanWalkSnipeTask.cs + + + HumanWalkSnipeTask.cs + + + HumanWalkSnipeTask.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Component + + + + + + + + + + AutoUpdateForm.cs + + + CaptchaSolveForm.cs + + + InitialTutorialForm.cs + + + SelectAccountForm.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + {05d2da44-1b8e-4cf7-94ed-4d52451cd095} + PokemonGo.RocketAPI + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/PoGoUtils/PokemonEvolutionHelper.cs b/PoGo.NecroBot.Logic/PoGoUtils/PokemonEvolutionHelper.cs new file mode 100644 index 000000000..47d45be3a --- /dev/null +++ b/PoGo.NecroBot.Logic/PoGoUtils/PokemonEvolutionHelper.cs @@ -0,0 +1,28 @@ +using POGOProtos.Enums; +using POGOProtos.Inventory.Item; +using System.Collections.Generic; + +namespace PoGo.NecroBot.Logic.PoGoUtils +{ + //mabe there a game master setting for this already + + public class PokemonEvolutionHelper + { + private static Dictionary> itemRequirements = new Dictionary>() + { + {ItemId.ItemDragonScale, new List() {PokemonId.Seadra } }, + { ItemId.ItemKingsRock, new List() {PokemonId.Poliwhirl, PokemonId.Slowpoke } }, + { ItemId.ItemMetalCoat, new List() {PokemonId.Scyther, PokemonId.Onix} }, + { ItemId.ItemUpGrade, new List() {PokemonId.Porygon2} }, + { ItemId.ItemSunStone, new List() {PokemonId.Gloom, PokemonId.Sunkern } } + }; + public ItemId GetEvolutionItemRequirement(PokemonId pokemon) + { + foreach (var item in itemRequirements) + { + if (item.Value.Contains(pokemon)) return item.Key; + } + return ItemId.ItemUnknown; + } + } +} diff --git a/PoGo.NecroBot.Logic/PoGoUtils/PokemonInfo.cs b/PoGo.NecroBot.Logic/PoGoUtils/PokemonInfo.cs new file mode 100644 index 000000000..824708eef --- /dev/null +++ b/PoGo.NecroBot.Logic/PoGoUtils/PokemonInfo.cs @@ -0,0 +1,71 @@ +#region using directives + +using System; +using POGOProtos.Data; +using POGOProtos.Enums; +using PokemonGo.RocketAPI.Helpers; +using PoGo.NecroBot.Logic.State; +using System.Threading.Tasks; + +#endregion + +namespace PoGo.NecroBot.Logic.PoGoUtils +{ + public static class PokemonInfo + { + public static int CalculateCp(PokemonData poke) + { + return PokemonCpUtils.GetCp(poke); + } + + public static int CalculateMaxCp(PokemonId pokemonId, int level = 40) + { + return PokemonCpUtils.GetAbsoluteMaxCp(pokemonId, level); + } + + public static double CalculatePokemonPerfection(PokemonData poke) + { + if (poke == null) + return 0; + + //TODO : Lets use the simple formulat att+def+sta /45 + + //if (Math.Abs(poke.CpMultiplier + poke.AdditionalCpMultiplier) <= 0) + return Math.Round((poke.IndividualAttack + poke.IndividualDefense + poke.IndividualStamina) / 45.0 * 100.0, 2); + + //GetBaseStats(poke.PokemonId); + //var maxCp = CalculateMaxCpMultiplier(poke.PokemonId); + //var minCp = CalculateMinCpMultiplier(poke); + //var curCp = CalculateCpMultiplier(poke); + + //return (curCp - minCp) / (maxCp - minCp) * 100.0; + } + + public static double GetLevel(PokemonData poke) + { + return PokemonCpUtils.GetLevel(poke); + } + + public static PokemonMove GetPokemonMove1(PokemonData poke) + { + var move1 = poke.Move1; + return move1; + } + + public static PokemonMove GetPokemonMove2(PokemonData poke) + { + var move2 = poke.Move2; + return move2; + } + + public static async Task GetCandy(ISession session, PokemonData pokemon) + { + return await session.Inventory.GetCandyCount(pokemon.PokemonId).ConfigureAwait(false); + } + + public static int GetPowerUpLevel(PokemonData poke) + { + return (int) (GetLevel(poke) * 2.0); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Properties/AssemblyInfo.cs b/PoGo.NecroBot.Logic/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..b04b2cbdf --- /dev/null +++ b/PoGo.NecroBot.Logic/Properties/AssemblyInfo.cs @@ -0,0 +1,45 @@ +#region using directives + +using System.Reflection; +using System.Runtime.InteropServices; + +#endregion + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. + +[assembly: AssemblyTitle("NecroBot-Private for Pokémon GO")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("PoGo.NecroBot.Logic")] +[assembly: AssemblyCopyright("Copyright NecroBot-Private Team © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. + +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM + +[assembly: Guid("0739e40d-c589-4aeb-93e5-ee8cd6773c60")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion1("1.0.0.181")] + + +[assembly: AssemblyVersion("1.0.0.376")] +[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyInformationalVersion("v1.0.0.376")] \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Properties/Resources.Designer.cs b/PoGo.NecroBot.Logic/Properties/Resources.Designer.cs new file mode 100644 index 000000000..9f1a727ad --- /dev/null +++ b/PoGo.NecroBot.Logic/Properties/Resources.Designer.cs @@ -0,0 +1,83 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace PoGo.NecroBot.Logic.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PoGo.NecroBot.Logic.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap ajax_loader { + get { + object obj = ResourceManager.GetObject("ajax-loader", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] msvc { + get { + object obj = ResourceManager.GetObject("msvc", resourceCulture); + return ((byte[])(obj)); + } + } + } +} diff --git a/PoGo.NecroBot.Logic/Properties/Resources.resx b/PoGo.NecroBot.Logic/Properties/Resources.resx new file mode 100644 index 000000000..a69a689fd --- /dev/null +++ b/PoGo.NecroBot.Logic/Properties/Resources.resx @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ..\Resources\ajax-loader.gif;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\msvc.cer;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Resources/ajax-loader.gif b/PoGo.NecroBot.Logic/Resources/ajax-loader.gif new file mode 100644 index 0000000000000000000000000000000000000000..0400236782031aba5d27c94e760fda63975ed74a GIT binary patch literal 847 zcmZ?wbhEHb6krfw_`<;O|NsA2YHELf{CF1{`eoX*SK8Wd+}wWMzWsg6me&>*pS!v~ z)ztic{`~9m4)QEFmIYKlU6 zW=V!ZVpd{BPJUvFUS?ieK7-;<7EUgpDjkq5AV)H=1}G@>rDV=qY9NxMsMV6NYV8b> zV-uxJ8$Phz>}ZJYWwBw(Yz>$w(8}1^x~OlBrcQ%r)Q?3fNgsB3OqyWPvcgnNwxZKC zTfs(He!g3T(^(3HS!+Aoy$El!_eutIFzqJ{mA*#hU49T~W7 z0?ZBAZ8%vP4EQ41EVOtz+m#iuS)Ks&q4MVm8n;9|w78B0h;ZsIX*Yat+{k6+eA>`^ zi$_b-LpBe+87en7Rx)r1?7Mh|^+p!AUSqh6!yUdzhD8P*oMNpy27OLfxcagMyq>+~ z3AHyZXJ)BU$>xpGp!i@LKb8+W!Wu)`0v5 zjJ*{OJg$ZUhfYf5#!4LDDdEv`q3wnSyFeF<36sKe4IPPPZi3=zRII2vok~9CmrFteyrc9TL5A-sYQlJ#^R)cIuWiYc(~yN{I+8Ijnmz rVMRy60j8eTz-^EsPmaOfMdq_M_mcK4_$GFXF>Cl~+#Gq4Pj literal 0 HcmV?d00001 diff --git a/PoGo.NecroBot.Logic/Resources/msvc.cer b/PoGo.NecroBot.Logic/Resources/msvc.cer new file mode 100644 index 000000000..96ba1667a --- /dev/null +++ b/PoGo.NecroBot.Logic/Resources/msvc.cer @@ -0,0 +1,36 @@ +-----BEGIN CERTIFICATE----- +MIIGWjCCBgGgAwIBAgIRAJtzYJj1v6UIpdZSq/YF1zUwCgYIKoZIzj0EAwIwgZIx +CzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAOBgNV +BAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBMaW1pdGVkMTgwNgYDVQQD +Ey9DT01PRE8gRUNDIERvbWFpbiBWYWxpZGF0aW9uIFNlY3VyZSBTZXJ2ZXIgQ0Eg +MjAeFw0xNzAxMjYwMDAwMDBaFw0xNzA3MzAyMzU5NTlaMGwxITAfBgNVBAsTGERv +bWFpbiBDb250cm9sIFZhbGlkYXRlZDEhMB8GA1UECxMYUG9zaXRpdmVTU0wgTXVs +dGktRG9tYWluMSQwIgYDVQQDExtzbmkxMTI4NjIuY2xvdWRmbGFyZXNzbC5jb20w +WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATon4Be/HsYijtXzLrCRhnercqtQyZ3 +M4pM5JajJW3hDDTV64LMfJGnWdHZa8jx5pz0U3D5DiP1C5cl3mqlDRWIo4IEWzCC +BFcwHwYDVR0jBBgwFoAUQAlhZ/C8g3FP3hIILG/U1Ct2PZYwHQYDVR0OBBYEFJwB +2MLF2Urtg7te2gUcNvN69kXMMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAA +MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjBPBgNVHSAESDBGMDoGCysG +AQQBsjEBAgIHMCswKQYIKwYBBQUHAgEWHWh0dHBzOi8vc2VjdXJlLmNvbW9kby5j +b20vQ1BTMAgGBmeBDAECATBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLmNv +bW9kb2NhNC5jb20vQ09NT0RPRUNDRG9tYWluVmFsaWRhdGlvblNlY3VyZVNlcnZl +ckNBMi5jcmwwgYgGCCsGAQUFBwEBBHwwejBRBggrBgEFBQcwAoZFaHR0cDovL2Ny +dC5jb21vZG9jYTQuY29tL0NPTU9ET0VDQ0RvbWFpblZhbGlkYXRpb25TZWN1cmVT +ZXJ2ZXJDQTIuY3J0MCUGCCsGAQUFBzABhhlodHRwOi8vb2NzcC5jb21vZG9jYTQu +Y29tMIICogYDVR0RBIICmTCCApWCG3NuaTExMjg2Mi5jbG91ZGZsYXJlc3NsLmNv +bYIVKi5hY3RpdmVzY2FuMTMub25saW5lghMqLmFuZ2VsdG9ycmVudDB5LnRrggsq +LmJpcnUwNC50a4INKi5ib29rb2ZyYS5jZoISKi5kYnpkaW1lbnNpb24uY29tghQq +LmRpcmVjdG1haWxjb3N0LmNvbYIYKi5lZ2dib3JvdWdoc3BvcnRzLmNvLnVrghEq +LmZvcnN0dWRlbnQuY2x1YoIPKi5pcHJpY2VlLnByZXNzghAqLmxlbmdhc2dzZHMu +Y29tghYqLm1hcnZlbGl0c2VydmljZXMuY29tggkqLm1uaWoudWuCDSoubXNuaXBl +ci5jb22CFyoubXlvbmdvaW5nc2NhbGVuZGFyLmV1ghQqLnByaW50LWEtcG9zdGVy +LmNvbYIVKi5za2FjaGF0bmV3eW9ya3hjLnRrghEqLnRvcnJlbnRpc2Q3MC50a4IT +YWN0aXZlc2NhbjEzLm9ubGluZYIRYW5nZWx0b3JyZW50MHkudGuCCWJpcnUwNC50 +a4ILYm9va29mcmEuY2aCEGRiemRpbWVuc2lvbi5jb22CEmRpcmVjdG1haWxjb3N0 +LmNvbYIWZWdnYm9yb3VnaHNwb3J0cy5jby51a4IPZm9yc3R1ZGVudC5jbHVigg1p +cHJpY2VlLnByZXNzgg5sZW5nYXNnc2RzLmNvbYIUbWFydmVsaXRzZXJ2aWNlcy5j +b22CB21uaWoudWuCC21zbmlwZXIuY29tghVteW9uZ29pbmdzY2FsZW5kYXIuZXWC +EnByaW50LWEtcG9zdGVyLmNvbYITc2thY2hhdG5ld3lvcmt4Yy50a4IPdG9ycmVu +dGlzZDcwLnRrMAoGCCqGSM49BAMCA0cAMEQCIEy8uzuY+GxkwDgWhGG2+rqQITEF +Igtr+Z20Y9556tHCAiBqol9omUKoFalOY9N0LuhaV0qEpKtSXGrbEQ0X5KC/Vw== +-----END CERTIFICATE----- diff --git a/PoGo.NecroBot.Logic/Service/AnalyticsService.cs b/PoGo.NecroBot.Logic/Service/AnalyticsService.cs new file mode 100644 index 000000000..574c550ba --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/AnalyticsService.cs @@ -0,0 +1,85 @@ +using Google.Apis.Analytics.v3.Data; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.State; +using System.Threading; +using System.Threading.Tasks; + +namespace PoGo.NecroBot.Logic.Service +{ + public class AnalyticsService + { + private const int POLLING_INTERVAL = 5000; + private Google.Apis.Analytics.v3.AnalyticsService service; + + private static AnalyticsData clientData = new AnalyticsData(); + + public AnalyticsService() + { + service = new Google.Apis.Analytics.v3.AnalyticsService(new Google.Apis.Services.BaseClientService.Initializer() + { + ApiKey = "JQkzeCAktUQEpp8arKipoJvmdjJbKURUuvHJfwkziWmJLjePfaXczjFdoDGyiyPe", ApplicationName = "NecroBot" + }); + } + + public class AnalyticsEvent : IEvent + { + public int EventType { get; set; } + public object Data { get; set; } + } + + public async Task StartAsync(Session session, CancellationToken cancellationToken) + { + await Task.Delay(25000, cancellationToken).ConfigureAwait(false); + + cancellationToken.ThrowIfCancellationRequested(); + + var initialized = await service.Init().ConfigureAwait(false); + if (initialized) + { + service.OnData += async (eventType, eventData) => + { + var analyticsEvent = new AnalyticsEvent { EventType = eventType, Data = eventData }; + await BotDataSocketClient.HandleEvent(analyticsEvent, session).ConfigureAwait(false); + }; + + while (true) + { + var request = service.Data.Ga.Get(service, clientData); + await request.ExecuteRequest(cancellationToken).ConfigureAwait(false); + await Task.Delay(POLLING_INTERVAL).ConfigureAwait(false); + } + } + } + + public static void HandleEvent(IEvent evt, ISession session) + { + } + + public static void Listen(IEvent evt, ISession session) + { + dynamic eve = evt; + + try + { + HandleEvent(eve, session); + } + catch + { + } + } + + public static void HandleEvent(SnipeFailedEvent e, ISession sesion) + { + if (e.EncounterId > 0) + clientData.Removes.Enqueue(e.ToPokemon()); + } + + private static void HandleEvent(EncounteredEvent e, ISession session) + { + if (e.IsRecievedFromSocket) + return; + + clientData.Adds.Enqueue(e.ToPokemon()); + } + } +} diff --git a/PoGo.NecroBot.Logic/Service/BotDataSocketClient.cs b/PoGo.NecroBot.Logic/Service/BotDataSocketClient.cs new file mode 100644 index 000000000..fbcc53a39 --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/BotDataSocketClient.cs @@ -0,0 +1,610 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using Newtonsoft.Json; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.Event.Snipe; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Tasks; +using POGOProtos.Enums; +using WebSocketSharp; +using Logger = PoGo.NecroBot.Logic.Logging.Logger; +using System.Runtime.Caching; +using System.Reflection; +using TinyIoC; +using static PoGo.NecroBot.Logic.Service.AnalyticsService; +using Pogo; +using PoGo.NecroBot.Logic.Utils; +using GeoCoordinatePortable; + +namespace PoGo.NecroBot.Logic.Service +{ + public class BotDataSocketClient + { + public class SocketMessage + { + public string Header { get; set; } + public string Body { get; set; } + public long TimeTimestamp { get; set; } + public string Hash { get; set; } + } + public class SocketClientUpdate + { + public List Pokemons { get; set; } + + public List SnipeFailedPokemons { get; set; } + public List ExpiredPokemons { get; set; } + public string ClientVersion { get; set; } + public List ManualSnipes { get; set; } + public string Identitier { get; set; } + + public SocketClientUpdate() + { + Identitier = TinyIoCContainer.Current.Resolve().LogicSettings.DataSharingConfig.DataServiceIdentification; + ManualSnipes = new List(); + Pokemons = new List(); + SnipeFailedPokemons = new List(); + ExpiredPokemons = new List(); + ClientVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString(); + + } + + public bool HasData() + { + return Pokemons.Count > 0 || SnipeFailedPokemons.Count > 0 || ExpiredPokemons.Count > 0; + } + } + private static SocketClientUpdate clientData = new SocketClientUpdate(); + + private const int POLLING_INTERVAL = 5000; + + public static void HandleEvent(AllBotSnipeEvent e, ISession session) + { + lock(clientData) + { + if(!string.IsNullOrEmpty(clientData.Identitier)) + { + clientData.ManualSnipes.Add(e.EncounterId); + } + } + } + public static void HandleEvent(IEvent evt, ISession session) + { + } + + public static void HandleEvent(SnipeFailedEvent e, ISession sesion) + { + lock (clientData) + { + clientData.SnipeFailedPokemons.Add(e); + } + } + + public static async Task HandleEvent(AnalyticsEvent e, ISession session) + { +#pragma warning disable IDE0018 // Inline variable declaration - Build.Bat Error Happens if We Do + Pokemon data = e.Data as Pokemon; + ulong encounterId; + var distance = LocationUtils.CalculateDistanceInMeters(new GeoCoordinate(session.Client.CurrentLatitude, session.Client.CurrentLongitude), new GeoCoordinate(data.Latitude, data.Longitude)); + var maxDistance = session.LogicSettings.EnableHumanWalkingSnipe ? (session.LogicSettings.HumanWalkingSnipeMaxDistance > 0 ? session.LogicSettings.HumanWalkingSnipeMaxDistance : 1500) : 10000; + if (distance > maxDistance) + return; + + switch (e.EventType) + { + case 1: + if (ulong.TryParse(data.EncounterId, out encounterId)) + { + var encounteredEvent = new EncounteredEvent + { + PokemonId = (PokemonId)data.PokemonId, + Latitude = data.Latitude, + Longitude = data.Longitude, + IV = data.Iv, + Level = data.Level, + Expires = new DateTime(1970, 1, 1, 0, 0, 0).AddMilliseconds(data.ExpiredTime), + ExpireTimestamp = data.ExpiredTime, + SpawnPointId = data.SpawnPointId, + EncounterId = data.EncounterId, + Move1 = data.Move1, + Move2 = data.Move2, + IsRecievedFromSocket = true + }; + + session.EventDispatcher.Send(encounteredEvent); + if (session.LogicSettings.DataSharingConfig.AutoSnipe) + { + var move1 = PokemonMove.MoveUnset; + var move2 = PokemonMove.MoveUnset; + Enum.TryParse(encounteredEvent.Move1, true, out move1); + Enum.TryParse(encounteredEvent.Move2, true, out move2); + + var added = await MSniperServiceTask.AddSnipeItem(session, new MSniperServiceTask.MSniperInfo2() + { + UniqueIdentifier = data.EncounterId, + Latitude = data.Latitude, + Longitude = data.Longitude, + EncounterId = encounterId, + SpawnPointId = data.SpawnPointId, + PokemonId = (short)data.PokemonId, + Level = data.Level, + Iv = data.Iv, + Move1 = move1, + Move2 = move2, + ExpiredTime = data.ExpiredTime + }).ConfigureAwait(false); + + if (added) + { + session.EventDispatcher.Send(new AutoSnipePokemonAddedEvent(encounteredEvent)); + } + } + } + break; + + case 2: + MSniperServiceTask.RemoveExpiredSnipeData(session, data.EncounterId); + break; + } +#pragma warning restore IDE0018 // Inline variable declaration - Build.Bat Error Happens if We Do + } + + public static void Listen(IEvent evt, ISession session) + { + dynamic eve = evt; + + try + { + HandleEvent(eve, session); + } + catch + { + } + } + + private static void HandleEvent(EncounteredEvent eve, ISession session) + { + lock (clientData) + { + if (eve.IsRecievedFromSocket || cache.Get(eve.EncounterId) != null) return; + clientData.Pokemons.Add(eve); + } + } + //private static SnipePokemonUpdateEvent lastEncouteredEvent; + private static void HandleEvent(SnipePokemonUpdateEvent eve, ISession session) + { + lock(clientData) + { + clientData.ExpiredPokemons.Add(eve.EncounterId); + } + } + private static string Serialize(dynamic evt) + { + var jsonSerializerSettings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All }; + + // Add custom seriaizer to convert uong to string (ulong shoud not appear to json according to json specs) + jsonSerializerSettings.Converters.Add(new IdToStringConverter()); + + string json = JsonConvert.SerializeObject(evt, Formatting.None, jsonSerializerSettings); + //json = Regex.Replace(json, @"\\\\|\\(""|')|(""|')", match => { + // if (match.Groups[1].Value == "\"") return "\""; // Unescape \" + // if (match.Groups[2].Value == "\"") return "'"; // Replace " with ' + // if (match.Groups[2].Value == "'") return "\\'"; // Escape ' + // return match.Value; // Leave \\ and \' unchanged + //}); + return json; + } + + static List processing = new List(); + + public static String SHA256Hash(String value) + { + StringBuilder Sb = new StringBuilder(); + using (SHA256 hash = SHA256.Create()) + { + Encoding enc = Encoding.UTF8; + Byte[] result = hash.ComputeHash(enc.GetBytes(value)); + + foreach (Byte b in result) + Sb.Append(b.ToString("x2")); + } + + return Sb.ToString(); + } + + public static async Task Start(Session session, string encryptKey, CancellationToken cancellationToken) + { + + //Disable autosniper service until finger out how to make it work with API change + + await Task.Delay(30000, cancellationToken); //delay running 30s + + ServicePointManager.Expect100Continue = false; + + cancellationToken.ThrowIfCancellationRequested(); + + while (true && !termintated) + { + var socketURL = servers.Dequeue(); + // Logger.Write($"Connecting to {socketURL} ...."); + await ConnectToServer(session, socketURL, encryptKey); + servers.Enqueue(socketURL); + } + + } + public static async Task ConnectToServer(ISession session, string socketURL, string encryptKey) + { + if (!string.IsNullOrEmpty(session.LogicSettings.DataSharingConfig.SnipeDataAccessKey)) + { + socketURL += "&access_key=" + session.LogicSettings.DataSharingConfig.SnipeDataAccessKey; + } + + int retries = 0; + using (var ws = new WebSocket(socketURL)) + { + ws.Log.Level = LogLevel.Fatal; ; + ws.Log.Output = (logData, message) => + { + //silenly, no log exception message to screen that scare people :) + }; + + ws.OnMessage += (sender, e) => { OnSocketMessageRecieved(session, sender, e); }; + + ws.Connect(); + while (true && !termintated) + { + try + { + if (retries == 3) + { + //failed to make connection to server times continuing, temporary stop for 10 mins. + /* + session.EventDispatcher.Send(new WarnEvent() + { + Message = $"Couldn't establish the connection to NecroBot socket server : {socketURL}" + }); + */ + if (session.LogicSettings.DataSharingConfig.EnableFailoverDataServers && servers.Count > 1) + { + break; + } + await Task.Delay(1 * 60 * 1000); + retries = 0; + } + + if (ws.ReadyState != WebSocketState.Open) + { + retries++; + ws.Connect(); + } + + while (ws.ReadyState == WebSocketState.Open && !termintated) + { + //Logger.Write("Connected to NecroBot data service."); + retries = 0; + + if (ws.IsAlive && clientData.HasData()) + { + var data = JsonConvert.SerializeObject(clientData);// Serialize(processing); + clientData = new SocketClientUpdate(); + + var message = Encrypt(data, encryptKey); + var actualMessage = JsonConvert.SerializeObject(message); + ws.Send($"42[\"client-update\",{actualMessage}]"); + } + else + { + var pingMessage = JsonConvert.SerializeObject(new { Ping = DateTime.Now }); + ws.Send($"42[\"ping-server\",{pingMessage}"); + } + await Task.Delay(POLLING_INTERVAL); + } + } + catch (IOException) + { + /* + session.EventDispatcher.Send(new WarnEvent + { + Message = "Disconnected from NecroBot socket. New connection will be established when service becomes available..." + }); + */ + } + catch (Exception) + { + } + finally + { + //everytime disconnected with server bot wil reconnect after 15 sec + await Task.Delay(POLLING_INTERVAL); + } + } + } + } + + private static void OnSocketMessageRecieved(ISession session, object sender, MessageEventArgs e) + { + try + { + OnPokemonRemoved(session, e.Data); + OnPokemonUpdateData(session, e.Data); + OnPokemonData(session, e.Data); + OnSnipePokemon(session, e.Data); + OnServerMessage(session, e.Data); + } + catch (Exception ex) + { + Logger.Debug("ERROR TO ADD SNIPE< DEBUG ONLY " + ex.Message + "\r\n " + ex.StackTrace); + } + } + + private static DateTime lastWarningMessage = DateTime.MinValue; + private static bool termintated = false; + private static void OnServerMessage(ISession session, string message) + { + var match = Regex.Match(message, "42\\[\"server-message\",(.*)]"); + if (match != null && !string.IsNullOrEmpty(match.Groups[1].Value)) + { + var messag = match.Groups[1].Value; + if (message.Contains("The connection has been denied") && lastWarningMessage > DateTime.Now.AddMinutes(-5)) return; + lastWarningMessage = DateTime.Now; + session.EventDispatcher.Send(new NoticeEvent() + { + Message = "(SNIPE SERVER) " + match.Groups[1].Value + }); + } + } + + private static void ONFPMBridgeData(ISession session, string message) + { + var match = Regex.Match(message, "42\\[\"fpm\",(.*)]"); + if (match != null && !string.IsNullOrEmpty(match.Groups[1].Value)) + { + //var data = JsonConvert.DeserializeObject>(match.Groups[1].Value); + + // jjskuld - Ignore CS4014 warning for now. +#pragma warning disable 4014 + HumanWalkSnipeTask.AddFastPokemapItem(match.Groups[1].Value); +#pragma warning restore 4014 + } + } + + public static bool CheckIfPokemonBeenCaught(double lat, double lng, PokemonId id, ulong encounterId, + ISession session) + { + if (session.Cache.Get(CatchPokemonTask.GetUsernameGeoLocationCacheKey(session.Settings.Username, id, lat, lng)) != null) return true; + if (encounterId > 0 && session.Cache[CatchPokemonTask.GetEncounterCacheKey(encounterId)] != null) return true; + + return false; + } + + private static void OnPokemonUpdateData(ISession session, string message) + { + var match = Regex.Match(message, "42\\[\"pokemon-update\",(.*)]"); + if (match != null && !string.IsNullOrEmpty(match.Groups[1].Value)) + { + var data = JsonConvert.DeserializeObject(match.Groups[1].Value); + MSniperServiceTask.RemoveExpiredSnipeData(session, data.EncounterId); + } + } + + private static void OnPokemonRemoved(ISession session, string message) + { + var match = Regex.Match(message, "42\\[\"pokemon-remove\",(.*)]"); + if (match != null && !string.IsNullOrEmpty(match.Groups[1].Value)) + { + var data = JsonConvert.DeserializeObject(match.Groups[1].Value); + MSniperServiceTask.RemoveExpiredSnipeData(session, data.EncounterId); + } + } + + private static MemoryCache cache = new MemoryCache("dump"); + + //static int count = 0; + private static void OnPokemonData(ISession session, string message) + { +#pragma warning disable IDE0018 // Inline variable declaration - Build.Bat Error Happens if We Do + ulong encounterid; + var match = Regex.Match(message, "42\\[\"pokemon\",(.*)]"); + if (match != null && !string.IsNullOrEmpty(match.Groups[1].Value)) + { + var data = JsonConvert.DeserializeObject(match.Groups[1].Value); + data.IsRecievedFromSocket = true; + if (Math.Abs(data.Latitude) > 90 || Math.Abs(data.Longitude) > 180) + { + return; + }; + + var distance = LocationUtils.CalculateDistanceInMeters(new GeoCoordinate(session.Client.CurrentLatitude, session.Client.CurrentLongitude), new GeoCoordinate(data.Latitude, data.Longitude)); + var maxDistance = session.LogicSettings.EnableHumanWalkingSnipe ? (session.LogicSettings.HumanWalkingSnipeMaxDistance > 0 ? session.LogicSettings.HumanWalkingSnipeMaxDistance : 1500) : 10000; + if (distance > maxDistance) + return; + + ulong.TryParse(data.EncounterId, out encounterid); + if (encounterid > 0 && cache.Get(encounterid.ToString()) == null) + { + cache.Add(encounterid.ToString(), DateTime.Now, DateTime.Now.AddMinutes(15)); + } + + session.EventDispatcher.Send(data); + if (session.LogicSettings.DataSharingConfig.AutoSnipe) + { + var move1 = PokemonMove.MoveUnset; + var move2 = PokemonMove.MoveUnset; + Enum.TryParse(data.Move1, true, out move1); + Enum.TryParse(data.Move2, true, out move2); + + bool caught = CheckIfPokemonBeenCaught(data.Latitude, data.Longitude, + data.PokemonId, encounterid, session); + if (!caught) + { + var added = MSniperServiceTask.AddSnipeItem(session, new MSniperServiceTask.MSniperInfo2() + { + UniqueIdentifier = data.EncounterId, + Latitude = data.Latitude, + Longitude = data.Longitude, + EncounterId = encounterid, + SpawnPointId = data.SpawnPointId, + PokemonId = (short)data.PokemonId, + Level = data.Level, + Iv = data.IV, + Move1 = move1, + Move2 = move2, + ExpiredTime = data.ExpireTimestamp + }).Result; + if (added) + { + session.EventDispatcher.Send(new AutoSnipePokemonAddedEvent(data)); + } + } + } + } +#pragma warning restore IDE0018 // Inline variable declaration - Build.Bat Error Happens if We Do + } + + private static void OnSnipePokemon(ISession session, string message) + { +#pragma warning disable IDE0018 // Inline variable declaration - Build.Bat Error Happens if We Do + ulong encounterid; + var match = Regex.Match(message, "42\\[\"snipe-pokemon\",(.*)]"); + if (match != null && !string.IsNullOrEmpty(match.Value) && !string.IsNullOrEmpty(match.Groups[1].Value)) + { + var data = JsonConvert.DeserializeObject(match.Groups[1].Value); + + //not your snipe item, return need more encrypt here and configuration to allow catch others item + if (string.IsNullOrEmpty(session.LogicSettings.DataSharingConfig.DataServiceIdentification) || + string.IsNullOrEmpty(data.RecieverId) || + data.RecieverId.ToLower() != session.LogicSettings.DataSharingConfig.DataServiceIdentification.ToLower()) return; + + var move1 = PokemonMove.Absorb; + var move2 = PokemonMove.Absorb; + Enum.TryParse(data.Move1, true, out move1); + Enum.TryParse(data.Move1, true, out move2); + ulong.TryParse(data.EncounterId, out encounterid); + + bool caught = CheckIfPokemonBeenCaught(data.Latitude, data.Longitude, data.PokemonId, encounterid, + session); + if (caught) + { + Logger.Write("[SNIPE IGNORED] - Your snipe pokemon has already been caught by bot", + Logic.Logging.LogLevel.Sniper); + return; + } + + MSniperServiceTask.AddSnipeItem(session, new MSniperServiceTask.MSniperInfo2() + { + UniqueIdentifier = data.EncounterId, + Latitude = data.Latitude, + Longitude = data.Longitude, + EncounterId = encounterid, + SpawnPointId = data.SpawnPointId, + Level = data.Level, + PokemonId = (short)data.PokemonId, + Iv = data.IV, + Move1 = move1, + ExpiredTime = data.ExpireTimestamp, + Move2 = move2 + }, true).Wait(); + } +#pragma warning restore IDE0018 // Inline variable declaration - Build.Bat Error Happens if We Do + } + private static Queue servers = new Queue(); + public static Task StartAsync(Session session, string encryptKey, + CancellationToken cancellationToken = default(CancellationToken)) + { + var config = session.LogicSettings.DataSharingConfig; + + if (config.EnableSyncData) + { + servers.Enqueue(config.DataRecieverURL); + + if (config.EnableFailoverDataServers) + { + foreach (var item in config.FailoverDataServers.Split(";".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)) + { + servers.Enqueue(item); + } + } + } + + return Task.Run(() => Start(session, encryptKey, cancellationToken), cancellationToken); + } + + public static SocketMessage Encrypt(string message, string encryptKey) + { + var encryptedtulp = Encrypt(message, encryptKey, false); + + var socketMessage = new SocketMessage() + { + TimeTimestamp = (long)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalMilliseconds, + Header = encryptedtulp.Item1, + Body = encryptedtulp.Item2 + }; + socketMessage.Hash = CalculateMD5Hash($"{socketMessage.TimeTimestamp}{socketMessage.Body}{socketMessage.Header}"); + + return socketMessage; + } + public static string CalculateMD5Hash(string input) + { + // step 1, calculate MD5 hash from input + + MD5 md5 = MD5.Create(); + + byte[] inputBytes = Encoding.ASCII.GetBytes(input); + + byte[] hash = md5.ComputeHash(inputBytes); + + // step 2, convert byte array to hex string + + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < hash.Length; i++) + + { + + sb.Append(hash[i].ToString("X2")); + + } + + return sb.ToString(); + + } + + + public static Tuple Encrypt(string toEncrypt, string key, bool useHashing) + { + byte[] keyArray; + byte[] toEncryptArray = Encoding.UTF8.GetBytes(toEncrypt); + + if (useHashing) + { + MD5CryptoServiceProvider hashmd5 = new MD5CryptoServiceProvider(); + keyArray = hashmd5.ComputeHash(Encoding.UTF8.GetBytes(key)); + } + else + keyArray = Encoding.UTF8.GetBytes(key); + + var tdes = new TripleDESCryptoServiceProvider() + { + Key = keyArray + }; + // tdes.Mode = CipherMode.CBC; // which is default + // tdes.Padding = PaddingMode.PKCS7; // which is default + + //Console.WriteLine("iv: {0}", Convert.ToBase64String(tdes.IV)); + + ICryptoTransform cTransform = tdes.CreateEncryptor(); + byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, + toEncryptArray.Length); + string iv = Convert.ToBase64String(tdes.IV); + string message = Convert.ToBase64String(resultArray, 0, resultArray.Length); + return new Tuple(iv, message); + } + + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/ConsoleEventListener.cs b/PoGo.NecroBot.Logic/Service/ConsoleEventListener.cs new file mode 100644 index 000000000..09284d183 --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/ConsoleEventListener.cs @@ -0,0 +1,827 @@ +#region using directives + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.Event.Gym; +using PoGo.NecroBot.Logic.Event.Player; +using PoGo.NecroBot.Logic.Logging; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Tasks; +using POGOProtos.Enums; +using POGOProtos.Inventory.Item; +using POGOProtos.Map.Fort; +using POGOProtos.Networking.Responses; +using PoGo.NecroBot.Logic.Event.Snipe; +using System.Linq; +using PoGo.NecroBot.Logic.Utils; +using PoGo.NecroBot.Logic.Model.Settings; +using System.IO; +using PokemonGo.RocketAPI; + +#endregion + +namespace PoGo.NecroBot.Logic.Service +{ + [SuppressMessage("ReSharper", "UnusedParameter.Local")] + public class ConsoleEventListener + { + public delegate void HumanWalkEventDelegate(HumanWalkingEvent e); + public static event HumanWalkEventDelegate HumanWalkEvent; + public static ISettings _Settings { get; set; } + public static GlobalSettings _settings;// { get; set; } + public static Session _session; + + private static void HandleEvent(ProfileEvent profileEvent, ISession session) + { + Logger.Write(session.Translation.GetTranslation(TranslationString.EventProfileLogin, + profileEvent.Profile.PlayerData.Username ?? "")); + } + + private static void HandleEvent(ErrorEvent errorEvent, ISession session) + { + Logger.Write(errorEvent.ToString(), LogLevel.Error, force: true); + } + + private static void HandleEvent(SnipePokemonUpdateEvent e, ISession session) + { + //move to resource later + if (e.IsRemoteEvent) + Logger.Write($"Expired snipe pokemon has been removed from queue : {e.Data.PokemonId} "); + } + + private static void HandleEvent(NoticeEvent noticeEvent, ISession session) + { + Logger.Write(noticeEvent.ToString()); + } + + public static void HandleEvent(PokestopLimitUpdate ev, ISession session) + { + Logger.Write($"(POKESTOP LIMIT) {ev.Value}/{ev.Limit}", LogLevel.Info, ConsoleColor.Yellow); + } + + public static void HandleEvent(CatchLimitUpdate ev, ISession session) + { + Logger.Write($"(CATCH LIMIT) {ev.Value}/{ev.Limit}", LogLevel.Info, ConsoleColor.Yellow); + } + + private static void HandleEvent(TargetLocationEvent ev, ISession session) + { + //Logger.Write(session.Translation.GetTranslation(TranslationString.TargetLocationSet, ev.Latitude, ev.Longitude), LogLevel.Info); + } + + private static void HandleEvent(BuddyUpdateEvent ev, ISession session) + { + Logger.Write( + session.Translation.GetTranslation( + TranslationString.BuddyPokemonUpdate, ev.Pokemon.PokemonId.ToString() + ), + LogLevel.Info + ); + } + + private static void HandleEvent(WarnEvent warnEvent, ISession session) + { + Logger.Write(warnEvent.ToString(), LogLevel.Warning); + + if (!warnEvent.RequireInput) return; + Logger.Write(session.Translation.GetTranslation(TranslationString.RequireInputText), LogLevel.Warning); + } + + private static void HandleEvent(UseLuckyEggEvent useLuckyEggEvent, ISession session) + { + Logger.Write( + session.Translation.GetTranslation(TranslationString.EventUsedLuckyEgg, useLuckyEggEvent.Count), + LogLevel.Egg + ); + } + + private static void HandleEvent(PokemonEvolveEvent pokemonEvolveEvent, ISession session) + { + string strPokemon = session.Translation.GetPokemonTranslation(pokemonEvolveEvent.Id); + string logMessage = pokemonEvolveEvent.Result == EvolvePokemonResponse.Types.Result.Success + ? session.Translation.GetTranslation(TranslationString.EventPokemonEvolvedSuccess, + strPokemon.PadRight(12, ' '), + session.Translation.GetPokemonTranslation(pokemonEvolveEvent.EvolvedPokemon.PokemonId).PadRight(12, ' '), + pokemonEvolveEvent.EvolvedPokemon.Cp.ToString("0").PadLeft(4, ' '), + pokemonEvolveEvent.EvolvedPokemon.Perfection().ToString("0.00").PadLeft(6, ' '), + pokemonEvolveEvent.Exp.ToString("0").PadLeft(4, ' '), + pokemonEvolveEvent.Candy.ToString("0").PadLeft(3, ' '), + pokemonEvolveEvent.EvolvedPokemon.Level().ToString("0.0").PadLeft(4, ' ')) + : session.Translation.GetTranslation(TranslationString.EventPokemonEvolvedFailed, + session.Translation.GetPokemonTranslation(pokemonEvolveEvent.Id).PadRight(12, ' '), + pokemonEvolveEvent.Result, + strPokemon); + logMessage = (pokemonEvolveEvent.Sequence > 0 ? $"{pokemonEvolveEvent.Sequence.ToString("0").PadLeft(2,' ')}. " : " ") + logMessage; + Logger.Write(logMessage, LogLevel.Evolve); + } + + private static void HandleEvent(TransferPokemonEvent transferPokemonEvent, ISession session) + { + if (session.LogicSettings.NotificationConfig.EnablePushBulletNotification && transferPokemonEvent.Slashed) + PushNotificationClient.SendNotification(session, $"Transferred Slashed Pokemon", $"{session.Translation.GetPokemonTranslation(transferPokemonEvent.PokemonId)}\n" + + $"Lvl: {transferPokemonEvent.Level}\n" + + $"CP: {transferPokemonEvent.Cp}/{transferPokemonEvent.BestCp}\n" + + $"IV: {transferPokemonEvent.Perfection.ToString("0.00")}\n", true).ConfigureAwait(false); + Logger.Write( + session.Translation.GetTranslation(TranslationString.EventPokemonTransferred, + session.Translation.GetPokemonTranslation(transferPokemonEvent.PokemonId).PadRight(12, ' '), + transferPokemonEvent.Cp.ToString("0").PadLeft(4, ' '), + transferPokemonEvent.Perfection.ToString("0.00").PadLeft(6, ' '), + transferPokemonEvent.BestCp.ToString("0").PadLeft(4, ' '), + transferPokemonEvent.BestPerfection.ToString("0.00").PadLeft(6, ' '), + transferPokemonEvent.Candy.ToString("0").PadLeft(4, ' '), + transferPokemonEvent.Level.ToString("0.0").PadLeft(4, ' '), + transferPokemonEvent.Slashed.ToString().PadLeft(5, ' ') + ), + LogLevel.Transfer + ); + } + + private static void HandleEvent(UpgradePokemonEvent upgradePokemonEvent, ISession session) + { + Logger.Write( + session.Translation.GetTranslation(TranslationString.EventPokemonUpgraded, + session.Translation.GetPokemonTranslation(upgradePokemonEvent.PokemonId).PadRight(12, ' '), + upgradePokemonEvent.Lvl.ToString("0.0").PadLeft(4, ' '), + upgradePokemonEvent.Cp.ToString("0").PadLeft(4, ' '), + upgradePokemonEvent.Perfection.ToString("0.00").PadLeft(6, ' '), + upgradePokemonEvent.BestCp.ToString("0").PadLeft(4, ' '), + upgradePokemonEvent.BestPerfection.ToString("0.00").PadLeft(6, ' '), + upgradePokemonEvent.USD.ToString("0").PadLeft(5, ' '), + upgradePokemonEvent.Candy.ToString("0").PadLeft(4, ' ')), + LogLevel.LevelUp); + } + + private static void HandleEvent(RenamePokemonEvent renamePokemonEvent, ISession session) + { + Logger.Write( + session.Translation.GetTranslation( + TranslationString.PokemonRename, + session.Translation.GetPokemonTranslation(renamePokemonEvent.PokemonId).PadRight(13), + renamePokemonEvent.OldNickname.PadRight(13), + renamePokemonEvent.NewNickname + ), + LogLevel.Info); + } + + private static void HandleEvent(ItemRecycledEvent itemRecycledEvent, ISession session) + { + Logger.Write( + session.Translation.GetTranslation( + TranslationString.EventItemRecycled, itemRecycledEvent.Count.ToString("0").PadLeft(3), itemRecycledEvent.Id + ), + LogLevel.Recycling + ); + } + + private static void HandleEvent(EggIncubatorStatusEvent eggIncubatorStatusEvent, ISession session) + { + Logger.Write(eggIncubatorStatusEvent.WasAddedNow + ? session.Translation.GetTranslation(TranslationString.IncubatorPuttingEgg, + eggIncubatorStatusEvent.KmToWalk.ToString("0.00").PadLeft(5)) + : session.Translation.GetTranslation(TranslationString.IncubatorStatusUpdate, + eggIncubatorStatusEvent.KmRemaining.ToString("0.00").PadLeft(5), + eggIncubatorStatusEvent.KmToWalk.ToString("0.00").PadLeft(5)), + LogLevel.Egg); + } + + private static void HandleEvent(EggHatchedEvent eggHatchedEvent, ISession session) + { + Logger.Write( + session.Translation.GetTranslation( + TranslationString.IncubatorEggHatched, + eggHatchedEvent.Dist.ToString("0.00").PadLeft(5), + session.Translation.GetPokemonTranslation(eggHatchedEvent.PokemonId).PadRight(13), + eggHatchedEvent.Level.ToString("0.0").PadLeft(4), + eggHatchedEvent.Cp.ToString("0").PadLeft(4), + eggHatchedEvent.MaxCp.ToString("0").PadLeft(4), + eggHatchedEvent.Perfection.ToString("0.00").PadLeft(6), + eggHatchedEvent.HXP.ToString("0").PadLeft(4), + eggHatchedEvent.HSD.ToString("0").PadLeft(4), + eggHatchedEvent.HCandy.ToString("0").PadLeft(4) + ), + LogLevel.Egg); + } + + private static void HandleEvent(FortUsedEvent fortUsedEvent, ISession session) + { + if (fortUsedEvent.InventoryFull) + { + Logger.Write(session.Translation.GetTranslation(TranslationString.InvFullPokestopLooting), fortUsedEvent.Fort.Type == FortType.Checkpoint ? LogLevel.Pokestop : LogLevel.Gym, ConsoleColor.Cyan); //LogLevel.Pokestop); + return; + } + + string PokemonDataEgg = "No"; + + if (fortUsedEvent.PokemonDataEgg != null && fortUsedEvent.PokemonDataEgg.IsEgg) + { + PokemonDataEgg = $"Yes {fortUsedEvent.PokemonDataEgg.EggKmWalkedTarget:0.0} Km"; + } + + string eventMessage = session.Translation.GetTranslation(TranslationString.EventFortUsed, fortUsedEvent.Name, + fortUsedEvent.Exp, fortUsedEvent.Gems, + fortUsedEvent.Items, fortUsedEvent.Badges, fortUsedEvent.BonusLoot, fortUsedEvent.RaidTickets, fortUsedEvent.TeamBonusLoot, PokemonDataEgg, session.Inventory.GetEggs().Result.Count(), fortUsedEvent.Latitude, fortUsedEvent.Longitude, fortUsedEvent.Altitude); + + if (fortUsedEvent.Fort.Type == FortType.Checkpoint) + Logger.Write(eventMessage, LogLevel.Pokestop); + else + Logger.Write(eventMessage, LogLevel.GymDisk); + + // TheWizard is Working on this. Don't incorporate till it's done. + //var globalSettings = new GlobalSettings(); + //_settings.Auth.CurrentAuthConfig.AccountLatitude = fortUsedEvent.Latitude; + //_settings.Auth.CurrentAuthConfig.AccountLongitude = fortUsedEvent.Longitude; + + //_session.Client.Player.SetCoordinates(fortUsedEvent.Latitude, fortUsedEvent.Longitude, fortUsedEvent.Altitude); + + //_settings.LocationConfig.AccountLatitude = fortUsedEvent.Latitude; + //_Settings.AccountLongitude = fortUsedEvent.Longitude; + + //_settings.LocationConfig.DefaultLatitude = fortUsedEvent.Latitude; + //_settings.LocationConfig.DefaultLongitude = fortUsedEvent.Longitude; + + //_session.Client.Settings.DefaultLatitude = fortUsedEvent.Latitude; + //_session.Client.Settings.DefaultLongitude = fortUsedEvent.Longitude; + + //_settings.Save(Path.Combine(_settings.ProfileConfigPath, "config.json")); + } + + private static void HandleEvent(FortFailedEvent fortFailedEvent, ISession session) + { + if (fortFailedEvent.Try != 1 && fortFailedEvent.Looted == false) + { + Logger.LineSelect(); // Replaces the last line to prevent spam. + } + + if (fortFailedEvent.Looted) + { + Logger.Write( + session.Translation.GetTranslation(TranslationString.SoftBanBypassed), + LogLevel.SoftBan, ConsoleColor.Green); + } + else + { + Logger.Write( + session.Translation.GetTranslation(TranslationString.EventFortFailed, fortFailedEvent.Name, + fortFailedEvent.Try, fortFailedEvent.Max), + LogLevel.SoftBan); + } + } + + private static void HandleEvent(FortTargetEvent fortTargetEvent, ISession session) + { + int intTimeForArrival = (int) (fortTargetEvent.Distance / + (session.LogicSettings.WalkingSpeedInKilometerPerHour * 0.5)); + + string targetType = ""; + if (fortTargetEvent.Type == FortType.Gym) + targetType = session.Translation.GetTranslation(TranslationString.Gym); // "Gym"; + else if (fortTargetEvent.Type == FortType.Checkpoint) + { + if (fortTargetEvent.Name != "User selected") + targetType = session.Translation.GetTranslation(TranslationString.Pokestop); // "Pokestop"; + else + targetType = "POI"; + } + + if (fortTargetEvent.Distance > 15) + Logger.Write( + session.Translation.GetTranslation(TranslationString.EventFortTargeted, Math.Round(fortTargetEvent.Distance).ToString("0").PadLeft(3, ' '), + intTimeForArrival.ToString("0").PadLeft(3,' '), fortTargetEvent.Route, + targetType, fortTargetEvent.Name), + LogLevel.Info, ConsoleColor.Gray); + } + + private static void HandleEvent(PokemonCaptureEvent pokemonCaptureEvent, ISession session) + { + Func returnRealBallName = a => + { + // ReSharper disable once SwitchStatementMissingSomeCases + switch (a) + { + case ItemId.ItemPokeBall: + return session.Translation.GetTranslation(TranslationString.Pokeball); + case ItemId.ItemGreatBall: + return session.Translation.GetTranslation(TranslationString.GreatPokeball); + case ItemId.ItemUltraBall: + return session.Translation.GetTranslation(TranslationString.UltraPokeball); + case ItemId.ItemMasterBall: + return session.Translation.GetTranslation(TranslationString.MasterPokeball); + default: + return session.Translation.GetTranslation(TranslationString.CommonWordUnknown); + } + }; + + var catchType = pokemonCaptureEvent.CatchType; + + string strStatus; + switch (pokemonCaptureEvent.Status) + { + case CatchPokemonResponse.Types.CatchStatus.CatchError: + strStatus = session.Translation.GetTranslation(TranslationString.CatchStatusError); + break; + case CatchPokemonResponse.Types.CatchStatus.CatchEscape: + strStatus = session.Translation.GetTranslation(TranslationString.CatchStatusEscape); + break; + case CatchPokemonResponse.Types.CatchStatus.CatchFlee: + strStatus = session.Translation.GetTranslation(TranslationString.CatchStatusFlee); + break; + case CatchPokemonResponse.Types.CatchStatus.CatchMissed: + strStatus = session.Translation.GetTranslation(TranslationString.CatchStatusMissed); + break; + case CatchPokemonResponse.Types.CatchStatus.CatchSuccess: + strStatus = session.Translation.GetTranslation(TranslationString.CatchStatusSuccess); + break; + default: + strStatus = pokemonCaptureEvent.Status.ToString(); + break; + } + + var catchStatus = pokemonCaptureEvent.Attempt > 1 + ? session.Translation.GetTranslation(TranslationString.CatchStatusAttempt, strStatus, + pokemonCaptureEvent.Attempt) + : session.Translation.GetTranslation(TranslationString.CatchStatus, strStatus); + + var familyCandies = pokemonCaptureEvent.Candy?.Candy_ > 0 + ? session.Translation.GetTranslation(TranslationString.Candies, pokemonCaptureEvent.Candy.Candy_) + : ""; + + string message; + + if (pokemonCaptureEvent.Status == CatchPokemonResponse.Types.CatchStatus.CatchSuccess) + { + if (session.LogicSettings.NotificationConfig.EnablePushBulletNotification && pokemonCaptureEvent.Shiny == "Yes") + PushNotificationClient.SendNotification(session, $"Shiny Pokemon Captured", $"{session.Translation.GetPokemonTranslation(pokemonCaptureEvent.Id)}\n" + + $"Lvl: {pokemonCaptureEvent.Level}\n" + + $"IV: {pokemonCaptureEvent.Perfection.ToString("0.00")}\n" + + $"CP: {pokemonCaptureEvent.Cp}/{pokemonCaptureEvent.MaxCp}\n" + + $"Lat: {pokemonCaptureEvent.Latitude.ToString("0.000000")}\n" + + $"Lon: {pokemonCaptureEvent.Longitude.ToString("0.000000")}", true).ConfigureAwait(false); + + if (session.LogicSettings.NotificationConfig.EnablePushBulletNotification && pokemonCaptureEvent.Perfection >= session.LogicSettings.FavoriteMinIvPercentage) + PushNotificationClient.SendNotification(session, $"High IV Pokemon Captured", $"{session.Translation.GetPokemonTranslation(pokemonCaptureEvent.Id)}\n" + + $"Lvl: {pokemonCaptureEvent.Level}\n" + + $"IV: {pokemonCaptureEvent.Perfection.ToString("0.00")}\n" + + $"CP: {pokemonCaptureEvent.Cp}/{pokemonCaptureEvent.MaxCp}\n" + + $"Lat: {pokemonCaptureEvent.Latitude.ToString("0.000000")}\n" + + $"Lon: {pokemonCaptureEvent.Longitude.ToString("0.000000")}", true).ConfigureAwait(false); + + message = session.Translation.GetTranslation(TranslationString.EventPokemonCaptureSuccess, catchStatus, + catchType, + session.Translation.GetPokemonTranslation(pokemonCaptureEvent.Id).PadRight(12, ' '), + pokemonCaptureEvent.Level.ToString("0.0").PadLeft(4, ' '), + pokemonCaptureEvent.Cp.ToString("0").PadLeft(4, ' '), + pokemonCaptureEvent.MaxCp.ToString("0").PadLeft(4, ' '), + pokemonCaptureEvent.Perfection.ToString("0.00").PadLeft(6, ' '), + pokemonCaptureEvent.Probability.ToString("0.00").PadLeft(6, ' '), + pokemonCaptureEvent.Distance.ToString("F2"), + returnRealBallName(pokemonCaptureEvent.Pokeball).PadRight(10, ' '), + pokemonCaptureEvent.BallAmount.ToString("0").PadLeft(3, ' '), + pokemonCaptureEvent.Exp.ToString("0").PadLeft(4, ' '), + pokemonCaptureEvent.Stardust.ToString("0").PadLeft(4, ' '), + familyCandies, + pokemonCaptureEvent.Latitude.ToString("0.000000"), + pokemonCaptureEvent.Longitude.ToString("0.000000"), + pokemonCaptureEvent.Move1.ToString().PadRight(16, ' '), + pokemonCaptureEvent.Move2.ToString().PadRight(16, ' '), + pokemonCaptureEvent.Rarity.ToString().PadRight(10, ' '), + pokemonCaptureEvent.CaptureReason, + pokemonCaptureEvent.Shiny.ToString().PadRight(3, ' '), + pokemonCaptureEvent.Form, + pokemonCaptureEvent.Costume, + pokemonCaptureEvent.Gender + ); + Logger.Write(message, LogLevel.Caught); + } + else + { + if (pokemonCaptureEvent.Status == CatchPokemonResponse.Types.CatchStatus.CatchFlee) + { + if (session.LogicSettings.NotificationConfig.EnablePushBulletNotification && pokemonCaptureEvent.Shiny == "Yes") + PushNotificationClient.SendNotification(session, $"Shiny Pokemon Ran Away", $"{session.Translation.GetPokemonTranslation(pokemonCaptureEvent.Id)}\n" + + $"Lvl: {pokemonCaptureEvent.Level}\n" + + $"IV: {pokemonCaptureEvent.Perfection.ToString("0.00")}\n" + + $"CP: {pokemonCaptureEvent.Cp}/{pokemonCaptureEvent.MaxCp}\n" + + $"Lat: {pokemonCaptureEvent.Latitude.ToString("0.000000")}\n" + + $"Lon: {pokemonCaptureEvent.Longitude.ToString("0.000000")}", true).ConfigureAwait(false); + + if (session.LogicSettings.NotificationConfig.EnablePushBulletNotification && pokemonCaptureEvent.Perfection >= session.LogicSettings.FavoriteMinIvPercentage) + PushNotificationClient.SendNotification(session, $"High IV Pokemon Ran Away", $"{session.Translation.GetPokemonTranslation(pokemonCaptureEvent.Id)}\n" + + $"Lvl: {pokemonCaptureEvent.Level}\n" + + $"IV: {pokemonCaptureEvent.Perfection.ToString("0.00")}\n" + + $"CP: {pokemonCaptureEvent.Cp}/{pokemonCaptureEvent.MaxCp}\n" + + $"Lat: {pokemonCaptureEvent.Latitude.ToString("0.000000")}\n" + + $"Lon: {pokemonCaptureEvent.Longitude.ToString("0.000000")}", true).ConfigureAwait(false); + } + message = session.Translation.GetTranslation(TranslationString.EventPokemonCaptureFailed, catchStatus, + catchType, + session.Translation.GetPokemonTranslation(pokemonCaptureEvent.Id).PadRight(12, ' '), + pokemonCaptureEvent.Level.ToString("0.0").PadLeft(4, ' '), + pokemonCaptureEvent.Cp.ToString("0").PadLeft(4, ' '), + pokemonCaptureEvent.MaxCp.ToString("0").PadLeft(4, ' '), + pokemonCaptureEvent.Perfection.ToString("0.00").PadLeft(6, ' '), + pokemonCaptureEvent.Probability.ToString("0.00").PadLeft(6, ' '), + pokemonCaptureEvent.Distance.ToString("F2"), + returnRealBallName(pokemonCaptureEvent.Pokeball).PadRight(10, ' '), + pokemonCaptureEvent.BallAmount.ToString("0").PadLeft(3, ' '), + pokemonCaptureEvent.Exp.ToString("0").PadLeft(4, ' '), + pokemonCaptureEvent.Latitude.ToString("0.000000"), + pokemonCaptureEvent.Longitude.ToString("0.000000"), + pokemonCaptureEvent.Move1.ToString().PadRight(15, ' '), + pokemonCaptureEvent.Move2.ToString().PadRight(15, ' '), + pokemonCaptureEvent.Rarity.ToString().PadRight(10, ' ') + ); + Logger.Write(message, LogLevel.Flee); + } + } + + private static void HandleEvent(NoPokeballEvent noPokeballEvent, ISession session) + { + Logger.Write( + session.Translation.GetTranslation(TranslationString.EventNoPokeballs, + noPokeballEvent.Id, noPokeballEvent.Cp), + LogLevel.Caught); + } + + private static void HandleEvent(UseBerryEvent useBerryEvent, ISession session) + { + string strBerry; + switch (useBerryEvent.BerryType) + { + case ItemId.ItemRazzBerry: + strBerry = session.Translation.GetTranslation(TranslationString.ItemRazzBerry); + break; + case ItemId.ItemNanabBerry: + strBerry = session.Translation.GetTranslation(TranslationString.ItemNanabBerry); + break; + case ItemId.ItemPinapBerry: + strBerry = session.Translation.GetTranslation(TranslationString.ItemPinapBerry); + break; + case ItemId.ItemWeparBerry: + strBerry = session.Translation.GetTranslation(TranslationString.ItemWeparBerry); + break; + case ItemId.ItemBlukBerry: + strBerry = session.Translation.GetTranslation(TranslationString.ItemBlukBerry); + break; + default: + strBerry = useBerryEvent.BerryType.ToString(); + break; + } + + Logger.Write( + session.Translation.GetTranslation(TranslationString.EventUseBerry, strBerry, useBerryEvent.Count), + LogLevel.Berry + ); + } + + private static void HandleEvent(SnipeEvent snipeEvent, ISession session) + { + Logger.Write(snipeEvent.ToString(), LogLevel.Sniper); + } + + private static void HandleEvent(SnipeScanEvent snipeScanEvent, ISession session) + { + Logger.Write(snipeScanEvent.PokemonId == PokemonId.Missingno + ? ((snipeScanEvent.Source != null) ? "(" + snipeScanEvent.Source + ") " : null) + + session.Translation.GetTranslation(TranslationString.SnipeScan, + $"{snipeScanEvent.Bounds.Latitude},{snipeScanEvent.Bounds.Longitude}") + : ((snipeScanEvent.Source != null) ? "(" + snipeScanEvent.Source + ") " : null) + + session.Translation.GetTranslation(TranslationString.SnipeScanEx, + session.Translation.GetPokemonTranslation(snipeScanEvent.PokemonId), + snipeScanEvent.Iv > 0 + ? snipeScanEvent.Iv.ToString(CultureInfo.InvariantCulture) + : session.Translation.GetTranslation(TranslationString.CommonWordUnknown), + $"{snipeScanEvent.Bounds.Latitude},{snipeScanEvent.Bounds.Longitude}"), LogLevel.Sniper); + } + + private static void HandleEvent(DisplayHighestsPokemonEvent displayHighestsPokemonEvent, ISession session) + { + if (session.LogicSettings.AmountOfPokemonToDisplayOnStart <= 0) + { + return; + } + + string strHeader; + //PokemonData | CP | IV | Level | MOVE1 | MOVE2 | Candy + switch (displayHighestsPokemonEvent.SortedBy) + { + case "Level": + strHeader = session.Translation.GetTranslation(TranslationString.DisplayHighestsLevelHeader); + break; + case "IV": + strHeader = session.Translation.GetTranslation(TranslationString.DisplayHighestsPerfectHeader); + break; + case "CP": + strHeader = session.Translation.GetTranslation(TranslationString.DisplayHighestsCpHeader); + break; + case "MOVE1": + strHeader = session.Translation.GetTranslation(TranslationString.DisplayHighestMove1Header); + break; + case "MOVE2": + strHeader = session.Translation.GetTranslation(TranslationString.DisplayHighestMove2Header); + break; + case "Candy": + strHeader = session.Translation.GetTranslation(TranslationString.DisplayHighestCandy); + break; + default: + strHeader = session.Translation.GetTranslation(TranslationString.DisplayHighestsHeader); + break; + } + var strPerfect = session.Translation.GetTranslation(TranslationString.CommonWordPerfect); + var strName = session.Translation.GetTranslation(TranslationString.CommonWordName).ToUpper(); + var move1 = session.Translation.GetTranslation(TranslationString.DisplayHighestMove1Header); + var move2 = session.Translation.GetTranslation(TranslationString.DisplayHighestMove2Header); + var candy = session.Translation.GetTranslation(TranslationString.DisplayHighestCandy); + + Logger.Write( + session.Translation.GetTranslation(TranslationString.HighestsPokemoHeader, strHeader), + LogLevel.Info, + ConsoleColor.Yellow + ); + foreach (var pokemon in displayHighestsPokemonEvent.PokemonList) + { + string strMove1 = session.Translation.GetPokemonMovesetTranslation(pokemon.Item5); + string strMove2 = session.Translation.GetPokemonMovesetTranslation(pokemon.Item6); + + Logger.Write( + session.Translation.GetTranslation( + TranslationString.HighestsPokemoCell, + pokemon.Item1.Cp.ToString().PadLeft(4, ' '), + pokemon.Item2.ToString().PadLeft(4, ' '), + pokemon.Item3.ToString("0.00").PadRight(6, ' '), + strPerfect, + pokemon.Item4.ToString("00").PadRight(2, ' '), + strName, + session.Translation.GetPokemonTranslation(pokemon.Item1.PokemonId).PadRight(12, ' '), + move1, + strMove1.PadRight(16, ' '), + move2, + strMove2.PadRight(16, ' '), + candy, + pokemon.Item7.ToString("00").PadRight(2, ' ') + ), + LogLevel.Info, + ConsoleColor.Yellow + ); + } + } + + private static void HandleEvent(EvolveCountEvent evolveCountEvent, ISession session) + { + if (evolveCountEvent.Evolves>0) + Logger.Write( + session.Translation.GetTranslation(TranslationString.PkmPotentialEvolveCount, evolveCountEvent.Evolves), + LogLevel.Info, ConsoleColor.Yellow + ); + } + + private static void HandleEvent(UpdateEvent updateEvent, ISession session) + { + Logger.Write(updateEvent.ToString(), LogLevel.Update); + } + + private static void HandleEvent(SnipeModeEvent event1, ISession session) + { + } + + private static void HandleEvent(PokeStopListEvent event1, ISession session) + { + } + + private static void HandleEvent(EggsListEvent event1, ISession session) + { + } + + private static void HandleEvent(InventoryListEvent event1, ISession session) + { + } + + private static void HandleEvent(PokemonListEvent event1, ISession session) + { + } + + private static void HandleEvent(LoginEvent e, ISession session) + { + Logger.Write( + session.Translation.GetTranslation(TranslationString.LoggingIn, e.AuthType, e.Username), + LogLevel.Info, ConsoleColor.DarkYellow + ); + } + + private static void HandleEvent(UpdatePositionEvent event1, ISession session) + { + //uncomment for more info about locations + //Logger.Write(event1.Latitude.ToString("0.0000000000") + "," + event1.Longitude.ToString("0.0000000000"), LogLevel.Debug, force: true); + } + + private static void HandleEvent(HumanWalkingEvent humanWalkingEvent, ISession session) + { + if (session.LogicSettings.ShowVariantWalking) + Logger.Write( + session.Translation.GetTranslation( + TranslationString.HumanWalkingVariant, + humanWalkingEvent.OldWalkingSpeed, + humanWalkingEvent.CurrentWalkingSpeed + ), + LogLevel.Info, + ConsoleColor.DarkCyan + ); + + HumanWalkEvent?.Invoke(humanWalkingEvent); + } + + private static void HandleEvent(KillSwitchEvent killSwitchEvent, ISession session) + { + if (killSwitchEvent.RequireStop) + { + Logger.Write(killSwitchEvent.Message, LogLevel.Warning); + Logger.Write(session.Translation.GetTranslation(TranslationString.RequireInputText), LogLevel.Warning); + } + else + Logger.Write(killSwitchEvent.Message, LogLevel.Info, ConsoleColor.White); + } + + private static void HandleEvent(HumanWalkSnipeEvent ev, ISession session) + { + switch (ev.Type) + { + case HumanWalkSnipeEventTypes.StartWalking: + var strPokemon = session.Translation.GetPokemonTranslation(ev.PokemonId); + Logger.Write(session.Translation.GetTranslation(TranslationString.HumanWalkSnipe, + strPokemon, + ev.Latitude, + ev.Longitude, + ev.Distance, + ev.Expires / 60, + ev.Expires % 60, + ev.Estimate / 60, + ev.Estimate % 60, + ev.SpinPokeStop ? "Yes" : "No", + ev.CatchPokemon ? "Yes" : "No", + ev.WalkSpeedApplied), + LogLevel.Sniper, + ConsoleColor.Yellow); + break; + case HumanWalkSnipeEventTypes.DestinationReached: + Logger.Write( + session.Translation.GetTranslation( + TranslationString.HumanWalkSnipeDestinationReached, + ev.Latitude, ev.Longitude, ev.PauseDuration + ), + LogLevel.Sniper + ); + break; + case HumanWalkSnipeEventTypes.PokemonScanned: + if (ev.Pokemons != null && ev.Pokemons.Count > 0 && ev.DisplayMessage) + Logger.Write( + session.Translation.GetTranslation(TranslationString.HumanWalkSnipeUpdate, + ev.Pokemons.Count, 2, 3), + LogLevel.Sniper, + ConsoleColor.DarkMagenta + ); + break; + case HumanWalkSnipeEventTypes.PokestopUpdated: + Logger.Write( + session.Translation.GetTranslation( + TranslationString.HumanWalkSnipeAddedPokestop, + ev.NearestDistance, + ev.Pokestops.Count + ), + LogLevel.Sniper, + ConsoleColor.Yellow + ); + break; + case HumanWalkSnipeEventTypes.NotEnoughtPalls: + Logger.Write( + session.Translation.GetTranslation( + TranslationString.HumanWalkSnipeNotEnoughtBalls, + ev.CurrentBalls, + ev.MinBallsToSnipe + ), + LogLevel.Sniper, + ConsoleColor.Yellow + ); + break; + case HumanWalkSnipeEventTypes.EncounterSnipePokemon: + Logger.Write(session.Translation.GetTranslation(TranslationString.HumanWalkSnipePokemonEncountered, + session.Translation.GetPokemonTranslation(ev.PokemonId), + ev.Latitude, + ev.Longitude)); + break; + case HumanWalkSnipeEventTypes.AddedSnipePokemon: + break; + case HumanWalkSnipeEventTypes.TargetedPokemon: + break; + case HumanWalkSnipeEventTypes.ClientRequestUpdate: + break; + case HumanWalkSnipeEventTypes.QueueUpdated: + break; + default: + break; + } + } + + private static void HandleEvent(GymDetailInfoEvent ev, ISession session) + { + var GymDeployed = new GymDeployResponse(); + var Deployed = GymDeployed.GymStatusAndDefenders.GymDefender.ToList(); + Logger.Write($"Visited Gym: {ev.Name} | Team: {ev.Team} | Gym Players: {ev.Players} | Free Spots: {6 - Deployed.Count}", LogLevel.Gym, + (ev.Team == TeamColor.Red) + ? ConsoleColor.Red + : (ev.Team == TeamColor.Yellow ? ConsoleColor.Yellow : ConsoleColor.Blue)); + } + + //TODO - move to string translation later. + private static void HandleEvent(GymDeployEvent ev, ISession session) + { + Logger.Write($"Great!!! Your {ev.PokemonId.ToString()} is now defending {ev.GymGetInfo.Name} GYM. | Free Spots: {6 - ev.GymGetInfo.GymStatusAndDefenders.GymDefender.Count()}", + LogLevel.Gym, ConsoleColor.Green); + + if (session.LogicSettings.NotificationConfig.EnablePushBulletNotification) + PushNotificationClient.SendNotification(session, $"Gym Post", $"Great!!! Your {ev.PokemonId.ToString()} is now defending {ev.GymGetInfo.Name} GYM.\nFree Spots: {6 - ev.GymGetInfo.GymStatusAndDefenders.GymDefender.Count()}", true).ConfigureAwait(false); + } + + private static void HandleEvent(GymBattleStarted ev, ISession session) + { + Logger.Write($"Battle started at gym: {ev.GymName}...", LogLevel.Gym, ConsoleColor.Blue); + } + + private static void HandleEvent(GymEventMessages ev, ISession session) + { + string sky = null; + if (ev.consoleColor == ConsoleColor.Red) sky = "Skipping..."; + Logger.Write($"{ev.Message} {sky}", + LogLevel.Gym, ev.consoleColor); + } + + private static void HandleEvent(GymListEvent ev, ISession session) + { + Logger.Write($"{ev.Gyms.Count} gyms has been added to farming area.", LogLevel.Gym, ConsoleColor.Cyan); + } + + private static void HandleEvent(GymWalkToTargetEvent ev, ISession session) + { + //Logger.Write( + // $"Traveling to gym: {ev.Name} | Lat: {ev.Latitude}, Lng: {ev.Longitude} | ({ev.Distance:0.00}m)", + // LogLevel.Gym, ConsoleColor.Cyan + //); + } + + private static void HandleEvent(GymTeamJoinEvent ev, ISession session) + { + switch (ev.Status) + { + case SetPlayerTeamResponse.Types.Status.Unset: + break; + case SetPlayerTeamResponse.Types.Status.Success: + Logger.Write($"(TEAM) Joined the {ev.Team} Team!", LogLevel.Gym, + (ev.Team == TeamColor.Red) + ? ConsoleColor.Red + : (ev.Team == TeamColor.Yellow ? ConsoleColor.Yellow : ConsoleColor.Blue)); + break; + case SetPlayerTeamResponse.Types.Status.TeamAlreadySet: + Logger.Write($"You have joined this team already! ", LogLevel.Gym, color: ConsoleColor.Red); + break; + case SetPlayerTeamResponse.Types.Status.Failure: + Logger.Write($"Unable to join team : {ev.Team.ToString()}", color: ConsoleColor.Red); + break; + default: + break; + } + } + + private static void HandleEvent(EventUsedPotion ev, ISession session) + { + Logger.Write( + $"Used {ev.Type,-8} Potion on {ev.PokemonId,-12} with CP: {ev.PokemonCp,4:###0}. Remaining: {ev.Remaining,3:##0}" + ); + } + + private static void HandleEvent(EventUsedRevive ev, ISession session) + { + Logger.Write( + $"Used {ev.Type,-8} Revive on {ev.PokemonId,-12} with CP: {ev.PokemonCp,4:###0}. Remaining: {ev.Remaining,3:##0}" + ); + } + + public static void HandleEvent(IEvent evt, ISession session) + { + } + + public void Listen(IEvent evt, ISession session) + { + dynamic eve = evt; + + try + { + HandleEvent(eve, session); + } + catch + { + } + } + } +} diff --git a/PoGo.NecroBot.Logic/Service/Elevation/BaseElevationService.cs b/PoGo.NecroBot.Logic/Service/Elevation/BaseElevationService.cs new file mode 100644 index 000000000..3fad1b9ad --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/Elevation/BaseElevationService.cs @@ -0,0 +1,34 @@ +using System; +using PoGo.NecroBot.Logic.Model.Settings; +using System.Threading.Tasks; + +namespace PoGo.NecroBot.Logic.Service.Elevation +{ + public abstract class BaseElevationService : IElevationService + { + protected GlobalSettings _settings; + protected string _apiKey; + + public abstract string GetServiceId(); + public abstract Task GetElevationFromWebService(double lat, double lng); + + public BaseElevationService(GlobalSettings settings) + { + _settings = settings; + } + + public async Task GetElevation(double lat, double lng) + { + return await GetElevationFromWebService(lat, lng).ConfigureAwait(false); + } + + public static double GetRandomElevation(double elevation) + { + // Adds a random elevation to the retrieved one. This was + // previously set to 5 meters but since it's happening with + // just a few seconds in between it is deemed unrealistic. + // Telling from real world examples ~1.2 meter fits better. + return elevation + (new Random().NextDouble() * 1.2); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/Elevation/ElevationService.cs b/PoGo.NecroBot.Logic/Service/Elevation/ElevationService.cs new file mode 100644 index 000000000..3964395f1 --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/Elevation/ElevationService.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using PoGo.NecroBot.Logic.Logging; +using PoGo.NecroBot.Logic.Model.Settings; +using PoGo.NecroBot.Logic.Model; +using System.Threading.Tasks; + +namespace PoGo.NecroBot.Logic.Service.Elevation +{ + public class ElevationService : IElevationService + { + private ElevationConfigContext _context = new ElevationConfigContext(); + private GlobalSettings _settings; + + private List ElevationServiceQueue = new List(); + public Dictionary ElevationServiceBlacklist = new Dictionary(); + + public ElevationService(GlobalSettings settings) + { + _settings = settings; + + if (_settings.MapzenWalkConfig.UseMapzenWalk) + { + if (!string.IsNullOrEmpty(settings.MapzenWalkConfig.MapzenElevationApiKey)) + ElevationServiceQueue.Add(new MapzenElevationService(settings)); + } + + if (_settings.GoogleWalkConfig.UseGoogleWalk) + { + if (!string.IsNullOrEmpty(settings.GoogleWalkConfig.GoogleElevationAPIKey)) + ElevationServiceQueue.Add(new GoogleElevationService(settings)); + } + + ElevationServiceQueue.Add(new RandomElevationService(settings)); + } + + public bool IsElevationServiceBlacklisted(Type strategy) + { + if (!ElevationServiceBlacklist.ContainsKey(strategy)) + return false; + + DateTime now = DateTime.Now; + DateTime blacklistExpiresAt = ElevationServiceBlacklist[strategy]; + if (blacklistExpiresAt < now) + { + // Blacklist expired + ElevationServiceBlacklist.Remove(strategy); + return false; + } + else + { + return true; + } + } + + public void BlacklistStrategy(Type strategy) + { + // Black list for 1 hour. + ElevationServiceBlacklist[strategy] = DateTime.Now.AddHours(1); + } + + public IElevationService GetService() + { + return ElevationServiceQueue.First(q => !IsElevationServiceBlacklisted(q.GetType())); + } + + public async Task GetElevation(double lat, double lng) + { + IElevationService service = GetService(); + + if (service is RandomElevationService) + { + // Don't hit the database for random elevation service. + return await service.GetElevation(lat, lng).ConfigureAwait(false); + } + + ElevationLocation elevationLocation = await ElevationLocation.FindOrUpdateInDatabase(_context, lat, lng, service).ConfigureAwait(false); + if (elevationLocation == null) + { + Logger.Write( + $"{service.GetServiceId()} response not reliable and will be blacklisted for one hour.", + LogLevel.Warning + ); + BlacklistStrategy(service.GetType()); + + Logger.Write( + $"Falling back to next elevation strategy: {GetService().GetServiceId()}.", + LogLevel.Warning + ); + + // After blacklisting, retry. + return await service.GetElevation(lat, lng).ConfigureAwait(false); + } + + return BaseElevationService.GetRandomElevation(elevationLocation.Altitude); + } + + public string GetServiceId() + { + IElevationService service = GetService(); + return service.GetServiceId(); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/Elevation/GoogleElevationService.cs b/PoGo.NecroBot.Logic/Service/Elevation/GoogleElevationService.cs new file mode 100644 index 000000000..e3a97540d --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/Elevation/GoogleElevationService.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Net; +using Newtonsoft.Json; +using PoGo.NecroBot.Logic.Exceptions; +using PoGo.NecroBot.Logic.Model.Settings; +using System.Threading.Tasks; +using System.Net.Http; + +namespace PoGo.NecroBot.Logic.Service.Elevation +{ + public class GoogleResponse + { + public string Status { get; set; } + public List Results { get; set; } + } + + public class GoogleElevationResults + { + public double Elevation { get; set; } + public double Resolution { get; set; } + public GoogleLocation Location { get; set; } + } + + public class GoogleLocation + { + public double Lat { get; set; } + public double Lng { get; set; } + } + + public class GoogleElevationService : BaseElevationService + { + public GoogleElevationService(GlobalSettings settings) : base(settings) + { + if (!string.IsNullOrEmpty(settings.GoogleWalkConfig.GoogleElevationAPIKey)) + _apiKey = settings.GoogleWalkConfig.GoogleElevationAPIKey; + } + + public override string GetServiceId() + { + return "Google Elevation Service"; + } + + public override async Task GetElevationFromWebService(double lat, double lng) + { + if (string.IsNullOrEmpty(_apiKey)) + return 0; + + using (HttpClient client = new HttpClient()) + { + try + { + string url = $"https://maps.googleapis.com/maps/api/elevation/json?key={_apiKey}&locations={lat},{lng}"; + + var responseContent = await client.GetAsync(url).ConfigureAwait(false); + if (responseContent.StatusCode != HttpStatusCode.OK) + return 0; + + var responseFromServer = await responseContent.Content.ReadAsStringAsync().ConfigureAwait(false); + GoogleResponse googleResponse = JsonConvert.DeserializeObject(responseFromServer); + + if (googleResponse.Status == "OK" && googleResponse.Results != null && + 0 < googleResponse.Results.Count && googleResponse.Results[0].Elevation > -100) + return googleResponse.Results[0].Elevation; + } + catch (ActiveSwitchByRuleException ex) + { + throw ex; + } + catch (Exception) + { + // If we get here for any reason, then just drop down and return 0. Will cause this elevation service to be blacklisted. + } + } + + return 0; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/Elevation/IElevationService.cs b/PoGo.NecroBot.Logic/Service/Elevation/IElevationService.cs new file mode 100644 index 000000000..5472bf136 --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/Elevation/IElevationService.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; + +namespace PoGo.NecroBot.Logic.Service.Elevation +{ + public interface IElevationService + { + string GetServiceId(); + Task GetElevation(double lat, double lng); + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/Elevation/MapzenElevationService.cs b/PoGo.NecroBot.Logic/Service/Elevation/MapzenElevationService.cs new file mode 100644 index 000000000..a49d5f4a1 --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/Elevation/MapzenElevationService.cs @@ -0,0 +1,58 @@ +using System; +using System.Net; +using Newtonsoft.Json.Linq; +using PoGo.NecroBot.Logic.Model.Settings; +using System.Threading.Tasks; +using System.Net.Http; +using PoGo.NecroBot.Logic.Exceptions; + +namespace PoGo.NecroBot.Logic.Service.Elevation +{ + public class MapzenElevationService : BaseElevationService + { + public MapzenElevationService(GlobalSettings settings) : base(settings) + { + if (!string.IsNullOrEmpty(settings.MapzenWalkConfig.MapzenElevationApiKey)) + _apiKey = settings.MapzenWalkConfig.MapzenElevationApiKey; + } + + public override string GetServiceId() + { + return "Mapzen Elevation Service"; + } + + public override async Task GetElevationFromWebService(double lat, double lng) + { + if (string.IsNullOrEmpty(_apiKey)) + return 0; + + using (HttpClient client = new HttpClient()) + { + try + { + string url = $"https://elevation.mapzen.com/height?json=" + "{\"shape\":[{\"lat\":" + lat + ",\"lon\":" + lng + "}]}" + $"&api_key={_apiKey}"; + + var responseContent = await client.GetAsync(url).ConfigureAwait(false); + if (responseContent.StatusCode != HttpStatusCode.OK) + return 0; + + var responseFromServer = await responseContent.Content.ReadAsStringAsync().ConfigureAwait(false); + JObject jsonObj = JObject.Parse(responseFromServer); + + JArray heights = (JArray)jsonObj["height"]; + return (double)heights[0]; + } + catch (ActiveSwitchByRuleException ex) + { + throw ex; + } + catch (Exception) + { + // If we get here for any reason, then just drop down and return 0. Will cause this elevation service to be blacklisted. + } + } + + return 0; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/Elevation/RandomElevationService.cs b/PoGo.NecroBot.Logic/Service/Elevation/RandomElevationService.cs new file mode 100644 index 000000000..318f9eda6 --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/Elevation/RandomElevationService.cs @@ -0,0 +1,30 @@ +using System; +using PoGo.NecroBot.Logic.Model.Settings; +using System.Threading.Tasks; + +namespace PoGo.NecroBot.Logic.Service.Elevation +{ + public class RandomElevationService : BaseElevationService + { + private double minElevation = 5; + private double maxElevation = 50; + private Random rand = new Random(); + + public RandomElevationService(GlobalSettings settings) : base(settings) + { + } + + public override string GetServiceId() + { + return "Random Elevation Service (NecroBot Default)"; + } + +// jjskuld - Ignore CS1998 warning for now. +#pragma warning disable 1998 + public override async Task GetElevationFromWebService(double lat, double lng) + { + return rand.NextDouble() * (maxElevation - minElevation) + minElevation; + } +#pragma warning restore 1998 + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/GoogleDirectionsService.cs b/PoGo.NecroBot.Logic/Service/GoogleDirectionsService.cs new file mode 100644 index 000000000..7cbeafc89 --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/GoogleDirectionsService.cs @@ -0,0 +1,151 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using Newtonsoft.Json; +using PoGo.NecroBot.Logic.Exceptions; +using PoGo.NecroBot.Logic.Model.Google; +using PoGo.NecroBot.Logic.Model.Google.GoogleObjects; +using PoGo.NecroBot.Logic.State; +using GeoCoordinatePortable; +using System.Threading.Tasks; + +namespace PoGo.NecroBot.Logic.Service +{ + public class GoogleDirectionsService + { + private readonly ISession _session; + private readonly bool _cache; + public List OldResults { get; set; } + + public GoogleDirectionsService(ISession session) + { + _session = session; + _cache = _session.LogicSettings.UseGoogleWalkCache; + OldResults = new List(); + } + + public async Task GetDirections(GeoCoordinate origin, List waypoints, GeoCoordinate destino) + { + GoogleResult googleResult = null; + try + { + if (_cache) + { + var item = OldResults + .FirstOrDefault(pesquisa => IsSameAdress(origin, waypoints, destino, pesquisa)); + if (item != null) + { + item.FromCache = true; + googleResult = item; + } + } + + if (googleResult == null) + { + var url = GetUrl(origin, waypoints, destino); + + using (var client = new HttpClient()) + { + client.BaseAddress = new Uri("https://maps.googleapis.com/maps/api/"); + client.DefaultRequestHeaders.Accept.Clear(); + client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + + HttpResponseMessage responseMessage = await client.GetAsync(url).ConfigureAwait(false); + var resposta = await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false); + var google = JsonConvert.DeserializeObject(resposta); + if (google.status.Equals("OVER_QUERY_LIMIT")) + { + // If we get an error, don't cache empty GoogleResult. Just return null. + return null; + } + + var resultadoPesquisa = new GoogleResult + { + Directions = google, + RequestDate = DateTime.Now, + Origin = origin, + Waypoints = waypoints, + Destiny = destino, + FromCache = false, + }; + + if (_cache) + SaveResult(resultadoPesquisa); + + googleResult = resultadoPesquisa; + } + } + + if (googleResult != null) + { + return GoogleWalk.Get(googleResult); + } + } + catch (ActiveSwitchByRuleException ex) + { + throw ex; + } + catch (Exception) + { + } + return null; + } + + private static bool IsSameAdress(GeoCoordinate origem, List waypoints, + GeoCoordinate destino, GoogleResult googleSearch) + { + var sameAdress = origem.GetDistanceTo(googleSearch.Origin) < 10 && + destino.GetDistanceTo(googleSearch.Destiny) < 10; + + var sameQuantityWaypoint = waypoints.Count == googleSearch.Waypoints.Count; + + if (!sameQuantityWaypoint) return sameAdress; + if (!sameAdress) return sameAdress; + + for (int i = 0; i < waypoints.Count; i++) + { + sameAdress = googleSearch.Waypoints[i].Equals(waypoints[i]); + + if (!sameAdress) + break; + } + return sameAdress; + } + + private string GetUrl(GeoCoordinate origem, List pontosDeParada, GeoCoordinate destino) + { + var url = $"directions/json?origin={origem.Latitude.ToString("R").Replace(",", ".")},{origem.Longitude.ToString("R").Replace(",", ".")}&destination={destino.Latitude.ToString("R").Replace(",", ".")},{destino.Longitude.ToString("R").Replace(",", ".")}&sensor=false"; + var waypoint = "&waypoints=optimize:false|"; + var possuiWayPoint = pontosDeParada.Any(); + for (var i = 0; i < pontosDeParada.Count; i++) + { + waypoint += $"{pontosDeParada[i].Latitude.ToString("R").Replace(",", ".")},{pontosDeParada[i].Longitude.ToString("R").Replace(",", ".")}|"; + } + if (possuiWayPoint) + url += waypoint.Substring(0, waypoint.Length - 1); + + if (!string.IsNullOrEmpty(_session.LogicSettings.GoogleApiKey)) + url += $"&key={_session.LogicSettings.GoogleApiKey}"; + + if (!string.IsNullOrEmpty(_session.LogicSettings.GoogleHeuristic)) + url += $"&mode={_session.LogicSettings.GoogleHeuristic}"; + + return url; + } + + private void SaveResult(GoogleResult googleResult) + { + var item = OldResults + .FirstOrDefault(pesquisa => IsSameAdress(googleResult.Origin, googleResult.Waypoints, googleResult.Destiny, pesquisa)); + if (item != null) + { + lock (OldResults) + OldResults.Remove(item); + } + + OldResults.Add(googleResult); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/MapzenDirectionsService.cs b/PoGo.NecroBot.Logic/Service/MapzenDirectionsService.cs new file mode 100644 index 000000000..b527e3efc --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/MapzenDirectionsService.cs @@ -0,0 +1,53 @@ +using System; +using System.IO; +using System.Net; +using PoGo.NecroBot.Logic.Model.Mapzen; +using PoGo.NecroBot.Logic.State; +using GeoCoordinatePortable; + +namespace PoGo.NecroBot.Logic.Service +{ + class MapzenDirectionsService + { + private readonly ISession _session; + + public MapzenDirectionsService(ISession session) + { + _session = session; + } + + public MapzenWalk GetDirections(GeoCoordinate sourceLocation, GeoCoordinate destLocation) + { + WebRequest request = WebRequest.Create(GetUrl(sourceLocation, destLocation)); + request.Credentials = CredentialCache.DefaultCredentials; + + try + { + using (WebResponse response = request.GetResponse()) + { + using (Stream dataStream = response.GetResponseStream()) + using (StreamReader reader = new StreamReader(dataStream)) + { + string responseFromServer = reader.ReadToEnd(); + return MapzenWalk.Get(responseFromServer, sourceLocation, destLocation); + } + } + } + catch (Exception) + { + } + + return null; + } + + private string GetUrl(GeoCoordinate sourceLocation, GeoCoordinate destLocation) + { + string url = "http://valhalla.mapzen.com/route?json={\"locations\":" + "[{\"lat\":" + sourceLocation.Latitude + ",\"lon\":" + sourceLocation.Longitude + "},{\"lat\":" + destLocation.Latitude + ",\"lon\":" + destLocation.Longitude + "}]," + $"\"costing\":\"{_session.LogicSettings.MapzenWalkHeuristic}\",\"directions_options\":" + "{\"narrative\":\"false\"}}"; + + if (!string.IsNullOrEmpty(_session.LogicSettings.MapzenTurnByTurnApiKey)) + url += $"&api_key={_session.LogicSettings.MapzenTurnByTurnApiKey}"; + + return url; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/PushNotificationListener.cs b/PoGo.NecroBot.Logic/Service/PushNotificationListener.cs new file mode 100644 index 000000000..9fd622040 --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/PushNotificationListener.cs @@ -0,0 +1,44 @@ +#region using directives + +using System; +using System.Diagnostics.CodeAnalysis; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Utils; +using System.Threading.Tasks; + +#endregion + +namespace PoGo.NecroBot.Logic.Service +{ + [SuppressMessage("ReSharper", "UnusedParameter.Local")] + internal class PushNotificationListener + { + private static async Task HandleEventAsync(ErrorEvent errorEvent, ISession session) + { + await PushNotificationClient.SendNotification(session, "Error occured", errorEvent.Message,true).ConfigureAwait(false); + } + + public static void HandleEvent(EncounteredEvent ev, ISession session) + { + } + + public static void HandleEvent(IEvent evt, ISession session) + { + } + + internal void Listen(IEvent evt, ISession session) + { + dynamic eve = evt; + + try + { + HandleEvent(eve, session); + } + catch (Exception) + { + // ignored + } + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/SniperEventListener.cs b/PoGo.NecroBot.Logic/Service/SniperEventListener.cs new file mode 100644 index 000000000..eff21190d --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/SniperEventListener.cs @@ -0,0 +1,56 @@ +#region using directives + +using System; +using System.Diagnostics.CodeAnalysis; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Tasks; +using System.Threading.Tasks; + +#endregion + +namespace PoGo.NecroBot.Logic.Service +{ + [SuppressMessage("ReSharper", "UnusedParameter.Local")] + public class SniperEventListener + { + private static void HandleEvent(PokemonCaptureEvent pokemonCaptureEvent, ISession session) + { + //remove pokemon from list + HumanWalkSnipeTask.UpdateCatchPokemon(pokemonCaptureEvent.Latitude, + pokemonCaptureEvent.Longitude, pokemonCaptureEvent.Id); + } + + public static async Task HandleEventAsync(EncounteredEvent ev, ISession session) + { + if (!ev.IsRecievedFromSocket) return; + + await HumanWalkSnipeTask.AddSnipePokemon("mypogosnipers.com", + ev.PokemonId, + ev.Latitude, + ev.Longitude, + ev.Expires, + ev.IV, + session + ).ConfigureAwait(false); + } + + public static void HandleEvent(IEvent evt, ISession session) + { + } + + public void Listen(IEvent evt, ISession session) + { + dynamic eve = evt; + + try + { + HandleEvent(eve, session); + } + catch (Exception) + { + // ignored + } + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/TelegramCommand/AccountsCommand.cs b/PoGo.NecroBot.Logic/Service/TelegramCommand/AccountsCommand.cs new file mode 100644 index 000000000..1a2cbfebc --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/TelegramCommand/AccountsCommand.cs @@ -0,0 +1,54 @@ +using System; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.State; +using TinyIoC; +using PoGo.NecroBot.Logic.Model; + +namespace PoGo.NecroBot.Logic.Service.TelegramCommand +{ + // TODO I18N + public class AccountsCommand : CommandMessage + { + public override string Command => "/accounts"; + public override bool StopProcess => true; + public override TranslationString DescriptionI18NKey => TranslationString.TelegramCommandAccountsDescription; + public override TranslationString MsgHeadI18NKey => TranslationString.TelegramCommandAccountsMsgHead; + + public AccountsCommand(TelegramUtils telegramUtils) : base(telegramUtils) + { + } + + #pragma warning disable 1998 // added to get rid of compiler warning. Remove this if async code is used below. + public override async Task OnCommand(ISession session, string cmd, Action callback) + #pragma warning restore 1998 + { + string[] messagetext = cmd.Split(' '); + + string message = GetMsgHead(session, session.Profile.PlayerData.Username) + "\r\n\r\n"; + if (messagetext[0].ToLower() != Command) + { + return false; + } + + var manager = TinyIoCContainer.Current.Resolve(); + if (manager.AllowMultipleBot()) + { + using (var db = new AccountConfigContext()) + { + foreach (var item in db.Account) + { + message = message + + $"{item.Username}({item.AuthType}) {item.Level} {item.GetRuntime()}\r\n"; + } + } + } + else + { + message = message + "Multiple bots are disabled. please use /profile for current account details"; + } + callback(message); + return true; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/TelegramCommand/AllCommand.cs b/PoGo.NecroBot.Logic/Service/TelegramCommand/AllCommand.cs new file mode 100644 index 000000000..0f93d74f1 --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/TelegramCommand/AllCommand.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.PoGoUtils; +using PoGo.NecroBot.Logic.State; +using POGOProtos.Data; +using static System.Text.RegularExpressions.Regex; + +namespace PoGo.NecroBot.Logic.Service.TelegramCommand +{ + public class AllCommand : CommandMessage + { + private const string DefaultOrderBy = "cp"; + + private string CommandParseRegex => "^(\\" + Command + ")(?>\\s+(?iv|cp))?\\s*"; + + public override string Command => "/all"; + public override string Arguments => "[iv|cp]"; + public override bool StopProcess => true; + public override TranslationString DescriptionI18NKey => TranslationString.TelegramCommandAllDescription; + public override TranslationString MsgHeadI18NKey => TranslationString.TelegramCommandAllMsgHead; + + public AllCommand(TelegramUtils telegramUtils) : base(telegramUtils) + { + } + + public override async Task OnCommand(ISession session, string cmd, Action callback) + { + var commandMatch = Match(cmd, CommandParseRegex); + + if (!commandMatch.Success) + { + return false; + } + + // Parse orderBy + var orderBy = string.IsNullOrEmpty(commandMatch.Groups["orderBy"].Value) + ? DefaultOrderBy + : commandMatch.Groups["orderBy"].Value; + + var allPokemonCount = (await session.Inventory.GetPokemons().ConfigureAwait(false)).Count(); + + // Get all Pokemon and 'orderBy' -> will never be null + var topPokemon = string.Equals("iv", orderBy) + ? await session.Inventory.GetHighestsPerfect(allPokemonCount).ConfigureAwait(false) + : await session.Inventory.GetHighestsCp(allPokemonCount).ConfigureAwait(false); + + var topPokemonList = topPokemon as IList ?? topPokemon.ToList(); + + var answerTextmessage = GetMsgHead(session, session.Profile.PlayerData.Username, topPokemonList.Count) + "\r\n\r\n"; + answerTextmessage = topPokemonList.Aggregate(answerTextmessage, (current, pokemon) + => current + session.Translation.GetTranslation( + TranslationString.ShowPokeSkillTemplate, + pokemon.Cp, + PokemonInfo.CalculatePokemonPerfection(pokemon).ToString("0.00"), + session.Translation.GetPokemonMovesetTranslation(PokemonInfo.GetPokemonMove1(pokemon)), + session.Translation.GetPokemonMovesetTranslation(PokemonInfo.GetPokemonMove2(pokemon)), + session.Translation.GetPokemonTranslation(pokemon.PokemonId) + ) + ); + + callback(answerTextmessage); + return true; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/TelegramCommand/CommandLocation.cs b/PoGo.NecroBot.Logic/Service/TelegramCommand/CommandLocation.cs new file mode 100644 index 000000000..75c645593 --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/TelegramCommand/CommandLocation.cs @@ -0,0 +1,54 @@ +using System; +using GeoCoordinatePortable; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.State; +using Telegram.Bot.Types; + +namespace PoGo.NecroBot.Logic.Service.TelegramCommand +{ + public abstract class CommandLocation : ICommandGenerify + { + protected readonly TelegramUtils TelegramUtils; + + protected CommandLocation(TelegramUtils telegramUtils) + { + TelegramUtils = telegramUtils; + } + + public abstract string Command { get; } + public virtual string Arguments => ""; + public abstract bool StopProcess { get; } + public abstract TranslationString DescriptionI18NKey { get; } + public abstract TranslationString MsgHeadI18NKey { get; } + + public abstract Task OnCommand(ISession session, string cmd, Action callback); + + public Task OnCommand(ISession session, string cmd, Message telegramMessage) + { + Action callback = async geo => + { + try + { + await TelegramUtils.SendLocation(geo, telegramMessage.Chat.Id).ConfigureAwait(false); + } + catch (Exception ex) + { + session.EventDispatcher.Send(new ErrorEvent {Message = ex.Message}); + session.EventDispatcher.Send(new ErrorEvent {Message = "Unkown Telegram Error occured. "}); + } + }; + return OnCommand(session, cmd, callback); + } + + public string GetDescription(ISession session, params object[] data) => + session.Translation.GetTranslation(DescriptionI18NKey, data); + + public string GetDescription(ISession session) => + session.Translation.GetTranslation(DescriptionI18NKey); + + public string GetMsgHead(ISession session, params object[] data) => + session.Translation.GetTranslation(MsgHeadI18NKey, data); + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/TelegramCommand/CommandMessage.cs b/PoGo.NecroBot.Logic/Service/TelegramCommand/CommandMessage.cs new file mode 100644 index 000000000..75dc2699c --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/TelegramCommand/CommandMessage.cs @@ -0,0 +1,53 @@ +using System; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.State; +using Telegram.Bot.Types; + +namespace PoGo.NecroBot.Logic.Service.TelegramCommand +{ + public abstract class CommandMessage : ICommandGenerify + { + protected readonly TelegramUtils TelegramUtils; + + protected CommandMessage(TelegramUtils telegramUtils) + { + TelegramUtils = telegramUtils; + } + + public abstract string Command { get; } + public virtual string Arguments => ""; + public abstract bool StopProcess { get; } + public abstract TranslationString DescriptionI18NKey { get; } + public abstract TranslationString MsgHeadI18NKey { get; } + + public abstract Task OnCommand(ISession session, string cmd, Action callback); + + public Task OnCommand(ISession session, string cmd, Message telegramMessage) + { + Action callback = async msg => + { + try + { + await TelegramUtils.SendMessage(msg, telegramMessage.Chat.Id).ConfigureAwait(false); + } + catch (Exception ex) + { + session.EventDispatcher.Send(new ErrorEvent {Message = ex.Message}); + session.EventDispatcher.Send(new ErrorEvent {Message = "Unkown Telegram Error occured. "}); + } + }; + return OnCommand(session, cmd, callback); + } + + public string GetDescription(ISession session, params object[] data) => + session.Translation.GetTranslation(DescriptionI18NKey, data); + + public virtual string GetDescription(ISession session) => + session.Translation.GetTranslation(DescriptionI18NKey); + + public string GetMsgHead(ISession session, params object[] data) => + session.Translation.GetTranslation(MsgHeadI18NKey, data); + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/TelegramCommand/ExitCommand.cs b/PoGo.NecroBot.Logic/Service/TelegramCommand/ExitCommand.cs new file mode 100644 index 000000000..6133d6c54 --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/TelegramCommand/ExitCommand.cs @@ -0,0 +1,30 @@ +using System; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.State; + +namespace PoGo.NecroBot.Logic.Service.TelegramCommand +{ + public class ExitCommand : CommandMessage + { + public override string Command => "/exit"; + public override bool StopProcess => true; + public override TranslationString DescriptionI18NKey => TranslationString.TelegramCommandExitDescription; + public override TranslationString MsgHeadI18NKey => TranslationString.TelegramCommandExitMsgHead; + + public ExitCommand(TelegramUtils telegramUtils) : base(telegramUtils) + { + } + + public override async Task OnCommand(ISession session, string cmd, Action callback) + { + if (cmd.ToLower() == Command) + { + callback(GetMsgHead(session, session.Profile.PlayerData.Username) + "\r\n\r\n"); + await Task.Delay(5000).ConfigureAwait(false); + Environment.Exit(0); + } + return false; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/TelegramCommand/HelpCommand.cs b/PoGo.NecroBot.Logic/Service/TelegramCommand/HelpCommand.cs new file mode 100644 index 000000000..e94af795f --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/TelegramCommand/HelpCommand.cs @@ -0,0 +1,48 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.State; + +namespace PoGo.NecroBot.Logic.Service.TelegramCommand +{ + public class HelpCommand : CommandMessage + { + public override string Command => "/help"; + public override bool StopProcess => true; + public override TranslationString DescriptionI18NKey => TranslationString.TelegramCommandHelpDescription; + public override TranslationString MsgHeadI18NKey => TranslationString.TelegramCommandHelpMsgHead; + + public HelpCommand(TelegramUtils telegramUtils) : base(telegramUtils) + { + } + + #pragma warning disable 1998 // added to get rid of compiler warning. Remove this if async code is used below. + public override async Task OnCommand(ISession session, string cmd, Action callback) + #pragma warning restore 1998 + { + if (cmd.ToLower() == Command) + { + string message = GetMsgHead(session, session.Profile.PlayerData.Username) + "\r\n\r\n"; + var iCommandInstances = AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(x => x.GetTypes()) + .Where(x => (typeof(ICommand).IsAssignableFrom(x)) && !x.IsInterface && !x.IsAbstract) + .Select(x => (ICommand) Activator.CreateInstance(x, TelegramUtils)); + + foreach (var instance in iCommandInstances) + { + var arguments = ""; + if (!string.IsNullOrEmpty(instance.Arguments)) + { + arguments = ' ' + instance.Arguments; + } + message += $"{instance.Command}{arguments} - {instance.GetDescription(session)}\r\n"; + } + + callback(message); + return true; + } + return false; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/TelegramCommand/ICommand.cs b/PoGo.NecroBot.Logic/Service/TelegramCommand/ICommand.cs new file mode 100644 index 000000000..3ed3961eb --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/TelegramCommand/ICommand.cs @@ -0,0 +1,21 @@ +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.State; +using Telegram.Bot.Types; + +namespace PoGo.NecroBot.Logic.Service.TelegramCommand +{ + internal interface ICommand + { + string Command { get; } + string Arguments { get; } + bool StopProcess { get; } + TranslationString DescriptionI18NKey { get; } + TranslationString MsgHeadI18NKey { get; } + + Task OnCommand(ISession session, string cmd, Message telegramMessage); + + string GetDescription(ISession session); + string GetMsgHead(ISession session, object[] args = null); + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/TelegramCommand/ICommandGenerify.cs b/PoGo.NecroBot.Logic/Service/TelegramCommand/ICommandGenerify.cs new file mode 100644 index 000000000..ac614c451 --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/TelegramCommand/ICommandGenerify.cs @@ -0,0 +1,11 @@ +using System; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.State; + +namespace PoGo.NecroBot.Logic.Service.TelegramCommand +{ + internal interface ICommandGenerify : ICommand + { + Task OnCommand(ISession session, string cmd, Action callback); + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/TelegramCommand/ItemsCommand.cs b/PoGo.NecroBot.Logic/Service/TelegramCommand/ItemsCommand.cs new file mode 100644 index 000000000..5b565224c --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/TelegramCommand/ItemsCommand.cs @@ -0,0 +1,61 @@ +using System; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.State; +using POGOProtos.Inventory.Item; + +namespace PoGo.NecroBot.Logic.Service.TelegramCommand +{ + public class ItemsCommand : CommandMessage + { + public override string Command => "/items"; + public override bool StopProcess => true; + public override TranslationString DescriptionI18NKey => TranslationString.TelegramCommandItemsDescription; + public override TranslationString MsgHeadI18NKey => TranslationString.TelegramCommandItemsMsgHead; + + public ItemsCommand(TelegramUtils telegramUtils) : base(telegramUtils) + { + } + + public override async Task OnCommand(ISession session, string cmd, Action callback) + { + if (cmd.ToLower() == Command) + { + string answerTextmessage = GetMsgHead(session, session.Profile.PlayerData.Username) + "\r\n\r\n"; + var inventory = session.Inventory; + answerTextmessage += session.Translation.GetTranslation(TranslationString.CurrentPokeballInv, + inventory.GetItemAmountByType(ItemId.ItemPokeBall), + inventory.GetItemAmountByType(ItemId.ItemGreatBall), + inventory.GetItemAmountByType(ItemId.ItemUltraBall), + inventory.GetItemAmountByType(ItemId.ItemMasterBall)); + answerTextmessage += "\n"; + answerTextmessage += session.Translation.GetTranslation(TranslationString.CurrentPotionInv, + inventory.GetItemAmountByType(ItemId.ItemPotion), + inventory.GetItemAmountByType(ItemId.ItemSuperPotion), + inventory.GetItemAmountByType(ItemId.ItemHyperPotion), + inventory.GetItemAmountByType(ItemId.ItemMaxPotion)); + answerTextmessage += "\n"; + answerTextmessage += session.Translation.GetTranslation(TranslationString.CurrentReviveInv, + inventory.GetItemAmountByType(ItemId.ItemRevive), + inventory.GetItemAmountByType(ItemId.ItemMaxRevive)); //, + //RaidBadge); + answerTextmessage += "\n"; + answerTextmessage += session.Translation.GetTranslation(TranslationString.CurrentMiscItemInv, + await session.Inventory.GetItemAmountByType(ItemId.ItemRazzBerry).ConfigureAwait(false) + + await session.Inventory.GetItemAmountByType(ItemId.ItemBlukBerry).ConfigureAwait(false) + + await session.Inventory.GetItemAmountByType(ItemId.ItemNanabBerry).ConfigureAwait(false) + + await session.Inventory.GetItemAmountByType(ItemId.ItemWeparBerry).ConfigureAwait(false) + + await session.Inventory.GetItemAmountByType(ItemId.ItemPinapBerry).ConfigureAwait(false), + await session.Inventory.GetItemAmountByType(ItemId.ItemIncenseOrdinary).ConfigureAwait(false) + + await session.Inventory.GetItemAmountByType(ItemId.ItemIncenseSpicy).ConfigureAwait(false) + + await session.Inventory.GetItemAmountByType(ItemId.ItemIncenseCool).ConfigureAwait(false) + + await session.Inventory.GetItemAmountByType(ItemId.ItemIncenseFloral).ConfigureAwait(false), + await session.Inventory.GetItemAmountByType(ItemId.ItemLuckyEgg).ConfigureAwait(false), + await session.Inventory.GetItemAmountByType(ItemId.ItemTroyDisk).ConfigureAwait(false)); + callback(answerTextmessage); + return true; + } + return false; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/TelegramCommand/LocCommand.cs b/PoGo.NecroBot.Logic/Service/TelegramCommand/LocCommand.cs new file mode 100644 index 000000000..fa93522c8 --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/TelegramCommand/LocCommand.cs @@ -0,0 +1,33 @@ +using System; +using GeoCoordinatePortable; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.State; + +namespace PoGo.NecroBot.Logic.Service.TelegramCommand +{ + public class LocCommand : CommandLocation + { + public override string Command => "/loc"; + public override bool StopProcess => true; + public override TranslationString DescriptionI18NKey => TranslationString.TelegramCommandLocDescription; + public override TranslationString MsgHeadI18NKey => TranslationString.TelegramCommandLocMsgHead; + + public LocCommand(TelegramUtils telegramUtils) : base(telegramUtils) + { + } + + #pragma warning disable 1998 // added to get rid of compiler warning. Remove this if async code is used below. + public override async Task OnCommand(ISession session, string cmd, Action callback) + #pragma warning restore 1998 + { + if (cmd.ToLower() == Command) + { + // TODO can we send a text together with the location? + callback(new GeoCoordinate(session.Client.CurrentLatitude, session.Client.CurrentLongitude)); + return true; + } + return false; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/TelegramCommand/LogsCommand.cs b/PoGo.NecroBot.Logic/Service/TelegramCommand/LogsCommand.cs new file mode 100644 index 000000000..adbcb88a6 --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/TelegramCommand/LogsCommand.cs @@ -0,0 +1,57 @@ +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.State; + +namespace PoGo.NecroBot.Logic.Service.TelegramCommand +{ + public class LogsCommand : CommandMessage + { + private const int DeafultLogEntries = 10; + + public override string Command => "/logs"; + public override string Arguments => "[n]"; + public override bool StopProcess => true; + public override TranslationString DescriptionI18NKey => TranslationString.TelegramCommandLogsDescription; + public override TranslationString MsgHeadI18NKey => TranslationString.TelegramCommandLogsMsgHead; + + public LogsCommand(TelegramUtils telegramUtils) : base(telegramUtils) + { + } + + public override string GetDescription(ISession session) => + base.GetDescription(session, DeafultLogEntries.ToString()); + + #pragma warning disable 1998 // added to get rid of compiler warning. Remove this if async code is used below. + public override async Task OnCommand(ISession session,string commandText, Action callback) + #pragma warning restore 1998 + { + var cmd = commandText.Split(' '); + + if (cmd[0].ToLower() == Command) + { + // var fi = new FileInfo(Assembly.GetExecutingAssembly().Location); + const string logPath = "logs"; + + var logDirectoryHandle = new DirectoryInfo(logPath); + var last = logDirectoryHandle.GetFiles().OrderByDescending(p => p.LastWriteTime).First(); + var alllines = File.ReadAllLines(last.FullName); + var numberOfLines = DeafultLogEntries; + if (cmd.Length > 1) + { + numberOfLines = Convert.ToInt32(cmd[1]); + } + var last10Lines = (alllines.Skip(Math.Max(0, alllines.Length - numberOfLines))) ; + var enumerable = last10Lines as string[] ?? last10Lines.ToArray(); + + var message = GetMsgHead(session, session.Profile.PlayerData.Username, enumerable.Length) + "\r\n\r\n"; + message = enumerable.Aggregate(message, (current, item) => current + (item + "\r\n")); + callback(message); + return true; + } + return false; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/TelegramCommand/PokedexCommand.cs b/PoGo.NecroBot.Logic/Service/TelegramCommand/PokedexCommand.cs new file mode 100644 index 000000000..51765b6ca --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/TelegramCommand/PokedexCommand.cs @@ -0,0 +1,80 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.State; +using POGOProtos.Enums; + +namespace PoGo.NecroBot.Logic.Service.TelegramCommand +{ + public class PokedexCommand : CommandMessage + { + public override string Command => "/pokedex"; + public override bool StopProcess => true; + public override TranslationString DescriptionI18NKey => TranslationString.TelegramCommandPokedexDescription; + public override TranslationString MsgHeadI18NKey => TranslationString.TelegramCommandPokedexMsgHead; + + public PokedexCommand(TelegramUtils telegramUtils) : base(telegramUtils) + { + } + + #pragma warning disable 1998 // added to get rid of compiler warning. Remove this if async code is used below. + public override async Task OnCommand(ISession session,string cmd, Action callback) + #pragma warning restore 1998 + { + if (cmd.ToLower() == Command) + { + var pokedex = await session.Inventory.GetPokeDexItems().ConfigureAwait(false); + var pokedexSort = pokedex.OrderBy(x => x.InventoryItemData.PokedexEntry.PokemonId); + var answerTextmessage = GetMsgHead(session, session.Profile.PlayerData.Username) + "\r\n\r\n"; + + answerTextmessage += session.Translation.GetTranslation(TranslationString.PokedexCatchedTelegram); + foreach (var pokedexItem in pokedexSort) + { + answerTextmessage += + session.Translation.GetTranslation(TranslationString.PokedexPokemonCatchedTelegram, + Convert.ToInt32(pokedexItem.InventoryItemData.PokedexEntry.PokemonId), + session.Translation.GetPokemonTranslation( + pokedexItem.InventoryItemData.PokedexEntry.PokemonId), + pokedexItem.InventoryItemData.PokedexEntry.TimesCaptured, + pokedexItem.InventoryItemData.PokedexEntry.TimesEncountered); + + if (answerTextmessage.Length > 3800) + { + callback(answerTextmessage); + answerTextmessage = ""; + } + } + + var pokemonsToCapture = + Enum.GetValues(typeof(PokemonId)) + .Cast() + .Except(pokedex.Select(x => x.InventoryItemData.PokedexEntry.PokemonId)); + + callback(answerTextmessage); + answerTextmessage = ""; + + answerTextmessage += session.Translation.GetTranslation(TranslationString.PokedexNeededTelegram); + + foreach (var pokedexItem in pokemonsToCapture) + { + if (Convert.ToInt32(pokedexItem) > 0) + { + answerTextmessage += + session.Translation.GetTranslation(TranslationString.PokedexPokemonNeededTelegram, + Convert.ToInt32(pokedexItem), session.Translation.GetPokemonTranslation(pokedexItem)); + + if (answerTextmessage.Length > 3800) + { + callback(answerTextmessage); + answerTextmessage = ""; + } + } + } + callback(answerTextmessage); + return true; + } + return false; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/TelegramCommand/ProfileCommand.cs b/PoGo.NecroBot.Logic/Service/TelegramCommand/ProfileCommand.cs new file mode 100644 index 000000000..c1b20f2dd --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/TelegramCommand/ProfileCommand.cs @@ -0,0 +1,54 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.State; + +namespace PoGo.NecroBot.Logic.Service.TelegramCommand +{ + // TODO I18N + public class ProfileCommand : CommandMessage + { + public override string Command => "/profile"; + public override bool StopProcess => true; + public override TranslationString DescriptionI18NKey => TranslationString.TelegramCommandProfileDescription; + public override TranslationString MsgHeadI18NKey => TranslationString.TelegramCommandProfileMsgHead; + + public ProfileCommand(TelegramUtils telegramUtils) : base(telegramUtils) + { + } + + public override async Task OnCommand(ISession session, string cmd, Action callback) + { + var playerStats = (await session.Inventory.GetPlayerStats().ConfigureAwait(false)).FirstOrDefault(); + if (cmd.ToLower() != Command || playerStats == null) + { + return false; + } + + var answerTextmessage = GetMsgHead(session, session.Profile.PlayerData.Username) + "\r\n\r\n"; + var pokemonInBag = (await session.Inventory.GetPokemons().ConfigureAwait(false)).ToList().Count; + answerTextmessage += session.Translation.GetTranslation( + TranslationString.TelegramCommandProfileMsgBody, + session.Profile.PlayerData.Username, + playerStats.Level, + session.Profile.PlayerData.Currencies[0].Amount, + playerStats.Experience, + playerStats.NextLevelXp - playerStats.Experience, + playerStats.PokemonsCaptured, + pokemonInBag - playerStats.PokemonsCaptured, + pokemonInBag, + playerStats.Evolutions, + playerStats.PokeStopVisits, + session.Inventory.GetTotalItemCount(), + session.Inventory.GetStarDust(), + playerStats.EggsHatched, + playerStats.UniquePokedexEntries, + playerStats.KmWalked + ); + + callback(answerTextmessage); + return true; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/TelegramCommand/RecycleCommand.cs b/PoGo.NecroBot.Logic/Service/TelegramCommand/RecycleCommand.cs new file mode 100644 index 000000000..509b67a8f --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/TelegramCommand/RecycleCommand.cs @@ -0,0 +1,33 @@ +using System; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Tasks; + +namespace PoGo.NecroBot.Logic.Service.TelegramCommand +{ + public class RecycleCommand : CommandMessage + { + public override string Command => "/recycle"; + public override bool StopProcess => true; + public override TranslationString DescriptionI18NKey => TranslationString.TelegramCommandRecycleDescription; + public override TranslationString MsgHeadI18NKey => TranslationString.TelegramCommandRecycleMsgHead; + + public RecycleCommand(TelegramUtils telegramUtils) : base(telegramUtils) + { + } + + public override async Task OnCommand(ISession session, string commandText, Action callback) + { + var cmd = commandText.Split(' '); + + if (cmd[0].ToLower() == Command) + { + await RecycleItemsTask.Execute(session, session.CancellationTokenSource.Token).ConfigureAwait(false); + callback(GetMsgHead(session, session.Profile.PlayerData.Username) + "\r\n\r\n"); + return true; + } + return false; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/TelegramCommand/RestartCommand.cs b/PoGo.NecroBot.Logic/Service/TelegramCommand/RestartCommand.cs new file mode 100644 index 000000000..358e38d05 --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/TelegramCommand/RestartCommand.cs @@ -0,0 +1,38 @@ +using System; +using System.Diagnostics; +using System.Reflection; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.State; + +namespace PoGo.NecroBot.Logic.Service.TelegramCommand +{ + public class RestartCommand : CommandMessage + { + public override string Command => "/restart"; + public override bool StopProcess => true; + public override TranslationString DescriptionI18NKey => TranslationString.TelegramCommandRestartDescription; + public override TranslationString MsgHeadI18NKey => TranslationString.TelegramCommandRestartMsgHead; + + public RestartCommand(TelegramUtils telegramUtils) : base(telegramUtils) + { + } + + public override async Task OnCommand(ISession session, string cmd, Action callback) + { + if (cmd.ToLower() == Command) + { + callback(GetMsgHead(session, session.Profile.PlayerData.Username) + "\r\n\r\n"); + await Task.Delay(5000).ConfigureAwait(false); + var assembly = Assembly.GetEntryAssembly().Location; + if (assembly != null) + { + Process.Start(assembly); + } + + Environment.Exit(-1); + } + return false; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/TelegramCommand/SnipeCommand.cs b/PoGo.NecroBot.Logic/Service/TelegramCommand/SnipeCommand.cs new file mode 100644 index 000000000..7866d17b1 --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/TelegramCommand/SnipeCommand.cs @@ -0,0 +1,43 @@ +using System; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Tasks; +using POGOProtos.Enums; + +namespace PoGo.NecroBot.Logic.Service.TelegramCommand +{ + public class SnipeCommand : CommandMessage + { + public override string Command => "/snipe"; + public override string Arguments => ""; + public override bool StopProcess => true; + public override TranslationString DescriptionI18NKey => TranslationString.TelegramCommandSnipeDescription; + public override TranslationString MsgHeadI18NKey => TranslationString.TelegramCommandSnipeMsgHead; + + public SnipeCommand(TelegramUtils telegramUtils) : base(telegramUtils) + { + } + + public override async Task OnCommand(ISession session, string commandText, Action callback) + { + var cmd = commandText.Split(' '); + + if (cmd[0].ToLower() == Command) + { + var pokemonData = cmd[1].Split(','); + PokemonId pid = (PokemonId) Enum.Parse(typeof(PokemonId), pokemonData[0].Trim(), true); + + await MSniperServiceTask.AddSnipeItem(session, new MSniperServiceTask.MSniperInfo2() + { + PokemonId = (short) pid, + Latitude = Convert.ToDouble(pokemonData[1].Trim()), + Longitude = Convert.ToDouble(pokemonData[2].Trim()) + }, true).ConfigureAwait(false); + callback(GetMsgHead(session, session.Profile.PlayerData.Username) + "\r\n\r\n"); + return true; + } + return false; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/TelegramCommand/StatusCommand.cs b/PoGo.NecroBot.Logic/Service/TelegramCommand/StatusCommand.cs new file mode 100644 index 000000000..e81b8f2ea --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/TelegramCommand/StatusCommand.cs @@ -0,0 +1,81 @@ +using System; +using System.Reflection; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.State; + +namespace PoGo.NecroBot.Logic.Service.TelegramCommand +{ + // TODO I18N + public class StatusCommand : CommandMessage + { + public override string Command => "/status"; + public override bool StopProcess => true; + public override TranslationString DescriptionI18NKey => TranslationString.TelegramCommandStatusDescription; + public override TranslationString MsgHeadI18NKey => TranslationString.TelegramCommandStatusMsgHead; + + public StatusCommand(TelegramUtils telegramUtils) : base(telegramUtils) + { + } + + #pragma warning disable 1998 // added to get rid of compiler warning. Remove this if async code is used below. + public override async Task OnCommand(ISession session, string cmd, Action callback) + #pragma warning restore 1998 + { + if (cmd.ToLower() == Command) + { + var NecroBotVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString(4); + var NecroBotStatistics = session.RuntimeStatistics; + var NecroBotStats = await NecroBotStatistics.GetCurrentInfo(session, session.Inventory).ConfigureAwait(false); + + var answerCatchLimit = "diabled"; + var answerPokestopLimit = "disabled"; + + if (session.LogicSettings.UseCatchLimit) + { + answerCatchLimit = string.Format( + "{0} / {1}", + session.Stats.GetNumPokemonsInLast24Hours(), + session.LogicSettings.CatchPokemonLimit + ); + } + + if (session.LogicSettings.UsePokeStopLimit) + { + answerPokestopLimit = string.Format( + "{0} / {1}", + session.Stats.GetNumPokestopsInLast24Hours(), + session.LogicSettings.PokeStopLimit + ); + } + + var answerTextmessage = GetMsgHead(session, session.Profile.PlayerData.Username) + "\r\n\r\n"; + + answerTextmessage += session.Translation.GetTranslation( + TranslationString.TelegramCommandStatusMsgBody, + NecroBotVersion, + session.Profile.PlayerData.Username, + NecroBotStatistics.FormatRuntime(), + NecroBotStats.Level, + NecroBotStats.HoursUntilLvl, + NecroBotStats.MinutesUntilLevel, + NecroBotStats.LevelupXp - NecroBotStats.CurrentXp, + NecroBotStatistics.TotalExperience/NecroBotStatistics.GetRuntime(), + NecroBotStatistics.TotalPokemons/NecroBotStatistics.GetRuntime(), + NecroBotStatistics.TotalStardust/NecroBotStatistics.GetRuntime(), + NecroBotStatistics.TotalPokemonTransferred, + NecroBotStatistics.TotalPokemonEvolved, + NecroBotStatistics.TotalItemsRemoved, + answerPokestopLimit, + answerCatchLimit, + session.Profile.PlayerData.Currencies[1].Amount, + session.Profile.PlayerData.Currencies[0].Amount + ); + + callback(answerTextmessage); + return true; + } + return false; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/TelegramCommand/TopCommand.cs b/PoGo.NecroBot.Logic/Service/TelegramCommand/TopCommand.cs new file mode 100644 index 000000000..e8ee44b34 --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/TelegramCommand/TopCommand.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.PoGoUtils; +using PoGo.NecroBot.Logic.State; +using POGOProtos.Data; +using static System.Text.RegularExpressions.Regex; + +namespace PoGo.NecroBot.Logic.Service.TelegramCommand +{ + public class TopCommand : CommandMessage + { + private const int DefaultCount = 10; + private const string DefaultOrderBy = "cp"; + + private string CommandParseRegex => "^(\\" + Command + ")(?>(?>\\s+(?iv|cp))|(?>\\s+(?\\d+))){0,2}\\s*"; + + public override string Command => "/top"; + public override string Arguments => "[iv|cp] [n]"; + public override bool StopProcess => true; + public override TranslationString DescriptionI18NKey => TranslationString.TelegramCommandTopDescription; + public override TranslationString MsgHeadI18NKey => TranslationString.TelegramCommandTopMsgHead; + + public TopCommand(TelegramUtils telegramUtils) : base(telegramUtils) + { + } + + public override string GetDescription(ISession session) => + base.GetDescription(session, DefaultCount.ToString()); + + public override async Task OnCommand(ISession session, string cmd, Action callback) + { + var commandMatch = Match(cmd, CommandParseRegex); + + if (!commandMatch.Success) + { + return false; + } + + // Parse count + int count; + try + { + count = string.IsNullOrEmpty(commandMatch.Groups["count"].Value) + ? DefaultCount + : Convert.ToInt32(commandMatch.Groups["count"].Value); + } + catch (FormatException) + { + // Exception should not be thrown ... + return false; + } + + // Parse orderBy + var orderBy = string.IsNullOrEmpty(commandMatch.Groups["orderBy"].Value) + ? DefaultOrderBy + : commandMatch.Groups["orderBy"].Value; + + // Get 'count' top Pokemon and 'orderBy' -> will never be null + var topPokemon = string.Equals("iv", orderBy) + ? await session.Inventory.GetHighestsPerfect(count).ConfigureAwait(false) + : await session.Inventory.GetHighestsCp(count).ConfigureAwait(false); + + var topPokemonList = topPokemon as IList ?? topPokemon.ToList(); + + var answerTextmessage = GetMsgHead(session, session.Profile.PlayerData.Username, topPokemonList.Count) + "\r\n\r\n"; + answerTextmessage = topPokemonList.Aggregate(answerTextmessage, (current, pokemon) + => current + session.Translation.GetTranslation( + TranslationString.ShowPokeSkillTemplate, + pokemon.Cp, + PokemonInfo.CalculatePokemonPerfection(pokemon).ToString("0.00"), + session.Translation.GetPokemonMovesetTranslation(PokemonInfo.GetPokemonMove1(pokemon)), + session.Translation.GetPokemonMovesetTranslation(PokemonInfo.GetPokemonMove2(pokemon)), + session.Translation.GetPokemonTranslation(pokemon.PokemonId) + ) + ); + + callback(answerTextmessage); + return true; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/TelegramService.cs b/PoGo.NecroBot.Logic/Service/TelegramService.cs new file mode 100644 index 000000000..778997d51 --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/TelegramService.cs @@ -0,0 +1,155 @@ +#region using directives + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.Service.TelegramCommand; +using PoGo.NecroBot.Logic.State; +using Telegram.Bot; +using Telegram.Bot.Args; +using Telegram.Bot.Types.Enums; +using Telegram.Bot.Types.ReplyMarkups; + +#endregion + +namespace PoGo.NecroBot.Logic.Service +{ + public class TelegramService + { + private DateTime _lastLoginTime; + private readonly TelegramUtils telegramUtils; + private bool _loggedIn; + private readonly ISession _session; + private readonly TelegramBotClient _bot; + private IEnumerable iCommandInstances; + private long lastChatId = 0; + private const string LOG_FILE = "config\\telegram.id"; + + public TelegramService(string apiKey, ISession session) + { + try + { + _session = session; + _bot = new TelegramBotClient(apiKey); + telegramUtils = new TelegramUtils(_bot, _session); + + iCommandInstances = AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(x => x.GetTypes()) + .Where(x => (typeof(ICommand).IsAssignableFrom(x)) && !x.IsInterface && !x.IsAbstract) + .Select(x => (ICommand) Activator.CreateInstance(x, telegramUtils)); + + var me = _bot.GetMeAsync().Result; + + _bot.OnMessage += OnTelegramMessageReceived; + _bot.StartReceiving(); + + _session.EventDispatcher.Send(new NoticeEvent {Message = "Using TelegramAPI with " + me.Username}); + + if (File.Exists(LOG_FILE)) + { + var s = File.ReadAllText(LOG_FILE); + if (!string.IsNullOrEmpty(s)) + { + lastChatId = Convert.ToInt64(s); + #pragma warning disable 4014 // added to get rid of compiler warning. Remove this if async code is used below. + telegramUtils.SendMessage(_session.Translation.GetTranslation(TranslationString.TelegramBotStarted), lastChatId); + #pragma warning restore 4014 + } + else + { + _session.EventDispatcher.Send(new NoticeEvent() { Message = _session.Translation.GetTranslation(TranslationString.TelegramNeedChatId) }); + } + } + } + catch (Exception ex) + { + _session.EventDispatcher.Send(new ErrorEvent {Message = ex.Message}); + _session.EventDispatcher.Send(new ErrorEvent {Message = "Unkown Telegram Error occured. "}); + } + } + + private async void OnTelegramMessageReceived(object sender, MessageEventArgs messageEventArgs) + { + var message = messageEventArgs.Message; + if (message == null || message.Type != MessageType.TextMessage) + return; + + var answerTextmessage = ""; + + if (_session.Profile == null || _session.Inventory == null) + { + return; + } + lastChatId = message.Chat.Id; + File.WriteAllText(LOG_FILE, lastChatId.ToString()); + var messagetext = message.Text.ToLower().Split(' '); + + if (!_loggedIn && messagetext[0].ToLower().Contains("/login")) + { + if (messagetext.Length == 2) + { + if (messagetext[1].ToLower().Contains(_session.LogicSettings.TelegramPassword)) + { + _loggedIn = true; + _lastLoginTime = DateTime.Now; + answerTextmessage += _session.Translation.GetTranslation(TranslationString.LoggedInTelegram); + await telegramUtils.SendMessage(answerTextmessage, message.Chat.Id).ConfigureAwait(false); + return; + } + answerTextmessage += _session.Translation.GetTranslation(TranslationString.LoginFailedTelegram); + await telegramUtils.SendMessage(answerTextmessage, message.Chat.Id).ConfigureAwait(false); + return; + } + answerTextmessage += _session.Translation.GetTranslation(TranslationString.NotLoggedInTelegram); + await telegramUtils.SendMessage(answerTextmessage, message.Chat.Id).ConfigureAwait(false); + return; + } + if (_loggedIn) + { + if (_lastLoginTime.AddMinutes(5).Ticks < DateTime.Now.Ticks) + { + _loggedIn = false; + answerTextmessage += _session.Translation.GetTranslation(TranslationString.NotLoggedInTelegram); + await telegramUtils.SendMessage(answerTextmessage, message.Chat.Id).ConfigureAwait(false); + return; + } + var remainingMins = _lastLoginTime.AddMinutes(5).Subtract(DateTime.Now).Minutes; + var remainingSecs = _lastLoginTime.AddMinutes(5).Subtract(DateTime.Now).Seconds; + answerTextmessage += _session.Translation.GetTranslation(TranslationString.LoginRemainingTime, + remainingMins, remainingSecs); + await telegramUtils.SendMessage(answerTextmessage, message.Chat.Id).ConfigureAwait(false); + return; + } + + bool handled = false; + foreach (var item in iCommandInstances) + { + try + { + handled = await item.OnCommand(_session, message.Text, message).ConfigureAwait(false); + if (handled) break; + } + catch (Exception) + { + } + } + + if (!handled) + { + HelpCommand helpCMD = new HelpCommand(telegramUtils); + await helpCMD.OnCommand(_session, helpCMD.Command, message).ConfigureAwait(false); + } + } + + [Obsolete("SendMessage in TelegramService.cs is deprecated, please use SendMessage.cs TelegramUtils.")] + public async Task SendMessage(string message) + { + if (string.IsNullOrEmpty(message) || lastChatId == 0) return; + await _bot.SendTextMessageAsync(lastChatId, message, replyMarkup: new ReplyKeyboardMarkup()).ConfigureAwait(false); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/TelegramUtils.cs b/PoGo.NecroBot.Logic/Service/TelegramUtils.cs new file mode 100644 index 000000000..e8994c301 --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/TelegramUtils.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using GeoCoordinatePortable; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.State; +using Telegram.Bot; +using Telegram.Bot.Types; +using Telegram.Bot.Types.ReplyMarkups; + +namespace PoGo.NecroBot.Logic.Service +{ + public class TelegramUtils + { + private const int MaxTelegramMsgLength = 4096; + + private readonly TelegramBotClient _bot; + private readonly ISession _session; + + public TelegramUtils(TelegramBotClient bot, ISession session) + { + _bot = bot; + _session = session; + } + + public async Task SendLocation(GeoCoordinate geo, Message telegramMessage) + { + await SendLocation(geo, telegramMessage.MessageId).ConfigureAwait(false); + } + + public async Task SendLocation(GeoCoordinate geo, long chatId) + { + if (chatId == 0) + { + _session.EventDispatcher.Send(new WarnEvent { Message = String.Format("Could not send location to 'Telegram', because given Chat id was '{0}'", 0) }); + } + await _bot.SendLocationAsync(chatId, (float) geo.Latitude, (float) geo.Longitude).ConfigureAwait(false); + } + + public async Task SendMessage(string message, Message telegramMessage) + { + await SendMessage(message, telegramMessage.MessageId).ConfigureAwait(false); + } + + public async Task SendMessage(string message, long chatId) + { + if (chatId == 0) + { + _session.EventDispatcher.Send(new WarnEvent { Message = String.Format("Could not send message to 'Telegram', because given Chat id was '{0}'", 0) }); + } + else if (string.IsNullOrEmpty(message)) + { + return; + } + + foreach (var msg in Split(message, MaxTelegramMsgLength)) + { + await _bot.SendTextMessageAsync(chatId, msg, replyMarkup: new ReplyKeyboardMarkup()).ConfigureAwait(false); + } + } + + static IEnumerable Split(string str, int maxChunkSize) + { + for (var i = 0; i < str.Length; i += maxChunkSize) + yield return str.Substring(i, Math.Min(maxChunkSize, str.Length - i)); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/DropItemHandler.cs b/PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/DropItemHandler.cs new file mode 100644 index 000000000..74ae6b79d --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/DropItemHandler.cs @@ -0,0 +1,27 @@ +#region using directives + +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Tasks; +using POGOProtos.Inventory.Item; +using SuperSocket.WebSocket; + +#endregion + +namespace PoGo.NecroBot.Logic.Service.WebSocketHandler.ActionCommands +{ + public class DropItemHandler : IWebSocketRequestHandler + { + public DropItemHandler() + { + Command = "DropItem"; + } + + public string Command { get; } + + public async Task Handle(ISession session, WebSocketSession webSocketSession, dynamic message) + { + await RecycleItemsTask.DropItem(session, (ItemId) message.ItemId, (int) message.Count); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/EvolvePokemonHandler.cs b/PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/EvolvePokemonHandler.cs new file mode 100644 index 000000000..6120a4a31 --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/EvolvePokemonHandler.cs @@ -0,0 +1,26 @@ +#region using directives + +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Tasks; +using SuperSocket.WebSocket; + +#endregion + +namespace PoGo.NecroBot.Logic.Service.WebSocketHandler.ActionCommands +{ + public class EvolvePokemonHandler : IWebSocketRequestHandler + { + public EvolvePokemonHandler() + { + Command = "EvolvePokemon"; + } + + public string Command { get; } + + public async Task Handle(ISession session, WebSocketSession webSocketSession, dynamic message) + { + await EvolveSpecificPokemonTask.Execute(session, (ulong) message.PokemonId); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/FastpokemapHandler.cs b/PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/FastpokemapHandler.cs new file mode 100644 index 000000000..ccb5ebc8d --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/FastpokemapHandler.cs @@ -0,0 +1,29 @@ +#region using directives + +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Tasks; +using SuperSocket.WebSocket; + +#endregion + +namespace PoGo.NecroBot.Logic.Service.WebSocketHandler.ActionCommands +{ + public class FastpokemapHandler : IWebSocketRequestHandler + { + public FastpokemapHandler() + { + Command = "Fastpokemap"; + } + + public string Command { get; } + + public async Task Handle(ISession session, WebSocketSession webSocketSession, dynamic message) + { + CatchState.AddFastPokemapItem(message.Data); + + await HumanWalkSnipeTask.AddFastPokemapItem(message.Data); + //Console.WriteLine(JsonConvert.DeserializeObject(message.Data)); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/FavoritePokemonHandler.cs b/PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/FavoritePokemonHandler.cs new file mode 100644 index 000000000..02c0fd017 --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/FavoritePokemonHandler.cs @@ -0,0 +1,26 @@ +#region using directives + +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Tasks; +using SuperSocket.WebSocket; + +#endregion + +namespace PoGo.NecroBot.Logic.Service.WebSocketHandler.ActionCommands +{ + public class FavoritePokemonHandler : IWebSocketRequestHandler + { + public FavoritePokemonHandler() + { + Command = "FavoritePokemon"; + } + + public string Command { get; } + + public async Task Handle(ISession session, WebSocketSession webSocketSession, dynamic message) + { + await FavoritePokemonTask.Execute(session, (ulong) message.PokemonId, (bool) message.Favorite); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/HumanSnipePriorityPokemonHandler.cs b/PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/HumanSnipePriorityPokemonHandler.cs new file mode 100644 index 000000000..0a5da9f78 --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/HumanSnipePriorityPokemonHandler.cs @@ -0,0 +1,26 @@ +#region using directives + +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Tasks; +using SuperSocket.WebSocket; + +#endregion + +namespace PoGo.NecroBot.Logic.Service.WebSocketHandler.ActionCommands +{ + public class HumanSnipePriorityPokemonHandler : IWebSocketRequestHandler + { + public HumanSnipePriorityPokemonHandler() + { + Command = "SnipePokemon"; + } + + public string Command { get; } + + public async Task Handle(ISession session, WebSocketSession webSocketSession, dynamic message) + { + await HumanWalkSnipeTask.TargetPokemonSnip(session, (string) message.Id); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/HumanSnipeRemovePokemonHandler.cs b/PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/HumanSnipeRemovePokemonHandler.cs new file mode 100644 index 000000000..a2e386c85 --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/HumanSnipeRemovePokemonHandler.cs @@ -0,0 +1,26 @@ +#region using directives + +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Tasks; +using SuperSocket.WebSocket; + +#endregion + +namespace PoGo.NecroBot.Logic.Service.WebSocketHandler.ActionCommands +{ + public class HumanSnipeRemovePokemonHandler : IWebSocketRequestHandler + { + public HumanSnipeRemovePokemonHandler() + { + Command = "RemovePokemon"; + } + + public string Command { get; } + + public async Task Handle(ISession session, WebSocketSession webSocketSession, dynamic message) + { + await HumanWalkSnipeTask.RemovePokemonFromQueue(session, (string) message.Id); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/LevelUpPokemonHandler.cs b/PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/LevelUpPokemonHandler.cs new file mode 100644 index 000000000..d40274d37 --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/LevelUpPokemonHandler.cs @@ -0,0 +1,26 @@ +#region using directives + +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Tasks; +using SuperSocket.WebSocket; + +#endregion + +namespace PoGo.NecroBot.Logic.Service.WebSocketHandler.ActionCommands +{ + public class LevelUpPokemonHandler : IWebSocketRequestHandler + { + public string Command { get; private set; } + + public LevelUpPokemonHandler() + { + Command = "LevelUpPokemon"; + } + + public async Task Handle(ISession session, WebSocketSession webSocketSession, dynamic message) + { + await LevelUpSpecificPokemonTask.Execute(session, (ulong) message.PokemonId); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/SetConfigHandler.cs b/PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/SetConfigHandler.cs new file mode 100644 index 000000000..21037b8c8 --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/SetConfigHandler.cs @@ -0,0 +1,69 @@ +#region using directives + +using System; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Linq; +using PoGo.NecroBot.Logic.State; +using SuperSocket.WebSocket; + +#endregion + +namespace PoGo.NecroBot.Logic.Service.WebSocketHandler.ActionCommands +{ + public class SetConfigHandler : IWebSocketRequestHandler + { + public SetConfigHandler() + { + Command = "SetConfig"; + } + + public string Command { get; } + + public async Task Handle(ISession session, WebSocketSession webSocketSession, dynamic message) + { + var profilePath = Path.Combine(Directory.GetCurrentDirectory(), ""); + var profileConfigPath = Path.Combine(profilePath, "config"); + var authFile = Path.Combine(profileConfigPath, "auth.json"); + var configFile = Path.Combine(profileConfigPath, "config.json"); + + var jsonSerializeSettings = new JsonSerializerSettings + { + DefaultValueHandling = DefaultValueHandling.Include, + Formatting = Formatting.Indented, + Converters = new JsonConverter[] {new StringEnumConverter {CamelCaseText = true}} + }; + + await Task.Run(() => + { + try + { + var authJson = JsonConvert.SerializeObject((JObject) message.AuthJson, jsonSerializeSettings); + if (!string.IsNullOrEmpty(authJson) && authJson != "null") + File.WriteAllText(authFile, authJson, Encoding.UTF8); + } + catch (Exception) + { + // ignored + } + }); + + await Task.Run(() => + { + try + { + var configJson = JsonConvert.SerializeObject((JObject) message.ConfigJson, jsonSerializeSettings); + if (!string.IsNullOrEmpty(configJson) && configJson != "null") + File.WriteAllText(configFile, configJson, Encoding.UTF8); + } + catch (Exception) + { + // ignored + } + }); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/SetMoveToTargetHandler.cs b/PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/SetMoveToTargetHandler.cs new file mode 100644 index 000000000..4aaba5ea5 --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/SetMoveToTargetHandler.cs @@ -0,0 +1,22 @@ +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Tasks; +using SuperSocket.WebSocket; + +namespace PoGo.NecroBot.Logic.Service.WebSocketHandler.ActionCommands +{ + public class SetMoveToTargetHandler : IWebSocketRequestHandler + { + public string Command { get; private set; } + + public SetMoveToTargetHandler() + { + Command = "SetMoveToTarget"; + } + + public async Task Handle(ISession session, WebSocketSession webSocketSession, dynamic message) + { + await SetMoveToTargetTask.Execute((double) message.Latitude, (double) message.Longitude, (string) message.FortId); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/SetTrainerNicknameHandler.cs b/PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/SetTrainerNicknameHandler.cs new file mode 100644 index 000000000..3645bc816 --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/SetTrainerNicknameHandler.cs @@ -0,0 +1,61 @@ +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.Model; +using PoGo.NecroBot.Logic.State; +using POGOProtos.Networking.Responses; +using SuperSocket.WebSocket; + +namespace PoGo.NecroBot.Logic.Service.WebSocketHandler.ActionCommands +{ + public class SetTrainerNicknameHandler : IWebSocketRequestHandler + { + public string Command { get; private set; } + + public SetTrainerNicknameHandler() + { + Command = "SetNickname"; + } + + public async Task Handle(ISession session, WebSocketSession webSocketSession, dynamic message) + { + string nickname = message.Data; + + if (nickname.Length > 15) + { + session.EventDispatcher.Send(new NoticeEvent() + { + Message = "You selected too long Desired name, max length: 15!" + }); + return; + } + if (nickname == session.Profile.PlayerData.Username) return; + + + using (var blocker = new BlockableScope(session, BotActions.UpdateProfile)) + { + if (!await blocker.WaitToRun()) return; + + var res = await session.Client.Misc.ClaimCodename(nickname); + if (res.Status == ClaimCodenameResponse.Types.Status.Success) + { + session.EventDispatcher.Send(new NoticeEvent() + { + Message = $"Your name is now: {res.Codename}" + }); + + session.EventDispatcher.Send(new NicknameUpdateEvent() + { + Nickname = res.Codename + }); + } + else + { + session.EventDispatcher.Send(new NoticeEvent() + { + Message = $"Couldn't change your nickname" + }); + } + } + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/TransferPokemonHandler.cs b/PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/TransferPokemonHandler.cs new file mode 100644 index 000000000..8ac5671fe --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/TransferPokemonHandler.cs @@ -0,0 +1,34 @@ +#region using directives + +using System.Collections.Generic; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Tasks; +using SuperSocket.WebSocket; + +#endregion + +namespace PoGo.NecroBot.Logic.Service.WebSocketHandler.ActionCommands +{ + public class TransferPokemonHandler : IWebSocketRequestHandler + { + public TransferPokemonHandler() + { + Command = "TransferPokemon"; + } + + public string Command { get; } + + public async Task Handle(ISession session, WebSocketSession webSocketSession, dynamic message) + { + await TransferPokemonTask.Execute( + session, + session.CancellationTokenSource.Token, + new List + { + (ulong) message.PokemonId + } + ); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/UpgradePokemonHandler.cs b/PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/UpgradePokemonHandler.cs new file mode 100644 index 000000000..efd1cd5e3 --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/WebSocketHandler/ActionCommands/UpgradePokemonHandler.cs @@ -0,0 +1,26 @@ +#region using directives + +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Tasks; +using SuperSocket.WebSocket; + +#endregion + +namespace PoGo.NecroBot.Logic.Service.WebSocketHandler.ActionCommands +{ + public class UpgradePokemonHandler : IWebSocketRequestHandler + { + public UpgradePokemonHandler() + { + Command = "UpgradePokemon"; + } + + public string Command { get; } + + public async Task Handle(ISession session, WebSocketSession webSocketSession, dynamic message) + { + await UpgradeSinglePokemonTask.Execute(session, (ulong) message.PokemonId, (bool) message.Max); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/WebSocketHandler/EncodingHelper.cs b/PoGo.NecroBot.Logic/Service/WebSocketHandler/EncodingHelper.cs new file mode 100644 index 000000000..5a008076b --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/WebSocketHandler/EncodingHelper.cs @@ -0,0 +1,43 @@ +#region using directives + +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +#endregion + +namespace PoGo.NecroBot.Logic.Service.WebSocketHandler +{ + internal class EncodingHelper + { + public static string Serialize(dynamic evt) + { + var jsonSerializerSettings = new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.All}; + + // Add custom seriaizer to convert uong to string (ulong shoud not appear to json according to json specs) + jsonSerializerSettings.Converters.Add(new IdToStringConverter()); + + return JsonConvert.SerializeObject(evt, Formatting.None, jsonSerializerSettings); + } + + public class IdToStringConverter : JsonConverter + { + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, + JsonSerializer serializer) + { + var jt = JToken.ReadFrom(reader); + return jt.Value(); + } + + public override bool CanConvert(Type objectType) + { + return typeof(long) == objectType || typeof(ulong) == objectType; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + serializer.Serialize(writer, value.ToString()); + } + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Events/ConfigResponce.cs b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Events/ConfigResponce.cs new file mode 100644 index 000000000..893efcba2 --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Events/ConfigResponce.cs @@ -0,0 +1,16 @@ +namespace PoGo.NecroBot.Logic.Service.WebSocketHandler.GetCommands.Events +{ + public class ConfigResponce : IWebSocketResponce + { + public ConfigResponce(dynamic data, string requestID) + { + Command = "ConfigWeb"; + Data = data; + RequestID = requestID; + } + + public string RequestID { get; } + public string Command { get; } + public dynamic Data { get; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Events/EggListResponce.cs b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Events/EggListResponce.cs new file mode 100644 index 000000000..f3d3c6f8c --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Events/EggListResponce.cs @@ -0,0 +1,16 @@ +namespace PoGo.NecroBot.Logic.Service.WebSocketHandler.GetCommands.Events +{ + public class EggListResponce : IWebSocketResponce + { + public EggListResponce(dynamic data, string requestID) + { + Command = "EggListWeb"; + Data = data; + RequestID = requestID; + } + + public string RequestID { get; } + public string Command { get; } + public dynamic Data { get; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Events/ItemListResponce.cs b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Events/ItemListResponce.cs new file mode 100644 index 000000000..2c6f036c4 --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Events/ItemListResponce.cs @@ -0,0 +1,16 @@ +namespace PoGo.NecroBot.Logic.Service.WebSocketHandler.GetCommands.Events +{ + internal class ItemListResponce : IWebSocketResponce + { + public ItemListResponce(dynamic data, string requestID) + { + Command = "ItemListWeb"; + Data = data; + RequestID = requestID; + } + + public string RequestID { get; } + public string Command { get; } + public dynamic Data { get; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Events/PokemonListResponce.cs b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Events/PokemonListResponce.cs new file mode 100644 index 000000000..ecd9bf5ad --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Events/PokemonListResponce.cs @@ -0,0 +1,22 @@ +#region using directives + +using PoGo.NecroBot.Logic.Event; + +#endregion + +namespace PoGo.NecroBot.Logic.Service.WebSocketHandler.GetCommands.Events +{ + public class PokemonListResponce : IWebSocketResponce, IEvent + { + public PokemonListResponce(dynamic data, string requestID) + { + Command = "PokemonListWeb"; + Data = data; + RequestID = requestID; + } + + public string RequestID { get; } + public string Command { get; } + public dynamic Data { get; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Events/SnipeListResponce.cs b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Events/SnipeListResponce.cs new file mode 100644 index 000000000..a37b9a57d --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Events/SnipeListResponce.cs @@ -0,0 +1,16 @@ +namespace PoGo.NecroBot.Logic.Service.WebSocketHandler.GetCommands.Events +{ + internal class SnipeListResponce : IWebSocketResponce + { + public SnipeListResponce(dynamic data, string requestID) + { + Command = "HumanWalkSnipEvent"; + Data = data; + RequestID = requestID; + } + + public string RequestID { get; } + public string Command { get; } + public dynamic Data { get; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Events/TrainerProfileResponce.cs b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Events/TrainerProfileResponce.cs new file mode 100644 index 000000000..c0fc68f48 --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Events/TrainerProfileResponce.cs @@ -0,0 +1,16 @@ +namespace PoGo.NecroBot.Logic.Service.WebSocketHandler.GetCommands.Events +{ + internal class TrainerProfileResponce : IWebSocketResponce + { + public TrainerProfileResponce(dynamic data, string requestID) + { + Command = "TrainerProfile"; + Data = data; + RequestID = requestID; + } + + public string RequestID { get; } + public string Command { get; } + public dynamic Data { get; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Events/WebResponce.cs b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Events/WebResponce.cs new file mode 100644 index 000000000..462147453 --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Events/WebResponce.cs @@ -0,0 +1,9 @@ +namespace PoGo.NecroBot.Logic.Service.WebSocketHandler.GetCommands.Events +{ + public class WebResponce : IWebSocketResponce + { + public string RequestID { get; set; } + public string Command { get; set; } + public dynamic Data { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/GetConfigHandler.cs b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/GetConfigHandler.cs new file mode 100644 index 000000000..faf2981d7 --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/GetConfigHandler.cs @@ -0,0 +1,26 @@ +#region using directives + +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Service.WebSocketHandler.GetCommands.Tasks; +using PoGo.NecroBot.Logic.State; +using SuperSocket.WebSocket; + +#endregion + +namespace PoGo.NecroBot.Logic.Service.WebSocketHandler.GetCommands +{ + internal class GetConfigHandler : IWebSocketRequestHandler + { + public GetConfigHandler() + { + Command = "GetConfig"; + } + + public string Command { get; } + + public async Task Handle(ISession session, WebSocketSession webSocketSession, dynamic message) + { + await GetConfigTask.Execute(session, webSocketSession, (string) message.RequestID); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/GetEggListHandler.cs b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/GetEggListHandler.cs new file mode 100644 index 000000000..2d6b1f3ff --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/GetEggListHandler.cs @@ -0,0 +1,26 @@ +#region using directives + +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Service.WebSocketHandler.GetCommands.Tasks; +using PoGo.NecroBot.Logic.State; +using SuperSocket.WebSocket; + +#endregion + +namespace PoGo.NecroBot.Logic.Service.WebSocketHandler.GetCommands +{ + internal class GetEggListHandler : IWebSocketRequestHandler + { + public GetEggListHandler() + { + Command = "GetEggList"; + } + + public string Command { get; } + + public async Task Handle(ISession session, WebSocketSession webSocketSession, dynamic message) + { + await GetEggListTask.Execute(session, webSocketSession, (string) message.RequestID); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/GetItemsListHandler.cs b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/GetItemsListHandler.cs new file mode 100644 index 000000000..f25a63b1a --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/GetItemsListHandler.cs @@ -0,0 +1,26 @@ +#region using directives + +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Service.WebSocketHandler.GetCommands.Tasks; +using PoGo.NecroBot.Logic.State; +using SuperSocket.WebSocket; + +#endregion + +namespace PoGo.NecroBot.Logic.Service.WebSocketHandler.GetCommands +{ + internal class GetItemsListHandler : IWebSocketRequestHandler + { + public GetItemsListHandler() + { + Command = "GetItemsList"; + } + + public string Command { get; } + + public async Task Handle(ISession session, WebSocketSession webSocketSession, dynamic message) + { + await GetItemListTask.Execute(session, webSocketSession, (string) message.RequestID); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/GetPokemonListHandler.cs b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/GetPokemonListHandler.cs new file mode 100644 index 000000000..9343fcc67 --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/GetPokemonListHandler.cs @@ -0,0 +1,26 @@ +#region using directives + +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Service.WebSocketHandler.GetCommands.Tasks; +using PoGo.NecroBot.Logic.State; +using SuperSocket.WebSocket; + +#endregion + +namespace PoGo.NecroBot.Logic.Service.WebSocketHandler.GetCommands +{ + public class GetPokemonListHandler : IWebSocketRequestHandler + { + public GetPokemonListHandler() + { + Command = "GetPokemonList"; + } + + public string Command { get; } + + public async Task Handle(ISession session, WebSocketSession webSocketSession, dynamic message) + { + await GetPokemonListTask.Execute(session, webSocketSession, (string) message.RequestID); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/GetPokemonSettingsHandler.cs b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/GetPokemonSettingsHandler.cs new file mode 100644 index 000000000..d59254a3e --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/GetPokemonSettingsHandler.cs @@ -0,0 +1,26 @@ +#region using directives + +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Service.WebSocketHandler.GetCommands.Tasks; +using PoGo.NecroBot.Logic.State; +using SuperSocket.WebSocket; + +#endregion + +namespace PoGo.NecroBot.Logic.Service.WebSocketHandler.GetCommands +{ + public class GetPokemonSettingsHandler : IWebSocketRequestHandler + { + public GetPokemonSettingsHandler() + { + Command = "GetPokemonSettings"; + } + + public string Command { get; } + + public async Task Handle(ISession session, WebSocketSession webSocketSession, dynamic message) + { + await GetPokemonSettingsTask.Execute(session, webSocketSession, (string) message.RequestID); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/GetPokemonSnipeListHandler.cs b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/GetPokemonSnipeListHandler.cs new file mode 100644 index 000000000..8f0d62bf2 --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/GetPokemonSnipeListHandler.cs @@ -0,0 +1,23 @@ +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Tasks; +using SuperSocket.WebSocket; + +namespace PoGo.NecroBot.Logic.Service.WebSocketHandler.GetCommands +{ + class GetPokemonSnipeListHandler : IWebSocketRequestHandler + { + public string Command { get; private set; } + + public GetPokemonSnipeListHandler() + { + Command = "PokemonSnipeList"; + } + + public async Task Handle(ISession session, WebSocketSession webSocketSession, dynamic message) + { + await HumanWalkSnipeTask.ExecuteFetchData(session); + //await GetPokemonSnipeListTask.Execute(session, webSocketSession, (string)message.RequestID); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/GetTrainerProfileHandler.cs b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/GetTrainerProfileHandler.cs new file mode 100644 index 000000000..b27fd6e86 --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/GetTrainerProfileHandler.cs @@ -0,0 +1,26 @@ +#region using directives + +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Service.WebSocketHandler.GetCommands.Tasks; +using PoGo.NecroBot.Logic.State; +using SuperSocket.WebSocket; + +#endregion + +namespace PoGo.NecroBot.Logic.Service.WebSocketHandler.GetCommands +{ + internal class GetTrainerProfileHandler : IWebSocketRequestHandler + { + public GetTrainerProfileHandler() + { + Command = "GetTrainerProfile"; + } + + public string Command { get; } + + public async Task Handle(ISession session, WebSocketSession webSocketSession, dynamic message) + { + await GetTrainerProfileTask.Execute(session, webSocketSession, (string) message.RequestID); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Helpers/ConfigWeb.cs b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Helpers/ConfigWeb.cs new file mode 100644 index 000000000..56ce0aece --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Helpers/ConfigWeb.cs @@ -0,0 +1,10 @@ +namespace PoGo.NecroBot.Logic.Service.WebSocketHandler.GetCommands.Helpers +{ + internal class ConfigWeb + { + public object AuthJson { get; set; } + public object AuthSchemaJson { get; set; } + public object ConfigJson { get; set; } + public object ConfigSchemaJson { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Helpers/EggListWeb.cs b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Helpers/EggListWeb.cs new file mode 100644 index 000000000..dc08c9eca --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Helpers/EggListWeb.cs @@ -0,0 +1,8 @@ +namespace PoGo.NecroBot.Logic.Service.WebSocketHandler.GetCommands.Helpers +{ + internal class EggListWeb + { + public object Incubators { get; set; } + public object UnusedEggs { get; set; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Helpers/PokemonListWeb.cs b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Helpers/PokemonListWeb.cs new file mode 100644 index 000000000..a12a49ec5 --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Helpers/PokemonListWeb.cs @@ -0,0 +1,26 @@ +#region using directives + +using PoGo.NecroBot.Logic.PoGoUtils; +using POGOProtos.Data; +using PoGo.NecroBot.Logic.State; + +#endregion + +namespace PoGo.NecroBot.Logic.Service.WebSocketHandler.GetCommands.Helpers +{ + public class PokemonListWeb + { + public PokemonData Base; + private readonly ISession _session; + + public PokemonListWeb(ISession session, PokemonData data) + { + Base = data; + _session = session; + } + + public double IvPerfection => PokemonInfo.CalculatePokemonPerfection(Base); + public double Level => PokemonInfo.GetLevel(Base); + public int FamilyCandies => PokemonInfo.GetCandy(_session, Base).Result; + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Helpers/TrainerProfileWeb.cs b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Helpers/TrainerProfileWeb.cs new file mode 100644 index 000000000..392b942e5 --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Helpers/TrainerProfileWeb.cs @@ -0,0 +1,21 @@ +#region using directives + +using POGOProtos.Data; +using POGOProtos.Data.Player; + +#endregion + +namespace PoGo.NecroBot.Logic.Service.WebSocketHandler.GetCommands.Helpers +{ + internal class TrainerProfileWeb + { + public PlayerData Profile; + public PlayerStats Stats; + + public TrainerProfileWeb(PlayerData profile, PlayerStats stats) + { + Profile = profile; + Stats = stats; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Tasks/GetConfigTask.cs b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Tasks/GetConfigTask.cs new file mode 100644 index 000000000..f672f9342 --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Tasks/GetConfigTask.cs @@ -0,0 +1,46 @@ +#region using directives + +using System.IO; +using System.Text; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Service.WebSocketHandler.GetCommands.Events; +using PoGo.NecroBot.Logic.Service.WebSocketHandler.GetCommands.Helpers; +using PoGo.NecroBot.Logic.State; +using SuperSocket.WebSocket; + +#endregion + +namespace PoGo.NecroBot.Logic.Service.WebSocketHandler.GetCommands.Tasks +{ + internal class GetConfigTask + { + public static async Task Execute(ISession session, WebSocketSession webSocketSession, string requestID) + { + await Task.Run(() => + { + var profilePath = Path.Combine(Directory.GetCurrentDirectory(), ""); + var profileConfigPath = Path.Combine(profilePath, "config"); + + var authFile = Path.Combine(profileConfigPath, "auth.json"); + var authSchemaFile = Path.Combine(profileConfigPath, "auth.schema.json"); + var authJson = File.ReadAllText(authFile, Encoding.UTF8); + var authSchemaJson = File.ReadAllText(authSchemaFile, Encoding.UTF8); + + var configFile = Path.Combine(profileConfigPath, "config.json"); + var configSchemaFile = Path.Combine(profileConfigPath, "config.schema.json"); + var configJson = File.ReadAllText(configFile, Encoding.UTF8); + var configSchemaJson = File.ReadAllText(configSchemaFile, Encoding.UTF8); + + var list = new ConfigWeb + { + AuthJson = authJson, + AuthSchemaJson = authSchemaJson, + ConfigJson = configJson, + ConfigSchemaJson = configSchemaJson + }; + + webSocketSession.Send(EncodingHelper.Serialize(new ConfigResponce(list, requestID))); + }); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Tasks/GetEggListTask.cs b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Tasks/GetEggListTask.cs new file mode 100644 index 000000000..617c1bf2a --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Tasks/GetEggListTask.cs @@ -0,0 +1,43 @@ +#region using directives + +using System.Linq; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Service.WebSocketHandler.GetCommands.Events; +using PoGo.NecroBot.Logic.Service.WebSocketHandler.GetCommands.Helpers; +using PoGo.NecroBot.Logic.State; +using POGOProtos.Inventory.Item; +using SuperSocket.WebSocket; + +#endregion + +namespace PoGo.NecroBot.Logic.Service.WebSocketHandler.GetCommands.Tasks +{ + internal class GetEggListTask + { + public static async Task Execute(ISession session, WebSocketSession webSocketSession, string requestID) + { + // using (var blocker = new BlockableScope(session, BotActions.Eggs)) + { + // if (!await blocker.WaitToRun()) return; + + var incubators = (await session.Inventory.GetEggIncubators()) + .Where(x => x.UsesRemaining > 0 || x.ItemId == ItemId.ItemIncubatorBasicUnlimited) + .OrderByDescending(x => x.ItemId == ItemId.ItemIncubatorBasicUnlimited) + .ToList(); + + var unusedEggs = (await session.Inventory.GetEggs()) + .Where(x => string.IsNullOrEmpty(x.EggIncubatorId)) + .OrderBy(x => x.EggKmWalkedTarget - x.EggKmWalkedStart) + .ToList(); + + + var list = new EggListWeb + { + Incubators = incubators, + UnusedEggs = unusedEggs + }; + webSocketSession.Send(EncodingHelper.Serialize(new EggListResponce(list, requestID))); + } + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Tasks/GetItemListTask.cs b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Tasks/GetItemListTask.cs new file mode 100644 index 000000000..5f13b599a --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Tasks/GetItemListTask.cs @@ -0,0 +1,25 @@ +#region using directives + +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Service.WebSocketHandler.GetCommands.Events; +using PoGo.NecroBot.Logic.State; +using SuperSocket.WebSocket; + +#endregion + +namespace PoGo.NecroBot.Logic.Service.WebSocketHandler.GetCommands.Tasks +{ + internal class GetItemListTask + { + public static async Task Execute(ISession session, WebSocketSession webSocketSession, string requestID) + { + // using (var blocker = new BlockableScope(session, BotActions.ListItems)) + { + //if (!await blocker.WaitToRun()) return; + + var allItems = await session.Inventory.GetItems(); + webSocketSession.Send(EncodingHelper.Serialize(new ItemListResponce(allItems, requestID))); + } + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Tasks/GetPokemonListTask.cs b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Tasks/GetPokemonListTask.cs new file mode 100644 index 000000000..b301e0782 --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Tasks/GetPokemonListTask.cs @@ -0,0 +1,30 @@ +#region using directives + +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Service.WebSocketHandler.GetCommands.Events; +using PoGo.NecroBot.Logic.Service.WebSocketHandler.GetCommands.Helpers; +using PoGo.NecroBot.Logic.State; +using SuperSocket.WebSocket; + +#endregion + +namespace PoGo.NecroBot.Logic.Service.WebSocketHandler.GetCommands.Tasks +{ + internal class GetPokemonListTask + { + public static async Task Execute(ISession session, WebSocketSession webSocketSession, string requestID) + { + //using (var blocker = new BlockableScope(session, BotActions.ListItems)) + { + //if (!await blocker.WaitToRun()) return; + + var allPokemonInBag = await session.Inventory.GetHighestsCp(1000); + var list = new List(); + allPokemonInBag.ToList().ForEach(o => list.Add(new PokemonListWeb(session, o))); + webSocketSession.Send(EncodingHelper.Serialize(new PokemonListResponce(list, requestID))); + } + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Tasks/GetPokemonSettingsTask.cs b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Tasks/GetPokemonSettingsTask.cs new file mode 100644 index 000000000..9ec6cd9b8 --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Tasks/GetPokemonSettingsTask.cs @@ -0,0 +1,30 @@ +#region using directives + +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Service.WebSocketHandler.GetCommands.Events; +using PoGo.NecroBot.Logic.State; +using SuperSocket.WebSocket; + +#endregion + +namespace PoGo.NecroBot.Logic.Service.WebSocketHandler.GetCommands.Tasks +{ + internal class GetPokemonSettingsTask + { + public static async Task Execute(ISession session, WebSocketSession webSocketSession, string requestID) + { + //using (var blocker = new BlockableScope(session, BotActions.PokemonSettings)) + { + // if (!await blocker.WaitToRun()) return; + + var settings = await session.Inventory.GetPokemonSettings(); + webSocketSession.Send(EncodingHelper.Serialize(new WebResponce + { + Command = "PokemonSettings", + Data = settings, + RequestID = requestID + })); + } + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Tasks/GetPokemonSnipeListTask.cs b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Tasks/GetPokemonSnipeListTask.cs new file mode 100644 index 000000000..129296fc8 --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Tasks/GetPokemonSnipeListTask.cs @@ -0,0 +1,22 @@ +#region using directives + +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Service.WebSocketHandler.GetCommands.Events; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Tasks; +using SuperSocket.WebSocket; + +#endregion + +namespace PoGo.NecroBot.Logic.Service.WebSocketHandler.GetCommands.Tasks +{ + internal class GetPokemonSnipeListTask + { + public static async Task Execute(ISession session, WebSocketSession webSocketSession, string requestID) + { + var allItems = await HumanWalkSnipeTask.GetCurrentQueueItems(session); + + webSocketSession.Send(EncodingHelper.Serialize(new SnipeListResponce(allItems, requestID))); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Tasks/GetTrainerProfileTask.cs b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Tasks/GetTrainerProfileTask.cs new file mode 100644 index 000000000..7148cb654 --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/WebSocketHandler/GetCommands/Tasks/GetTrainerProfileTask.cs @@ -0,0 +1,30 @@ +#region using directives + +using System.Linq; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Service.WebSocketHandler.GetCommands.Events; +using PoGo.NecroBot.Logic.Service.WebSocketHandler.GetCommands.Helpers; +using PoGo.NecroBot.Logic.State; +using SuperSocket.WebSocket; + +#endregion + +namespace PoGo.NecroBot.Logic.Service.WebSocketHandler.GetCommands.Tasks +{ + internal class GetTrainerProfileTask + { + public static async Task Execute(ISession session, WebSocketSession webSocketSession, string requestID) + { + //using (var blocker = new BlockableScope(session, BotActions.GetProfile)) + { + // if (!await blocker.WaitToRun()) return; + + var playerStats = (await session.Inventory.GetPlayerStats()).FirstOrDefault(); + if (playerStats == null) + return; + var tmpData = new TrainerProfileWeb(session.Profile.PlayerData, playerStats); + webSocketSession.Send(EncodingHelper.Serialize(new TrainerProfileResponce(tmpData, requestID))); + } + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/WebSocketHandler/IWebSocketRequestHandler.cs b/PoGo.NecroBot.Logic/Service/WebSocketHandler/IWebSocketRequestHandler.cs new file mode 100644 index 000000000..db8db512a --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/WebSocketHandler/IWebSocketRequestHandler.cs @@ -0,0 +1,16 @@ +#region using directives + +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.State; +using SuperSocket.WebSocket; + +#endregion + +namespace PoGo.NecroBot.Logic.Service.WebSocketHandler +{ + internal interface IWebSocketRequestHandler + { + string Command { get; } + Task Handle(ISession session, WebSocketSession webSocketSession, dynamic message); + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/WebSocketHandler/IWebSocketResponce.cs b/PoGo.NecroBot.Logic/Service/WebSocketHandler/IWebSocketResponce.cs new file mode 100644 index 000000000..851898bd6 --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/WebSocketHandler/IWebSocketResponce.cs @@ -0,0 +1,9 @@ +namespace PoGo.NecroBot.Logic.Service.WebSocketHandler +{ + internal interface IWebSocketResponce + { + string RequestID { get; } + string Command { get; } + dynamic Data { get; } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/WebSocketHandler/WebSocketEventManager.cs b/PoGo.NecroBot.Logic/Service/WebSocketHandler/WebSocketEventManager.cs new file mode 100644 index 000000000..607862037 --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/WebSocketHandler/WebSocketEventManager.cs @@ -0,0 +1,59 @@ +#region using directives + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.State; +using SuperSocket.WebSocket; + +#endregion + +namespace PoGo.NecroBot.Logic.Service.WebSocketHandler +{ + internal class WebSocketEventManager + { + private readonly Dictionary _registerdHandlers = + new Dictionary(); + + public void RegisterHandler(string actionName, IWebSocketRequestHandler action) + { + try + { + _registerdHandlers.Add(actionName, action); + } + catch + { + // ignore + } + } + + public async Task Handle(ISession session, WebSocketSession webSocketSession, dynamic message) + { + if (_registerdHandlers.ContainsKey((string) message.Command)) + { + await _registerdHandlers[(string) message.Command].Handle(session, webSocketSession, message); + } + } + + // Registers all IWebSocketRequestHandler's automatically. + + public static WebSocketEventManager CreateInstance() + { + var manager = new WebSocketEventManager(); + + var type = typeof(IWebSocketRequestHandler); + var types = AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(s => s.GetTypes()) + .Where(p => type.IsAssignableFrom(p) && p.IsClass); + + foreach (var plugin in types) + { + var instance = (IWebSocketRequestHandler) Activator.CreateInstance(plugin); + manager.RegisterHandler(instance.Command, instance); + } + + return manager; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/WebSocketInterface.cs b/PoGo.NecroBot.Logic/Service/WebSocketInterface.cs new file mode 100644 index 000000000..7ea652d3b --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/WebSocketInterface.cs @@ -0,0 +1,233 @@ +#region using directives + +using System.Linq; +using System; +using System.Collections.Generic; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using PoGo.NecroBot.Logic.Service.WebSocketHandler; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.Logging; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Tasks; +using SuperSocket.SocketBase; +using SuperSocket.SocketBase.Config; +using SuperSocket.WebSocket; + +#endregion + +namespace PoGo.NecroBot.Logic.Service +{ + public class WebSocketInterface + { + private readonly WebSocketServer _server; + private readonly Session _session; + private readonly WebSocketEventManager _websocketHandler; + private PokeStopListEvent _lastPokeStopList; + private ProfileEvent _lastProfile; + + public WebSocketInterface(int port, Session session) + { + _session = session; + var translations = session.Translation; + _server = new WebSocketServer(); + _websocketHandler = WebSocketEventManager.CreateInstance(); + var config = new ServerConfig + { + Name = "NecroWebSocket", + Mode = SocketMode.Tcp, + MaxRequestLength = int.MaxValue, + Certificate = new CertificateConfig + { + FilePath = @"cert.pfx", + Password = "necro" + }, + Listeners = new List + { + new ListenerConfig + { + Ip = "Any", + Port = port, + Security = "tls" + }, + new ListenerConfig + { + Ip = "Any", + Port = port + 1, + Security = "none" + } + } + }; + + var setupComplete = _server.Setup(config); + + if (setupComplete == false) + { + Logger.Write(translations.GetTranslation(TranslationString.WebSocketFailStart, port), LogLevel.Error); + return; + } + + _server.NewMessageReceived += HandleMessage; + _server.NewSessionConnected += HandleSession; + + if (_server.Start()) + { + Logger.Write(translations.GetTranslation(TranslationString.WebSocketStarted, port, port + 1), LogLevel.Info); + + } + else + { + Logger.Write($"Counld't start socket server at port {port}, this port may be in use or blocked, please change your config to another port and restart bot if you want to use web gui.", LogLevel.Error); + } + } + + private void Broadcast(string message) + { + foreach (var session in _server.GetAllSessions()) + { + try + { + session.Send(message); + } + catch (Exception) + { +#if DEBUG + //Logger.Write(ex.Message); +#endif + } + } + } + + private void HandleEvent(PokeStopListEvent evt) + { + if (_lastPokeStopList != null) + { + _lastPokeStopList.Forts.RemoveAll(x => evt.Forts.Any(t => x.Id == x.Id)); + _lastPokeStopList.Forts.AddRange(evt.Forts); + } + else + _lastPokeStopList = evt; + } + + private void HandleEvent(ProfileEvent evt) + { + _lastProfile = evt; + } + + private async void HandleMessage(WebSocketSession session, string message) + { + switch (message) + { + case "PokemonList": + await PokemonListTask.Execute(_session); + break; + case "EggsList": + await EggsListTask.Execute(_session); + break; + case "InventoryList": + await InventoryListTask.Execute(_session); + break; + case "PokemonSnipeList": + await HumanWalkSnipeTask.ExecuteFetchData(_session); + break; + } + + // Setup to only send data back to the session that requested it. + try + { + dynamic decodedMessage = JObject.Parse(message); + var handle = _websocketHandler?.Handle(_session, session, decodedMessage); + if (handle != null) + await handle; + } + catch (Exception) + { +#if DEBUG + //Logger.Write(ex.Message); +#endif + } + + // When we first get a message from the web socket, turn off log buffering. + // This allows us to flush out buffered LogEvent messages to the GUI. + Logger.TurnOffLogBuffering(); + } + + private void HandleSession(WebSocketSession session) + { + if (_lastProfile != null) + session.Send(Serialize(_lastProfile)); + + if (_lastPokeStopList != null) + session.Send(Serialize(_lastPokeStopList)); + + try + { + session.Send(Serialize(new UpdatePositionEvent + { + Latitude = _session.Client.CurrentLatitude, + Longitude = _session.Client.CurrentLongitude + })); + } + catch (Exception ) + { +#if DEBUG + //Logger.Write(ex.Message); +#endif + } + } + + public void HandleEvent(IEvent evt) + { + } + + public void Listen(IEvent evt, Session session) + { + dynamic eve = evt; + + try + { + HandleEvent(eve); + } + catch (Exception ) + { +#if DEBUG + //Logger.Write(ex.Message); +#endif + // ignored + } + + Broadcast(Serialize(eve)); + } + + private static string Serialize(dynamic evt) + { + var jsonSerializerSettings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All }; + + // Add custom seriaizer to convert uong to string (ulong shoud not appear to json according to json specs) + jsonSerializerSettings.Converters.Add(new IdToStringConverter()); + + return JsonConvert.SerializeObject(evt, Formatting.None, jsonSerializerSettings); + } + } + + public class IdToStringConverter : JsonConverter + { + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, + JsonSerializer serializer) + { + var jt = JToken.ReadFrom(reader); + return jt.Value(); + } + + public override bool CanConvert(Type objectType) + { + return typeof(long) == objectType || typeof(ulong) == objectType; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + serializer.Serialize(writer, value.ToString()); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Service/YoursDirectionsService.cs b/PoGo.NecroBot.Logic/Service/YoursDirectionsService.cs new file mode 100644 index 000000000..77b442b0f --- /dev/null +++ b/PoGo.NecroBot.Logic/Service/YoursDirectionsService.cs @@ -0,0 +1,55 @@ +using System; +using System.IO; +using System.Net; +using PoGo.NecroBot.Logic.Model.Yours; +using PoGo.NecroBot.Logic.State; +using GeoCoordinatePortable; + +namespace PoGo.NecroBot.Logic.Service +{ + class YoursDirectionsService + { + private readonly ISession _session; + + public YoursDirectionsService(ISession session) + { + _session = session; + } + + public YoursWalk GetDirections(GeoCoordinate sourceLocation, GeoCoordinate destLocation) + { + WebRequest request = WebRequest.Create(GetUrl(sourceLocation, destLocation)); + request.Credentials = CredentialCache.DefaultCredentials; + + try + { + using (WebResponse response = request.GetResponse()) + { + using (Stream dataStream = response.GetResponseStream()) + using (StreamReader reader = new StreamReader(dataStream)) + { + string responseFromServer = reader.ReadToEnd(); + return YoursWalk.Get(responseFromServer); + } + } + } + catch (Exception) + { + } + + return null; + } + + private string GetUrl(GeoCoordinate sourceLocation, GeoCoordinate destLocation) + { + string url = $"http://www.yournavigation.org/api/dev/route.php?format=geojson&flat={sourceLocation.Latitude}&flon={sourceLocation.Longitude}&tlat={destLocation.Latitude}&tlon={destLocation.Longitude}&fast=1&layer=mapnik"; + + if (!string.IsNullOrEmpty(_session.LogicSettings.YoursWalkHeuristic)) + url += $"&v={_session.LogicSettings.YoursWalkHeuristic}"; + else + url += $"&v=bicycle"; + + return url; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/State/BlockableScope.cs b/PoGo.NecroBot.Logic/State/BlockableScope.cs new file mode 100644 index 000000000..7dc0a2151 --- /dev/null +++ b/PoGo.NecroBot.Logic/State/BlockableScope.cs @@ -0,0 +1,29 @@ +using System; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Model; + +namespace PoGo.NecroBot.Logic.State +{ + public class BlockableScope : IDisposable + { + private ISession session; + + private BotActions action; + + public BlockableScope(ISession session, BotActions action) + { + this.session = session; + this.action = action; + } + + public async Task WaitToRun(int timeout = 60000) + { + return await session.WaitUntilActionAccept(action, timeout).ConfigureAwait(false); + } + + public void Dispose() + { + session.Actions.RemoveAll(x => x == action); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/State/BotSwitcherState.cs b/PoGo.NecroBot.Logic/State/BotSwitcherState.cs new file mode 100644 index 000000000..15ef2a2bc --- /dev/null +++ b/PoGo.NecroBot.Logic/State/BotSwitcherState.cs @@ -0,0 +1,52 @@ +using System.Threading; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.Tasks; +using POGOProtos.Enums; +using System; + +namespace PoGo.NecroBot.Logic.State +{ + public class BotSwitcherState : IState + { + private EncounteredEvent encounterData; + private PokemonId pokemonToCatch; + + public BotSwitcherState(PokemonId pokemon) + { + pokemonToCatch = pokemon; + } + + public BotSwitcherState(PokemonId pokemon, EncounteredEvent encounterData) : this(pokemon) + { + this.encounterData = encounterData; + } + + public async Task Execute(ISession session, CancellationToken cancellationToken) + { + if (encounterData == null) + { + session.Client.Player.UpdatePlayerLocation(session.Client.CurrentLatitude, + session.Client.CurrentLongitude, session.Client.CurrentAltitude, 10); + await Task.Delay(1000, cancellationToken).ConfigureAwait(false); + await CatchNearbyPokemonsTask.Execute(session, cancellationToken, pokemonToCatch).ConfigureAwait(false); + await CatchLurePokemonsTask.Execute(session, cancellationToken).ConfigureAwait(false); + } + else + { + //snipe pokemon + await MSniperServiceTask.CatchWithSnipe(session, new MSniperServiceTask.MSniperInfo2() + { + AddedTime = DateTime.Now, + Latitude = encounterData.Latitude, + Longitude = encounterData.Longitude, + Iv = encounterData.IV, + PokemonId =(short)encounterData.PokemonId, + SpawnPointId = encounterData.SpawnPointId, + EncounterId = Convert.ToUInt64(encounterData.EncounterId) + }, session.CancellationTokenSource.Token).ConfigureAwait(false); + } + return new InfoState(); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/State/CatchState.cs b/PoGo.NecroBot.Logic/State/CatchState.cs new file mode 100644 index 000000000..7f4c498af --- /dev/null +++ b/PoGo.NecroBot.Logic/State/CatchState.cs @@ -0,0 +1,174 @@ +#region using directives + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Newtonsoft.Json; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.Model; +using PoGo.NecroBot.Logic.Tasks; +using PoGo.NecroBot.Logic.Utils; +using POGOProtos.Enums; +using POGOProtos.Map.Pokemon; +using POGOProtos.Networking.Responses; + +#endregion + +namespace PoGo.NecroBot.Logic.State +{ + public class CatchState : IState + { + public class CatchablePokemon + { + public ulong EncounteredId { get; set; } + public bool Checked { get; set; } + public DateTime ExpiredTime { get; set; } + public double Latitude { get; internal set; } + public double Longitude { get; internal set; } + public string SpawnId { get; internal set; } + + public PokemonId PokemonId { get; internal set; } + + public MapPokemon ToMapPokemon() + { + return new MapPokemon() + { + EncounterId = EncounteredId, + Latitude = Latitude, + Longitude = Longitude, + SpawnPointId = SpawnId, + PokemonId = PokemonId + }; + } + } + + public async Task Execute(ISession session, CancellationToken cancellationToken) + { + //Console.WriteLine($"waiting data....., {data.Count}"); + CatchablePokemon pkm = null; + var currentLatitude = session.Client.CurrentLatitude; + var currentLongitude = session.Client.CurrentLongitude; + + do + { + pkm = data.FirstOrDefault(p => !p.Checked && + p.ExpiredTime > DateTime.Now && + LocationUtils.CalculateDistanceInMeters( + currentLatitude, + currentLongitude, + p.Latitude, + p.Longitude + ) < 100); + if (pkm == null) break; + + EncounterResponse encounter; + try + { + //await + // LocationUtils.UpdatePlayerLocationWithAltitude(session, + // new GeoCoordinate(pkm.Latitude, pkm.Longitude, session.Client.CurrentAltitude), 0).ConfigureAwait(false); // Set speed to 0 for random speed. + + await session.Navigation.Move( + new MapLocation(pkm.Latitude, pkm.Longitude, session.Client.CurrentAltitude), + null, session, cancellationToken).ConfigureAwait(false); + encounter = await session.Client.Encounter.EncounterPokemon(pkm.EncounteredId, pkm.SpawnId).ConfigureAwait(false); + } + finally + { + //await + // LocationUtils.UpdatePlayerLocationWithAltitude(session, + // new GeoCoordinate(currentLatitude, currentLongitude, session.Client.CurrentAltitude), 0).ConfigureAwait(false); // Set speed to 0 for random speed. + } + switch (encounter.Status) + { + case EncounterResponse.Types.Status.EncounterSuccess: + await CatchPokemonTask.Execute(session, cancellationToken, encounter, pkm.ToMapPokemon(), + currentFortData: null, sessionAllowTransfer: true).ConfigureAwait(false); + break; + case EncounterResponse.Types.Status.PokemonInventoryFull: + if (session.LogicSettings.TransferDuplicatePokemon) + { + await TransferDuplicatePokemonTask.Execute(session, cancellationToken).ConfigureAwait(false); + } + else + { + session.EventDispatcher.Send(new WarnEvent + { + Message = session.Translation.GetTranslation(TranslationString.InvFullTransferManually) + }); + } + + if (EvolvePokemonTask.IsActivated(session)) + { + await EvolvePokemonTask.Execute(session, cancellationToken).ConfigureAwait(false); + } + break; + + default: + session.EventDispatcher.Send(new WarnEvent + { + Message = + session.Translation.GetTranslation( + TranslationString.EncounterProblem, encounter.Status) + }); + break; + } + pkm.Checked = true; + await Task.Delay(1000).ConfigureAwait(false); + } while (pkm != null); + await Task.Delay(3000).ConfigureAwait(false); + return this; + } + + private static List data = new List(); + + public static PokemonId GetId(string name) + { + var t = name[0]; + var realName = new StringBuilder(name.ToLower()); + realName[0] = t; + try + { + var p = (PokemonId) Enum.Parse(typeof(PokemonId), realName.ToString()); + return p; + } + catch (Exception) + { + } + return PokemonId.Missingno; + } + + private static CatchablePokemon Map(HumanWalkSnipeTask.FastPokemapItem result) + { + return new CatchablePokemon + { + Latitude = result.lnglat.coordinates[1], + Longitude = result.lnglat.coordinates[0], + PokemonId = GetId(result.pokemon_id), + ExpiredTime = result.expireAt.ToLocalTime(), + SpawnId = result.spawn_id, + //Source = "Fastpokemap" + EncounteredId = Convert.ToUInt64(result.encounter_id) + }; + } + + public static void AddFastPokemapItem(dynamic jsonData) + { + var list = JsonConvert.DeserializeObject>(jsonData.ToString()); + + //List result = new List(); + foreach (var item in list) + { + var snipeItem = Map(item); + if (!data.Exists(p => p.EncounteredId == snipeItem.EncounteredId)) + { + data.Add(snipeItem); + } + } + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/State/CheckTosState.cs b/PoGo.NecroBot.Logic/State/CheckTosState.cs new file mode 100644 index 000000000..4775ba15f --- /dev/null +++ b/PoGo.NecroBot.Logic/State/CheckTosState.cs @@ -0,0 +1,354 @@ +#region using directives + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Google.Protobuf.Collections; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.Forms; +using PoGo.NecroBot.Logic.Utils; +using POGOProtos.Enums; +using POGOProtos.Networking.Responses; +using POGOProtos.Data.Player; +using System; +using System.IO; + +#endregion + +namespace PoGo.NecroBot.Logic.State +{ + public class CheckTosState : IState + { + public async Task Execute(ISession session, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + // Always get a fresh PlayerData when checking tutorial state. + var tutState = (await session.Client.Player.GetPlayer().ConfigureAwait(false)).PlayerData.TutorialState; + + if (tutState.Contains(TutorialState.FirstTimeExperienceComplete)) + { + // If we somehow marked the tutorial as complete but have not yet created an avatar, + // then create it. + if (!tutState.Contains(TutorialState.AvatarSelection)) + { + var avatarRes = await session.Client.Player.SetAvatar(new PlayerAvatar() + { + Backpack = 0, + Eyes = 0, + Avatar = 0, + Hair = 0, + Hat = 0, + Pants = 0, + Shirt = 0, + Shoes = 0, + Skin = 0 + }).ConfigureAwait(false); + + EncounterTutorialCompleteResponse res = await + session.Client.Misc.MarkTutorialComplete(new RepeatedField() + { + TutorialState.AvatarSelection + }).ConfigureAwait(false); + } + + if (!tutState.Contains(TutorialState.PokemonBerry)) + { + await + session.Client.Misc.MarkTutorialComplete(new RepeatedField() + { + TutorialState.PokemonBerry + }).ConfigureAwait(false); + session.EventDispatcher.Send(new NoticeEvent() + { + Message = "Finish extra tutorial : Berry tutorial" + }); + await DelayingUtils.DelayAsync(3000, 2000, cancellationToken).ConfigureAwait(false); + } + + return new InfoState(); + } + + if (!tutState.Contains(TutorialState.LegalScreen)) + { + EncounterTutorialCompleteResponse res = await + session.Client.Misc.MarkTutorialComplete(new RepeatedField() + { + TutorialState.LegalScreen + }).ConfigureAwait(false); + + if (res.Result == EncounterTutorialCompleteResponse.Types.Result.Success) + { + session.EventDispatcher.Send(new NoticeEvent() + { + Message = "Just read the Niantic ToS, looks legit, accepting!" + }); + await DelayingUtils.DelayAsync(5000, 2000, cancellationToken).ConfigureAwait(false); + } + else + { + session.EventDispatcher.Send(new NoticeEvent() + { + Message = "Error reading the Niantic ToS!" + }); + } + } + + if (session.LogicSettings.SkipFirstTimeTutorial) + { + session.EventDispatcher.Send(new NoticeEvent() + { + Message = "Skipping the first time tutorial." + }); + + return new InfoState(); + } + + if (!session.LogicSettings.AutoFinishTutorial) + { + InitialTutorialForm form = new InitialTutorialForm(this, tutState, session); + + if (form.ShowDialog() == DialogResult.OK) + { + } + else + { + return new CheckTosState(); + } + + } + else + { + if (!tutState.Contains(TutorialState.AvatarSelection)) + { + //string genderString = GlobalSettings.PromptForString(session.Translation, session.Translation.GetTranslation(TranslationString.FirstStartSetupAutoCompleteTutGenderPrompt), new string[] { "Male", "Female" }, "You didn't set a valid gender.", false); + + //Gender gen; + //switch (genderString) + //{ + // case "Male": + // case "male": + // gen = Gender.Male; + // break; + // case "Female": + // case "female": + // gen = Gender.Female; + // break; + // default: + // // We should never get here, since the prompt should only allow valid options. + // gen = Gender.Male; + // break; + //} + + + var avatarRes = await session.Client.Player.SetAvatar(new PlayerAvatar() + { + Backpack = 0, + Eyes = 0, + Hair = 0, + Hat = 0, + Pants = 0, + Shirt = 0, + Shoes = 0, + Skin = 0 , + Avatar =1 + }).ConfigureAwait(false); + if (avatarRes.Status == SetAvatarResponse.Types.Status.AvatarAlreadySet || + avatarRes.Status == SetAvatarResponse.Types.Status.Success) + { + await session.Client.Misc.MarkTutorialComplete(new RepeatedField() + { + TutorialState.AvatarSelection + }).ConfigureAwait(false); + session.EventDispatcher.Send(new NoticeEvent() + { + Message = $"Selected your avatar, now you are Male!" + }); + } + } + if (!tutState.Contains(TutorialState.PokemonCapture)) + { + await CatchFirstPokemon(session, cancellationToken).ConfigureAwait(false); + } + if (!tutState.Contains(TutorialState.NameSelection)) + { + await SelectNickname(session, cancellationToken).ConfigureAwait(false); + } + if (!tutState.Contains(TutorialState.FirstTimeExperienceComplete)) + { + await + session.Client.Misc.MarkTutorialComplete(new RepeatedField() + { + TutorialState.FirstTimeExperienceComplete + }).ConfigureAwait(false); + session.EventDispatcher.Send(new NoticeEvent() + { + Message = "First time experience complete, looks like i just spinned an virtual pokestop :P" + }); + await DelayingUtils.DelayAsync(3000, 2000, cancellationToken).ConfigureAwait(false); + } + + if (!tutState.Contains(TutorialState.PokemonBerry)) + { + await + session.Client.Misc.MarkTutorialComplete(new RepeatedField() + { + TutorialState.PokemonBerry + }).ConfigureAwait(false); + session.EventDispatcher.Send(new NoticeEvent() + { + Message = "Finish berry tutorial..." + }); + await DelayingUtils.DelayAsync(3000, 2000, cancellationToken).ConfigureAwait(false); + } + + } + return new InfoState(); + } + + public async Task CatchFirstPokemon(ISession session, CancellationToken cancellationToken) + { + var firstPokeList = new List + { + PokemonId.Bulbasaur, + PokemonId.Charmander, + PokemonId.Squirtle + }; + //string pokemonString = GlobalSettings.PromptForString(session.Translation, + // session.Translation.GetTranslation(TranslationString.FirstStartSetupAutoCompleteTutStarterPrompt), + // new string[] { "Bulbasaur", "Charmander", "Squirtle" }, "You didn't enter a valid pokemon.", false); + //var firstpokenum = 0; + //switch (pokemonString) + //{ + // case "Bulbasaur": + // case "bulbasaur": + // firstpokenum = 0; + // break; + // case "Charmander": + // case "charmander": + // firstpokenum = 1; + // break; + // case "Squirtle": + // case "squirtle": + // firstpokenum = 2; + // break; + // default: + // // We should never get here. + // firstpokenum = 0; + // break; + //} + + var firstPoke = firstPokeList[(new Random()).Next(0,2)]; + + var res = await session.Client.Encounter.EncounterTutorialComplete(firstPoke).ConfigureAwait(false); + await DelayingUtils.DelayAsync(7000, 2000, cancellationToken).ConfigureAwait(false); + if (res.Result != EncounterTutorialCompleteResponse.Types.Result.Success) return false; + session.EventDispatcher.Send(new NoticeEvent() + { + Message = $"Caught Tutorial pokemon! it's {firstPoke}!" + }); + return true; + } + + public async Task SelectNickname(ISession session, CancellationToken cancellationToken) + { + var nickname = !session.Settings.Username.Contains("@") ? session.Settings.Username : session.Settings.Username.Split(new char[] { '@' })[0]; + + while (true) + { + //string nickname = GlobalSettings.PromptForString(session.Translation, + // session.Translation.GetTranslation(TranslationString.FirstStartSetupAutoCompleteTutNicknamePrompt), + // null, "You entered an invalid nickname."); + + //if (nickname.Length > 15 || nickname.Length == 0) + //{ + // session.EventDispatcher.Send(new ErrorEvent() + // { + // Message = "Your desired nickname is too long (max length 15 characters)!" + // }); + // continue; + //} + + if (nickname.Length > 15) nickname = nickname.Substring(0, 15); + + var res = await session.Client.Misc.ClaimCodename(nickname).ConfigureAwait(false); + + bool markTutorialComplete = false; + string errorText = null; + string warningText = null; + string infoText = null; + bool nameIsvalid = true; + switch (res.Status) + { + case ClaimCodenameResponse.Types.Status.Unset: + errorText = "Unset, somehow"; + break; + case ClaimCodenameResponse.Types.Status.Success: + infoText = $"Your name is now: {res.Codename}"; + markTutorialComplete = true; + break; + case ClaimCodenameResponse.Types.Status.CodenameNotAvailable: + nameIsvalid = false; + errorText = $"That nickname ({nickname}) isn't available, pick another one!"; + break; + case ClaimCodenameResponse.Types.Status.CodenameNotValid: + nameIsvalid = false; + errorText = $"That nickname ({nickname}) isn't valid, pick another one!"; + break; + case ClaimCodenameResponse.Types.Status.CurrentOwner: + nameIsvalid = false; + warningText = $"You already own that nickname!"; + markTutorialComplete = true; + break; + case ClaimCodenameResponse.Types.Status.CodenameChangeNotAllowed: + warningText = "You can't change your nickname anymore!"; + markTutorialComplete = true; + break; + default: + errorText = "Unknown Niantic error while changing nickname."; + break; + } + + if (!string.IsNullOrEmpty(infoText)) + { + session.EventDispatcher.Send(new NoticeEvent() + { + Message = infoText + }); + } + else if (!string.IsNullOrEmpty(warningText)) + { + session.EventDispatcher.Send(new WarnEvent() + { + Message = warningText + }); + } + else if (!string.IsNullOrEmpty(errorText)) + { + session.EventDispatcher.Send(new ErrorEvent() + { + Message = errorText + }); + if(!nameIsvalid) + { + nickname = Path.GetFileNameWithoutExtension(Path.GetRandomFileName()); + continue; + } + + } + + if (markTutorialComplete) + { + await session.Client.Misc.MarkTutorialComplete(new RepeatedField() + { + TutorialState.NameSelection + }).ConfigureAwait(false); + + await DelayingUtils.DelayAsync(3000, 2000, cancellationToken).ConfigureAwait(false); + return res.Status == ClaimCodenameResponse.Types.Status.Success; + } + } + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/State/FarmState.cs b/PoGo.NecroBot.Logic/State/FarmState.cs new file mode 100644 index 000000000..2be29827d --- /dev/null +++ b/PoGo.NecroBot.Logic/State/FarmState.cs @@ -0,0 +1,56 @@ +#region using directives + +using System.Threading; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Tasks; + +#endregion + +namespace PoGo.NecroBot.Logic.State +{ + public class FarmState : IState + { + public async Task Execute(ISession session, CancellationToken cancellationToken) + { + if (session.LogicSettings.UseNearActionRandom) + { + await HumanRandomActionTask.Execute(session, cancellationToken).ConfigureAwait(false); + } + else + { + if (session.LogicSettings.UseEggIncubators) + await UseIncubatorsTask.Execute(session, cancellationToken).ConfigureAwait(false); + if (session.LogicSettings.TransferDuplicatePokemon) + await TransferDuplicatePokemonTask.Execute(session, cancellationToken).ConfigureAwait(false); + if (session.LogicSettings.TransferWeakPokemon) + await TransferWeakPokemonTask.Execute(session, cancellationToken).ConfigureAwait(false); + if (EvolvePokemonTask.IsActivated(session)) + await EvolvePokemonTask.Execute(session, cancellationToken).ConfigureAwait(false); + if (session.LogicSettings.UseLuckyEggConstantly) + await UseLuckyEggConstantlyTask.Execute(session, cancellationToken).ConfigureAwait(false); + if (session.LogicSettings.UseIncenseConstantly) + await UseIncenseConstantlyTask.Execute(session, cancellationToken).ConfigureAwait(false); + + await GetPokeDexCount.Execute(session, cancellationToken).ConfigureAwait(false); + + if (session.LogicSettings.RenamePokemon) + await RenamePokemonTask.Execute(session, cancellationToken).ConfigureAwait(false); + + await RecycleItemsTask.Execute(session, cancellationToken).ConfigureAwait(false); + + if (session.LogicSettings.AutomaticallyLevelUpPokemon) + await LevelUpPokemonTask.Execute(session, cancellationToken).ConfigureAwait(false); + } + + await SelectBuddyPokemonTask.Execute(session, cancellationToken).ConfigureAwait(false); + + + if (session.LogicSettings.UseGpxPathing) + await FarmPokestopsGpxTask.Execute(session, cancellationToken).ConfigureAwait(false); + else + await FarmPokestopsTask.Execute(session, cancellationToken).ConfigureAwait(false); + + return this; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/State/GymTeamState.cs b/PoGo.NecroBot.Logic/State/GymTeamState.cs new file mode 100644 index 000000000..be96f09e7 --- /dev/null +++ b/PoGo.NecroBot.Logic/State/GymTeamState.cs @@ -0,0 +1,219 @@ +using PoGo.NecroBot.Logic.Tasks; +using POGOProtos.Data; +using POGOProtos.Map.Fort; +using POGOProtos.Networking.Responses; +using POGOProtos.Settings.Master; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace PoGo.NecroBot.Logic.State +{ + public class GymTeamState : IDisposable + { + /// + /// Cache time in seconds + /// + private const long _cacheTime = 2 * 60; + public List MyPokemons { get; private set; } + public List OtherDefenders { get; private set; } + public List MyTeam { get; private set; } + public IEnumerable MoveSettings { get; set; } + public long TimeToDodge { get; set; } + public long LastWentDodge { get; set; } + public SwitchPokemonData SwithAttacker { get; set; } + public string BerriesGymId { get; set; } + public string CapturedGymId { get; set; } + + public GymTeamState() + { + MyTeam = new List(); + MyPokemons = new List(); + OtherDefenders = new List(); + TimeToDodge = 0; + SwithAttacker = null; + } + + public void AddPokemon(ISession session, PokemonData pokemon, bool isMine = true) + { + if (isMine && MyPokemons.Any(a => a.Data.Id == pokemon.Id)) + return; + + if (!isMine && OtherDefenders.Any(a => a.Data.Id == pokemon.Id)) + return; + + if (isMine) + MyPokemons.Add(new MyPokemonStat(session, pokemon)); + else + OtherDefenders.Add(new AnyPokemonStat(session, pokemon)); + } + + public void AddToTeam(ISession session, PokemonData pokemon) + { + if (!MyPokemons.Any(a => a.Data.Id == pokemon.Id)) + MyPokemons.Add(new MyPokemonStat(session, pokemon)); + + if (!MyTeam.Any(a => a.Attacker.Id == pokemon.Id)) + MyTeam.Add(new GymPokemon() { Attacker = pokemon, HpState = pokemon.StaminaMax }); + } + + public async Task LoadMyPokemons(ISession session) + { + MyPokemons.Clear(); + var pokemons = await session.Inventory.GetPokemons().ConfigureAwait(false); + foreach (var pokemon in pokemons.Where(w => w.Cp >= session.LogicSettings.GymConfig.MinCpToUseInAttack)) + { + MyPokemonStat mps = new MyPokemonStat(session, pokemon); + MyPokemons.Add(mps); + } + } + + public void Dispose() + { + if (MyTeam != null) + MyTeam.Clear(); + + if (MyPokemons != null) + MyPokemons.Clear(); + + if (OtherDefenders != null) + OtherDefenders.Clear(); + } + } + + public class GymPokemon : IDisposable + { + public PokemonData Attacker { get; set; } + + public int HpState { get; set; } + + public void Dispose() + { + throw new NotImplementedException(); + } + } + + public class AnyPokemonStat : IDisposable + { + public PokemonData Data { get; set; } + + public MoveSettings Attack { get; set; } + + public MoveSettings SpecialAttack { get; set; } + + public POGOProtos.Enums.PokemonType MainType { get; set; } + + public POGOProtos.Enums.PokemonType ExtraType { get; set; } + + public AnyPokemonStat(ISession session, PokemonData pokemon) + { + Data = pokemon; + + var pokemonsSetting = session.Inventory.GetPokemonSettings().Result; + + MainType = pokemonsSetting.Where(f => f.PokemonId == Data.PokemonId).Select(s => s.Type).FirstOrDefault(); + ExtraType = pokemonsSetting.Where(f => f.PokemonId == Data.PokemonId).Select(s => s.Type2).FirstOrDefault(); + + Attack = session.Inventory.GetMoveSetting(Data.Move1).Result; + SpecialAttack = session.Inventory.GetMoveSetting(Data.Move2).Result; + } + + public void Dispose() + { + + } + } + + public class MyPokemonStat : AnyPokemonStat + { + + public Dictionary TypeFactor { get; private set; } + + public MyPokemonStat(ISession session, PokemonData pokemon) : base(session, pokemon) + { + TypeFactor = new Dictionary(); + + foreach (var type in Enum.GetValues(typeof(POGOProtos.Enums.PokemonType))) + { + GetFactorAgainst((POGOProtos.Enums.PokemonType)type); + } + } + + private int GetFactorAgainst(POGOProtos.Enums.PokemonType type) + { + if (TypeFactor.Keys.Contains(type)) + return TypeFactor[type]; + + int factor = 0; + if (UseGymBattleTask.GetBestTypes(type).Any(a => a == Attack.PokemonType)) + { + factor += 2; + if (MainType == Attack.PokemonType || ExtraType == Attack.PokemonType) + factor += 1; + } + if (UseGymBattleTask.GetWorstTypes(type).Any(a => a == Attack.PokemonType)) factor -= 2; + + if (UseGymBattleTask.GetBestTypes(type).Any(a => a == SpecialAttack.PokemonType)) + { + factor += 2; + if (MainType == SpecialAttack.PokemonType || ExtraType == SpecialAttack.PokemonType) + factor += 1; + } + if (UseGymBattleTask.GetWorstTypes(type).Any(a => a == SpecialAttack.PokemonType)) factor -= 2; + + TypeFactor.Add(type, factor); + + return factor; + } + + public int GetFactorAgainst(ISession session, int cp, bool isTraining) + { + decimal percent = 0.0M; + if (cp > Data.Cp) + percent = (decimal)Data.Cp / (decimal)cp * -100.0M; + else + percent = (decimal)cp / (decimal)Data.Cp * 100.0M; + + int factor = (int)((100.0M - Math.Abs(percent)) / 5.0M) * Math.Sign(percent); + + if (isTraining && cp <= Data.Cp) + factor -= 100; + + return factor; + } + + private int GetFactorAgainst(PokemonSettings pokemon) + { + int factor = GetFactorAgainst(pokemon.Type); + factor += GetFactorAgainst(pokemon.Type2); + return factor; + } + + // jjskuld - Ignore CS0108 warning for now. +#pragma warning disable 0108 + public void Dispose() + { + if (TypeFactor != null) + TypeFactor.Clear(); + } +#pragma warning restore 0108 + } + + public class SwitchPokemonData + { + public ulong OldAttacker { get; private set; } + public ulong NewAttacker { get; private set; } + + public int AttackDuration + { + get { return 1000; } + } + + public SwitchPokemonData(ulong Old, ulong New) + { + OldAttacker = Old; + NewAttacker = New; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/State/IState.cs b/PoGo.NecroBot.Logic/State/IState.cs new file mode 100644 index 000000000..8bf0d267c --- /dev/null +++ b/PoGo.NecroBot.Logic/State/IState.cs @@ -0,0 +1,14 @@ +#region using directives + +using System.Threading; +using System.Threading.Tasks; + +#endregion + +namespace PoGo.NecroBot.Logic.State +{ + public interface IState + { + Task Execute(ISession session, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/State/IdleState.cs b/PoGo.NecroBot.Logic/State/IdleState.cs new file mode 100644 index 000000000..d6731087d --- /dev/null +++ b/PoGo.NecroBot.Logic/State/IdleState.cs @@ -0,0 +1,60 @@ +using System; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Event; + +namespace PoGo.NecroBot.Logic.State +{ + public class IdleState : IState + { + public async Task Ping() + { + using (var client = new HttpClient()) + { + try + { + await client.GetStringAsync("https://pokehash.buddyauth.com/api/hash/versions").ConfigureAwait(false); + return true; + } + catch (Exception) + { + } + } + return false; + } + + public async Task Execute(ISession session, CancellationToken cancellationToken) + { + session.EventDispatcher.Send(new WarnEvent() + { + Message = "Hash server may be down, Bot will enter IDLE state until service becomes available. Ping interval, 5 sec..." + }); + + Console.WriteLine(); + + var start = DateTime.Now; + var lastPing = DateTime.Now; + + bool alive = false; + while (!alive) + { + alive = await Ping().ConfigureAwait(false); + lastPing = DateTime.Now; + if (!alive) + { + Console.SetCursorPosition(0, Console.CursorTop - 1); + var ts = DateTime.Now - start; + session.EventDispatcher.Send(new ErrorEvent() + { + Message = $"Hash API server down time : {ts.ToString(@"hh\:mm\:ss")} Last Ping: {lastPing.ToString("T")}" + }); + + await Task.Delay(5000).ConfigureAwait(false); + } + } + + return new LoginState(); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/State/InfoState.cs b/PoGo.NecroBot.Logic/State/InfoState.cs new file mode 100644 index 000000000..096d78475 --- /dev/null +++ b/PoGo.NecroBot.Logic/State/InfoState.cs @@ -0,0 +1,21 @@ +#region using directives + +using System.Threading; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Tasks; + +#endregion + +namespace PoGo.NecroBot.Logic.State +{ + public class InfoState : IState + { + public async Task Execute(ISession session, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + await DisplayPokemonStatsTask.Execute(session).ConfigureAwait(false); + + return new FarmState(); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/State/LoadSaveState.cs b/PoGo.NecroBot.Logic/State/LoadSaveState.cs new file mode 100644 index 000000000..02aaf293b --- /dev/null +++ b/PoGo.NecroBot.Logic/State/LoadSaveState.cs @@ -0,0 +1,156 @@ +#region using directives + +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.Utils; + +#endregion + +namespace PoGo.NecroBot.Logic.State +{ + public class LoadSaveState : IState + { + public async Task Execute(ISession session, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var coordsPath = Path.Combine(session.LogicSettings.ProfileConfigPath, "LastPos.ini"); + if (File.Exists(coordsPath)) + { + var latLngFromFile = LoadPositionFromDisk(session); + if (latLngFromFile != null) + { + var distance = LocationUtils.CalculateDistanceInMeters(latLngFromFile.Item1, latLngFromFile.Item2, + session.Settings.DefaultLatitude, session.Settings.DefaultLongitude); + var lastModified = File.Exists(coordsPath) ? (DateTime?) File.GetLastWriteTime(coordsPath) : null; + if (lastModified != null) + { + var hoursSinceModified = (DateTime.Now - lastModified).HasValue + ? (double?) ((DateTime.Now - lastModified).Value.Minutes / 60.0) + : null; + if (hoursSinceModified != null && hoursSinceModified != 0) + { + var kmph = distance / 1000 / (double) hoursSinceModified; + if (kmph < 80) // If speed required to get to the default location is < 80km/hr + { + File.Delete(coordsPath); + session.EventDispatcher.Send(new WarnEvent + { + Message = + session.Translation.GetTranslation(TranslationString.RealisticTravelDetected) + }); + } + else + { + session.EventDispatcher.Send(new WarnEvent + { + Message = + session.Translation.GetTranslation(TranslationString.NotRealisticTravel, kmph.ToString("0.00")) + }); + } + } + } + } + } + + session.EventDispatcher.Send(new UpdatePositionEvent + { + Latitude = session.Client.CurrentLatitude, + Longitude = session.Client.CurrentLongitude + }); + + session.EventDispatcher.Send(new WarnEvent + { + Message = + session.Translation.GetTranslation(TranslationString.WelcomeWarning, session.Client.CurrentLatitude, + session.Client.CurrentLongitude), + RequireInput = session.LogicSettings.StartupWelcomeDelay + }); + // Bugfix: Make sure to wait for keypress + if (session.LogicSettings.StartupWelcomeDelay) Console.ReadKey(); + + + if (session.LogicSettings.UseGoogleWalk && !session.LogicSettings.UseGpxPathing) + { + if (string.IsNullOrWhiteSpace(session.LogicSettings.GoogleApiKey)) + { + session.EventDispatcher.Send(new WarnEvent + { + Message = session.Translation.GetTranslation(TranslationString.GoogleAPIWarning) + }); + } + } + + await Task.Delay(3000, cancellationToken).ConfigureAwait(false); + return new CheckTosState(); + } + private static double lastLat = 0; + private static double lastLng = 0; + public static void SaveLocationToDisk(ISession session, double lat, double lng, double speed) + { + if (session.Stats.IsSnipping) + return; + + // Make sure new location is valid. + if (!LocationUtils.IsValidLocation(lat, lng)) + return; + + // If last location is valid, make sure distance between last location and new location is less than 1000m. + if ((lastLat != 0 || lastLng != 0) && LocationUtils.CalculateDistanceInMeters(lat, lng, lastLat, lastLng) > 1000) + return; + + // Don't save new position if it is outside of our max travel distance. + if (LocationUtils.CalculateDistanceInMeters(lat, lng, session.Settings.DefaultLatitude, session.Settings.DefaultLongitude) > session.LogicSettings.MaxTravelDistanceInMeters) + return; + + lastLat = lat; + lastLng = lng; + var coordsPath = Path.Combine(session.LogicSettings.ProfileConfigPath, "LastPos.ini"); + File.WriteAllText(coordsPath, $"{lat}:{lng}"); + } + + public static Tuple LoadPositionFromDisk(ISession session) + { + if ( + File.Exists(Path.Combine(session.LogicSettings.ProfileConfigPath, "LastPos.ini")) && + File.ReadAllText(Path.Combine(session.LogicSettings.ProfileConfigPath, "LastPos.ini")).Contains(":")) + { + var latlngFromFile = + File.ReadAllText(Path.Combine(session.LogicSettings.ProfileConfigPath, "LastPos.ini")); + var latlng = latlngFromFile.Split(':'); + if (latlng[0].Length != 0 && latlng[1].Length != 0) + { + try + { + var latitude = Convert.ToDouble(latlng[0]); + var longitude = Convert.ToDouble(latlng[1]); + + if (Math.Abs(latitude) <= 90 && Math.Abs(longitude) <= 180) + { + return new Tuple(latitude, longitude); + } + session.EventDispatcher.Send(new WarnEvent + { + Message = session.Translation.GetTranslation(TranslationString.CoordinatesAreInvalid) + }); + return null; + } + catch (FormatException) + { + session.EventDispatcher.Send(new WarnEvent + { + Message = session.Translation.GetTranslation(TranslationString.CoordinatesAreInvalid) + }); + return null; + } + } + } + + return null; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/State/LoginState.cs b/PoGo.NecroBot.Logic/State/LoginState.cs new file mode 100644 index 000000000..cb9e41c8c --- /dev/null +++ b/PoGo.NecroBot.Logic/State/LoginState.cs @@ -0,0 +1,379 @@ +#region using directives + +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Google.Protobuf; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.Event.Player; +using PoGo.NecroBot.Logic.Exceptions; +using PoGo.NecroBot.Logic.Logging; +using PokemonGo.RocketAPI.Enums; +using PokemonGo.RocketAPI.Exceptions; +using POGOProtos.Enums; +using TinyIoC; +using PokemonGo.RocketAPI.Util; + +#endregion + +namespace PoGo.NecroBot.Logic.State +{ + public class LoginState : IState + { + private PokemonId pokemonToCatch; + private EncounteredEvent encounterData; + + public LoginState(PokemonId pokemonToCatch = PokemonId.Missingno, EncounteredEvent encounterData = null) + { + this.encounterData = encounterData; + this.pokemonToCatch = pokemonToCatch; + } + + public async Task Execute(ISession session, CancellationToken cancellationToken) + { + var accountManager = TinyIoCContainer.Current.Resolve(); + + // cancellationToken.ThrowIfCancellationRequested(); + session.EventDispatcher.Send(new LoginEvent( + session.Settings.AuthType, $"{session.Settings.Username}" + )); + + //session.EventDispatcher.Send(new NoticeEvent + //{ + // Message = session.Translation.GetTranslation(TranslationString.LoggingIn, session.Settings.AuthType) + //}); + + CheckLogin(session, cancellationToken); + + bool successfullyLoggedIn = false; + try + { + if (session.Settings.AuthType == AuthType.Google || session.Settings.AuthType == AuthType.Ptc) + { + session.Profile = await session.Client.Login.DoLogin().ConfigureAwait(false); + successfullyLoggedIn = true; + } + else + { + session.EventDispatcher.Send(new ErrorEvent + { + Message = session.Translation.GetTranslation(TranslationString.WrongAuthType) + }); + } + } + catch (AggregateException ae) + { + throw ae.Flatten().InnerException; + } + catch (APIBadRequestException) + { + session.EventDispatcher.Send(new ErrorEvent + { + Message = session.Translation.GetTranslation(TranslationString.LoginInvalid) + }); + + await Task.Delay(2000, cancellationToken).ConfigureAwait(false); + throw new LoginFailedException(); + } + catch (AccessTokenExpiredException) + { + session.EventDispatcher.Send(new ErrorEvent + { + Message = session.Translation.GetTranslation(TranslationString.AccessTokenExpired) + }); + return new LoginState(); + } + catch (PtcOfflineException) + { + session.EventDispatcher.Send(new ErrorEvent + { + Message = session.Translation.GetTranslation(TranslationString.PtcOffline) + }); + session.EventDispatcher.Send(new NoticeEvent + { + Message = session.Translation.GetTranslation(TranslationString.TryingAgainIn, 20) + }); + } + catch(PtcLoginException ex) + { + throw ex; + } + catch (AccountNotVerifiedException ex) + { + session.EventDispatcher.Send(new ErrorEvent + { + Message = session.Translation.GetTranslation(TranslationString.AccountNotVerified) + }); + await Task.Delay(2000, cancellationToken).ConfigureAwait(false); + throw ex; + } + catch(GoogleTwoFactorException e) + { + + session.EventDispatcher.Send(new ErrorEvent + { + RequireExit = true, + Message = e.Message + + }); + session.EventDispatcher.Send(new ErrorEvent + { + Message = session.Translation.GetTranslation(TranslationString.ExitNowAfterEnterKey) + }); + Console.Read(); + Environment.Exit(0); + + } + catch (GoogleException e) + { + if (e.Message.Contains("NeedsBrowser")) + { + session.EventDispatcher.Send(new ErrorEvent + { + Message = session.Translation.GetTranslation(TranslationString.GoogleTwoFactorAuth) + }); + session.EventDispatcher.Send(new ErrorEvent + { + Message = session.Translation.GetTranslation(TranslationString.GoogleTwoFactorAuthExplanation) + }); + await Task.Delay(7000, cancellationToken).ConfigureAwait(false); + try + { + Process.Start("https://security.google.com/settings/security/apppasswords"); + } + catch (Exception) + { + session.EventDispatcher.Send(new ErrorEvent + { + Message = "https://security.google.com/settings/security/apppasswords" + }); + throw; + } + } + session.EventDispatcher.Send(new ErrorEvent + { + RequireExit = true, + Message = session.Translation.GetTranslation(TranslationString.GoogleError) + }); + await Task.Delay(2000, cancellationToken).ConfigureAwait(false); + Environment.Exit(0); + } + catch (ActiveSwitchByRuleException) + { + } + catch (OperationCanceledException) + { + //just continue login if this happen, most case is bot switching... + } + catch (InvalidProtocolBufferException ex) when (ex.Message.Contains("SkipLastField")) + { + session.EventDispatcher.Send(new ErrorEvent + { + RequireExit =true, + Message = session.Translation.GetTranslation(TranslationString.IPBannedError) + }); + await Task.Delay(2000, cancellationToken).ConfigureAwait(false); + Environment.Exit(0); + } + catch (MinimumClientVersionException ex) + { + // We need to terminate the client. + session.EventDispatcher.Send(new ErrorEvent + { + Message = session.Translation + .GetTranslation( + TranslationString.MinimumClientVersionException, + ex.CurrentApiVersion.ToString(), + ex.MinimumClientVersion.ToString() + ) + }); + session.EventDispatcher.Send(new ErrorEvent() { RequireExit = true, Message = session.Translation.GetTranslation(TranslationString.ExitNowAfterEnterKey, LogLevel.Error) }); + + Console.ReadKey(); + Environment.Exit(1); + } + catch (CaptchaException captcha) + { + throw captcha; + } + catch (Exception e) + { + Logger.Write(e.ToString()); + await Task.Delay(20000, cancellationToken).ConfigureAwait(false); + return this; + } + finally + { + var currentAccount = accountManager?.GetCurrentAccount(); + if (currentAccount != null) + { + currentAccount.LastLogin = successfullyLoggedIn ? "Success" : "Failure"; + currentAccount.LastLoginTimestamp = TimeUtil.GetCurrentTimestampInMilliseconds(); + accountManager.UpdateLocalAccount(currentAccount); + } + } + try + { + await DownloadProfile(session).ConfigureAwait(false); + if (session.Profile == null) + { + await Task.Delay(20000, cancellationToken).ConfigureAwait(false); + Logger.Write( + "Due to login failure your player profile could not be retrieved. Press any key to retry login.", + LogLevel.Warning + ); + Console.ReadKey(); + } + else + { + if (successfullyLoggedIn) + { + var currentAccount = accountManager?.GetCurrentAccount(); + if (currentAccount != null) + { + if (session.Profile.Banned) + { + currentAccount.LastLogin = "Banned"; + accountManager.UpdateLocalAccount(currentAccount); + } + else + { + if (session.Profile.Warn) + { + currentAccount.LastLogin = "Warned"; + accountManager.UpdateLocalAccount(currentAccount); + } + + if (currentAccount.Nickname != session.Profile.PlayerData.Username) + { + currentAccount.Nickname = session.Profile.PlayerData.Username; + accountManager.UpdateLocalAccount(currentAccount); + } + } + } + } + } + + if (session.LogicSettings.UseRecyclePercentsInsteadOfTotals) + { + int totalPercent = session.LogicSettings.PercentOfInventoryPokeballsToKeep + + session.LogicSettings.PercentOfInventoryPotionsToKeep + + session.LogicSettings.PercentOfInventoryRevivesToKeep + + session.LogicSettings.PercentOfInventoryBerriesToKeep + + session.LogicSettings.PercentOfInventoryEvolutionToKeep; + + if (totalPercent > 100) + { + + session.EventDispatcher.Send(new ErrorEvent() { RequireExit = true, Message = session.Translation.GetTranslation(TranslationString.TotalRecyclePercentGreaterThan100) }); + Logger.Write("Press any key to exit, then fix your config and run the bot again.", LogLevel.Warning); + + Console.ReadKey(); + Environment.Exit(1); + } + else + { + Logger.Write(session.Translation.GetTranslation(TranslationString.UsingRecyclePercentsInsteadOfTotals, session.Profile.PlayerData.MaxItemStorage), LogLevel.Info); + Logger.Write(session.Translation.GetTranslation(TranslationString.PercentPokeballsToKeep, session.LogicSettings.PercentOfInventoryPokeballsToKeep, (int)Math.Floor(session.LogicSettings.PercentOfInventoryPokeballsToKeep / 100.0 * session.Profile.PlayerData.MaxItemStorage)), LogLevel.Info); + Logger.Write(session.Translation.GetTranslation(TranslationString.PercentPotionsToKeep, session.LogicSettings.PercentOfInventoryPotionsToKeep, (int)Math.Floor(session.LogicSettings.PercentOfInventoryPotionsToKeep / 100.0 * session.Profile.PlayerData.MaxItemStorage)), LogLevel.Info); + Logger.Write(session.Translation.GetTranslation(TranslationString.PercentRevivesToKeep, session.LogicSettings.PercentOfInventoryRevivesToKeep, (int)Math.Floor(session.LogicSettings.PercentOfInventoryRevivesToKeep / 100.0 * session.Profile.PlayerData.MaxItemStorage)), LogLevel.Info); + Logger.Write(session.Translation.GetTranslation(TranslationString.PercentBerriesToKeep, session.LogicSettings.PercentOfInventoryBerriesToKeep, (int)Math.Floor(session.LogicSettings.PercentOfInventoryBerriesToKeep / 100.0 * session.Profile.PlayerData.MaxItemStorage)), LogLevel.Info); + Logger.Write(session.Translation.GetTranslation(TranslationString.PercentEvolutionToKeep, session.LogicSettings.PercentOfInventoryEvolutionToKeep, (int)Math.Floor(session.LogicSettings.PercentOfInventoryEvolutionToKeep / 100.0 * session.Profile.PlayerData.MaxItemStorage)), LogLevel.Info); + } + } + else + { + int maxTheoreticalItems = session.LogicSettings.TotalAmountOfPokeballsToKeep + + session.LogicSettings.TotalAmountOfPotionsToKeep + + session.LogicSettings.TotalAmountOfRevivesToKeep + + session.LogicSettings.TotalAmountOfBerriesToKeep + + session.LogicSettings.TotalAmountOfEvolutionToKeep; + + if (maxTheoreticalItems > session.Profile.PlayerData.MaxItemStorage) + { + session.EventDispatcher.Send(new ErrorEvent() { RequireExit = true, Message = session.Translation.GetTranslation(TranslationString.MaxItemsCombinedOverMaxItemStorage, maxTheoreticalItems, session.Profile.PlayerData.MaxItemStorage) }); + + Logger.Write("Press any key to exit, then fix your config and run the bot again.", LogLevel.Warning); + Console.ReadKey(); + Environment.Exit(1); + } + } + } + catch (ActiveSwitchByRuleException) + { + } + catch (OperationCanceledException) + { + //just continue login if this happen, most case is bot switching... + } + catch (CaptchaException ex) + { + throw ex; + } + + catch (APIBadRequestException) + { + throw new LoginFailedException(); + } + catch (Exception ex) + { + throw ex; + } + session.LoggedTime = DateTime.Now; + session.EventDispatcher.Send(new LoggedEvent() + { + Profile = session.Profile + }); + if (pokemonToCatch != PokemonId.Missingno) + { + return new BotSwitcherState(pokemonToCatch, encounterData); + } + return new LoadSaveState(); + } + + private static void CheckLogin(ISession session, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (session.Settings.Username == null || session.Settings.Password == null) + { + session.EventDispatcher.Send(new ErrorEvent + { + RequireExit = true, + Message = session.Translation.GetTranslation(TranslationString.MissingCredentials) + }); + Console.Read(); + Environment.Exit(0); + } + } + + public async Task DownloadProfile(ISession session) + { + try + { + //TODO : need get all data at 1 call here to save speed login. + session.Profile = await session.Inventory.GetPlayerData().ConfigureAwait(false); + var stats = await session.Inventory.GetPlayerStats().ConfigureAwait(false); + + // TODO Remove + //TinyIoCContainer.Current.Resolve().Logged(session.Profile, stats); + + session.EventDispatcher.Send(new ProfileEvent {Profile = session.Profile, Stats = stats}); + } + catch (UriFormatException e) + { + session.EventDispatcher.Send(new ErrorEvent {Message = e.ToString()}); + } + catch (CaptchaException ex) + { + throw ex; + } + catch (Exception ex) + { + session.EventDispatcher.Send(new ErrorEvent {Message = ex.ToString()}); + } + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/State/Session.cs b/PoGo.NecroBot.Logic/State/Session.cs new file mode 100644 index 000000000..ea81e6bff --- /dev/null +++ b/PoGo.NecroBot.Logic/State/Session.cs @@ -0,0 +1,290 @@ +#region using directives + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.Caching; +using System.Threading; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.Event.Inventory; +using PoGo.NecroBot.Logic.Interfaces.Configuration; +using PoGo.NecroBot.Logic.Logging; +using PoGo.NecroBot.Logic.Model; +using PoGo.NecroBot.Logic.Model.Settings; +using PoGo.NecroBot.Logic.Service; +using PoGo.NecroBot.Logic.Service.Elevation; +using PoGo.NecroBot.Logic.Tasks; +using PoGo.NecroBot.Logic.Utils; +using PokemonGo.RocketAPI; +using PokemonGo.RocketAPI.Extensions; +using POGOProtos.Map.Fort; +using POGOProtos.Networking.Responses; +using TinyIoC; + +#endregion + +namespace PoGo.NecroBot.Logic.State +{ + public interface ISession + { + ISettings Settings { get; set; } + Inventory Inventory { get; } + Client Client { get; set; } + GetPlayerResponse Profile { get; set; } + Navigation Navigation { get; } + ILogicSettings LogicSettings { get; set; } + ITranslation Translation { get; } + IEventDispatcher EventDispatcher { get; } + TelegramService Telegram { get; set; } + SessionStats Stats { get; } + IElevationService ElevationService { get; set; } + List Forts { get; set; } + List VisibleForts { get; set; } + bool ReInitSessionWithNextBot(Account authConfig = null, double lat = 0, double lng = 0, double att = 0); + void AddForts(List mapObjects); + void AddVisibleForts(List mapObjects); + Task WaitUntilActionAccept(BotActions action, int timeout = 30000); + List Actions { get; } + CancellationTokenSource CancellationTokenSource { get; set; } + MemoryCache Cache { get; set; } + DateTime LoggedTime { get; set; } + DateTime CatchBlockTime { get; set; } + Statistics RuntimeStatistics { get; } + GymTeamState GymState { get; set; } + double KnownLatitudeBeforeSnipe { get; set; } + double KnownLongitudeBeforeSnipe { get; set; } + bool SaveBallForByPassCatchFlee { set; get; } + } + + public class Session : ISession + { + public Session(GlobalSettings globalSettings,ISettings settings, ILogicSettings logicSettings, IElevationService elevationService) : this( + globalSettings, settings, logicSettings, elevationService, Common.Translation.Load(logicSettings)) + { + LoggedTime = DateTime.Now; + } + + public bool SaveBallForByPassCatchFlee { get; set; } + public DateTime LoggedTime { get; set; } + private List accounts; + + public List Actions + { + get { return botActions; } + } + + public Session(GlobalSettings globalSettings, ISettings settings, ILogicSettings logicSettings, + IElevationService elevationService, ITranslation translation) + { + GlobalSettings = globalSettings; + CancellationTokenSource = new CancellationTokenSource(); + Forts = new List(); + VisibleForts = new List(); + Cache = new MemoryCache("NecroBot2"); + accounts = new List(); + EventDispatcher = new EventDispatcher(); + LogicSettings = logicSettings; + RuntimeStatistics = new Statistics(); + + ElevationService = elevationService; + + Settings = settings; + + Translation = translation; + Reset(settings, LogicSettings); + Stats = new SessionStats(this); + + AnalyticsService = new AnalyticsService(); + + accounts.AddRange(logicSettings.Bots); + if (!accounts.Any(x => x.AuthType == settings.AuthType && x.Username == settings.Username)) + { + accounts.Add(new AuthConfig() + { + AuthType = settings.AuthType, + Password = settings.Password, + Username = settings.Username, + AutoExitBotIfAccountFlagged = settings.AutoExitBotIfAccountFlagged, + AccountLatitude = settings.AccountLatitude, + AccountLongitude = settings.AccountLongitude, + AccountActive = settings.AccountActive + }); + } + if (File.Exists("runtime.log")) + { + var lines = File.ReadAllLines("runtime.log"); + foreach (var item in lines) + { + var arr = item.Split(';'); + var acc = accounts.FirstOrDefault(p => p.Username == arr[0]); + if (acc != null) + { + acc.RuntimeTotal = Convert.ToDouble(arr[1]); + } + } + } + + GymState = new GymTeamState(); + } + + public List Forts { get; set; } + public List VisibleForts { get; set; } + public GlobalSettings GlobalSettings { get; set; } + public ISettings Settings { get; set; } + public Inventory Inventory { get; private set; } + public Client Client { get; set; } + public GetPlayerResponse Profile { get; set; } + public Navigation Navigation { get; private set; } + public ILogicSettings LogicSettings { get; set; } + public ITranslation Translation { get; } + public IEventDispatcher EventDispatcher { get; } + public TelegramService Telegram { get; set; } + public SessionStats Stats { get; set; } + public IElevationService ElevationService { get; set; } + public AnalyticsService AnalyticsService { get; set; } + public CancellationTokenSource CancellationTokenSource { get; set; } + public MemoryCache Cache { get; set; } + + public List Accounts + { + get { return accounts; } + } + + public DateTime CatchBlockTime { get; set; } + public Statistics RuntimeStatistics { get; } + private List botActions = new List(); + + public void Reset(ISettings settings, ILogicSettings logicSettings) + { + KnownLatitudeBeforeSnipe = 0; + KnownLongitudeBeforeSnipe = 0; + if(GlobalSettings.Auth.DeviceConfig.UseRandomDeviceId) + { + settings.DeviceId = DeviceConfig.GetDeviceId(settings.Username); + Logger.Debug($"Username : {Settings.Username} , Device ID :{Settings.DeviceId}"); + } + Client = new Client(settings); + // ferox wants us to set this manually + Inventory = new Inventory(this, Client, logicSettings, async () => + { + var candy = (await Inventory.GetPokemonFamilies().ConfigureAwait(false)).ToList(); + var pokemonSettings = (await Inventory.GetPokemonSettings().ConfigureAwait(false)).ToList(); + EventDispatcher.Send(new InventoryRefreshedEvent(null, pokemonSettings, candy)); + }); + Navigation = new Navigation(Client, logicSettings); + Navigation.WalkStrategy.UpdatePositionEvent += + (session, lat, lng,s) => EventDispatcher.Send(new UpdatePositionEvent {Latitude = lat, Longitude = lng, Speed = s}); + + Navigation.WalkStrategy.UpdatePositionEvent += LoadSaveState.SaveLocationToDisk; + } + + //TODO : Need add BotManager to manage all feature related to multibot, + public bool ReInitSessionWithNextBot(Account bot = null, double lat = 0, double lng = 0, double att = 0) + { + CatchBlockTime = DateTime.Now; //remove any block + MSniperServiceTask.BlockSnipe(); + VisibleForts.Clear(); + Forts.Clear(); + + var manager = TinyIoCContainer.Current.Resolve(); + var nextBot = manager.GetSwitchableAccount(bot); + var Account = !string.IsNullOrEmpty(nextBot.Nickname) ? nextBot.Nickname : nextBot.Username; + var session = TinyIoCContainer.Current.Resolve(); + var TotXP = 0; + + for (int i = 0; i < nextBot.Level + 1; i++) + { + TotXP = TotXP + Statistics.GetXpDiff(i); + } + + long? XP = nextBot.CurrentXp; + if (XP == null) { XP = 0; } + long? SD = nextBot.Stardust; + if (SD == null) { SD = 0; } + long? Lvl = nextBot.Level; + if (Lvl == null) { Lvl = 0; } + var NLevelXP = nextBot.NextLevelXp; + if (nextBot.NextLevelXp == null) { NLevelXP = 0; } + + Logger.Write($"Account changed to {Account}", LogLevel.BotStats); + + if (session.LogicSettings.NotificationConfig.EnablePushBulletNotification) + PushNotificationClient.SendNotification(session, $"Account changed to", $"{Account}\n" + + $"Lvl: {Lvl}\n" + + $"XP : {XP:#,##0} ({(double)XP / ((double)NLevelXP) * 100:#0.00}%)\n" + + $"SD : {SD:#,##0}", true).ConfigureAwait(false); + +#if DEBUG + Thread.Sleep(1000); //Pauses execution for 1 sec. Personal use for TheWizard. I need a 1 sec gap between this and next logger line. +#endif + if (nextBot != null) + manager.SwitchAccounts(nextBot); + + Settings.DefaultAltitude = att == 0 ? Client.CurrentAltitude : att; + Settings.DefaultLatitude = lat == 0 ? Client.CurrentLatitude : lat; + Settings.DefaultLongitude = lng == 0 ? Client.CurrentLongitude : lng; + Stats = new SessionStats(this); + Reset(Settings, LogicSettings); + //CancellationTokenSource.Cancel(); + CancellationTokenSource = new CancellationTokenSource(); + + EventDispatcher.Send(new BotSwitchedEvent(nextBot) + { + }); + + if (LogicSettings.MultipleBotConfig.DisplayList) + { + manager.DumpAccountList(); + } + return true; + } + + public void AddForts(List data) + { + data.RemoveAll(x => LocationUtils.CalculateDistanceInMeters(x.Latitude, x.Longitude, Settings.DefaultLatitude, Settings.DefaultLongitude) > 10000); + + Forts.RemoveAll(p => data.Any(x => x.Id == p.Id && x.Type == FortType.Checkpoint)); + Forts.AddRange(data.Where(x => x.Type == FortType.Checkpoint)); + foreach (var item in data.Where(p => p.Type == FortType.Gym)) + { + var exist = Forts.FirstOrDefault(x => x.Id == item.Id); + if (exist != null && exist.CooldownCompleteTimestampMs > DateTime.UtcNow.ToUnixTime()) + { + continue; + } + else + { + Forts.RemoveAll(x => x.Id == item.Id); + Forts.Add(item); + } + } + } + + public void AddVisibleForts(List mapObjects) + { + var notexist = mapObjects.Where(p => !VisibleForts.Any(x => x.Id == p.Id)); + VisibleForts.AddRange(notexist); + } + + public async Task WaitUntilActionAccept(BotActions action, int timeout = 30000) + { + if (botActions.Count == 0) return true; + var waitTimes = 0; + while (true && waitTimes < timeout) + { + if (botActions.Count == 0) return true; + ///implement logic of action dependent + waitTimes += 1000; + await Task.Delay(1000).ConfigureAwait(false); + } + return false; //timedout + } + public GymTeamState GymState { get; set; } + + public double KnownLatitudeBeforeSnipe { get; set; } + public double KnownLongitudeBeforeSnipe { get; set; } + } +} diff --git a/PoGo.NecroBot.Logic/State/SessionStats.cs b/PoGo.NecroBot.Logic/State/SessionStats.cs new file mode 100644 index 000000000..1f8cf11db --- /dev/null +++ b/PoGo.NecroBot.Logic/State/SessionStats.cs @@ -0,0 +1,217 @@ +using System; +using System.IO; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.Event; +using System.Linq; + +namespace PoGo.NecroBot.Logic.State +{ + public class SessionStats + { + const string DB_NAME = @"SessionStats.db"; + const string POKESTOP_STATS_COLLECTION = "PokeStopTimestamps"; + const string POKEMON_STATS_COLLECTION = "PokemonTimestamps"; + + public int SnipeCount { get; set; } + public DateTime LastSnipeTime { get; set; } + public DateTime StartTime { get; set; } + public bool IsSnipping { get; internal set; } + + private ISession ownerSession; + + class PokeStopTimestamp + { + public Int64 Timestamp { get; set; } + } + + class PokemonTimestamp + { + public Int64 Timestamp { get; set; } + } + + DateTime lastPrintPokestopMessage = DateTime.Now; + + public bool SearchThresholdExceeds(ISession session, bool printMessage) + { + if (!session.LogicSettings.UsePokeStopLimit) return false; + //if (_pokestopLimitReached || _pokestopTimerReached) return true; + + CleanOutExpiredStats(); + + // Check if user defined max Pokestops reached + var timeDiff = (DateTime.Now - session.Stats.StartTime); + + if (GetNumPokestopsInLast24Hours() >= session.LogicSettings.PokeStopLimit) + { + if (printMessage && lastPrintPokestopMessage.AddSeconds(60) < DateTime.Now) + { + lastPrintPokestopMessage = DateTime.Now; + session.EventDispatcher.Send(new ErrorEvent + { + Message = session.Translation.GetTranslation(TranslationString.PokestopLimitReached) + }); + } + //_pokestopLimitReached = true; + return true; + } + + // Check if user defined time since start reached + else if (timeDiff.TotalSeconds >= session.LogicSettings.PokeStopLimitMinutes * 60) + { + session.EventDispatcher.Send(new ErrorEvent + { + Message = session.Translation.GetTranslation(TranslationString.PokestopTimerReached) + }); + + //_pokestopTimerReached = true; + return true; + } + + return false; // Continue running + } + + + DateTime lastPrintCatchMessage = DateTime.Now; + + public bool CatchThresholdExceeds(ISession session, bool printMessage = true) + { + if (!session.LogicSettings.UseCatchLimit) return false; + + CleanOutExpiredStats(); + + var timeDiff = (DateTime.Now - session.Stats.StartTime); + + // Check if user defined max AMOUNT of Catches reached + if (GetNumPokemonsInLast24Hours() >= session.LogicSettings.CatchPokemonLimit) + { + if (printMessage && lastPrintCatchMessage.AddSeconds(60) < DateTime.Now) + { + lastPrintCatchMessage = DateTime.Now; + session.EventDispatcher.Send(new ErrorEvent + { + Message = session.Translation.GetTranslation(TranslationString.CatchLimitReached) + }); + } + // _catchPokemonLimitReached = true; + return true; + } + + // Check if user defined TIME since start reached + else if (timeDiff.TotalSeconds >= session.LogicSettings.CatchPokemonLimitMinutes * 60) + { + session.EventDispatcher.Send(new ErrorEvent + { + Message = session.Translation.GetTranslation(TranslationString.CatchTimerReached) + }); + + //_catchPokemonTimerReached = true; + return true; + } + + return false; + } + + public bool IsPokestopLimit(ISession session) + { + if (!session.LogicSettings.UsePokeStopLimit) return false; + + CleanOutExpiredStats(); + + if (GetNumPokestopsInLast24Hours() >= session.LogicSettings.PokeStopLimitMinutes) + return true; + //TODO - Other logic should come here, but I don't think we need + return false; + } + + public SessionStats(ISession session) + { + StartTime = DateTime.Now; + ownerSession = session; + } + + private static string GetUsername(ISession session) + { + return session.Settings.Username; + } + + private static string GetDBPath(ISession session, string username) + { + var path = Path.Combine(session.LogicSettings.ProfileConfigPath, username); + + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + path = Path.Combine(path, DB_NAME); + return path; + } + + public void AddPokestopTimestamp(Int64 ts) + { + var manager = TinyIoC.TinyIoCContainer.Current.Resolve(); + var db = manager.GetDbContext(); + var existing = db.PokestopTimestamp.Where(t => t.Timestamp == ts).FirstOrDefault(); + if (existing == null) + { + var currentAccount = manager.GetCurrentAccount(); + + var stat = new Model.PokestopTimestamp + { + Timestamp = ts, + Account = manager.GetCurrentAccount() + }; + db.PokestopTimestamp.Add(stat); + db.SaveChanges(); + } + } + + public void AddPokemonTimestamp(Int64 ts) + { + var manager = TinyIoC.TinyIoCContainer.Current.Resolve(); + var db = manager.GetDbContext(); + var existing = db.PokemonTimestamp.Where(t => t.Timestamp == ts).FirstOrDefault(); + if (existing == null) + { + var currentAccount = manager.GetCurrentAccount(); + + var stat = new Model.PokemonTimestamp + { + Timestamp = ts, + Account = manager.GetCurrentAccount() + }; + db.PokemonTimestamp.Add(stat); + db.SaveChanges(); + } + } + + public void CleanOutExpiredStats() + { + var manager = TinyIoC.TinyIoCContainer.Current.Resolve(); + var db = manager.GetDbContext(); + long TSminus24h = DateTime.Now.AddHours(-24).Ticks; + var pokestopTimestampsToDelete = db.PokestopTimestamp.Where(t => t.Account == manager.GetCurrentAccount() && t.Timestamp < TSminus24h); + db.PokestopTimestamp.RemoveRange(pokestopTimestampsToDelete); + + var pokemonTimestampsToDelete = db.PokemonTimestamp.Where(t => t.Account == manager.GetCurrentAccount() && t.Timestamp < TSminus24h); + db.PokemonTimestamp.RemoveRange(pokemonTimestampsToDelete); + db.SaveChanges(); + } + + public int GetNumPokestopsInLast24Hours() + { + var manager = TinyIoC.TinyIoCContainer.Current.Resolve(); + var db = manager.GetDbContext(); + var TSminus24h = DateTime.Now.AddHours(-24).Ticks; + return db.PokestopTimestamp.Count(s => manager.GetCurrentAccount() == s.Account && s.Timestamp >= TSminus24h); + } + + public int GetNumPokemonsInLast24Hours() + { + var manager = TinyIoC.TinyIoCContainer.Current.Resolve(); + var db = manager.GetDbContext(); + var TSminus24h = DateTime.Now.AddHours(-24).Ticks; + return db.PokemonTimestamp.Count(s => manager.GetCurrentAccount() == s.Account && s.Timestamp >= TSminus24h); + } + } +} diff --git a/PoGo.NecroBot.Logic/State/StateMachine.cs b/PoGo.NecroBot.Logic/State/StateMachine.cs new file mode 100644 index 000000000..a19aee39d --- /dev/null +++ b/PoGo.NecroBot.Logic/State/StateMachine.cs @@ -0,0 +1,417 @@ +#region using directives + +using System; +using System.IO; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Captcha; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.Exceptions; +using PoGo.NecroBot.Logic.Logging; +using PoGo.NecroBot.Logic.Model.Settings; +using PoGo.NecroBot.Logic.Tasks; +using PoGo.NecroBot.Logic.Utils; +using PokemonGo.RocketAPI.Exceptions; +using static System.Threading.Tasks.Task; +using TinyIoC; +using PoGo.NecroBot.Logic.Model; + +#endregion + +namespace PoGo.NecroBot.Logic.State +{ + public class StateMachine + { + private IState _initialState; + + public Task AsyncStart(IState initialState, Session session, string subPath, bool excelConfigAllowed = false) + { + return Run(() => Start(initialState, session, subPath, excelConfigAllowed)); + } + + public void SetFailureState(IState state) + { + _initialState = state; + } + + public void ReInitializeSession(ISession session, GlobalSettings globalSettings, Account requestedAccount = null) + { + if (session.LogicSettings.MultipleBotConfig.StartFromDefaultLocation) + { + session.ReInitSessionWithNextBot(requestedAccount, globalSettings.LocationConfig.DefaultLatitude, globalSettings.LocationConfig.DefaultLongitude, session.Client.CurrentAltitude); + } + else + { + session.ReInitSessionWithNextBot(); //current location + } + } + + public async Task Start(IState initialState, ISession session, string subPath, bool excelConfigAllowed = false) + { + var manager = TinyIoCContainer.Current.Resolve(); + + GlobalSettings globalSettings = null; + + var state = initialState; + var profilePath = Path.Combine(Directory.GetCurrentDirectory(), subPath); + var profileConfigPath = Path.Combine(profilePath, "config"); + globalSettings = GlobalSettings.Load(subPath); + + FileSystemWatcher configWatcher = new FileSystemWatcher() + { + Path = profileConfigPath, + Filter = "config.json", + NotifyFilter = NotifyFilters.LastWrite, + EnableRaisingEvents = true + }; + configWatcher.Changed += (sender, e) => + { + if (e.ChangeType == WatcherChangeTypes.Changed) + { + globalSettings = GlobalSettings.Load(subPath); + session.LogicSettings = new LogicSettings(globalSettings); + // BUG: duplicate boolean negation will take no effect + configWatcher.EnableRaisingEvents = !configWatcher.EnableRaisingEvents; + configWatcher.EnableRaisingEvents = !configWatcher.EnableRaisingEvents; + Logger.Write(" ##### config.json ##### ", LogLevel.Info); + } + }; + + //watch the excel config file + if (excelConfigAllowed) + { + // TODO - await is legal here! USE it or use pragma to suppress compilerwarning and write a comment why it is not used + // TODO: Attention - do not touch (add pragma) when you do not know what you are doing ;) + // jjskuld - Ignore CS4014 warning for now. + #pragma warning disable 4014 + Run(async () => + { + while (true) + { + try + { + FileInfo inf = new FileInfo($"{profileConfigPath}\\config.xlsm"); + if (inf.LastWriteTime > DateTime.Now.AddSeconds(-5)) + { + globalSettings = ExcelConfigHelper.ReadExcel(globalSettings, inf.FullName); + session.LogicSettings = new LogicSettings(globalSettings); + Logger.Write(" ##### config.xlsm ##### ", LogLevel.Info); + } + await Delay(5000).ConfigureAwait(false); + } + catch (Exception) + { + // TODO Bad practice! Wanna log this? + } + } + }); + #pragma warning restore 4014 + } + + int apiCallFailured = 0; + do + { + try + { + state = await state.Execute(session, session.CancellationTokenSource.Token).ConfigureAwait(false); + + // Exit the bot if both catching and looting has reached its limits + if ((UseNearbyPokestopsTask._pokestopLimitReached || + UseNearbyPokestopsTask._pokestopTimerReached) && + session.Stats.CatchThresholdExceeds(session)) + { + session.EventDispatcher.Send(new ErrorEvent + { + Message = session.Translation.GetTranslation(TranslationString.ExitDueToLimitsReached) + }); + + session.CancellationTokenSource.Cancel(); + + // A bit rough here; works but can be improved + await Delay(10000).ConfigureAwait(false); + state = null; + session.CancellationTokenSource.Dispose(); + Environment.Exit(0); + } + } + catch (APIBadRequestException) + { + session.EventDispatcher.Send(new ErrorEvent() {Message = "An unexpected error has occured. Logging in again... (Bad API Request)" }); + + /* changed state if bad request + * + * if (manager.AllowMultipleBot()) + ReInitializeSession(session, globalSettings); + state = new LoginState();*/ + await Delay(1000).ConfigureAwait(false); + state = _initialState; + } + catch (AccountNotVerifiedException) + { + if (manager.AllowMultipleBot()) + { + ReInitializeSession(session, globalSettings); + state = new LoginState(); + } + else + { + Console.Read(); + Environment.Exit(0); + } + } + catch(ActiveSwitchAccountManualException ex) + { + session.EventDispatcher.Send(new WarnEvent { Message = "Switch bot account requested by: User" }); + ReInitializeSession(session, globalSettings, ex.RequestedAccount); + state = new LoginState(); + } + catch (ActiveSwitchByPokemonException rsae) + { + if (rsae.Snipe && rsae.EncounterData != null) + session.EventDispatcher.Send(new WarnEvent { Message = $"Detected a good pokemon with snipe {rsae.EncounterData.PokemonId.ToString()} IV:{rsae.EncounterData.IV} Move:{rsae.EncounterData.Move1}/ Move:{rsae.EncounterData.Move2} LV: Move:{rsae.EncounterData.Level}" }); + else + { + session.EventDispatcher.Send(new WarnEvent { Message = "Encountered a good pokemon, switch bots to catch him too." }); + if (session.LogicSettings.NotificationConfig.EnablePushBulletNotification) + await PushNotificationClient.SendNotification(session, $"Switch bot account", $"Encountered a good pokemon, switch bots to catch him too.", true).ConfigureAwait(false); + } + session.ReInitSessionWithNextBot(rsae.Bot, session.Client.CurrentLatitude, session.Client.CurrentLongitude, session.Client.CurrentAltitude); + state = new LoginState(rsae.LastEncounterPokemonId, rsae.EncounterData); + } + catch (ActiveSwitchByRuleException se) + { + session.EventDispatcher.Send(new WarnEvent { Message = $"Switch bot account activated by: {se.MatchedRule.ToString()} - {se.ReachedValue}" }); + //if (session.LogicSettings.NotificationConfig.EnablePushBulletNotification) + // await PushNotificationClient.SendNotification(session, $"Switch bot account", $"Activated by: {se.MatchedRule.ToString()} - {se.ReachedValue}", true).ConfigureAwait(false); + + if (se.MatchedRule == SwitchRules.EmptyMap) + { + TinyIoCContainer.Current.Resolve().BlockCurrentBot(90); + ReInitializeSession(session, globalSettings); + } + else if (se.MatchedRule == SwitchRules.PokestopSoftban) + { + TinyIoCContainer.Current.Resolve().BlockCurrentBot(); + ReInitializeSession(session, globalSettings); + } + else if (se.MatchedRule == SwitchRules.CatchFlee) + { + TinyIoCContainer.Current.Resolve().BlockCurrentBot(60); + ReInitializeSession(session, globalSettings); + } + else + { + if (se.MatchedRule == SwitchRules.CatchLimitReached || + se.MatchedRule == SwitchRules.SpinPokestopReached) + { + // TODO - await is legal here! USE it or use pragma to suppress compilerwarning and write a comment why it is not used + // TODO: Attention - do not touch (add pragma) when you do not know what you are doing ;) + // jjskuld - Ignore CS4014 warning for now. + + if (session.LogicSettings.NotificationConfig.EnablePushBulletNotification) + await PushNotificationClient.SendNotification(session, $"{se.MatchedRule} - {session.Settings.Username}", $"This bot has reach limit, it will be blocked for {session.LogicSettings.MultipleBotConfig.OnLimitPauseTimes} mins for safety.", true).ConfigureAwait(false); + + session.EventDispatcher.Send(new WarnEvent() { Message = $"You reach limited. bot will sleep for {session.LogicSettings.MultipleBotConfig.OnLimitPauseTimes} min" }); + + TinyIoCContainer.Current.Resolve().BlockCurrentBot(session.LogicSettings.MultipleBotConfig.OnLimitPauseTimes); + + ReInitializeSession(session, globalSettings); + } + else + { + ReInitializeSession(session, globalSettings); + } + } + //return to login state + state = new LoginState(); + } + catch (InvalidResponseException e) + { + session.EventDispatcher.Send(new ErrorEvent { Message = $"Niantic Servers unstable, throttling API Calls. {e.Message}" }); + await Delay(1000).ConfigureAwait(false); + if (manager.AllowMultipleBot()) + { + apiCallFailured++; + if (apiCallFailured > 20) + { + apiCallFailured = 0; + TinyIoCContainer.Current.Resolve().BlockCurrentBot(30); + + ReInitializeSession(session, globalSettings); + } + } + state = new LoginState(); + } + catch (SessionInvalidatedException e) + { + session.EventDispatcher.Send(new ErrorEvent { Message = $"Hashing Servers errors, throttling calls. {e.Message}" }); + await Delay(1000).ConfigureAwait(false); + if (manager.AllowMultipleBot()) + { + apiCallFailured++; + if (apiCallFailured > 3) + { + apiCallFailured = 0; + TinyIoCContainer.Current.Resolve().BlockCurrentBot(30); + + ReInitializeSession(session, globalSettings); + } + } + + // Resetting position + session.EventDispatcher.Send(new ErrorEvent { Message = $"Resetting position before relogging in." }); + // TheWizard1328 - Changed this to CurrentLocation from DefaultLocation because Bot would JUMP back to DefaultLocation and could be considered as teleporting even in a short distance. + session.Client.Player.UpdatePlayerLocation(session.Client.CurrentLatitude, session.Client.CurrentLongitude, session.Client.CurrentAltitude, 0); + state = new LoginState(); + } + catch (OperationCanceledException) + { + session.EventDispatcher.Send(new ErrorEvent {Message = "Current Operation was canceled."}); + if (manager.AllowMultipleBot()) + { + TinyIoCContainer.Current.Resolve().BlockCurrentBot(30); + ReInitializeSession(session, globalSettings); + } + state = new LoginState(); + } + catch(PtcLoginException ex) + { + if (session.LogicSettings.NotificationConfig.EnablePushBulletNotification) + await PushNotificationClient.SendNotification(session, $"PTC Login failed!!!! {session.Settings.Username}", session.Translation.GetTranslation(TranslationString.PtcLoginFail), true).ConfigureAwait(false); + + if (manager.AllowMultipleBot()) + { + TinyIoCContainer.Current.Resolve().BlockCurrentBot(60); //need remove acc + ReInitializeSession(session, globalSettings); + state = new LoginState(); + } + else { + session.EventDispatcher.Send(new ErrorEvent { RequireExit = true, Message = session.Translation.GetTranslation(TranslationString.ExitNowAfterEnterKey) }); + session.EventDispatcher.Send(new ErrorEvent { RequireExit = true, Message = session.Translation.GetTranslation(TranslationString.PtcLoginFail) + $" ({ex.Message})"}); + + Console.ReadKey(); + Environment.Exit(1); + } + } + catch (LoginFailedException) + { + // TODO - await is legal here! USE it or use pragma to suppress compilerwarning and write a comment why it is not used + // TODO: Attention - do not touch (add pragma) when you do not know what you are doing ;) + // jjskuld - Ignore CS4014 warning for now. + if (session.LogicSettings.NotificationConfig.EnablePushBulletNotification) + await PushNotificationClient.SendNotification(session, $"Banned!!!! {session.Settings.Username}", session.Translation.GetTranslation(TranslationString.AccountBanned), true).ConfigureAwait(false); + + if (manager.AllowMultipleBot()) + { + var accountManager = TinyIoCContainer.Current.Resolve(); + var currentAccount = accountManager?.GetCurrentAccount(); + currentAccount.AccountActive = false; + accountManager.UpdateLocalAccount(currentAccount); + globalSettings.Auth.Bots[(int)currentAccount.Id].AccountActive = false; + globalSettings.Auth.Save(Path.Combine(globalSettings.ProfileConfigPath, "auth.json")); + + TinyIoCContainer.Current.Resolve().BlockCurrentBot(24 * 60); //need remove acc + ReInitializeSession(session, globalSettings); + state = new LoginState(); + } + else { + session.EventDispatcher.Send(new ErrorEvent { RequireExit = true, Message = session.Translation.GetTranslation(TranslationString.ExitNowAfterEnterKey) }); + Console.ReadKey(); + Environment.Exit(1); + } + } + catch (MinimumClientVersionException ex) + { + // We need to terminate the client. + session.EventDispatcher.Send(new ErrorEvent + { + Message = session.Translation.GetTranslation(TranslationString.MinimumClientVersionException, ex.CurrentApiVersion.ToString(), ex.MinimumClientVersion.ToString()) + }); + + session.EventDispatcher.Send(new ErrorEvent { RequireExit = true, Message = session.Translation.GetTranslation(TranslationString.ExitNowAfterEnterKey) }); + Console.ReadKey(); + Environment.Exit(1); + } + catch (TokenRefreshException ex) + { + session.EventDispatcher.Send(new ErrorEvent() {Message = ex.Message}); + + if (manager.AllowMultipleBot()) + ReInitializeSession(session, globalSettings); + state = new LoginState(); + } + catch (PtcOfflineException) + { + session.EventDispatcher.Send(new ErrorEvent { Message = session.Translation.GetTranslation(TranslationString.PtcOffline) }); + session.EventDispatcher.Send(new NoticeEvent { Message = session.Translation.GetTranslation(TranslationString.TryingAgainIn, 15) }); + + await Delay(1000).ConfigureAwait(false); + state = _initialState; + } + catch (GoogleOfflineException) + { + session.EventDispatcher.Send(new ErrorEvent { Message = session.Translation.GetTranslation(TranslationString.GoogleOffline) }); + session.EventDispatcher.Send(new NoticeEvent { Message = session.Translation.GetTranslation(TranslationString.TryingAgainIn, 15) }); + + await Delay(15000).ConfigureAwait(false); + state = _initialState; + } + catch (AccessTokenExpiredException) + { + session.EventDispatcher.Send(new NoticeEvent { Message = "Access Token Expired. Logging in again..." }); + state = _initialState; + } + catch (CaptchaException captchaException) + { + var resolved = await CaptchaManager.SolveCaptcha(session, captchaException.Url).ConfigureAwait(false); + if (!resolved) + { + if (session.LogicSettings.NotificationConfig.EnablePushBulletNotification) + await PushNotificationClient.SendNotification(session, $"Captcha required {session.Settings.Username}", session.Translation.GetTranslation(TranslationString.CaptchaShown), true).ConfigureAwait(false); + + session.EventDispatcher.Send(new WarnEvent { Message = session.Translation.GetTranslation(TranslationString.CaptchaShown) }); + Logger.Debug("Captcha not resolved"); + if (manager.AllowMultipleBot()) + { + Logger.Debug("Change account"); + TinyIoCContainer.Current.Resolve().BlockCurrentBot(15); + ReInitializeSession(session, globalSettings); + state = new LoginState(); + } + else + { + session.EventDispatcher.Send(new ErrorEvent { Message = session.Translation.GetTranslation(TranslationString.ExitNowAfterEnterKey) }); + Console.ReadKey(); + Environment.Exit(0); + } + } + else + { + //resolve captcha + state = new LoginState(); + } + } + catch (HasherException ex) + { + session.EventDispatcher.Send(new ErrorEvent {Message = ex.Message}); + // session.EventDispatcher.Send(new ErrorEvent { Message = session.Translation.GetTranslation(TranslationString.ExitNowAfterEnterKey) }); + state = new IdleState(); + //Console.ReadKey(); + //System.Environment.Exit(1); + } + catch (Exception ex) + { + session.EventDispatcher.Send(new ErrorEvent { Message = "Pokemon Servers might be offline / unstable. Trying again..." }); + session.EventDispatcher.Send(new ErrorEvent { Message = "Error: " + ex }); + if (state is LoginState) + { + } + else + state = _initialState; + } + } while (state != null); + configWatcher.EnableRaisingEvents = false; + configWatcher.Dispose(); + } + } +} diff --git a/PoGo.NecroBot.Logic/State/VersionCheckState.cs b/PoGo.NecroBot.Logic/State/VersionCheckState.cs new file mode 100644 index 000000000..6025794e1 --- /dev/null +++ b/PoGo.NecroBot.Logic/State/VersionCheckState.cs @@ -0,0 +1,252 @@ +#region using directives + +using System; +using System.Diagnostics; +using System.IO; +using System.IO.Compression; +using System.Media; +using System.Reflection; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Newtonsoft.Json.Linq; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.Logging; +using PoGo.NecroBot.Logic.Forms; +using System.Net.Http; + +#endregion + +namespace PoGo.NecroBot.Logic.State +{ + public class VersionCheckState : IState + { + public const string VersionUri = + "https://raw.githubusercontent.com/NecroBot-Private/NecroBot/master/PoGo.NecroBot.Logic/Properties/AssemblyInfo.cs"; + + public const string RemoteReleaseUrl = + "https://github.com/NecroBot-Private/NecroBot/releases/download/v"; + + public const string ChangelogUri = + "https://raw.githubusercontent.com/NecroBot-Private/NecroBot/master/CHANGELOG.md"; + + public static Version RemoteVersion; + + public async Task Execute(ISession session, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + await CleanupOldFiles().ConfigureAwait(false); + + if (!session.LogicSettings.CheckForUpdates) + { + session.EventDispatcher.Send(new UpdateEvent + { + Message = session.Translation.GetTranslation(TranslationString.CheckForUpdatesDisabled, + Assembly.GetExecutingAssembly().GetName().Version.ToString(3)) + }); + + return new LoginState(); + } + + var autoUpdate = session.LogicSettings.AutoUpdate; + var isLatest = await IsLatest().ConfigureAwait(false); + if (isLatest) + { + session.EventDispatcher.Send(new UpdateEvent + { + Message = + session.Translation.GetTranslation(TranslationString.GotUpToDateVersion, Assembly.GetExecutingAssembly().GetName().Version.ToString(4)) + }); + return new LoginState(); + } + + SystemSounds.Asterisk.Play(); + + string zipName = $"NecroBot2.Console.{RemoteVersion.ToString()}.zip"; + if (Assembly.GetEntryAssembly().FullName.ToLower().Contains("NecroBot2.win")) + { + zipName = $"NecroBot2.WIN.{RemoteVersion.ToString()}.zip"; + } + var downloadLink = $"{RemoteReleaseUrl}{RemoteVersion}/{zipName}"; + + var baseDir = Directory.GetCurrentDirectory(); + var downloadFilePath = Path.Combine(baseDir, zipName); + var tempPath = Path.Combine(baseDir, "tmp"); + var extractedDir = Path.Combine(tempPath, "NecroBot2"); + var destinationDir = baseDir + Path.DirectorySeparatorChar; + bool updated = false; + AutoUpdateForm autoUpdateForm = new AutoUpdateForm() + { + Session = session, + DownloadLink = downloadLink, + ChangelogLink = ChangelogUri, + Destination = downloadFilePath, + AutoUpdate = true, + CurrentVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString(), + LatestVersion = $"{RemoteVersion}" + }; + + updated = (autoUpdateForm.ShowDialog() == DialogResult.OK); + + + if (!updated) + { + Logger.Write("Update Skipped", LogLevel.Update); + return new LoginState(); + } + + if (!UnpackFile(downloadFilePath, extractedDir)) + return new LoginState(); + + session.EventDispatcher.Send(new UpdateEvent + { + Message = session.Translation.GetTranslation(TranslationString.FinishedUnpackingFiles) + }); + + if (!MoveAllFiles(extractedDir, destinationDir)) + return new LoginState(); + + session.EventDispatcher.Send(new UpdateEvent + { + Message = session.Translation.GetTranslation(TranslationString.UpdateFinished) + }); + + Process.Start(Assembly.GetEntryAssembly().Location); + Environment.Exit(-1); + return null; + } + + public static async Task CleanupOldFiles() + { + var tmpDir = Path.Combine(Directory.GetCurrentDirectory(), "tmp"); + + if (Directory.Exists(tmpDir)) + { + Directory.Delete(tmpDir, true); + } + + var di = new DirectoryInfo(Directory.GetCurrentDirectory()); + var files = di.GetFiles("*.old", SearchOption.AllDirectories); + + foreach (var file in files) + { + try + { + if (file.Name.Contains("vshost") || file.Name.Contains(".gpx.old") || file.Name.Contains("chromedriver.exe.old")) + continue; + File.Delete(file.FullName); + } + catch (Exception e) + { + Logger.Write(e.ToString()); + } + } + await Task.Delay(200).ConfigureAwait(false); + } + + private async static Task DownloadServerVersion() + { + using (HttpClient client = new HttpClient()) + { + var responseContent = await client.GetAsync(VersionUri).ConfigureAwait(false); + return await responseContent.Content.ReadAsStringAsync().ConfigureAwait(false); + } + } + + private static JObject GetJObject(string filePath) + { + return JObject.Parse(File.ReadAllText(filePath)); + } + + + public static async Task IsLatest() + { + try + { + var regex = new Regex(@"\[assembly\: AssemblyVersion\(""(\d{1,})\.(\d{1,})\.(\d{1,})\.(\d{1,})""\)\]"); + var match = regex.Match(await DownloadServerVersion().ConfigureAwait(false)); + + if (!match.Success) + return false; + + var gitVersion = new Version($"{match.Groups[1]}.{match.Groups[2]}.{match.Groups[3]}.{match.Groups[4]}"); + RemoteVersion = gitVersion; + if (gitVersion > Assembly.GetExecutingAssembly().GetName().Version) + return false; + } + catch (Exception) + { + return true; //better than just doing nothing when git server down + } + + return true; + } + + public static bool MoveAllFiles(string sourceFolder, string destFolder) + { + if (!Directory.Exists(destFolder)) + Directory.CreateDirectory(destFolder); + + var oldfiles = Directory.GetFiles(destFolder); + foreach (var old in oldfiles) + { + if (old.Contains("vshost") || old.Contains(".gpx") || old.Contains("config.json") || + old.Contains("config.xlsm") || old.Contains("auth.json") || old.Contains("SessionStats.db") || + old.Contains("LastPos.ini") || old.Contains("chromedriver.exe") || old.Contains("accounts.db")) continue; + if (File.Exists(old + ".old")) continue; + File.Move(old, old + ".old"); + } + + try + { + var files = Directory.GetFiles(sourceFolder); + foreach (var file in files) + { + if (file.Contains("vshost") || file.Contains(".gpx")) continue; + var name = Path.GetFileName(file); + var dest = Path.Combine(destFolder, name); + try { + File.Copy(file, dest, true); + } + catch(Exception ) + { + Logger.Write($"Error occurred while copy {file}, This seem like chromedriver.exe is being locked, you need manually copy after you close all chrome instance or ignore it"); + } + } + + var folders = Directory.GetDirectories(sourceFolder); + + foreach (var folder in folders) + { + var name = Path.GetFileName(folder); + if (name == null) continue; + var dest = Path.Combine(destFolder, name); + MoveAllFiles(folder, dest); + } + } + catch (Exception) + { + return false; + } + return true; + } + + public static bool UnpackFile(string sourceTarget, string destPath) + { + var source = sourceTarget; + var dest = destPath; + try + { + ZipFile.ExtractToDirectory(source, dest); + } + catch (Exception) + { + return false; + } + return true; + } + } +} diff --git a/PoGo.NecroBot.Logic/StatisticsAggregator.cs b/PoGo.NecroBot.Logic/StatisticsAggregator.cs new file mode 100644 index 000000000..1ed5d08d5 --- /dev/null +++ b/PoGo.NecroBot.Logic/StatisticsAggregator.cs @@ -0,0 +1,182 @@ +#region using directives + +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.Exceptions; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Utils; +using POGOProtos.Networking.Responses; + +#endregion + +namespace PoGo.NecroBot.Logic +{ + public class StatisticsAggregator + { + private readonly Statistics _stats; + + public Statistics GetCurrent() + { + return _stats; + } + + public StatisticsAggregator(Statistics stats) + { + _stats = stats; + } + + public void HandleEvent(string evt, ISession session) + { + } + + public void HandleEvent(BotSwitchedEvent evt, ISession session) + { + _stats.Reset(); + } + + + private void HandleEvent(UseLuckyEggEvent event1, ISession session) + { + } + + public void HandleEvent(UpdateEvent evt, ISession session) + { + } + + public void HandleEvent(UpdatePositionEvent evt, ISession session) + { + } + + public void HandleEvent(EggIncubatorStatusEvent evt, ISession session) + { + } + + public void HandleEvent(ProfileEvent evt, ISession session) + { + _stats.SetUsername(evt.Profile); + _stats.Dirty(session.Inventory, session); + } + + public void HandleEvent(SnipeModeEvent evt, ISession session) + { + } + + public void HandleEvent(ErrorEvent evt, ISession session) + { + } + + public void HandleEvent(SnipeScanEvent evt, ISession session) + { + } + + public void HandleEvent(NoticeEvent evt, ISession session) + { + } + + public void HandleEvent(WarnEvent evt, ISession session) + { + } + + public void HandleEvent(PokemonEvolveEvent evt, ISession session) + { + _stats.TotalExperience += evt.Exp; + _stats.TotalPokemonEvolved++; + _stats.Dirty(session.Inventory, session); + } + + public void HandleEvent(TransferPokemonEvent evt, ISession session) + { + _stats.TotalPokemonTransferred++; + _stats.Dirty(session.Inventory, session); + } + + public void HandleEvent(ItemRecycledEvent evt, ISession session) + { + _stats.TotalItemsRemoved++; + _stats.Dirty(session.Inventory, session); + } + + public void HandleEvent(FortUsedEvent evt, ISession session) + { + _stats.TotalExperience += evt.Exp; + _stats.TotalPokestops++; + _stats.Dirty(session.Inventory, session); + } + + public void HandleEvent(FortTargetEvent evt, ISession session) + { + } + + public void HandleEvent(PokemonCaptureEvent evt, ISession session) + { + if (evt.Status == CatchPokemonResponse.Types.CatchStatus.CatchSuccess) + { + _stats.TotalExperience += evt.Exp; + _stats.TotalPokemons++; + _stats.TotalStardust = evt.Stardust; + _stats.Dirty(session.Inventory, session); + } + } + + public void HandleEvent(NoPokeballEvent evt, ISession session) + { + } + + public void HandleEvent(DisplayHighestsPokemonEvent evt, ISession session) + { + } + + public void HandleEvent(UseBerryEvent evt, ISession session) + { + } + + public void HandleEvent(IEvent evt, ISession session) + { + } + + public void Listen(IEvent evt, ISession session) + { + dynamic eve = evt; + + try + { + HandleEvent(eve, session); + } + catch (ActiveSwitchByRuleException ex) + { + _stats.Reset(); + throw ex; //do not stop handle bot switcher account. + } + catch + { + } + } + + private void HandleEvent(PokeStopListEvent event1, ISession session) + { + } + + private void HandleEvent(EggHatchedEvent event1, ISession session) + { + } + + private void HandleEvent(EggsListEvent event1, ISession session) + { + } + + private void HandleEvent(EvolveCountEvent event1, ISession session) + { + } + + private void HandleEvent(FortFailedEvent event1, ISession session) + { + } + + private void HandleEvent(PokemonListEvent event1, ISession session) + { + } + + private void HandleEvent(SnipeEvent event1, ISession session) + { + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Strategies/Walk/BaseWalkStrategy.cs b/PoGo.NecroBot.Logic/Strategies/Walk/BaseWalkStrategy.cs new file mode 100644 index 000000000..84f2741a5 --- /dev/null +++ b/PoGo.NecroBot.Logic/Strategies/Walk/BaseWalkStrategy.cs @@ -0,0 +1,249 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.Interfaces.Configuration; +using PoGo.NecroBot.Logic.Model; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Utils; +using PokemonGo.RocketAPI; +using GeoCoordinatePortable; + +namespace PoGo.NecroBot.Logic.Strategies.Walk +{ + abstract class BaseWalkStrategy : IWalkStrategy + { + public static Client _client; + + protected double _currentWalkingSpeed = 0; + protected const double SpeedDownTo = 10 / 3.6; + protected double _minStepLengthInMeters = 1.3d; + protected bool isCancelled = false; + protected readonly Random _randWalking = new Random(); + + public event UpdatePositionDelegate UpdatePositionEvent; + public virtual List Points { get; set; } + + public abstract Task Walk(IGeoLocation targetLocation, Func functionExecutedWhileWalking, ISession session, CancellationToken cancellationToken, double walkSpeed = 0.0); + + public virtual string RouteName { get; } + + public BaseWalkStrategy(Client client) + { + _client = client; + } + + public void OnStartWalking(ISession session, IGeoLocation desination, double calculatedDistance = 0.0) + { + var distance = calculatedDistance; + if (distance == 0) + { + distance = CalculateDistance(session.Client.CurrentLatitude, session.Client.CurrentLongitude, + desination.Latitude, desination.Longitude); + } + + if (desination is FortLocation) + { + var fortLocation = desination as FortLocation; + session.EventDispatcher.Send(new FortTargetEvent + { + Name = desination.Name, + Distance = distance, + Route = RouteName, + Type = fortLocation.FortData.Type + }); + } + } + + internal void DoUpdatePositionEvent(ISession session, double latitude, double longitude, double speed, double variant = 0.0) + { + UpdatePositionEvent?.Invoke(session, latitude, longitude, speed); + } + + /// + /// Cell phones Gps systems can't generate accurate GEO, the average best they can is 5 meter. + /// http://gis.stackexchange.com/questions/43617/what-is-the-maximum-theoretical-accuracy-of-gps + /// + public async Task GenerateUnaccurateGeocoordinate(GeoCoordinate geo, double nextWaypointBearing) + { + var minBearing = Convert.ToInt32(nextWaypointBearing - 40); + minBearing = minBearing > 0 ? minBearing : minBearing * -1; + var maxBearing = Convert.ToInt32(nextWaypointBearing + 40); + maxBearing = maxBearing < 360 ? maxBearing : 360 - maxBearing; + + var randomBearingDegrees = _randWalking.NextDouble() + + _randWalking.Next( + Math.Min(minBearing, maxBearing), + Math.Max(minBearing, maxBearing) + ); + + var randomDistance = _randWalking.NextDouble() * 3; + + return await LocationUtils.CreateWaypoint(geo, randomDistance, randomBearingDegrees).ConfigureAwait(false); + } + + public Task RedirectToNextFallbackStrategy(ILogicSettings logicSettings, + IGeoLocation targetLocation, Func functionExecutedWhileWalking, ISession session, + CancellationToken cancellationToken, double walkSpeed = 0.0) + { + // If we need to fall-back, then blacklist current strategy for 1 hour. + session.Navigation.BlacklistStrategy(GetType()); + + IWalkStrategy nextStrategy = session.Navigation.GetStrategy(logicSettings); + + return nextStrategy.Walk(targetLocation, functionExecutedWhileWalking, session, cancellationToken); + } + + public async Task DoWalk(List points, ISession session, + Func functionExecutedWhileWalking, GeoCoordinate sourceLocation, GeoCoordinate targetLocation, + CancellationToken cancellationToken, double walkSpeed = 0.0) + { + var currentLocation = new GeoCoordinate(_client.CurrentLatitude, _client.CurrentLongitude, + _client.CurrentAltitude); + + //filter google defined waypoints and remove those that are too near to the previous ones + var waypointsDists = new Dictionary, double>(); + var minWaypointsDistance = RandomizeStepLength(_minStepLengthInMeters); + + for (var i = 0; i < points.Count; i++) + { + if (i > 0) + { + var dist = LocationUtils.CalculateDistanceInMeters(points[i - 1], points[i]); + waypointsDists[new Tuple(points[i - 1], points[i])] = dist; + } + } + + var tooNearPoints = waypointsDists.Where(kvp => kvp.Value < minWaypointsDistance) + .Select(kvp => kvp.Key.Item1) + .ToList(); + foreach (var tooNearPoint in tooNearPoints) + { + points.Remove(tooNearPoint); + } + if (points.Any()) //check if first waypoint is the current location (this is what google returns), in such case remove it! + { + var firstStep = points.First(); + if (firstStep == currentLocation) + points.Remove(points.First()); + } + + Points = points; + + var walkedPointsList = new List(); + foreach (var nextStep in points) + { + currentLocation = new GeoCoordinate(_client.CurrentLatitude, _client.CurrentLongitude); + if (_currentWalkingSpeed <= 0) + _currentWalkingSpeed = session.LogicSettings.WalkingSpeedInKilometerPerHour; + if (session.LogicSettings.UseWalkingSpeedVariant && walkSpeed == 0) + _currentWalkingSpeed = session.Navigation.VariantRandom(session, _currentWalkingSpeed); + + var speedInMetersPerSecond = (walkSpeed > 0 ? walkSpeed : _currentWalkingSpeed) / 3.6; + + var nextStepBearing = LocationUtils.DegreeBearing(currentLocation, nextStep); + //particular steps are limited by minimal length, first step is calculated from the original speed per second (distance in 1s) + var nextStepDistance = Math.Max(RandomizeStepLength(_minStepLengthInMeters), speedInMetersPerSecond); + + var waypoint = await LocationUtils.CreateWaypoint(currentLocation, nextStepDistance, nextStepBearing).ConfigureAwait(false); + walkedPointsList.Add(waypoint); + + var previousLocation = + currentLocation; //store the current location for comparison and correction purposes + var requestSendDateTime = DateTime.Now; + await LocationUtils.UpdatePlayerLocationWithAltitude(session, waypoint, + (float)speedInMetersPerSecond).ConfigureAwait(false); + + var realDistanceToTarget = LocationUtils.CalculateDistanceInMeters(currentLocation, targetLocation); + if (realDistanceToTarget < 2) + break; + + do + { + cancellationToken.ThrowIfCancellationRequested(); + TinyIoC.TinyIoCContainer.Current.Resolve().ThrowIfSwitchAccountRequested(); + var msToPositionChange = (DateTime.Now - requestSendDateTime).TotalMilliseconds; + currentLocation = new GeoCoordinate(_client.CurrentLatitude, _client.CurrentLongitude); + var currentDistanceToWaypoint = LocationUtils.CalculateDistanceInMeters(currentLocation, nextStep); + realDistanceToTarget = LocationUtils.CalculateDistanceInMeters(currentLocation, targetLocation); + + var realSpeedinMperS = nextStepDistance / (msToPositionChange / 1000); + var realDistanceWalked = LocationUtils.CalculateDistanceInMeters(previousLocation, currentLocation); + //if the real calculated speed is lower than the one expected, we will raise the speed for the following step + double speedRaise = 0; + if (realSpeedinMperS < speedInMetersPerSecond) + speedRaise = speedInMetersPerSecond - realSpeedinMperS; + double distanceRaise = 0; + if (realDistanceWalked < nextStepDistance) + distanceRaise = nextStepDistance - realDistanceWalked; + + var realDistanceToTargetSpeedDown = + LocationUtils.CalculateDistanceInMeters(currentLocation, targetLocation); + if (realDistanceToTargetSpeedDown < 40) + if (speedInMetersPerSecond > SpeedDownTo) + speedInMetersPerSecond = SpeedDownTo; + + if (session.LogicSettings.UseWalkingSpeedVariant && walkSpeed == 0) + { + _currentWalkingSpeed = session.Navigation.VariantRandom(session, _currentWalkingSpeed); + speedInMetersPerSecond = _currentWalkingSpeed / 3.6; + } + speedInMetersPerSecond += speedRaise; + if (walkSpeed > 0) + { + speedInMetersPerSecond = walkSpeed / 3.6; + } + nextStepBearing = LocationUtils.DegreeBearing(currentLocation, nextStep); + + //setting next step distance is limited by the target and the next waypoint distance (we don't want to miss them) + //also the minimal step length is used as we don't want to spend minutes jumping by cm lengths + nextStepDistance = Math.Min(Math.Min(realDistanceToTarget, currentDistanceToWaypoint), + //also add the distance raise (bot overhead corrections) to the normal step length + Math.Max(RandomizeStepLength(_minStepLengthInMeters) + distanceRaise, + (msToPositionChange / 1000) * speedInMetersPerSecond) + distanceRaise); + int timeToWalk = (int)((nextStepDistance * 1000) / speedInMetersPerSecond); + //Logger.Debug($"nextStepDistance {nextStepDistance} need {timeToWalk} ms"); + + waypoint = await LocationUtils.CreateWaypoint(currentLocation, nextStepDistance, nextStepBearing).ConfigureAwait(false); + walkedPointsList.Add(waypoint); + + //store the current location for comparison and correction purposes + previousLocation = currentLocation; + requestSendDateTime = DateTime.Now; + await LocationUtils.UpdatePlayerLocationWithAltitude(session, waypoint, (float)speedInMetersPerSecond).ConfigureAwait(false); + + UpdatePositionEvent?.Invoke(session, waypoint.Latitude, waypoint.Longitude, _currentWalkingSpeed); + + await Task.Delay(timeToWalk).ConfigureAwait(false); + if (functionExecutedWhileWalking != null) + await functionExecutedWhileWalking().ConfigureAwait(false); // look for pokemon + } while (LocationUtils.CalculateDistanceInMeters(currentLocation, nextStep) >= 2); + + UpdatePositionEvent?.Invoke(session, nextStep.Latitude, nextStep.Longitude, _currentWalkingSpeed); + } + } + + /// + /// Basic step length is given but we want to randomize it a bit to avoid usage of steps of the same length + /// + /// Length of the step in meters + /// + protected double RandomizeStepLength(double initialStepLength) + { + var randFactor = 0.3d; + var initialStepLengthMm = initialStepLength * 1000; + var randomMin = (int)(initialStepLengthMm * (1 - randFactor)); + var randomMax = (int)(initialStepLengthMm * (1 + randFactor)); + var randStep = _randWalking.Next(randomMin, randomMax); + return randStep / 1000d; + } + + public virtual double CalculateDistance(double sourceLat, double sourceLng, double destinationLat, + double destinationLng, ISession session = null) + { + return LocationUtils.CalculateDistanceInMeters(sourceLat, sourceLng, destinationLat, destinationLng); + } + } +} diff --git a/PoGo.NecroBot.Logic/Strategies/Walk/FlyStrategy.cs b/PoGo.NecroBot.Logic/Strategies/Walk/FlyStrategy.cs new file mode 100644 index 000000000..d38261d63 --- /dev/null +++ b/PoGo.NecroBot.Logic/Strategies/Walk/FlyStrategy.cs @@ -0,0 +1,86 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Model; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Utils; +using PokemonGo.RocketAPI; +using GeoCoordinatePortable; + +namespace PoGo.NecroBot.Logic.Strategies.Walk +{ + class FlyStrategy : BaseWalkStrategy + { + public FlyStrategy(Client client) : base(client) + { + } + + public override string RouteName => "NecroBot Flying"; + + + public override async Task Walk(IGeoLocation targetLocation, + Func functionExecutedWhileWalking, ISession session, CancellationToken cancellationToken, + double walkSpeed = 0.0) + { + var curLocation = new GeoCoordinate(_client.CurrentLatitude, _client.CurrentLongitude); + var destinaionCoordinate = new GeoCoordinate(targetLocation.Latitude, targetLocation.Longitude); + + var dist = LocationUtils.CalculateDistanceInMeters(curLocation, destinaionCoordinate); + if (dist >= 100) + { + var nextWaypointDistance = dist * 70 / 100; + var nextWaypointBearing = LocationUtils.DegreeBearing(curLocation, destinaionCoordinate); + + var waypoint = await LocationUtils.CreateWaypoint(curLocation, nextWaypointDistance, nextWaypointBearing).ConfigureAwait(false); + var sentTime = DateTime.Now; + + // We are setting speed to 0, so it will be randomly generated speed. + await LocationUtils.UpdatePlayerLocationWithAltitude(session, waypoint, 0).ConfigureAwait(false); + base.DoUpdatePositionEvent(session, waypoint.Latitude, waypoint.Longitude, walkSpeed,0); + + do + { + cancellationToken.ThrowIfCancellationRequested(); + TinyIoC.TinyIoCContainer.Current.Resolve().ThrowIfSwitchAccountRequested(); + var millisecondsUntilGetUpdatePlayerLocationResponse = + (DateTime.Now - sentTime).TotalMilliseconds; + + curLocation = new GeoCoordinate(_client.CurrentLatitude, _client.CurrentLongitude); + var currentDistanceToTarget = LocationUtils.CalculateDistanceInMeters(curLocation, destinaionCoordinate); + + dist = LocationUtils.CalculateDistanceInMeters(curLocation, destinaionCoordinate); + + if (dist >= 100) + nextWaypointDistance = dist * 70 / 100; + else + nextWaypointDistance = dist; + + nextWaypointBearing = LocationUtils.DegreeBearing(curLocation, destinaionCoordinate); + waypoint = await LocationUtils.CreateWaypoint(curLocation, nextWaypointDistance, nextWaypointBearing).ConfigureAwait(false); + sentTime = DateTime.Now; + // We are setting speed to 0, so it will be randomly generated speed. + await LocationUtils.UpdatePlayerLocationWithAltitude(session, waypoint, 0).ConfigureAwait(false); + base.DoUpdatePositionEvent(session, waypoint.Latitude, waypoint.Longitude, walkSpeed); + + + if (functionExecutedWhileWalking != null) + await functionExecutedWhileWalking().ConfigureAwait(false); // look for pokemon + } while (LocationUtils.CalculateDistanceInMeters(curLocation, destinaionCoordinate) >= 10); + } + else + { + // We are setting speed to 0, so it will be randomly generated speed. + await LocationUtils.UpdatePlayerLocationWithAltitude(session, targetLocation.ToGeoCoordinate(), 0).ConfigureAwait(false); + base.DoUpdatePositionEvent(session, targetLocation.Latitude, targetLocation.Longitude,walkSpeed); + if (functionExecutedWhileWalking != null) + await functionExecutedWhileWalking().ConfigureAwait(false); // look for pokemon + } + } + + public override double CalculateDistance(double sourceLat, double sourceLng, double destinationLat, + double destinationLng, ISession session = null) + { + return LocationUtils.CalculateDistanceInMeters(sourceLat, sourceLng, destinationLat, destinationLng); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Strategies/Walk/GoogleStrategy.cs b/PoGo.NecroBot.Logic/Strategies/Walk/GoogleStrategy.cs new file mode 100644 index 000000000..975448295 --- /dev/null +++ b/PoGo.NecroBot.Logic/Strategies/Walk/GoogleStrategy.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Model; +using PoGo.NecroBot.Logic.Service; +using PoGo.NecroBot.Logic.State; +using PokemonGo.RocketAPI; +using GeoCoordinatePortable; + +namespace PoGo.NecroBot.Logic.Strategies.Walk +{ + class GoogleStrategy : BaseWalkStrategy, IWalkStrategy + { + private GoogleDirectionsService _googleDirectionsService; + + public GoogleStrategy(Client client) : base(client) + { + _googleDirectionsService = null; + } + + public override string RouteName => "Google Walk"; + + public override async Task Walk(IGeoLocation targetLocation, + Func functionExecutedWhileWalking, ISession session, CancellationToken cancellationToken, + double walkSpeed = 0.0) + { + GetGoogleInstance(session); + _minStepLengthInMeters = session.LogicSettings.DefaultStepLength; + var currentLocation = new GeoCoordinate(_client.CurrentLatitude, _client.CurrentLongitude, _client.CurrentAltitude); + var destinaionCoordinate = new GeoCoordinate(targetLocation.Latitude, targetLocation.Longitude); + + var googleWalk = await _googleDirectionsService.GetDirections(currentLocation, new List(), destinaionCoordinate).ConfigureAwait(false); + + if (googleWalk == null) + { + await RedirectToNextFallbackStrategy(session.LogicSettings, targetLocation, functionExecutedWhileWalking, session, cancellationToken, walkSpeed).ConfigureAwait(false); + return; + } + + base.OnStartWalking(session, targetLocation, googleWalk.Distance); + + List points = googleWalk.Waypoints; + await DoWalk(points, session, functionExecutedWhileWalking, currentLocation, destinaionCoordinate, cancellationToken, walkSpeed).ConfigureAwait(false); + } + + private void GetGoogleInstance(ISession session) + { + if (_googleDirectionsService == null) + _googleDirectionsService = new GoogleDirectionsService(session); + } + + public override double CalculateDistance(double sourceLat, double sourceLng, double destinationLat, + double destinationLng, ISession session = null) + { + // Too expensive to calculate true distance. + return 1.5 * base.CalculateDistance(sourceLat, sourceLng, destinationLat, destinationLng); + + /* + if (session != null) + GetGoogleInstance(session); + + //Check Google direction service is initialized + if (_googleDirectionsService != null) + { + var googleResult = _googleDirectionsService.GetDirections(new GeoCoordinate(sourceLat, sourceLng), new List(), new GeoCoordinate(destinationLat, destinationLng)); + if (googleResult == null || googleResult.Directions.status.Equals("OVER_QUERY_LIMIT")) + { + return 1.5 * base.CalculateDistance(sourceLat, sourceLng, destinationLat, destinationLng); + } + else + { + //There are two ways to get distance value. One is counting from waypoint list. + //The other is getting from google service. The result value is different. + + ////Count distance from waypoint list + //var googleWalk = GoogleWalk.Get(googleResult); + //List points = googleWalk.Waypoints; + //double distance = 0; + //for (var i = 0; i < points.Count; i++) + //{ + // if (i > 0) + // { + // distance += LocationUtils.CalculateDistanceInMeters(points[i - 1], points[i]); + // } + //} + //return distance; + + //Get distance from Google service + return googleResult.GetDistance(); + } + } + else + { + return 1.5 * base.CalculateDistance(sourceLat, sourceLng, destinationLat, destinationLng); + } + */ + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Strategies/Walk/HumanPathWalkingStrategy.cs b/PoGo.NecroBot.Logic/Strategies/Walk/HumanPathWalkingStrategy.cs new file mode 100644 index 000000000..51c87c7da --- /dev/null +++ b/PoGo.NecroBot.Logic/Strategies/Walk/HumanPathWalkingStrategy.cs @@ -0,0 +1,100 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Model; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Utils; +using PokemonGo.RocketAPI; +using GeoCoordinatePortable; + +namespace PoGo.NecroBot.Logic.Strategies.Walk +{ + class HumanPathWalkingStrategy : BaseWalkStrategy + { + private double CurrentWalkingSpeed = 0; + + public HumanPathWalkingStrategy(Client client) : base(client) + { + } + + public override string RouteName => "NecroBot GPX"; + + public override async Task Walk(IGeoLocation targetLocation, + Func functionExecutedWhileWalking, ISession session, CancellationToken cancellationToken, + double walkSpeed = 0.0) + { + cancellationToken.ThrowIfCancellationRequested(); + TinyIoC.TinyIoCContainer.Current.Resolve().ThrowIfSwitchAccountRequested(); + var destinaionCoordinate = new GeoCoordinate(targetLocation.Latitude, targetLocation.Longitude); + //PlayerUpdateResponse result = null; + + if (CurrentWalkingSpeed <= 0) + CurrentWalkingSpeed = session.LogicSettings.WalkingSpeedInKilometerPerHour; + if (session.LogicSettings.UseWalkingSpeedVariant && walkSpeed == 0) + CurrentWalkingSpeed = session.Navigation.VariantRandom(session, CurrentWalkingSpeed); + + var rw = new Random(); + var speedInMetersPerSecond = (walkSpeed > 0 ? walkSpeed : CurrentWalkingSpeed) / 3.6; + var sourceLocation = new GeoCoordinate(_client.CurrentLatitude, _client.CurrentLongitude); + LocationUtils.CalculateDistanceInMeters(sourceLocation, destinaionCoordinate); + var nextWaypointBearing = LocationUtils.DegreeBearing(sourceLocation, destinaionCoordinate); + var nextWaypointDistance = speedInMetersPerSecond; + var waypoint = await LocationUtils.CreateWaypoint(sourceLocation, nextWaypointDistance, nextWaypointBearing).ConfigureAwait(false); + var requestSendDateTime = DateTime.Now; + var requestVariantDateTime = DateTime.Now; + + await LocationUtils.UpdatePlayerLocationWithAltitude(session, waypoint, (float) speedInMetersPerSecond).ConfigureAwait(false); + + double SpeedVariantSec = rw.Next(1000, 10000); + base.DoUpdatePositionEvent(session, waypoint.Latitude, waypoint.Longitude, walkSpeed, CurrentWalkingSpeed); + + do + { + cancellationToken.ThrowIfCancellationRequested(); + TinyIoC.TinyIoCContainer.Current.Resolve().ThrowIfSwitchAccountRequested(); + var millisecondsUntilGetUpdatePlayerLocationResponse = + (DateTime.Now - requestSendDateTime).TotalMilliseconds; + var millisecondsUntilVariant = + (DateTime.Now - requestVariantDateTime).TotalMilliseconds; + + sourceLocation = new GeoCoordinate(_client.CurrentLatitude, _client.CurrentLongitude); + var currentDistanceToTarget = LocationUtils + .CalculateDistanceInMeters(sourceLocation, destinaionCoordinate); + + //if (currentDistanceToTarget < 40) + //{ + // if (speedInMetersPerSecond > SpeedDownTo) + // { + // //Logger.Write("We are within 40 meters of the target. Speeding down to 10 km/h to not pass the target.", LogLevel.Info); + // speedInMetersPerSecond = SpeedDownTo; + // } + //} + + if (session.LogicSettings.UseWalkingSpeedVariant && walkSpeed == 0) + { + CurrentWalkingSpeed = session.Navigation.VariantRandom(session, CurrentWalkingSpeed); + speedInMetersPerSecond = CurrentWalkingSpeed / 3.6; + } + + nextWaypointDistance = Math.Min(currentDistanceToTarget, + millisecondsUntilGetUpdatePlayerLocationResponse / 1000 * speedInMetersPerSecond); + nextWaypointBearing = LocationUtils.DegreeBearing(sourceLocation, destinaionCoordinate); + waypoint = await LocationUtils.CreateWaypoint(sourceLocation, nextWaypointDistance, nextWaypointBearing).ConfigureAwait(false); + + requestSendDateTime = DateTime.Now; + await LocationUtils.UpdatePlayerLocationWithAltitude(session, waypoint, (float) speedInMetersPerSecond).ConfigureAwait(false); + + base.DoUpdatePositionEvent(session, waypoint.Latitude, waypoint.Longitude, CurrentWalkingSpeed); + + if (functionExecutedWhileWalking != null) + await functionExecutedWhileWalking().ConfigureAwait(false); // look for pokemon & hit stops + } while (LocationUtils.CalculateDistanceInMeters(sourceLocation, destinaionCoordinate) >= 2); + } + + public override double CalculateDistance(double sourceLat, double sourceLng, double destinationLat, + double destinationLng, ISession session = null) + { + return LocationUtils.CalculateDistanceInMeters(sourceLat, sourceLng, destinationLat, destinationLng); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Strategies/Walk/HumanStrategy.cs b/PoGo.NecroBot.Logic/Strategies/Walk/HumanStrategy.cs new file mode 100644 index 000000000..2125f8986 --- /dev/null +++ b/PoGo.NecroBot.Logic/Strategies/Walk/HumanStrategy.cs @@ -0,0 +1,99 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Model; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Utils; +using PokemonGo.RocketAPI; +using GeoCoordinatePortable; + +namespace PoGo.NecroBot.Logic.Strategies.Walk +{ + class HumanStrategy : BaseWalkStrategy + { + private double CurrentWalkingSpeed = 0; + + public HumanStrategy(Client client) : base(client) + { + } + + public override string RouteName => "NecroBot Walk"; + + public override async Task Walk(IGeoLocation targetLocation, + Func functionExecutedWhileWalking, ISession session, CancellationToken cancellationToken, + double walkSpeed = 0.0) + { + base.OnStartWalking(session, targetLocation); + + var destinaionCoordinate = new GeoCoordinate(targetLocation.Latitude, targetLocation.Longitude); + + if (CurrentWalkingSpeed <= 0) + CurrentWalkingSpeed = session.LogicSettings.WalkingSpeedInKilometerPerHour; + if (session.LogicSettings.UseWalkingSpeedVariant && walkSpeed == 0) + CurrentWalkingSpeed = session.Navigation.VariantRandom(session, CurrentWalkingSpeed); + + var rw = new Random(); + var speedInMetersPerSecond = (walkSpeed > 0 ? walkSpeed : CurrentWalkingSpeed) / 3.6; + + var sourceLocation = new GeoCoordinate(_client.CurrentLatitude, _client.CurrentLongitude); + + var nextWaypointBearing = LocationUtils.DegreeBearing(sourceLocation, destinaionCoordinate); + + + var nextWaypointDistance = speedInMetersPerSecond; + var waypoint = await LocationUtils.CreateWaypoint(sourceLocation, nextWaypointDistance, nextWaypointBearing).ConfigureAwait(false); + var requestSendDateTime = DateTime.Now; + var requestVariantDateTime = DateTime.Now; + + await LocationUtils.UpdatePlayerLocationWithAltitude(session, waypoint, (float) speedInMetersPerSecond).ConfigureAwait(false); + + double SpeedVariantSec = rw.Next(1000, 10000); + base.DoUpdatePositionEvent(session, waypoint.Latitude, waypoint.Longitude, CurrentWalkingSpeed); + + do + { + cancellationToken.ThrowIfCancellationRequested(); + TinyIoC.TinyIoCContainer.Current.Resolve().ThrowIfSwitchAccountRequested(); + var millisecondsUntilGetUpdatePlayerLocationResponse = + (DateTime.Now - requestSendDateTime).TotalMilliseconds; + var millisecondsUntilVariant = + (DateTime.Now - requestVariantDateTime).TotalMilliseconds; + + sourceLocation = new GeoCoordinate(_client.CurrentLatitude, _client.CurrentLongitude); + var currentDistanceToTarget = LocationUtils + .CalculateDistanceInMeters(sourceLocation, destinaionCoordinate); + + if (currentDistanceToTarget < 40) + if (speedInMetersPerSecond > SpeedDownTo) + speedInMetersPerSecond = SpeedDownTo; + + if (session.LogicSettings.UseWalkingSpeedVariant && walkSpeed == 0) + { + CurrentWalkingSpeed = session.Navigation.VariantRandom(session, CurrentWalkingSpeed); + } + + speedInMetersPerSecond = (walkSpeed > 0 ? walkSpeed : CurrentWalkingSpeed) / 3.6; + + nextWaypointDistance = Math.Min(currentDistanceToTarget, + millisecondsUntilGetUpdatePlayerLocationResponse / 1000 * speedInMetersPerSecond); + nextWaypointBearing = LocationUtils.DegreeBearing(sourceLocation, destinaionCoordinate); + var testeBear = LocationUtils.DegreeBearing(sourceLocation, new GeoCoordinate(40.780396, -73.974844)); + waypoint = await LocationUtils.CreateWaypoint(sourceLocation, nextWaypointDistance, nextWaypointBearing).ConfigureAwait(false); + + requestSendDateTime = DateTime.Now; + await LocationUtils.UpdatePlayerLocationWithAltitude(session, waypoint, (float) speedInMetersPerSecond).ConfigureAwait(false); + + base.DoUpdatePositionEvent(session, waypoint.Latitude, waypoint.Longitude, CurrentWalkingSpeed); + + if (functionExecutedWhileWalking != null) + await functionExecutedWhileWalking().ConfigureAwait(false); // look for pokemon + } while (LocationUtils.CalculateDistanceInMeters(sourceLocation, destinaionCoordinate) >= (new Random()).Next(1, 10)); + } + + public override double CalculateDistance(double sourceLat, double sourceLng, double destinationLat, + double destinationLng, ISession session = null) + { + return LocationUtils.CalculateDistanceInMeters(sourceLat, sourceLng, destinationLat, destinationLng); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Strategies/Walk/IWalkStrategy.cs b/PoGo.NecroBot.Logic/Strategies/Walk/IWalkStrategy.cs new file mode 100644 index 000000000..be811e999 --- /dev/null +++ b/PoGo.NecroBot.Logic/Strategies/Walk/IWalkStrategy.cs @@ -0,0 +1,23 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Model; +using PoGo.NecroBot.Logic.State; +using GeoCoordinatePortable; +using System.Collections.Generic; + +namespace PoGo.NecroBot.Logic.Strategies.Walk +{ + public interface IWalkStrategy + { + string RouteName { get; } + List Points { get; set; } + event UpdatePositionDelegate UpdatePositionEvent; + + Task Walk(IGeoLocation destinationLocation, Func functionExecutedWhileWalking, + ISession session, CancellationToken cancellationToken, double customWalkingSpeed = 0.0); + + double CalculateDistance(double sourceLat, double sourceLng, double destinationLat, double destinationLng, + ISession session = null); + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Strategies/Walk/MapzenNavigationStrategy.cs b/PoGo.NecroBot.Logic/Strategies/Walk/MapzenNavigationStrategy.cs new file mode 100644 index 000000000..cb606400f --- /dev/null +++ b/PoGo.NecroBot.Logic/Strategies/Walk/MapzenNavigationStrategy.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Model; +using PoGo.NecroBot.Logic.Model.Mapzen; +using PoGo.NecroBot.Logic.Service; +using PoGo.NecroBot.Logic.State; +using PokemonGo.RocketAPI; +using GeoCoordinatePortable; + +namespace PoGo.NecroBot.Logic.Strategies.Walk +{ + class MapzenNavigationStrategy : BaseWalkStrategy, IWalkStrategy + { + private MapzenDirectionsService _mapzenDirectionsService; + + public MapzenNavigationStrategy(Client client) : base(client) + { + _mapzenDirectionsService = null; + } + + public override string RouteName => "Mapzen Walk"; + + public override async Task Walk(IGeoLocation targetLocation, + Func functionExecutedWhileWalking, ISession session, CancellationToken cancellationToken, + double walkSpeed = 0.0) + { + GetMapzenInstance(session); + var sourceLocation = new GeoCoordinate( + _client.CurrentLatitude, + _client.CurrentLongitude, + _client.CurrentAltitude + ); + var destinaionCoordinate = new GeoCoordinate(targetLocation.Latitude, targetLocation.Longitude); + MapzenWalk mapzenWalk = _mapzenDirectionsService.GetDirections(sourceLocation, destinaionCoordinate); + + if (mapzenWalk == null) + { + await RedirectToNextFallbackStrategy( + session.LogicSettings, + targetLocation, + functionExecutedWhileWalking, + session, + cancellationToken + ).ConfigureAwait(false); + return; + } + + base.OnStartWalking(session, targetLocation, mapzenWalk.Distance); + List points = mapzenWalk.Waypoints; + await DoWalk(points, session, functionExecutedWhileWalking, sourceLocation, + destinaionCoordinate, cancellationToken, walkSpeed).ConfigureAwait(false); + } + + private void GetMapzenInstance(ISession session) + { + if (_mapzenDirectionsService == null) + _mapzenDirectionsService = new MapzenDirectionsService(session); + } + + public override double CalculateDistance(double sourceLat, double sourceLng, double destinationLat, + double destinationLng, ISession session = null) + { + // Too expensive to calculate true distance. + return 1.5 * base.CalculateDistance(sourceLat, sourceLng, destinationLat, destinationLng); + + /* + if (session != null) + GetMapzenInstance(session); + + if (_mapzenDirectionsService != null) + { + var mapzenResult = _mapzenDirectionsService.GetDirections(new GeoCoordinate(sourceLat, sourceLng), new GeoCoordinate(destinationLat, destinationLng)); + if (string.IsNullOrEmpty(mapzenResult) || mapzenResult.StartsWith(" "Yours Walk"; + + public override async Task Walk(IGeoLocation targetLocation, + Func functionExecutedWhileWalking, ISession session, CancellationToken cancellationToken, + double walkSpeed = 0.0) + { + GetYoursInstance(session); + var destinaionCoordinate = new GeoCoordinate(targetLocation.Latitude, targetLocation.Longitude); + var sourceLocation = new GeoCoordinate(_client.CurrentLatitude, _client.CurrentLongitude, _client.CurrentAltitude); + var yoursWalk = _yoursDirectionsService.GetDirections(sourceLocation, destinaionCoordinate); + + if (yoursWalk == null) + { + await RedirectToNextFallbackStrategy(session.LogicSettings, targetLocation, functionExecutedWhileWalking, session, cancellationToken).ConfigureAwait(false); + return; + } + + base.OnStartWalking(session, targetLocation, yoursWalk.Distance); + List points = yoursWalk.Waypoints; + await DoWalk(points, session, functionExecutedWhileWalking, sourceLocation, destinaionCoordinate, cancellationToken, walkSpeed).ConfigureAwait(false); + } + + private void GetYoursInstance(ISession session) + { + if (_yoursDirectionsService == null) + _yoursDirectionsService = new YoursDirectionsService(session); + } + + public override double CalculateDistance(double sourceLat, double sourceLng, double destinationLat, + double destinationLng, ISession session = null) + { + // Too expensive to calculate true distance. + return 1.5 * base.CalculateDistance(sourceLat, sourceLng, destinationLat, destinationLng); + + /* + if (session != null) + GetYoursInstance(session); + + if (_yoursDirectionsService != null) + { + var yoursResult = _yoursDirectionsService.GetDirections(new GeoCoordinate(sourceLat, sourceLng), new GeoCoordinate(destinationLat, destinationLng)); + if (string.IsNullOrEmpty(yoursResult) || yoursResult.StartsWith(" pokemonsToTransfer, CancellationToken cancellationToken) + { + if (pokemonsToTransfer.Count() > 0) + { + if (session.LogicSettings.UseBulkTransferPokemon) + { + int page = pokemonsToTransfer.Count() / session.LogicSettings.BulkTransferSize + 1; + for (int i = 0; i < page; i++) + { + TinyIoC.TinyIoCContainer.Current.Resolve().ThrowIfSwitchAccountRequested(); + var batchTransfer = pokemonsToTransfer.Skip(i * session.LogicSettings.BulkTransferSize).Take(session.LogicSettings.BulkTransferSize); + var t = await session.Client.Inventory.TransferPokemons(batchTransfer.Select(x => x.Id).ToList()).ConfigureAwait(false); + if (t.Result == ReleasePokemonResponse.Types.Result.Success) + { + foreach (var duplicatePokemon in batchTransfer) + { + await PrintPokemonInfo(session, duplicatePokemon).ConfigureAwait(false); + } + } + else session.EventDispatcher.Send(new WarnEvent() { Message = session.Translation.GetTranslation(TranslationString.BulkTransferFailed, pokemonsToTransfer.Count()) }); + } + } + else + { + foreach (var pokemon in pokemonsToTransfer) + { + TinyIoC.TinyIoCContainer.Current.Resolve().ThrowIfSwitchAccountRequested(); + cancellationToken.ThrowIfCancellationRequested(); + + await session.Client.Inventory.TransferPokemon(pokemon.Id).ConfigureAwait(false); + + await PrintPokemonInfo(session, pokemon).ConfigureAwait(false); + + // Padding the TransferEvent with player-choosen delay before instead of after. + // This is to remedy too quick transfers, often happening within a second of the + // previous action otherwise + + await DelayingUtils.DelayAsync(session.LogicSettings.TransferActionDelay, 0, cancellationToken).ConfigureAwait(false); + } + } + } + } + + public static async Task PrintPokemonInfo(ISession session, PokemonData duplicatePokemon) + { + var bestPokemonOfType = (session.LogicSettings.PrioritizeIvOverCp + ? await session.Inventory.GetHighestPokemonOfTypeByIv(duplicatePokemon).ConfigureAwait(false) + : await session.Inventory.GetHighestPokemonOfTypeByCp(duplicatePokemon).ConfigureAwait(false)) ?? + duplicatePokemon; + + var ev = new TransferPokemonEvent + { + Id = duplicatePokemon.Id, + PokemonId = duplicatePokemon.PokemonId, + Slashed = duplicatePokemon.IsBad, + Perfection = PokemonInfo.CalculatePokemonPerfection(duplicatePokemon), + Cp = duplicatePokemon.Cp, + BestCp = bestPokemonOfType.Cp, + BestPerfection = PokemonInfo.CalculatePokemonPerfection(bestPokemonOfType), + Candy = await session.Inventory.GetCandyCount(duplicatePokemon.PokemonId).ConfigureAwait(false), + Level = PokemonInfo.GetLevel(duplicatePokemon) + }; + + if (await session.Inventory.GetCandyFamily(duplicatePokemon.PokemonId).ConfigureAwait(false) != null) + { + ev.FamilyId = (await session.Inventory.GetCandyFamily(duplicatePokemon.PokemonId).ConfigureAwait(false)).FamilyId; + } + + session.EventDispatcher.Send(ev); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Tasks/CatchIncensePokemonsTask.cs b/PoGo.NecroBot.Logic/Tasks/CatchIncensePokemonsTask.cs new file mode 100644 index 000000000..0651d7cc5 --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/CatchIncensePokemonsTask.cs @@ -0,0 +1,119 @@ +#region using directives + +using System; +using System.Threading; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.Logging; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Utils; +using POGOProtos.Map.Pokemon; +using POGOProtos.Networking.Responses; +using System.Collections.Generic; + +#endregion + +namespace PoGo.NecroBot.Logic.Tasks +{ + //add delegate + public delegate void PokemonsEncounterDelegate(List pokemons); + + public static class CatchIncensePokemonsTask + { + public static async Task Execute(ISession session, CancellationToken cancellationToken) + { + TinyIoC.TinyIoCContainer.Current.Resolve().ThrowIfSwitchAccountRequested(); + cancellationToken.ThrowIfCancellationRequested(); + if (!session.LogicSettings.CatchPokemon || session.CatchBlockTime > DateTime.Now) return; + + Logger.Write( + session.Translation.GetTranslation(TranslationString.LookingForIncensePokemon), + LogLevel.Debug + ); + + var incensePokemon = await session.Client.Map.GetIncensePokemons().ConfigureAwait(false); + + if (incensePokemon.Result == GetIncensePokemonResponse.Types.Result.IncenseEncounterAvailable) + { + var pokemon = new MapPokemon + { + EncounterId = incensePokemon.EncounterId, + ExpirationTimestampMs = incensePokemon.DisappearTimestampMs, + Latitude = incensePokemon.Latitude, + Longitude = incensePokemon.Longitude, + PokemonId = incensePokemon.PokemonId, + SpawnPointId = incensePokemon.EncounterLocation + }; + + //add delegate function + OnPokemonEncounterEvent(new List { pokemon }); + + if (session.Cache.Get(CatchPokemonTask.GetEncounterCacheKey(incensePokemon.EncounterId)) != null) + return; //pokemon been ignore before + + if ((session.LogicSettings.UsePokemonToCatchLocallyListOnly && !session.LogicSettings.PokemonToCatchLocally.Pokemon.Contains(pokemon.PokemonId)) + || (session.LogicSettings.UsePokemonToNotCatchFilter && session.LogicSettings.PokemonsNotToCatch.Contains(pokemon.PokemonId))) + { + Logger.Write(session.Translation.GetTranslation(TranslationString.PokemonIgnoreFilter, + session.Translation.GetPokemonTranslation(pokemon.PokemonId))); + } + else + { + var distance = LocationUtils.CalculateDistanceInMeters(session.Client.CurrentLatitude, + session.Client.CurrentLongitude, pokemon.Latitude, pokemon.Longitude); + await Task.Delay(distance > 100 ? 500 : 100, cancellationToken).ConfigureAwait(false); + + var encounter = + await + session.Client.Encounter.EncounterIncensePokemon((ulong) pokemon.EncounterId, + pokemon.SpawnPointId).ConfigureAwait(false); + + if (encounter.Result == IncenseEncounterResponse.Types.Result.IncenseEncounterSuccess + && session.LogicSettings.CatchPokemon) + { + //await CatchPokemonTask.Execute(session, cancellationToken, encounter, pokemon).ConfigureAwait(false); + await CatchPokemonTask.Execute(session, cancellationToken, encounter, pokemon, + currentFortData: null, sessionAllowTransfer: true).ConfigureAwait(false); + } + else if (encounter.Result == IncenseEncounterResponse.Types.Result.PokemonInventoryFull) + { + if (session.LogicSettings.TransferDuplicatePokemon || session.LogicSettings.TransferWeakPokemon) + { + session.EventDispatcher.Send(new WarnEvent + { + Message = session.Translation.GetTranslation(TranslationString.InvFullTransferring) + }); + if (session.LogicSettings.TransferDuplicatePokemon) + await TransferDuplicatePokemonTask.Execute(session, cancellationToken).ConfigureAwait(false); + if (session.LogicSettings.TransferWeakPokemon) + await TransferWeakPokemonTask.Execute(session, cancellationToken).ConfigureAwait(false); + if (EvolvePokemonTask.IsActivated(session)) + await EvolvePokemonTask.Execute(session, cancellationToken).ConfigureAwait(false); + } + else + session.EventDispatcher.Send(new WarnEvent + { + Message = session.Translation.GetTranslation(TranslationString.InvFullTransferManually) + }); + } + else + { + session.EventDispatcher.Send(new WarnEvent + { + Message = + session.Translation.GetTranslation(TranslationString.EncounterProblem, encounter.Result) + }); + } + } + } + } + //add delegate event + public static event PokemonsEncounterDelegate PokemonEncounterEvent; + + private static void OnPokemonEncounterEvent(List pokemons) + { + PokemonEncounterEvent?.Invoke(pokemons); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Tasks/CatchLurePokemonsTask.cs b/PoGo.NecroBot.Logic/Tasks/CatchLurePokemonsTask.cs new file mode 100644 index 000000000..36178f9ef --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/CatchLurePokemonsTask.cs @@ -0,0 +1,162 @@ +#region using directives + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.Logging; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Utils; +using PokemonGo.RocketAPI.Extensions; +using POGOProtos.Map.Fort; +using POGOProtos.Map.Pokemon; +using POGOProtos.Networking.Responses; + +#endregion + +namespace PoGo.NecroBot.Logic.Tasks +{ + //add delegate + public delegate void PokemonsEncounterLureDelegate(List pokemons); + public static class CatchLurePokemonsTask + { + public static async Task Execute(ISession session, FortData currentFortData, + CancellationToken cancellationToken) + { + TinyIoC.TinyIoCContainer.Current.Resolve().ThrowIfSwitchAccountRequested(); + cancellationToken.ThrowIfCancellationRequested(); + if (!session.LogicSettings.CatchPokemon || + session.CatchBlockTime > DateTime.Now) return; + + if (session.KnownLongitudeBeforeSnipe != 0 && session.KnownLatitudeBeforeSnipe != 0 && + LocationUtils.CalculateDistanceInMeters(session.KnownLatitudeBeforeSnipe, + session.KnownLongitudeBeforeSnipe, + session.Client.CurrentLatitude, + session.Client.CurrentLongitude) > 1000) + { + Logger.Write($"ERROR - Bot is stuck at snipe location({session.Client.CurrentLatitude},{session.Client.CurrentLongitude}). Teleport him back home - if you see this message please PM samuraitruong on Discord"); + + session.Client.Player.SetCoordinates(session.KnownLatitudeBeforeSnipe, session.KnownLongitudeBeforeSnipe, session.Client.CurrentAltitude); + return; + } + + + Logger.Write(session.Translation.GetTranslation(TranslationString.LookingForLurePokemon), LogLevel.Debug); + + var fortId = currentFortData.Id; + + var pokemonId = currentFortData.LureInfo.ActivePokemonId; + + if ((session.LogicSettings.UsePokemonToCatchLocallyListOnly && + !session.LogicSettings.PokemonToCatchLocally.Pokemon.Contains(pokemonId)) || + (session.LogicSettings.UsePokemonToNotCatchFilter && + session.LogicSettings.PokemonsNotToCatch.Contains(pokemonId))) + { + session.EventDispatcher.Send(new NoticeEvent + { + Message = session.Translation.GetTranslation(TranslationString.PokemonSkipped, pokemonId) + }); + } + else + { + var encounterId = currentFortData.LureInfo.EncounterId; + if (session.Cache.Get(CatchPokemonTask.GetEncounterCacheKey(currentFortData.LureInfo.EncounterId)) != null) + return; //pokemon been ignore before + + var encounter = await session.Client.Encounter.EncounterLurePokemon(encounterId, fortId).ConfigureAwait(false); + + if (encounter.Result == DiskEncounterResponse.Types.Result.Success && + session.LogicSettings.CatchPokemon) + { + var pokemon = new MapPokemon + { + EncounterId = encounterId, + ExpirationTimestampMs = currentFortData.LureInfo.LureExpiresTimestampMs, + Latitude = currentFortData.Latitude, + Longitude = currentFortData.Longitude, + PokemonId = currentFortData.LureInfo.ActivePokemonId, + SpawnPointId = currentFortData.Id + }; + + //add delegate function + OnPokemonEncounterEvent(new List { pokemon }); + + // Catch the Pokemon + await CatchPokemonTask.Execute(session, cancellationToken, encounter, pokemon, + currentFortData, sessionAllowTransfer: true).ConfigureAwait(false); + } + else if (encounter.Result == DiskEncounterResponse.Types.Result.PokemonInventoryFull) + { + if (session.LogicSettings.TransferDuplicatePokemon || session.LogicSettings.TransferWeakPokemon) + { + session.EventDispatcher.Send(new WarnEvent + { + Message = session.Translation.GetTranslation(TranslationString.InvFullTransferring) + }); + if (session.LogicSettings.TransferDuplicatePokemon) + await TransferDuplicatePokemonTask.Execute(session, cancellationToken).ConfigureAwait(false); + if (session.LogicSettings.TransferWeakPokemon) + await TransferWeakPokemonTask.Execute(session, cancellationToken).ConfigureAwait(false); + if (EvolvePokemonTask.IsActivated(session)) + await EvolvePokemonTask.Execute(session, cancellationToken).ConfigureAwait(false); + } + else + session.EventDispatcher.Send(new WarnEvent + { + Message = session.Translation.GetTranslation(TranslationString.InvFullTransferManually) + }); + } + else + { + if (encounter.Result.ToString().Contains("NotAvailable")) return; + session.EventDispatcher.Send(new WarnEvent + { + Message = + session.Translation.GetTranslation(TranslationString.EncounterProblemLurePokemon, + encounter.Result) + }); + } + } + } + + public static async Task Execute(ISession session, CancellationToken cancellationToken) + { + // Looking for any lure pokestop neaby + + var mapObjects = await session.Client.Map.GetMapObjects().ConfigureAwait(false); + var pokeStops = mapObjects.MapCells.SelectMany(i => i.Forts) + .Where( + i => + (i.Type == FortType.Checkpoint) && + i.CooldownCompleteTimestampMs < DateTime.UtcNow.ToUnixTime() + ); + + session.AddForts(pokeStops.ToList()); + + var forts = session.Forts.Where(p => p.Type == FortType.Checkpoint); + List luredNearBy = new List(); + + foreach (FortData fort in forts) + { + var distance = LocationUtils.CalculateDistanceInMeters(session.Client.CurrentLatitude, + session.Client.CurrentLongitude, fort.Latitude, fort.Longitude); + if (distance < 40 && fort.LureInfo != null) + { + luredNearBy.Add(fort); + await Execute(session, fort, cancellationToken).ConfigureAwait(false); + } + } + ; + } + //add delegate event + public static event PokemonsEncounterLureDelegate PokemonEncounterEvent; + + private static void OnPokemonEncounterEvent(List pokemons) + { + PokemonEncounterEvent?.Invoke(pokemons); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Tasks/CatchNearbyPokemonsTask.cs b/PoGo.NecroBot.Logic/Tasks/CatchNearbyPokemonsTask.cs new file mode 100644 index 000000000..2c4b6196c --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/CatchNearbyPokemonsTask.cs @@ -0,0 +1,242 @@ +#region using directives + +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.Exceptions; +using PoGo.NecroBot.Logic.Logging; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Utils; +using POGOProtos.Enums; +using POGOProtos.Inventory.Item; +using POGOProtos.Map.Pokemon; +using POGOProtos.Networking.Responses; +using TinyIoC; +using System.Collections.Generic; +using GeoCoordinatePortable; + +#endregion + +namespace PoGo.NecroBot.Logic.Tasks +{ + public static class CatchNearbyPokemonsTask + { + //add delegate + public delegate void PokemonsEncounterDelegate(List pokemons); + + public static async Task Execute(ISession session, CancellationToken cancellationToken, + PokemonId priority = PokemonId.Missingno, bool sessionAllowTransfer = true) + { + var manager = TinyIoCContainer.Current.Resolve(); + manager.ThrowIfSwitchAccountRequested(); + cancellationToken.ThrowIfCancellationRequested(); + + if (!session.LogicSettings.CatchPokemon) return; + + var totalBalls = (await session.Inventory.GetItems().ConfigureAwait(false)).Where(x => x.ItemId == ItemId.ItemPokeBall || x.ItemId == ItemId.ItemGreatBall || x.ItemId == ItemId.ItemUltraBall).Sum(x => x.Count); + + if (session.SaveBallForByPassCatchFlee && totalBalls < 130) + { + return ; + } + + if (session.Stats.CatchThresholdExceeds(session)) + { + if (manager.AllowMultipleBot() && + session.LogicSettings.MultipleBotConfig.SwitchOnCatchLimit && + manager.AllowSwitch() + ) + { + throw new ActiveSwitchByRuleException() + { + MatchedRule = SwitchRules.CatchLimitReached, + ReachedValue = session.LogicSettings.CatchPokemonLimit + }; + } + return; + } + + Logger.Write(session.Translation.GetTranslation(TranslationString.LookingForPokemon), LogLevel.Debug); + + var nearbyPokemons = await GetNearbyPokemons(session).ConfigureAwait(false); + + if (nearbyPokemons == null) return; + + Logger.Write($"Spotted {nearbyPokemons.Count()} pokemon in the area. Trying to catch them all.", LogLevel.Debug); + + var priorityPokemon = nearbyPokemons.Where(p => p.PokemonId == priority).FirstOrDefault(); + var pokemons = nearbyPokemons.Where(p => p.PokemonId != priority).ToList(); + + //add pokemons to map + OnPokemonEncounterEvent(pokemons.ToList()); + + EncounterResponse encounter = null; + //if that is snipe pokemon and inventories if full, execute transfer to get more room for pokemon + if (priorityPokemon != null) + { + pokemons.Insert(0, priorityPokemon); + await LocationUtils.UpdatePlayerLocationWithAltitude(session, + new GeoCoordinate(priorityPokemon.Latitude, priorityPokemon.Longitude, session.Client.CurrentAltitude), 0).ConfigureAwait(false); // Set speed to 0 for random speed. + encounter = await session.Client.Encounter + .EncounterPokemon(priorityPokemon.EncounterId, priorityPokemon.SpawnPointId).ConfigureAwait(false); + + if (encounter.Status == EncounterResponse.Types.Status.PokemonInventoryFull) + { + if (session.LogicSettings.TransferDuplicatePokemon) + await TransferDuplicatePokemonTask.Execute(session, cancellationToken).ConfigureAwait(false); + + if (session.LogicSettings.TransferWeakPokemon) + await TransferWeakPokemonTask.Execute(session, cancellationToken).ConfigureAwait(false); + + if (EvolvePokemonTask.IsActivated(session)) + await EvolvePokemonTask.Execute(session, cancellationToken).ConfigureAwait(false); + } + } + + var allitems = await session.Inventory.GetItems().ConfigureAwait(false); + var pokeBallsCount = allitems.FirstOrDefault(i => i.ItemId == ItemId.ItemPokeBall)?.Count ?? 0; + var greatBallsCount = allitems.FirstOrDefault(i => i.ItemId == ItemId.ItemGreatBall)?.Count ?? 0; + var ultraBallsCount = allitems.FirstOrDefault(i => i.ItemId == ItemId.ItemUltraBall)?.Count ?? 0; + var masterBallsCount = allitems.FirstOrDefault(i => i.ItemId == ItemId.ItemMasterBall)?.Count ?? 0; + //masterBallsCount = masterBallsCount ?? 0; //return null ATM. need this code to logic check work + var PokeBalls = pokeBallsCount + greatBallsCount + ultraBallsCount + masterBallsCount; + + if (pokemons.Count > 0) + if (PokeBalls >= session.LogicSettings.PokeballsToKeepForSnipe) // Don't display if not enough Pokeballs - TheWizrad1328 + Logger.Write($"Catching {pokemons.Count} Pokemon Nearby...", LogLevel.Info); + else + Logger.Write($"collecting {session.LogicSettings.PokeballsToKeepForSnipe - PokeBalls} more Pokeballs. Catching of pokemon is temporarily susspended...", LogLevel.Info); + + foreach (var pokemon in pokemons) + { + await MSniperServiceTask.Execute(session, cancellationToken).ConfigureAwait(false); + + /* + if (LocationUtils.CalculateDistanceInMeters(pokemon.Latitude, pokemon.Longitude, session.Client.CurrentLatitude, session.Client.CurrentLongitude) > session.Client.GlobalSettings.MapSettings.EncounterRangeMeters) + { + Logger.Debug($"THIS POKEMON IS TOO FAR, {pokemon.Latitude}, {pokemon.Longitude}"); + continue; + } + */ + + cancellationToken.ThrowIfCancellationRequested(); + TinyIoC.TinyIoCContainer.Current.Resolve().ThrowIfSwitchAccountRequested(); + + if (session.Cache.GetCacheItem(CatchPokemonTask.GetEncounterCacheKey(pokemon.EncounterId)) != null) + { + continue; //this pokemon has been skipped because not meet with catch criteria before. + } + + if (PokeBalls < session.LogicSettings.PokeballsToKeepForSnipe && session.CatchBlockTime < DateTime.Now) + { + session.CatchBlockTime = DateTime.Now.AddMinutes(session.LogicSettings.OutOfBallCatchBlockTime); + Logger.Write(session.Translation.GetTranslation(TranslationString.CatchPokemonDisable, + session.LogicSettings.OutOfBallCatchBlockTime, session.LogicSettings.PokeballsToKeepForSnipe)); + return; + } + + if (session.CatchBlockTime > DateTime.Now) return; + + if ((session.LogicSettings.UsePokemonToCatchLocallyListOnly && + !session.LogicSettings.PokemonToCatchLocally.Pokemon.Contains(pokemon.PokemonId)) || + (session.LogicSettings.UsePokemonToNotCatchFilter && + session.LogicSettings.PokemonsNotToCatch.Contains(pokemon.PokemonId))) + { + Logger.Write(session.Translation.GetTranslation(TranslationString.PokemonSkipped, + session.Translation.GetPokemonTranslation(pokemon.PokemonId))); + continue; + } + + /* + var distance = LocationUtils.CalculateDistanceInMeters(session.Client.CurrentLatitude, + session.Client.CurrentLongitude, pokemon.Latitude, pokemon.Longitude); + await Task.Delay(distance > 100 ? 500 : 100, cancellationToken).ConfigureAwait(false); + */ + + //to avoid duplicated encounter when snipe priority pokemon + + if (encounter == null || encounter.Status != EncounterResponse.Types.Status.EncounterSuccess) + { + //await LocationUtils.UpdatePlayerLocationWithAltitude(session, + // new GeoCoordinate(pokemon.Latitude, pokemon.Longitude, session.Client.CurrentAltitude), 0).ConfigureAwait(false); // Set speed to 0 for random speed. + encounter = + await session.Client.Encounter.EncounterPokemon(pokemon.EncounterId, pokemon.SpawnPointId).ConfigureAwait(false); + } + + if (encounter.Status == EncounterResponse.Types.Status.EncounterSuccess && + session.LogicSettings.CatchPokemon) + { + // Catch the Pokemon + await CatchPokemonTask.Execute(session, cancellationToken, encounter, pokemon, + currentFortData: null, sessionAllowTransfer: sessionAllowTransfer).ConfigureAwait(false); + } + else if (encounter.Status == EncounterResponse.Types.Status.PokemonInventoryFull) + { + if (session.LogicSettings.TransferDuplicatePokemon || session.LogicSettings.TransferWeakPokemon) + { + session.EventDispatcher.Send(new WarnEvent + { + Message = session.Translation.GetTranslation(TranslationString.InvFullTransferring) + }); + if (session.LogicSettings.TransferDuplicatePokemon) + await TransferDuplicatePokemonTask.Execute(session, cancellationToken).ConfigureAwait(false); + if (session.LogicSettings.TransferWeakPokemon) + await TransferWeakPokemonTask.Execute(session, cancellationToken).ConfigureAwait(false); + if (EvolvePokemonTask.IsActivated(session)) + await EvolvePokemonTask.Execute(session, cancellationToken).ConfigureAwait(false); + } + else + session.EventDispatcher.Send(new WarnEvent + { + Message = session.Translation.GetTranslation(TranslationString.InvFullTransferManually) + }); + } + else + { + session.EventDispatcher.Send(new WarnEvent + { + Message = + session.Translation.GetTranslation(TranslationString.EncounterProblem, encounter.Status) + }); + } + encounter = null; + // If pokemon is not last pokemon in list, create delay between catches, else keep moving. + if (!Equals(pokemons.ElementAtOrDefault(pokemons.Count() - 1), pokemon)) + { + await Task.Delay(session.LogicSettings.DelayBetweenPokemonCatch, cancellationToken).ConfigureAwait(false); + } + } + } + + public static async Task> GetNearbyPokemons(ISession session) + { + var mapObjects = await session.Client.Map.GetMapObjects().ConfigureAwait(false); + if (mapObjects == null || mapObjects.MapCells == null) return null; + + var forts = mapObjects.MapCells.SelectMany(p => p.Forts).ToList(); + var nearbyPokemons = mapObjects.MapCells.SelectMany(x => x.NearbyPokemons).ToList(); + + session.EventDispatcher.Send(new PokeStopListEvent(forts, nearbyPokemons)); + + var pokemons = mapObjects.MapCells.SelectMany(i => i.CatchablePokemons) + .Where(pokemon=>LocationUtils.CalculateDistanceInMeters(pokemon.Latitude, pokemon.Longitude, session.Client.CurrentLatitude, session.Client.CurrentLongitude) <= session.Client.GlobalSettings.MapSettings.EncounterRangeMeters * 3) + .OrderBy( + i => + LocationUtils.CalculateDistanceInMeters(session.Client.CurrentLatitude, + session.Client.CurrentLongitude, + i.Latitude, i.Longitude)); + + return pokemons; + } + //add delegate event + public static event PokemonsEncounterDelegate PokemonEncounterEvent; + + private static void OnPokemonEncounterEvent(List pokemons) + { + PokemonEncounterEvent?.Invoke(pokemons); + } + } +} diff --git a/PoGo.NecroBot.Logic/Tasks/CatchPokemonTask.cs b/PoGo.NecroBot.Logic/Tasks/CatchPokemonTask.cs new file mode 100644 index 000000000..a0c16610b --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/CatchPokemonTask.cs @@ -0,0 +1,841 @@ +#region using directives + +using System; +using System.Globalization; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.Exceptions; +using PoGo.NecroBot.Logic.Logging; +using PoGo.NecroBot.Logic.Model; +using PoGo.NecroBot.Logic.Model.Settings; +using PoGo.NecroBot.Logic.PoGoUtils; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Utils; +using POGOProtos.Data; +using POGOProtos.Inventory.Item; +using POGOProtos.Map.Fort; +using POGOProtos.Map.Pokemon; +using POGOProtos.Networking.Responses; +using TinyIoC; +using POGOProtos.Enums; +using System.Collections.Generic; +#endregion + +namespace PoGo.NecroBot.Logic.Tasks +{ + public static class CatchPokemonTask + { + public static Dictionary AmountOfBerries; + private static Random Random => new Random((int)DateTime.Now.Ticks); + + public static string GetEncounterCacheKey(string encounterId) + { + return encounterId; + } + + public static string GetEncounterCacheKey(ulong encounterId) + { + return GetEncounterCacheKey(encounterId.ToString()); + } + + public static string GetUsernameEncounterCacheKey(string username, string encounterId) + { + return username + encounterId; + } + + public static string GetUsernameEncounterCacheKey(string username, ulong encounterId) + { + return GetUsernameEncounterCacheKey(username, encounterId.ToString()); + } + + public static string GetUsernameGeoLocationCacheKey(string username, PokemonId pokemonId, double latitude, double longitude) + { + return $"{username}{pokemonId}{Math.Round(latitude, 6)}{Math.Round(longitude, 6)}"; + } + + // Structure of calling Tasks + + // ## From CatchNearbyPokemonTask + // await CatchPokemonTask.Execute(session, cancellationToken, encounter, pokemon, currentFortData: null, sessionAllowTransfer:sessionAllowTransfer).ConfigureAwait(false); + + // ## From CatchLurePokemonTask + // await CatchPokemonTask.Execute(session, cancellationToken, encounter, pokemon, currentFortData, sessionAllowTransfer: true).ConfigureAwait(false); + + // ## From CatchIncensePokemonTask + // await CatchPokemonTask.Execute(session, cancellationToken, encounter, pokemon, currentFortData: null, sessionAllowTransfer: true).ConfigureAwait(false); + + // ## From SnipePokemonTask + // await CatchPokemonTask.Execute(session, cancellationToken, encounter, pokemon, currentFortData: null, sessionAllowTransfer: true).ConfigureAwait(false); + + // ## From MSniperServiceTask + // await CatchPokemonTask.Execute(session, cancellationToken, encounter, pokemon, currentFortData: null, sessionAllowTransfer: true).ConfigureAwait(false); + + private static int CatchFleeContinuouslyCount = 0; + public static readonly int BALL_REQUIRED_TO_BYPASS_CATCHFLEE = 150; + + /// + /// Because this function sometime being called inside loop, return true it mean we don't want break look, false it mean not need to call this , break a loop from caller function + /// + /// + /// + /// + /// + /// + /// + /// + public static async Task Execute(ISession session, + CancellationToken cancellationToken, + dynamic encounter, + MapPokemon pokemon, + FortData currentFortData, + bool sessionAllowTransfer) + { + var manager = TinyIoCContainer.Current.Resolve(); + manager.ThrowIfSwitchAccountRequested(); + // If the encounter is null nothing will work below, so exit now + if (encounter == null) return true; + + var totalBalls = (await session.Inventory.GetItems().ConfigureAwait(false)).Where(x => x.ItemId == ItemId.ItemPokeBall || x.ItemId == ItemId.ItemGreatBall || x.ItemId == ItemId.ItemUltraBall).Sum(x => x.Count); + + if (session.SaveBallForByPassCatchFlee && totalBalls < BALL_REQUIRED_TO_BYPASS_CATCHFLEE) + { + return false; + } + + // Exit if user defined max limits reached + if (session.Stats.CatchThresholdExceeds(session)) + { + if (manager.AllowMultipleBot() && + session.LogicSettings.MultipleBotConfig.SwitchOnCatchLimit && + TinyIoCContainer.Current.Resolve().AllowSwitch()) + { + throw new ActiveSwitchByRuleException() + { + MatchedRule = SwitchRules.CatchLimitReached, + ReachedValue = session.LogicSettings.CatchPokemonLimit + }; + } + return false; + } + using (var block = new BlockableScope(session, BotActions.Catch)) + { + if (!await block.WaitToRun().ConfigureAwait(false)) return true; + + AmountOfBerries = new Dictionary(); + + cancellationToken.ThrowIfCancellationRequested(); + + float probability = encounter.CaptureProbability?.CaptureProbability_[0]; + + PokemonData encounteredPokemon; + long unixTimeStamp; + ulong _encounterId; + string _spawnPointId; + + // Calling from CatchNearbyPokemonTask and SnipePokemonTask + if (encounter is EncounterResponse && + (encounter?.Status == EncounterResponse.Types.Status.EncounterSuccess)) + { + encounteredPokemon = encounter.WildPokemon?.PokemonData; + unixTimeStamp = encounter.WildPokemon?.LastModifiedTimestampMs + + encounter.WildPokemon?.TimeTillHiddenMs; + _spawnPointId = encounter.WildPokemon?.SpawnPointId; + _encounterId = encounter.WildPokemon?.EncounterId; + } + // Calling from CatchIncensePokemonTask + else if (encounter is IncenseEncounterResponse && + (encounter?.Result == IncenseEncounterResponse.Types.Result.IncenseEncounterSuccess)) + { + encounteredPokemon = encounter?.PokemonData; + unixTimeStamp = pokemon.ExpirationTimestampMs; + _spawnPointId = pokemon.SpawnPointId; + _encounterId = pokemon.EncounterId; + } + // Calling from CatchLurePokemon + else if (encounter is DiskEncounterResponse && + encounter?.Result == DiskEncounterResponse.Types.Result.Success && + !(currentFortData == null)) + { + encounteredPokemon = encounter?.PokemonData; + unixTimeStamp = currentFortData.LureInfo.LureExpiresTimestampMs; + _spawnPointId = currentFortData.Id; + _encounterId = currentFortData.LureInfo.EncounterId; + } + else return true; // No success to work with, exit + + // Check for pokeballs before proceeding + var pokeball = await GetBestBall(session, encounteredPokemon, probability).ConfigureAwait(false); + if (pokeball == ItemId.ItemUnknown) + { + Logger.Write(session.Translation.GetTranslation(TranslationString.ZeroPokeballInv)); + return false; + } + + // Calculate CP and IV + var pokemonCp = encounteredPokemon?.Cp; + var pokemonIv = PokemonInfo.CalculatePokemonPerfection(encounteredPokemon); + var lv = PokemonInfo.GetLevel(encounteredPokemon); + + // Calculate distance away + var latitude = encounter is EncounterResponse || encounter is IncenseEncounterResponse + ? pokemon.Latitude + : currentFortData.Latitude; + var longitude = encounter is EncounterResponse || encounter is IncenseEncounterResponse + ? pokemon.Longitude + : currentFortData.Longitude; + + var distance = LocationUtils.CalculateDistanceInMeters(session.Client.CurrentLatitude, + session.Client.CurrentLongitude, latitude, longitude); + if (session.LogicSettings.ActivateMSniper) + { + var newdata = new MSniperServiceTask.EncounterInfo() + { + EncounterId = _encounterId.ToString(), + Iv = Math.Round(pokemonIv, 2), + Latitude = latitude.ToString("G17", CultureInfo.InvariantCulture), + Longitude = longitude.ToString("G17", CultureInfo.InvariantCulture), + PokemonId = (int)(encounteredPokemon?.PokemonId ?? 0), + PokemonName = encounteredPokemon?.PokemonId.ToString(), + SpawnPointId = _spawnPointId, + Move1 = PokemonInfo.GetPokemonMove1(encounteredPokemon).ToString(), + Move2 = PokemonInfo.GetPokemonMove2(encounteredPokemon).ToString(), + Expiration = unixTimeStamp + }; + session.EventDispatcher.Send(newdata); + } + + DateTime expiredDate = + new DateTime(1970, 1, 1, 0, 0, 0).AddMilliseconds(Convert.ToDouble(unixTimeStamp)); + var encounterEV = new EncounteredEvent() + { + Latitude = latitude, + Longitude = longitude, + PokemonId = encounteredPokemon.PokemonId, + IV = pokemonIv, + Level = (int)lv, + Expires = expiredDate.ToUniversalTime(), + ExpireTimestamp = unixTimeStamp, + SpawnPointId = _spawnPointId, + EncounterId = _encounterId.ToString(), + Move1 = PokemonInfo.GetPokemonMove1(encounteredPokemon).ToString(), + Move2 = PokemonInfo.GetPokemonMove2(encounteredPokemon).ToString(), + }; + + //add catch to avoid snipe duplicate + string uniqueCacheKey = CatchPokemonTask.GetUsernameGeoLocationCacheKey(session.Settings.Username, encounterEV.PokemonId, encounterEV.Latitude, encounterEV.Longitude); + session.Cache.Add(uniqueCacheKey, encounterEV, DateTime.Now.AddMinutes(30)); + + session.EventDispatcher.Send(encounterEV); + + if (IsNotMetWithCatchCriteria(session, encounteredPokemon, pokemonIv, lv, pokemonCp)) + { + session.EventDispatcher.Send(new NoticeEvent + { + Message = session.Translation.GetTranslation(TranslationString.PokemonSkipped, + encounteredPokemon.PokemonId) + }); + session.Cache.Add(CatchPokemonTask.GetEncounterCacheKey(_encounterId), encounteredPokemon, expiredDate); + Logger.Write( + $"Filter catch not met. {encounteredPokemon.PokemonId.ToString()} IV {pokemonIv} lv {lv} {pokemonCp} move1 {PokemonInfo.GetPokemonMove1(encounteredPokemon)} move 2 {PokemonInfo.GetPokemonMove2(encounteredPokemon)}"); + return true; + } + + CatchPokemonResponse caughtPokemonResponse = null; + var lastThrow = CatchPokemonResponse.Types.CatchStatus.CatchSuccess; // Initializing lastThrow + var attemptCounter = 1; + + // Main CatchPokemon-loop + do + { + if (session.LogicSettings.UseHumanlikeDelays) + { + await DelayingUtils.DelayAsync(session.LogicSettings.BeforeCatchDelay, 0, session.CancellationTokenSource.Token).ConfigureAwait(false); + } + + if ((session.LogicSettings.MaxPokeballsPerPokemon > 0 && + attemptCounter > session.LogicSettings.MaxPokeballsPerPokemon)) + break; + + pokeball = await GetBestBall(session, encounteredPokemon, probability).ConfigureAwait(false); + if (pokeball == ItemId.ItemUnknown) + { + session.EventDispatcher.Send(new NoPokeballEvent + { + Id = encounter is EncounterResponse ? pokemon.PokemonId : encounter?.PokemonData.PokemonId, + Cp = encounteredPokemon.Cp + }); + return false; + } + + // Determine whether to use berries or not + if (lastThrow != CatchPokemonResponse.Types.CatchStatus.CatchMissed) + { + //AmountOfBerries++; + //if (AmountOfBerries <= session.LogicSettings.MaxBerriesToUsePerPokemon) + await UseBerry(session, + encounterEV.PokemonId, + _encounterId, + _spawnPointId, + pokemonIv, + pokemonCp ?? 10000, //unknown CP pokemon, want to use berry + encounterEV.Level, + probability, + cancellationToken).ConfigureAwait(false); + } + + bool hitPokemon = true; + + //default to excellent throw + var normalizedRecticleSize = 1.95; + + //default spin + var spinModifier = 1.0; + + //Humanized throws + if (session.LogicSettings.EnableHumanizedThrows) + { + //thresholds: https://gist.github.com/anonymous/077d6dea82d58b8febde54ae9729b1bf + var spinTxt = "Curve"; + var hitTxt = "Excellent"; + if (pokemonCp > session.LogicSettings.ForceExcellentThrowOverCp || + pokemonIv > session.LogicSettings.ForceExcellentThrowOverIv) + { + normalizedRecticleSize = Random.NextDouble() * (1.95 - 1.7) + 1.7; + } + else if (pokemonCp >= session.LogicSettings.ForceGreatThrowOverCp || + pokemonIv >= session.LogicSettings.ForceGreatThrowOverIv) + { + normalizedRecticleSize = Random.NextDouble() * (1.95 - 1.3) + 1.3; + hitTxt = "Great"; + } + else + { + var regularThrow = 100 - (session.LogicSettings.ExcellentThrowChance + + session.LogicSettings.GreatThrowChance + + session.LogicSettings.NiceThrowChance); + var rnd = Random.Next(1, 101); + + if (rnd <= regularThrow) + { + normalizedRecticleSize = Random.NextDouble() * (1 - 0.1) + 0.1; + hitTxt = "Ordinary"; + } + else if (rnd <= regularThrow + session.LogicSettings.NiceThrowChance) + { + normalizedRecticleSize = Random.NextDouble() * (1.3 - 1) + 1; + hitTxt = "Nice"; + } + else if (rnd <= + regularThrow + session.LogicSettings.NiceThrowChance + + session.LogicSettings.GreatThrowChance) + { + normalizedRecticleSize = Random.NextDouble() * (1.7 - 1.3) + 1.3; + hitTxt = "Great"; + } + + if (Random.NextDouble() * 100 > session.LogicSettings.CurveThrowChance) + { + spinModifier = 0.0; + spinTxt = "Straight"; + } + } + + // Round to 2 decimals + normalizedRecticleSize = Math.Round(normalizedRecticleSize, 2); + + // Missed throw check + int missChance = Random.Next(1, 101); + if (missChance <= session.LogicSettings.ThrowMissPercentage && + session.LogicSettings.EnableMissedThrows) + { + hitPokemon = false; + } + + Logger.Write($"(Threw ball) {hitTxt} throw, {spinTxt}-ball, HitPokemon = {hitPokemon}...", + LogLevel.Debug); + } + + if (CatchFleeContinuouslyCount >= 3 && session.LogicSettings.ByPassCatchFlee) + { + MSniperServiceTask.BlockSnipe(); + + if (totalBalls <= BALL_REQUIRED_TO_BYPASS_CATCHFLEE) + { + Logger.Write("You don't have enough balls to bypass catchflee"); + return false; + } + List ballToByPass = new List(); + var numPokeBalls = await session.Inventory.GetItemAmountByType(ItemId.ItemPokeBall).ConfigureAwait(false); + for (int i = 0; i < numPokeBalls - 1; i++) + { + ballToByPass.Add(ItemId.ItemPokeBall); + } + var numGreatBalls = await session.Inventory.GetItemAmountByType(ItemId.ItemGreatBall).ConfigureAwait(false); + for (int i = 0; i < numGreatBalls - 1; i++) + { + ballToByPass.Add(ItemId.ItemGreatBall); + } + var numUltraBalls = await session.Inventory.GetItemAmountByType(ItemId.ItemUltraBall).ConfigureAwait(false); + for (int i = 0; i < numUltraBalls - 1; i++) + { + ballToByPass.Add(ItemId.ItemUltraBall); + } + bool catchMissed = true; + + Random r = new Random(); + for (int i = 0; i < ballToByPass.Count - 1; i++) + { + if (i > 130 && r.Next(0, 100) <= 30) + { + catchMissed = false; + } + else + { + catchMissed = true; + } + + caughtPokemonResponse = + await session.Client.Encounter.CatchPokemon( + encounter is EncounterResponse || encounter is IncenseEncounterResponse + ? pokemon.EncounterId + : _encounterId, + encounter is EncounterResponse || encounter is IncenseEncounterResponse + ? pokemon.SpawnPointId + : currentFortData.Id, ballToByPass[i], 1.0, 1.0, !catchMissed).ConfigureAwait(false); + await session.Inventory.UpdateInventoryItem(ballToByPass[i]).ConfigureAwait(false); + + await Task.Delay(100).ConfigureAwait(false); + Logger.Write($"CatchFlee By pass : {ballToByPass[i].ToString()} , Attempt {i}, result {caughtPokemonResponse.Status}"); + + if (caughtPokemonResponse.Status != CatchPokemonResponse.Types.CatchStatus.CatchMissed) + { + session.SaveBallForByPassCatchFlee = false; + CatchFleeContinuouslyCount = 0; + break; + } + } + } + else + { + caughtPokemonResponse = + await session.Client.Encounter.CatchPokemon( + encounter is EncounterResponse || encounter is IncenseEncounterResponse + ? pokemon.EncounterId + : _encounterId, + encounter is EncounterResponse || encounter is IncenseEncounterResponse + ? pokemon.SpawnPointId + : currentFortData.Id, pokeball, normalizedRecticleSize, spinModifier, hitPokemon).ConfigureAwait(false); + await session.Inventory.UpdateInventoryItem(pokeball).ConfigureAwait(false); + } + + var evt = new PokemonCaptureEvent() + { + Status = caughtPokemonResponse.Status, + CaptureReason = caughtPokemonResponse.CaptureReason, + Latitude = latitude, + Longitude = longitude + }; + + lastThrow = caughtPokemonResponse.Status; // sets lastThrow status + + if (caughtPokemonResponse.Status == CatchPokemonResponse.Types.CatchStatus.CatchSuccess) + { + evt.Shiny = (await session.Inventory.GetPokemons().ConfigureAwait(false)).First(x => x.Id == caughtPokemonResponse.CapturedPokemonId).PokemonDisplay.Shiny ? "Yes" : "No"; + evt.Form = (await session.Inventory.GetPokemons().ConfigureAwait(false)).First(x => x.Id == caughtPokemonResponse.CapturedPokemonId).PokemonDisplay.Form.ToString().Replace("Unown", "").Replace("Unset", "Normal"); + evt.Costume = (await session.Inventory.GetPokemons().ConfigureAwait(false)).First(x => x.Id == caughtPokemonResponse.CapturedPokemonId).PokemonDisplay.Costume.ToString().Replace("Unset", "Regular"); + evt.Gender = (await session.Inventory.GetPokemons().ConfigureAwait(false)).First(x => x.Id == caughtPokemonResponse.CapturedPokemonId).PokemonDisplay.Gender.ToString(); + + var totalExp = 0; + var stardust = caughtPokemonResponse.CaptureAward.Stardust.Sum(); + var totalStarDust = session.Inventory.UpdateStarDust(stardust); + var CaptuerXP = caughtPokemonResponse.CaptureAward.Xp.Sum(); + + if (encounteredPokemon != null) + { + encounteredPokemon.Id = caughtPokemonResponse.CapturedPokemonId; + } + foreach (var xp in caughtPokemonResponse.CaptureAward.Xp) + { + totalExp += xp; + } + + //This accounts for XP for CatchFlee + if (totalExp < 1) + { totalExp = 25; } + + evt.Exp = totalExp; + evt.Stardust = stardust; + evt.UniqueId = caughtPokemonResponse.CapturedPokemonId; + evt.Candy = await session.Inventory.GetCandyFamily(pokemon.PokemonId).ConfigureAwait(false); + evt.totalStarDust = totalStarDust; + + if (session.LogicSettings.AutoFavoriteShinyOnCatch) + { + if (evt.Shiny == "Yes") + { + await FavoritePokemonTask.Execute(session, encounteredPokemon.Id, true); + Logger.Write($"You've caught a Shiny Pokemon ({encounteredPokemon.Nickname}) and it has been Favorited."); + } + } + } + + if (caughtPokemonResponse.Status == CatchPokemonResponse.Types.CatchStatus.CatchSuccess || + caughtPokemonResponse.Status == CatchPokemonResponse.Types.CatchStatus.CatchFlee) + { + // Also count catch flee against the catch limit + if (session.LogicSettings.UseCatchLimit) + { + session.Stats.AddPokemonTimestamp(DateTime.Now.Ticks); + session.EventDispatcher.Send(new CatchLimitUpdate(session.Stats.GetNumPokemonsInLast24Hours(), session.LogicSettings.CatchPokemonLimit)); + } + } + + evt.CatchType = encounter is EncounterResponse + ? session.Translation.GetTranslation(TranslationString.CatchTypeNormal) + : encounter is DiskEncounterResponse + ? session.Translation.GetTranslation(TranslationString.CatchTypeLure) + : session.Translation.GetTranslation(TranslationString.CatchTypeIncense); + evt.CatchTypeText = encounter is EncounterResponse + ? "normal" + : encounter is DiskEncounterResponse + ? "lure" + : "incense"; + evt.Id = encounter is EncounterResponse + ? pokemon.PokemonId + : encounter?.PokemonData.PokemonId; + evt.EncounterId = _encounterId; + evt.Move1 = PokemonInfo.GetPokemonMove1(encounteredPokemon); + evt.Move2 = PokemonInfo.GetPokemonMove2(encounteredPokemon); + evt.Expires = pokemon?.ExpirationTimestampMs ?? 0; + evt.SpawnPointId = _spawnPointId; + evt.Level = PokemonInfo.GetLevel(encounteredPokemon); + evt.Cp = encounteredPokemon.Cp; + evt.MaxCp = PokemonInfo.CalculateMaxCp(encounteredPokemon.PokemonId); + evt.Perfection = Math.Round(PokemonInfo.CalculatePokemonPerfection(encounteredPokemon), 2); + evt.Probability = Math.Round(probability * 100, 2); + evt.Distance = distance; + evt.Pokeball = pokeball; + evt.Attempt = attemptCounter; + + //await session.Inventory.RefreshCachedInventory().ConfigureAwait(false); + + evt.BallAmount = await session.Inventory.GetItemAmountByType(pokeball).ConfigureAwait(false); + evt.Rarity = PokemonGradeHelper.GetPokemonGrade(evt.Id).ToString(); + + session.EventDispatcher.Send(evt); + + attemptCounter++; + + // If Humanlike delays are used + if (session.LogicSettings.UseHumanlikeDelays) + { + switch (caughtPokemonResponse.Status) + { + case CatchPokemonResponse.Types.CatchStatus.CatchError: + await DelayingUtils.DelayAsync(session.LogicSettings.CatchErrorDelay, 0, session.CancellationTokenSource.Token).ConfigureAwait(false); + break; + case CatchPokemonResponse.Types.CatchStatus.CatchSuccess: + await DelayingUtils.DelayAsync(session.LogicSettings.CatchSuccessDelay, 0, session.CancellationTokenSource.Token).ConfigureAwait(false); + break; + case CatchPokemonResponse.Types.CatchStatus.CatchEscape: + await DelayingUtils.DelayAsync(session.LogicSettings.CatchEscapeDelay, 0, session.CancellationTokenSource.Token).ConfigureAwait(false); + break; + case CatchPokemonResponse.Types.CatchStatus.CatchFlee: + await DelayingUtils.DelayAsync(session.LogicSettings.CatchFleeDelay, 0, session.CancellationTokenSource.Token).ConfigureAwait(false); + break; + case CatchPokemonResponse.Types.CatchStatus.CatchMissed: + await DelayingUtils.DelayAsync(session.LogicSettings.CatchMissedDelay, 0, session.CancellationTokenSource.Token).ConfigureAwait(false); + break; + default: + break; + } + } + else await DelayingUtils.DelayAsync(session.LogicSettings.DelayBetweenPlayerActions, 0, session.CancellationTokenSource.Token).ConfigureAwait(false); + } while (caughtPokemonResponse.Status == CatchPokemonResponse.Types.CatchStatus.CatchMissed || + caughtPokemonResponse.Status == CatchPokemonResponse.Types.CatchStatus.CatchEscape); + + if (caughtPokemonResponse.Status == CatchPokemonResponse.Types.CatchStatus.CatchFlee) + { + CatchFleeContinuouslyCount++; + if (CatchFleeContinuouslyCount >= 3 && session.LogicSettings.ByPassCatchFlee) + { + session.SaveBallForByPassCatchFlee = true; + Logger.Write("Seem that bot has been catch flee softban, Bot will start save 100 balls to by pass it."); + } + if (manager.AllowMultipleBot() && !session.LogicSettings.ByPassCatchFlee) + { + if (CatchFleeContinuouslyCount > session.LogicSettings.MultipleBotConfig.CatchFleeCount && + TinyIoCContainer.Current.Resolve().AllowSwitch()) + { + CatchFleeContinuouslyCount = 0; + session.SaveBallForByPassCatchFlee = false; + + throw new ActiveSwitchByRuleException() + { + MatchedRule = SwitchRules.CatchFlee, + ReachedValue = session.LogicSettings.MultipleBotConfig.CatchFleeCount + }; + } + } + } + else + { + //reset if not catch flee. + if (caughtPokemonResponse.Status != CatchPokemonResponse.Types.CatchStatus.CatchMissed) + { + CatchFleeContinuouslyCount = 0; + MSniperServiceTask.UnblockSnipe(); + } + } + + session.Actions.RemoveAll(x => x == BotActions.Catch); + + if (MultipleBotConfig.IsMultiBotActive(session.LogicSettings, manager)) + ExecuteSwitcher(session, encounterEV); + + if (session.LogicSettings.TransferDuplicatePokemonOnCapture && + session.LogicSettings.TransferDuplicatePokemon && + sessionAllowTransfer && + caughtPokemonResponse != null && + caughtPokemonResponse.Status == CatchPokemonResponse.Types.CatchStatus.CatchSuccess) + { + if (session.LogicSettings.UseNearActionRandom) + await HumanRandomActionTask.TransferRandom(session, cancellationToken).ConfigureAwait(false); + else + await TransferDuplicatePokemonTask.Execute(session, cancellationToken).ConfigureAwait(false); + } + } + return true; + } + + private static void ExecuteSwitcher(ISession session, EncounteredEvent encounterEV) + { + //if distance is very far. that is snip pokemon + var accountManager = TinyIoCContainer.Current.Resolve(); + session.Cache.Add(CatchPokemonTask.GetUsernameEncounterCacheKey(session.Settings.Username, encounterEV.EncounterId), encounterEV, DateTime.Now.AddMinutes(15)); + + var evalNextBot = accountManager.FindAvailableAccountForPokemonSwitch(encounterEV.EncounterId); + if (evalNextBot == null) + return; + + if (session.Stats.IsSnipping + //assume that all pokemon catch from 250+m is snipe + || LocationUtils.CalculateDistanceInMeters( + encounterEV.Latitude, + encounterEV.Longitude, + session.Client.CurrentLatitude, + session.Client.CurrentLongitude + ) > 1000) + { + + var snipePokemonFiler = session.LogicSettings.PokemonSnipeFilters.GetFilter(encounterEV.PokemonId); + + if (session.LogicSettings.PokemonSnipeFilters.ContainsKey(encounterEV.PokemonId)) + { + var filter = session.LogicSettings.PokemonSnipeFilters[encounterEV.PokemonId]; + if (accountManager.AllowMultipleBot() && + filter.AllowMultiAccountSnipe && + filter.IsMatch(encounterEV.IV, + (PokemonMove)Enum.Parse(typeof(PokemonMove), encounterEV.Move1), + (PokemonMove)Enum.Parse(typeof(PokemonMove), encounterEV.Move2), + encounterEV.Level, true)) + { + //throw + throw new ActiveSwitchByPokemonException() + { + EncounterData = encounterEV, + LastLatitude = encounterEV.Latitude, + LastLongitude = encounterEV.Longitude, + LastEncounterPokemonId = encounterEV.PokemonId, + Snipe = true, + Bot = evalNextBot + }; + } + } + return; + } + + if (MultipleBotConfig.IsMultiBotActive(session.LogicSettings, accountManager) && + session.LogicSettings.MultipleBotConfig.OnRarePokemon && + ( + session.LogicSettings.MultipleBotConfig.MinIVToSwitch < encounterEV.IV || + ( + session.LogicSettings.BotSwitchPokemonFilters.ContainsKey(encounterEV.PokemonId) && + ( + session.LogicSettings.BotSwitchPokemonFilters[encounterEV.PokemonId].IV < encounterEV.IV || + (session.LogicSettings.BotSwitchPokemonFilters[encounterEV.PokemonId].LV > 0 && session + .LogicSettings.BotSwitchPokemonFilters[encounterEV.PokemonId] + .LV < encounterEV.Level) + ) + ) + )) + { + if (evalNextBot != null) + { + //cancel all running task. + session.CancellationTokenSource.Cancel(); + throw new ActiveSwitchByPokemonException() + { + LastLatitude = encounterEV.Latitude, + LastLongitude = encounterEV.Longitude, + LastEncounterPokemonId = encounterEV.PokemonId, + Bot = evalNextBot + }; + } + } + } + + private static bool IsNotMetWithCatchCriteria(ISession session, PokemonData encounteredPokemon, + double pokemonIv, double lv, int? cp) + { + if (session.LogicSettings.UsePokemonToNotCatchFilter && + session.LogicSettings.PokemonsNotToCatch.Contains(encounteredPokemon.PokemonId)) return true; + if (session.LogicSettings.UseTransferFilterToCatch && + session.LogicSettings.PokemonsTransferFilter.ContainsKey(encounteredPokemon.PokemonId)) + { + var filter = session.LogicSettings.PokemonsTransferFilter[encounteredPokemon.PokemonId]; + if (filter != null && filter.CatchOnlyPokemonMeetTransferCriteria) + { + var move1 = PokemonInfo.GetPokemonMove1(encounteredPokemon).ToString(); + var move2 = PokemonInfo.GetPokemonMove2(encounteredPokemon).ToString(); + + if (filter.MovesOperator == "or" && + (filter.Moves.Count > 0 && + filter.Moves.Any(x => x[0] == encounteredPokemon.Move1 && x[1] == encounteredPokemon.Move2))) + { + return true; //he has the moves we don't meed. + } + + if (filter.KeepMinOperator == "and" + && ((cp.HasValue && cp.Value < filter.KeepMinCp) + || pokemonIv < filter.KeepMinIvPercentage + || (filter.UseKeepMinLvl && lv < filter.KeepMinLvl)) + && ( + filter.Moves.Count == 0 || + filter.Moves.Any(x => x[0] == encounteredPokemon.Move1 && x[1] == encounteredPokemon.Move2) + )) + { + return true; //not catch pokemon + } + + if (filter.KeepMinOperator == "or" && ((!cp.HasValue || cp < filter.KeepMinCp) + && pokemonIv < filter.KeepMinIvPercentage + && (!filter.UseKeepMinLvl || lv < filter.KeepMinLvl)) + && ( + filter.Moves.Count == 0 || + filter.Moves.Any(x => x[0] == encounteredPokemon.Move1 && x[1] == encounteredPokemon.Move2) + )) + { + return true; //not catch pokemon + } + } + } + return false; + } + + public static async Task GetBestBall(ISession session, PokemonData encounteredPokemon, + float probability) + { + var pokemonCp = encounteredPokemon.Cp; + var pokemonId = encounteredPokemon.PokemonId; + var iV = Math.Round(PokemonInfo.CalculatePokemonPerfection(encounteredPokemon), 2); + var pokeBallsCount = await session.Inventory.GetItemAmountByType(ItemId.ItemPokeBall).ConfigureAwait(false); + var greatBallsCount = await session.Inventory.GetItemAmountByType(ItemId.ItemGreatBall).ConfigureAwait(false); + var ultraBallsCount = await session.Inventory.GetItemAmountByType(ItemId.ItemUltraBall).ConfigureAwait(false); + var masterBallsCount = await session.Inventory.GetItemAmountByType(ItemId.ItemMasterBall).ConfigureAwait(false); + + if (masterBallsCount > 0 && ( + session.LogicSettings.UseBallOperator.BoolFunc( + pokemonCp >= session.LogicSettings.UseMasterBallAboveCp, + probability < session.LogicSettings.UseMasterBallBelowCatchProbability + ) + || session.LogicSettings.PokemonToUseMasterball.Contains(pokemonId))) + return ItemId.ItemMasterBall; + + + if (ultraBallsCount > 0 && + session.LogicSettings.UseBallOperator.BoolFunc(pokemonCp >= session.LogicSettings.UseUltraBallAboveCp, + iV >= session.LogicSettings.UseUltraBallAboveIv, + probability < session.LogicSettings.UseUltraBallBelowCatchProbability)) + return ItemId.ItemUltraBall; + + if (greatBallsCount > 0 && session.LogicSettings.UseBallOperator.BoolFunc(pokemonCp >= session.LogicSettings.UseGreatBallAboveCp, + iV >= session.LogicSettings.UseGreatBallAboveIv, + probability < session.LogicSettings.UseGreatBallBelowCatchProbability) + ) + return ItemId.ItemGreatBall; + + if (pokeBallsCount > 0) + return ItemId.ItemPokeBall; + if (greatBallsCount > 0) + return ItemId.ItemGreatBall; + if (ultraBallsCount > 0) + return ItemId.ItemUltraBall; + if (masterBallsCount > 0 && !session.LogicSettings.PokemonToUseMasterball.Any()) + return ItemId.ItemMasterBall; + + return ItemId.ItemUnknown; + } + + public static async Task UseBerry(ISession session, + PokemonId pokemonId, + ulong encounterId, + string spawnPointId, + double pokemonIv, + float pokemonCp, + float pokemonLv, + float probability, + CancellationToken cancellationToken) + { + + var itemToUses = session.LogicSettings.ItemUseFilters; + + foreach (var item in itemToUses) + { + var inventoryItems = await session.Inventory.GetItems().ConfigureAwait(false); + var berries = inventoryItems.Where(p => p.ItemId == item.Key); + var berry = berries.FirstOrDefault(); + + if (berry == null || berry.Count <= 0) + continue; + + var filter = item.Value; + var itemRecycleFilter = session.LogicSettings.ItemRecycleFilter.FirstOrDefault(x => x.Key == item.Key); + + if ((filter.UseIfExceedBagRecycleFilter && + itemRecycleFilter.Key == item.Key && + itemRecycleFilter.Value < berry.Count) + || ((filter.Pokemons.Count == 0 || filter.Pokemons.Contains(pokemonId)) && + (!AmountOfBerries.ContainsKey(item.Key) || AmountOfBerries[item.Key] < filter.MaxItemsUsePerPokemon) && + filter.Operator.BoolFunc( + pokemonIv >= filter.UseItemMinIV, + pokemonCp >= filter.UseItemMinCP, + probability < filter.CatchProbability, + pokemonLv >= filter.UseItemMinLevel))) + { + var useCaptureItem = await session.Client.Encounter.UseItemEncounter(encounterId, item.Key, spawnPointId).ConfigureAwait(false); + //berry.Count -= 1; + if (useCaptureItem.Status == UseItemEncounterResponse.Types.Status.Success) + { + if (!AmountOfBerries.ContainsKey(item.Key)) + { + AmountOfBerries.Add(item.Key, 0); + } + AmountOfBerries[item.Key] = AmountOfBerries[item.Key] + 1; + session.EventDispatcher.Send(new UseBerryEvent { BerryType = item.Key, Count = berry.Count - 1 }); + await session.Inventory.UpdateInventoryItem(berry.ItemId).ConfigureAwait(false); + break;//cant only use 1 berries at 1 + } + else + { + Logger.Debug($"Use berries result : {useCaptureItem.Status}"); + } + } + await DelayingUtils.DelayAsync(session.LogicSettings.DelayBetweenPlayerActions, 500, cancellationToken).ConfigureAwait(false); + } + } + } +} diff --git a/PoGo.NecroBot.Logic/Tasks/DisplayPokemonStatsTask.cs b/PoGo.NecroBot.Logic/Tasks/DisplayPokemonStatsTask.cs new file mode 100644 index 000000000..40f360055 --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/DisplayPokemonStatsTask.cs @@ -0,0 +1,174 @@ +#region using directives + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.DataDumper; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.PoGoUtils; +using PoGo.NecroBot.Logic.State; + +#endregion + +namespace PoGo.NecroBot.Logic.Tasks +{ + public class DisplayPokemonStatsTask + { + public static List PokemonId = new List(); + + public static List PokemonIdcp = new List(); + private static MultiAccountManager _MultiAccountManager; + + public static async Task Execute(ISession session) + { + var highestsPokemonCp = await + session.Inventory.GetHighestsCp(session.LogicSettings.AmountOfPokemonToDisplayOnStart).ConfigureAwait(false); + + var pokemonPairedWithStatsCp = + highestsPokemonCp.Select( + pokemon => + Tuple.Create( + pokemon, + PokemonInfo.CalculateMaxCp(pokemon.PokemonId), + PokemonInfo.CalculatePokemonPerfection(pokemon), + PokemonInfo.GetLevel(pokemon), + PokemonInfo.GetPokemonMove1(pokemon), + PokemonInfo.GetPokemonMove2(pokemon), + PokemonInfo.GetCandy(session, pokemon).Result + ) + ) + .ToList(); + + var highestsPokemonPerfect = await + session.Inventory.GetHighestsPerfect(session.LogicSettings.AmountOfPokemonToDisplayOnStart).ConfigureAwait(false); + + var pokemonPairedWithStatsIv = + highestsPokemonPerfect.Select( + pokemon => + Tuple.Create( + pokemon, + PokemonInfo.CalculateMaxCp(pokemon.PokemonId), + PokemonInfo.CalculatePokemonPerfection(pokemon), + PokemonInfo.GetLevel(pokemon), + PokemonInfo.GetPokemonMove1(pokemon), + PokemonInfo.GetPokemonMove2(pokemon), + PokemonInfo.GetCandy(session, pokemon).Result + ) + ) + .ToList(); + + session.EventDispatcher.Send( + new DisplayHighestsPokemonEvent + { + SortedBy = "CP", + PokemonList = pokemonPairedWithStatsCp + }); + + session.EventDispatcher.Send( + new DisplayHighestsPokemonEvent + { + SortedBy = "IV", + PokemonList = pokemonPairedWithStatsIv + }); + + var allPokemonInBag = session.LogicSettings.PrioritizeIvOverCp + ? await session.Inventory.GetHighestsPerfect(1000).ConfigureAwait(false) + : await session.Inventory.GetHighestsCp(1000).ConfigureAwait(false); + if (session.LogicSettings.DumpPokemonStats) + { + _MultiAccountManager = new MultiAccountManager(); + var account = _MultiAccountManager.GetCurrentAccount(); + string dumpFileName = account.Nickname; // "-PokeBagStats"; + + //If user dump file exists then cancel file dump + if (File.Exists(Path.Combine(Path.Combine(session.LogicSettings.ProfilePath, "Dumps"), $"{dumpFileName}-NecroBot2 DumpFile.csv"))) return; + + try + { + Dumper.ClearDumpFile(session, dumpFileName); + + string[] data = + { + "Pokemon", + "Candies", + "Slashed", + "Nickname", + "Level", + "CP", + "IV", + "Power Ups", + "Favorite", + "Stamina", + "Stamina Max", + "Move1", + "Move2", + "Owner Name", + "Origin", + "Height(M)", + "Weight(KG)", + "Attack", + "Defense", + "Stamina", + "CP Multi", + "Gyms Attacked", + "Gyms Defended", + "Creationtimems", + "Add CP Multi" + }; + Dumper.Dump(session, data, dumpFileName); + + // set culture to OS default + CultureInfo prevCulture = Thread.CurrentThread.CurrentCulture; + CultureInfo culture = CultureInfo.CurrentUICulture; + Thread.CurrentThread.CurrentCulture = culture; + + foreach (var pokemon in allPokemonInBag) + { + string[] pokemonData = + { + session.Translation.GetPokemonTranslation(pokemon.PokemonId).Replace(' ', '_'), + session.Inventory.GetCandyCount(pokemon.PokemonId).ToString(), // PokemonInfo.GetCandy(session, pokemon.PokemonId).ToString(), + pokemon.IsBad.ToString(), + pokemon.Nickname.Replace(' ', '_'), + PokemonInfo.GetLevel(pokemon).ToString(), + pokemon.Cp.ToString(), + PokemonInfo.CalculatePokemonPerfection(pokemon).ToString(), + pokemon.NumUpgrades.ToString(), + pokemon.Favorite.ToString(), + pokemon.Stamina.ToString(), + pokemon.StaminaMax.ToString(), + pokemon.Move1.ToString(), + pokemon.Move2.ToString(), + pokemon.OwnerName, + pokemon.Origin.ToString(), + pokemon.HeightM.ToString(), + pokemon.WeightKg.ToString(), + pokemon.IndividualAttack.ToString(), + pokemon.IndividualDefense.ToString(), + pokemon.IndividualStamina.ToString(), + pokemon.CpMultiplier.ToString(), + pokemon.BattlesAttacked.ToString(), + pokemon.BattlesDefended.ToString(), + pokemon.CreationTimeMs.ToString(), + pokemon.AdditionalCpMultiplier.ToString() + }; + Dumper.Dump(session, pokemonData, dumpFileName); + } + + // restore culture + Thread.CurrentThread.CurrentCulture = prevCulture; + } + catch (IOException) + { + session.EventDispatcher.Send( + new ErrorEvent {Message = $"Could not write {dumpFileName} dump file."} + ); + } + } + } + } +} diff --git a/PoGo.NecroBot.Logic/Tasks/EggsListTask.cs b/PoGo.NecroBot.Logic/Tasks/EggsListTask.cs new file mode 100644 index 000000000..3962fbd41 --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/EggsListTask.cs @@ -0,0 +1,48 @@ +#region using directives + +using System.Linq; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Utils; +using POGOProtos.Inventory.Item; + +#endregion + +namespace PoGo.NecroBot.Logic.Tasks +{ + public class EggsListTask + { + public static async Task Execute(ISession session) + { + // Refresh inventory so that the player stats are fresh + //await session.Inventory.RefreshCachedInventory().ConfigureAwait(false); + + var playerStats = (await session.Inventory.GetPlayerStats().ConfigureAwait(false)).FirstOrDefault(); + if (playerStats == null) + return; + + var kmWalked = playerStats.KmWalked; + + var incubators = (await session.Inventory.GetEggIncubators().ConfigureAwait(false)) + .Where(x => x.UsesRemaining > 0 || x.ItemId == ItemId.ItemIncubatorBasicUnlimited) + .OrderByDescending(x => x.ItemId == ItemId.ItemIncubatorBasicUnlimited) + .ToList(); + + var unusedEggs = (await session.Inventory.GetEggs().ConfigureAwait(false)) + .Where(x => string.IsNullOrEmpty(x.EggIncubatorId)) + .OrderBy(x => x.EggKmWalkedTarget - x.EggKmWalkedStart) + .ToList(); + + session.EventDispatcher.Send( + new EggsListEvent + { + PlayerKmWalked = kmWalked, + Incubators = incubators, + UnusedEggs = unusedEggs + }); + + await DelayingUtils.DelayAsync(session.LogicSettings.DelayBetweenPlayerActions, 0, session.CancellationTokenSource.Token).ConfigureAwait(false); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Tasks/EvolvePokemonTask.cs b/PoGo.NecroBot.Logic/Tasks/EvolvePokemonTask.cs new file mode 100644 index 000000000..9aa4f3fc5 --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/EvolvePokemonTask.cs @@ -0,0 +1,283 @@ +#region using directives + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Utils; +using POGOProtos.Data; +using POGOProtos.Inventory.Item; +using POGOProtos.Networking.Responses; +using TinyIoC; +using PoGo.NecroBot.Logic.Logging; +using PoGo.NecroBot.Logic.Model.Settings; +using POGOProtos.Enums; + +#endregion + +namespace PoGo.NecroBot.Logic.Tasks +{ + public class EvolvePokemonTask + { + public static bool IsActivated(ISession session) + { + if (session.LogicSettings.TriggerEvolveAsSoonAsFilterIsMatched + || session.LogicSettings.TriggerEvolveIfLuckyEggIsActive + || session.LogicSettings.TriggerEvolveOnEvolutionCount + || session.LogicSettings.TriggerEvolveOnStorageUsageAbsolute + || session.LogicSettings.TriggerEvolveOnStorageUsagePercentage) + { + return true; + } + return false; + } + + public static async Task Execute(ISession session, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var pokemonToEvolveTask = await session.Inventory + .GetPokemonToEvolve(session.LogicSettings.PokemonEvolveFilters).ConfigureAwait(false); + var pokemonsToEvolve = pokemonToEvolveTask.Where(p => p != null).ToList(); + int evolutionCount = pokemonsToEvolve.Count(); + + session.EventDispatcher.Send(new EvolveCountEvent + { + Evolves = evolutionCount + }); + + if (evolutionCount > 0 && ( + session.LogicSettings.TriggerEvolveAsSoonAsFilterIsMatched + || IsTriggerByEvolutionCount(session, evolutionCount) + || await IsTriggerByLuckyEggActive(session).ConfigureAwait(false) + || await IsTriggerByStorageUsagePercentage(session, evolutionCount).ConfigureAwait(false) + || await IsTriggerByStorageUsageAbsolute(session, evolutionCount).ConfigureAwait(false))) + { + if (await ShouldUseLuckyEgg(session, pokemonsToEvolve).ConfigureAwait(false)) + { + await UseLuckyEgg(session).ConfigureAwait(false); + } + await Evolve(session, cancellationToken, pokemonsToEvolve).ConfigureAwait(false); + } + } + + private static async Task IsTriggerByLuckyEggActive(ISession session) + { + if(session.LogicSettings.TriggerEvolveIfLuckyEggIsActive) + { + TimeSpan luckyEggRemainingTime = await session.Inventory.GetLuckyEggRemainingTime().ConfigureAwait(false); + + if (luckyEggRemainingTime.TotalSeconds > 0) + { + Logger.Write(session.Translation.GetTranslation(TranslationString.UseLuckyEggActive, luckyEggRemainingTime.Minutes, luckyEggRemainingTime.Seconds), LogLevel.Info, ConsoleColor.DarkGreen); + return true; + } + } + return false; + } + + private static bool IsTriggerByEvolutionCount(ISession session, int pokemonsToEvolveCount) + { + if (!session.LogicSettings.TriggerEvolveOnEvolutionCount) + { + return false; + } + + int luckyEggMin = session.LogicSettings.TriggerEvolveOnEvolutionCountValue; + int missingPossibleEvolutions = luckyEggMin - pokemonsToEvolveCount; + + if (missingPossibleEvolutions > 0) + { + session.EventDispatcher.Send(new UpdateEvent() + { + Message = session.Translation.GetTranslation( + TranslationString.WaitingForMoreEvolutionsToEvolve, + missingPossibleEvolutions, + pokemonsToEvolveCount, + luckyEggMin) + }); + return false; + } + return true; + } + + private static async Task IsTriggerByStorageUsagePercentage(ISession session, int pokemonsToEvolveCount) + { + if (!session.LogicSettings.TriggerEvolveOnStorageUsagePercentage) + { + return false; + } + + var maxStorage = session.Profile.PlayerData.MaxPokemonStorage; + int thresholdFromRelConfig = Convert.ToInt32(maxStorage * session.LogicSettings.TriggerEvolveOnStorageUsagePercentageValue / 100.0f); + return await IsTriggerByStorageUsage(session, pokemonsToEvolveCount, thresholdFromRelConfig).ConfigureAwait(false); + } + + private static async Task IsTriggerByStorageUsageAbsolute(ISession session, int pokemonsToEvolveCount) + { + if (!session.LogicSettings.TriggerEvolveOnStorageUsageAbsolute) + { + return false; + } + + int thresholdFromAbsConfig = session.LogicSettings.TriggerEvolveOnStorageUsageAbsoluteValue; + return await IsTriggerByStorageUsage(session, pokemonsToEvolveCount, thresholdFromAbsConfig).ConfigureAwait(false); + } + + private static async Task IsTriggerByStorageUsage(ISession session, int pokemonsToEvolveCount, int storageThresholdAbs) + { + var maxStorage = session.Profile.PlayerData.MaxPokemonStorage; + var neededPokemonsToStartEvolve = Math.Max(0, Math.Min(storageThresholdAbs, maxStorage)); + + // Calculate missing pokemons until storage full enough + var totalPokemon = await session.Inventory.GetPokemons().ConfigureAwait(false); + int missingPokemonsInStorage = neededPokemonsToStartEvolve - totalPokemon.Count(); + + if (missingPokemonsInStorage > 0) + { + session.EventDispatcher.Send(new UpdateEvent() + { + Message = session.Translation.GetTranslation( + TranslationString.WaitingForMorePokemonToEvolve, + pokemonsToEvolveCount, + missingPokemonsInStorage, + totalPokemon.Count(), + neededPokemonsToStartEvolve, + 0.0 // Deprecated + ) + }); + return false; + } + + return true; + } + + public static async Task UseLuckyEgg(ISession session) + { + var inventoryContent = await session.Inventory.GetItems().ConfigureAwait(false); + var luckyEgg = inventoryContent.FirstOrDefault(p => p.ItemId == ItemId.ItemLuckyEgg); + + if (luckyEgg.Count == 0) // We tried to use egg but we don't have any more. Just return. + return; + + TimeSpan luckyEggRemainingTime = await session.Inventory.GetLuckyEggRemainingTime().ConfigureAwait(false); + if (luckyEggRemainingTime.TotalSeconds > 0) + return; // There is still an egg active + + var responseLuckyEgg = await session.Client.Inventory.UseItemXpBoost().ConfigureAwait(false); + if (responseLuckyEgg.Result == UseItemXpBoostResponse.Types.Result.Success) + { + // Get refreshed lucky egg so we have an accurate count. + luckyEgg = inventoryContent.FirstOrDefault(p => p.ItemId == ItemId.ItemLuckyEgg); + + if (luckyEgg != null) session.EventDispatcher.Send(new UseLuckyEggEvent { Count = luckyEgg.Count }); + TinyIoCContainer.Current.Resolve().DisableSwitchAccountUntil(DateTime.Now.AddMinutes(30)); + } + await DelayingUtils.DelayAsync(session.LogicSettings.DelayBetweenPlayerActions, 0, session.CancellationTokenSource.Token).ConfigureAwait(false); + } + + public static async Task GetRequireEvolveItem(ISession session, PokemonId from, PokemonId to) + { + var settings = (await session.Inventory.GetPokemonSettings().ConfigureAwait(false)).FirstOrDefault(x => x.PokemonId == from); + if (settings == null) return ItemId.ItemUnknown; + + var branch = settings.EvolutionBranch.FirstOrDefault(x => x.Evolution == to); + if (branch == null) return ItemId.ItemUnknown; + return branch.EvolutionItemRequirement; + } + + private static async Task Evolve(ISession session, CancellationToken cancellationToken, List pokemonToEvolve) + { + int sequence = 1; + foreach (var pokemon in pokemonToEvolve) + { + var filter = session.LogicSettings.PokemonEvolveFilters.GetFilter(pokemon.PokemonId); + if (await session.Inventory.CanEvolvePokemon(pokemon, filter).ConfigureAwait(false)) + { + try + { + TimeSpan luckyEggRemainingTime = await session.Inventory.GetLuckyEggRemainingTime().ConfigureAwait(false); + if (luckyEggRemainingTime.TotalSeconds <= 0) + // do not waste lucky egg + cancellationToken.ThrowIfCancellationRequested(); + + var evolveResponse = await session.Client.Inventory.EvolvePokemon(pokemon.Id, filter == null ? ItemId.ItemUnknown : await GetRequireEvolveItem(session, pokemon.PokemonId, filter.EvolveToPokemonId)).ConfigureAwait(false); + var CandyUsed = session.Inventory.GetCandyCount(pokemon.PokemonId); + + if (evolveResponse.Result == EvolvePokemonResponse.Types.Result.Success) + { + session.EventDispatcher.Send(new PokemonEvolveEvent + { + Id = pokemon.PokemonId, + Exp = evolveResponse.ExperienceAwarded, + UniqueId = pokemon.Id, + Result = evolveResponse.Result, + Sequence = pokemonToEvolve.Count() == 1 ? 0 : sequence++, + EvolvedPokemon = evolveResponse.EvolvedPokemonData, + Candy = await CandyUsed + }); + } + + if (!pokemonToEvolve.Last().Equals(pokemon)) + { + await DelayingUtils.DelayAsync(session.LogicSettings.EvolveActionDelay, 0, session.CancellationTokenSource.Token).ConfigureAwait(false); + } + } + catch + { + Logger.Write("ERROR - Evolve failed", color: ConsoleColor.Red); + } + } + } + } + + private static async Task ShouldUseLuckyEgg(ISession session, List pokemonToEvolve) + { + var inventoryContent = await session.Inventory.GetItems().ConfigureAwait(false); + var luckyEggItemData = inventoryContent.Where(p => p.ItemId == ItemId.ItemLuckyEgg).FirstOrDefault(); + + if (session.LogicSettings.EvolveApplyLuckyEggOnEvolutionCount && luckyEggItemData?.Count > 0) + { + int applyLuckyEggThreshold = session.LogicSettings.EvolveApplyLuckyEggOnEvolutionCountValue; + if (pokemonToEvolve.Count >= applyLuckyEggThreshold) + { + return true; + } + + var evolvablePokemon = await session.Inventory.GetPokemons().ConfigureAwait(false); + var deltaPokemonToUseLuckyEgg = applyLuckyEggThreshold - pokemonToEvolve.Count; + var availableSpace = session.Profile.PlayerData.MaxPokemonStorage - evolvablePokemon.Count(); + + if (deltaPokemonToUseLuckyEgg > availableSpace) + { + var possibleLimitInThisIteration = pokemonToEvolve.Count + availableSpace; + + session.EventDispatcher.Send(new NoticeEvent() + { + Message = session.Translation.GetTranslation( + TranslationString.UseLuckyEggsMinPokemonAmountTooHigh, + applyLuckyEggThreshold, + possibleLimitInThisIteration + ) + }); + } + else + { + session.EventDispatcher.Send(new NoticeEvent() + { + Message = session.Translation.GetTranslation( + TranslationString.CatchMorePokemonToUseLuckyEgg, + deltaPokemonToUseLuckyEgg + ) + }); + } + + } + return false; + } + } +} diff --git a/PoGo.NecroBot.Logic/Tasks/EvolveSpecificPokemonTask.cs b/PoGo.NecroBot.Logic/Tasks/EvolveSpecificPokemonTask.cs new file mode 100644 index 000000000..d5891070c --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/EvolveSpecificPokemonTask.cs @@ -0,0 +1,77 @@ +#region using directives + +using System.Linq; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.Model; +using PoGo.NecroBot.Logic.State; +using POGOProtos.Enums; +using POGOProtos.Inventory.Item; + +#endregion + +namespace PoGo.NecroBot.Logic.Tasks +{ + public class EvolveSpecificPokemonTask + { + public static async Task Execute(ISession session, ulong pokemonId, PokemonId evolveToId = PokemonId.Missingno) + { + using (var blocker = new BlockableScope(session, BotActions.Envolve)) + { + if (!await blocker.WaitToRun().ConfigureAwait(false)) + { + session.EventDispatcher.Send(new PokemonEvolveEvent + { + OriginalId = pokemonId, + Cancelled = true + }); + return; + } + + var all = await session.Inventory.GetPokemons().ConfigureAwait(false); + var pokemons = all.OrderByDescending(x => x.Cp).ThenBy(n => n.StaminaMax); + var pokemon = pokemons.FirstOrDefault(p => p.Id == pokemonId); + + if (pokemon == null) return; + + if (!await session.Inventory.CanEvolvePokemon(pokemon, new Model.Settings.EvolveFilter() { + EvolveTo = evolveToId.ToString() + }).ConfigureAwait(false)) + return; + ItemId itemToEvolve = ItemId.ItemUnknown; + var pkmSetting = await session.Inventory.GetPokemonSetting(pokemon.PokemonId).ConfigureAwait(false); + + if (evolveToId != PokemonId.Missingno && pkmSetting != null) + { + var evolution = pkmSetting.EvolutionBranch.FirstOrDefault(x => x.Evolution == evolveToId); + if(evolution!= null) + { + itemToEvolve = evolution.EvolutionItemRequirement; + if(itemToEvolve != ItemId.ItemUnknown && await session.Inventory.GetItemAmountByType(itemToEvolve).ConfigureAwait(false) == 0) + { + session.EventDispatcher.Send(new PokemonEvolveEvent + { + OriginalId = pokemonId, + Cancelled = true + }); + return; + } + } + } + + var evolveResponse = await session.Client.Inventory.EvolvePokemon(pokemon.Id, itemToEvolve).ConfigureAwait(false); + + session.EventDispatcher.Send(new PokemonEvolveEvent + { + OriginalId = pokemonId, + Id = pokemon.PokemonId, + Exp = evolveResponse.ExperienceAwarded, + UniqueId = pokemon.Id, + Result = evolveResponse.Result, + EvolvedPokemon = evolveResponse.EvolvedPokemonData + }); + + } + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Tasks/FarmPokestopsGPXTask.cs b/PoGo.NecroBot.Logic/Tasks/FarmPokestopsGPXTask.cs new file mode 100644 index 000000000..ba25bede0 --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/FarmPokestopsGPXTask.cs @@ -0,0 +1,120 @@ +#region using directives + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.Model; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Utils; + +#endregion + +namespace PoGo.NecroBot.Logic.Tasks +{ + public static class FarmPokestopsGpxTask + { + private static int? _resumeTrack = null; + private static int? _resumeTrackSeg = null; + private static int? _resumeTrackPt = null; + + public static async Task Execute(ISession session, CancellationToken cancellationToken) + { + var tracks = GetGpxTracks(session); + var eggWalker = new EggWalker(1000, session); + + if (!_resumeTrack.HasValue || !_resumeTrackSeg.HasValue || !_resumeTrackPt.HasValue) + { + _resumeTrack = session.LogicSettings.ResumeTrack; + _resumeTrackSeg = session.LogicSettings.ResumeTrackSeg; + _resumeTrackPt = session.LogicSettings.ResumeTrackPt; + + // initialize the variables in UseNearbyPokestopsTask here, as this is a fresh start. + UseNearbyPokestopsTask.Initialize(); + } + + for (var curTrk = _resumeTrack.Value; curTrk < tracks.Count; curTrk++) + { + _resumeTrack = curTrk; + cancellationToken.ThrowIfCancellationRequested(); + TinyIoC.TinyIoCContainer.Current.Resolve().ThrowIfSwitchAccountRequested(); + var track = tracks.ElementAt(curTrk); + var trackSegments = track.Segments; + + for (var curTrkSeg = _resumeTrackSeg.Value; curTrkSeg < trackSegments.Count; curTrkSeg++) + { + _resumeTrackSeg = curTrkSeg; + cancellationToken.ThrowIfCancellationRequested(); + TinyIoC.TinyIoCContainer.Current.Resolve().ThrowIfSwitchAccountRequested(); + + var trackPoints = trackSegments.ElementAt(curTrkSeg).TrackPoints; + + for (var curTrkPt = _resumeTrackPt.Value; curTrkPt < trackPoints.Count; curTrkPt++) + { + _resumeTrackPt = curTrkPt; + cancellationToken.ThrowIfCancellationRequested(); + TinyIoC.TinyIoCContainer.Current.Resolve().ThrowIfSwitchAccountRequested(); + var nextPoint = trackPoints.ElementAt(curTrkPt); + var distance = LocationUtils.CalculateDistanceInMeters(session.Client.CurrentLatitude, + session.Client.CurrentLongitude, + Convert.ToDouble(nextPoint.Lat, CultureInfo.InvariantCulture), + Convert.ToDouble(nextPoint.Lon, CultureInfo.InvariantCulture)); + + if (distance > 5000) + { + session.EventDispatcher.Send(new ErrorEvent + { + Message = + session.Translation.GetTranslation(TranslationString.DesiredDestTooFar, + nextPoint.Lat, nextPoint.Lon, session.Client.CurrentLatitude, + session.Client.CurrentLongitude) + }); + break; + } + var lat = Convert.ToDouble(trackPoints.ElementAt(curTrkPt).Lat, CultureInfo.InvariantCulture); + var lng = Convert.ToDouble(trackPoints.ElementAt(curTrkPt).Lon, CultureInfo.InvariantCulture); + + IGeoLocation destination = new GPXPointLocation(lat, lng, + await LocationUtils.GetElevation(session.ElevationService, lat, lng).ConfigureAwait(false)); + + await session.Navigation.Move(destination, + async () => + { + await MSniperServiceTask.Execute(session, cancellationToken).ConfigureAwait(false); + + await CatchNearbyPokemonsTask.Execute(session, cancellationToken).ConfigureAwait(false); + //Catch Incense Pokemon + await CatchIncensePokemonsTask.Execute(session, cancellationToken).ConfigureAwait(false); + await UseNearbyPokestopsTask.Execute(session, cancellationToken).ConfigureAwait(false); + }, + session, + cancellationToken).ConfigureAwait(false); + + await eggWalker.ApplyDistance(distance, cancellationToken).ConfigureAwait(false); + + // Return to FarmState/StateMachine if we have reached both user defined limits + if ((UseNearbyPokestopsTask._pokestopLimitReached || + UseNearbyPokestopsTask._pokestopTimerReached) && + session.Stats.CatchThresholdExceeds(session)) + return; + } //end trkpts + _resumeTrackPt = 0; + } //end trksegs + _resumeTrackSeg = 0; + } //end tracks + _resumeTrack = 0; + } + + private static List GetGpxTracks(ISession session) + { + var xmlString = File.ReadAllText(session.LogicSettings.GpxFile); + var readgpx = new GpxReader(xmlString, session); + return readgpx.Tracks; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Tasks/FarmPokestopsTask.cs b/PoGo.NecroBot.Logic/Tasks/FarmPokestopsTask.cs new file mode 100644 index 000000000..7349d7484 --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/FarmPokestopsTask.cs @@ -0,0 +1,63 @@ +#region using directives + +using System.Threading; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.Logging; +using PoGo.NecroBot.Logic.Model; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Utils; +using GeoCoordinatePortable; + +#endregion + +namespace PoGo.NecroBot.Logic.Tasks +{ + public static class FarmPokestopsTask + { + private static bool checkForMoveBackToDefault = true; + + public static async Task Execute(ISession session, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + TinyIoC.TinyIoCContainer.Current.Resolve().ThrowIfSwitchAccountRequested(); + var distanceFromStart = LocationUtils.CalculateDistanceInMeters( + session.Settings.DefaultLatitude, session.Settings.DefaultLongitude, + session.Client.CurrentLatitude, session.Client.CurrentLongitude); + + await LocationUtils.UpdatePlayerLocationWithAltitude(session, new GeoCoordinate(session.Client.CurrentLatitude, session.Client.CurrentLongitude, session.Client.CurrentAltitude), session.Client.CurrentSpeed).ConfigureAwait(false); + // Edge case for when the client somehow ends up outside the defined radius + if (session.LogicSettings.MaxTravelDistanceInMeters != 0 && checkForMoveBackToDefault && + distanceFromStart > session.LogicSettings.MaxTravelDistanceInMeters) + { + checkForMoveBackToDefault = false; + Logger.Write( + session.Translation.GetTranslation(TranslationString.FarmPokestopsOutsideRadius, distanceFromStart), + LogLevel.Warning); + + var eggWalker = new EggWalker(1000, session); + + var defaultLocation = new MapLocation(session.Settings.DefaultLatitude, + session.Settings.DefaultLongitude, + await LocationUtils.GetElevation(session.ElevationService, session.Settings.DefaultLatitude, + session.Settings.DefaultLongitude).ConfigureAwait(false) + ); + + await session.Navigation.Move(defaultLocation, + async () => { await MSniperServiceTask.Execute(session, cancellationToken).ConfigureAwait(false); }, + session, + cancellationToken).ConfigureAwait(false); + + // we have moved this distance, so apply it immediately to the egg walker. + await eggWalker.ApplyDistance(distanceFromStart, cancellationToken).ConfigureAwait(false); + } + checkForMoveBackToDefault = false; + + await CatchNearbyPokemonsTask.Execute(session, cancellationToken).ConfigureAwait(false); + + // initialize the variables in UseNearbyPokestopsTask here, as this is a fresh start. + UseNearbyPokestopsTask.Initialize(); + await UseNearbyPokestopsTask.Execute(session, cancellationToken).ConfigureAwait(false); + } + } +} diff --git a/PoGo.NecroBot.Logic/Tasks/FavoritePokemonTask.cs b/PoGo.NecroBot.Logic/Tasks/FavoritePokemonTask.cs new file mode 100644 index 000000000..31e7344a2 --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/FavoritePokemonTask.cs @@ -0,0 +1,91 @@ +#region using directives + +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.Event.Inventory; +using PoGo.NecroBot.Logic.PoGoUtils; +using PoGo.NecroBot.Logic.State; +using POGOProtos.Networking.Responses; +using PoGo.NecroBot.Logic.Utils; + +#endregion + +namespace PoGo.NecroBot.Logic.Tasks +{ + public class FavoritePokemonTask + { + public static async Task Execute(ISession session, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + //await session.Inventory.RefreshCachedInventory().ConfigureAwait(false); + + if (!session.LogicSettings.AutoFavoritePokemon) return; + + var pokemons = (await session.Inventory.GetPokemons().ConfigureAwait(false)).Where(x=>x.Favorite ==0); + + foreach (var pokemon in pokemons) + { + cancellationToken.ThrowIfCancellationRequested(); + TinyIoC.TinyIoCContainer.Current.Resolve().ThrowIfSwitchAccountRequested(); + var perfection = Math.Round(PokemonInfo.CalculatePokemonPerfection(pokemon)); + + if (session.LogicSettings.FavoriteOperator.BoolFunc( + perfection >= session.LogicSettings.FavoriteMinIvPercentage , + pokemon.Cp >= session.LogicSettings.FavoriteMinCp, + PokemonInfo.GetLevel(pokemon) >= session.LogicSettings.FavoriteMinLevel)) + { + var response = await session.Client.Inventory.SetFavoritePokemon(pokemon.Id, true).ConfigureAwait(false); + if (response.Result == SetFavoritePokemonResponse.Types.Result.Success) + { + session.EventDispatcher.Send(new NoticeEvent + { + Message = + session.Translation.GetTranslation(TranslationString.PokemonFavorite, perfection, + session.Translation.GetPokemonTranslation(pokemon.PokemonId), pokemon.Cp) + }); + } + } + } + } + + public static async Task Execute(ISession session, ulong pokemonId, bool favorite) + { + //using (var blocker = new BlockableScope(session, BotActions.Favorite)) + //{ + //if (!await blocker.WaitToRun().ConfigureAwait(false)) return; + + var pokemon = (await session.Inventory.GetPokemons().ConfigureAwait(false)).FirstOrDefault(p => p.Id == pokemonId); + if (pokemon != null) + { + var perfection = Math.Round(PokemonInfo.CalculatePokemonPerfection(pokemon)); + + var response = await session.Client.Inventory.SetFavoritePokemon(pokemonId, favorite).ConfigureAwait(false); + if (response.Result == SetFavoritePokemonResponse.Types.Result.Success) + { + // Reload pokemon to refresh favorite flag. + pokemon = (await session.Inventory.GetPokemons().ConfigureAwait(false)).FirstOrDefault(p => p.Id == pokemonId); + + session.EventDispatcher.Send(new FavoriteEvent(pokemon, response)); + + string message; + if (favorite) + message = session.Translation.GetTranslation(TranslationString.PokemonFavorite, perfection, + session.Translation.GetPokemonTranslation(pokemon.PokemonId), pokemon.Cp); + else + message = session.Translation.GetTranslation(TranslationString.PokemonUnFavorite, perfection, + session.Translation.GetPokemonTranslation(pokemon.PokemonId), pokemon.Cp); + + session.EventDispatcher.Send(new NoticeEvent + { + Message = message + }); + } + } + //} + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Tasks/GetGymBadgeDetailsTask.cs b/PoGo.NecroBot.Logic/Tasks/GetGymBadgeDetailsTask.cs new file mode 100644 index 000000000..1c67073dc --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/GetGymBadgeDetailsTask.cs @@ -0,0 +1,14 @@ +using PoGo.NecroBot.Logic.State; +using POGOProtos.Map.Fort; +using System.Threading.Tasks; + +namespace PoGo.NecroBot.Logic.Tasks +{ + public class GetGymBadgeDetailsTask + { + public static async Task Execute(ISession session, FortData fort) + { + var response = await session.Client.Fort.GetGymBadgeDetails(fort.Id, fort.Latitude, fort.Longitude).ConfigureAwait(false); + } + } +} diff --git a/PoGo.NecroBot.Logic/Tasks/GetPokeDexCount.cs b/PoGo.NecroBot.Logic/Tasks/GetPokeDexCount.cs new file mode 100644 index 000000000..935f74e71 --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/GetPokeDexCount.cs @@ -0,0 +1,34 @@ +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.Logging; +using PoGo.NecroBot.Logic.State; + +namespace PoGo.NecroBot.Logic.Tasks +{ + public class GetPokeDexCount + { + public static async Task Execute(ISession session, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + //await session.Inventory.RefreshCachedInventory().ConfigureAwait(false); + + var PokeDex = await session.Inventory.GetPokeDexItems().ConfigureAwait(false); + var _totalUniqueEncounters = PokeDex.Select( + i => new + { + Pokemon = i.InventoryItemData.PokedexEntry.PokemonId, + Captures = i.InventoryItemData.PokedexEntry.TimesCaptured + } + ); + var _totalCaptures = _totalUniqueEncounters.Count(i => i.Captures > 0); + var _totalData = PokeDex.Count(); + + Logger.Write(session.Translation + .GetTranslation(TranslationString.AmountPkmSeenCaught, _totalData, 252, _totalCaptures, 252) + ); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Tasks/GymFeedPokemonTask.cs b/PoGo.NecroBot.Logic/Tasks/GymFeedPokemonTask.cs new file mode 100644 index 000000000..b176dcd83 --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/GymFeedPokemonTask.cs @@ -0,0 +1,66 @@ +using PoGo.NecroBot.Logic.Logging; +using PoGo.NecroBot.Logic.State; +using POGOProtos.Data; +using POGOProtos.Inventory.Item; +using POGOProtos.Map.Fort; +using POGOProtos.Networking.Responses; +using System.Threading.Tasks; + +namespace PoGo.NecroBot.Logic.Tasks +{ + public class GymFeedPokemonTask + { + public static async Task Execute(ISession session, FortData gym, ItemData item, PokemonData pokemon, int startingQuantity = 1) + { + var response = await session.Client.Fort.GymFeedPokemon(gym.Id, item.ItemId, pokemon.Id, startingQuantity).ConfigureAwait(false); + switch (response.Result) + { + case GymFeedPokemonResponse.Types.Result.Success: + Logger.Write($"Succes", LogLevel.Info); + break; + case GymFeedPokemonResponse.Types.Result.ErrorCannotUse: + Logger.Write($"Error Cannot Use {item.ItemId}!", LogLevel.Error); + break; + case GymFeedPokemonResponse.Types.Result.ErrorGymBusy: + Logger.Write($"Error Gym Busy!", LogLevel.Error); + break; + case GymFeedPokemonResponse.Types.Result.ErrorGymClosed: + Logger.Write($"Error Gym Closed!", LogLevel.Error); + break; + case GymFeedPokemonResponse.Types.Result.ErrorNoBerriesLeft: + Logger.Write($"Error No Berries Left!", LogLevel.Error); + break; + case GymFeedPokemonResponse.Types.Result.ErrorNotInRange: + Logger.Write($"Error Not In Range!", LogLevel.Error); + break; + case GymFeedPokemonResponse.Types.Result.ErrorPokemonFull: + Logger.Write($"Error Pokemon Full!", LogLevel.Error); + break; + case GymFeedPokemonResponse.Types.Result.ErrorPokemonNotThere: + Logger.Write($"Error Pokemon Not There!", LogLevel.Error); + break; + case GymFeedPokemonResponse.Types.Result.ErrorRaidActive: + Logger.Write($"Error Raid Active!", LogLevel.Error); + break; + case GymFeedPokemonResponse.Types.Result.ErrorTooFast: + Logger.Write($"Error Too Fast!", LogLevel.Error); + break; + case GymFeedPokemonResponse.Types.Result.ErrorTooFrequent: + Logger.Write($"Error Too Frequent!", LogLevel.Error); + break; + case GymFeedPokemonResponse.Types.Result.ErrorWrongCount: + Logger.Write($"Error Wrong Count!", LogLevel.Error); + break; + case GymFeedPokemonResponse.Types.Result.ErrorWrongTeam: + Logger.Write($"Error Wrong Team!", LogLevel.Error); + break; + case GymFeedPokemonResponse.Types.Result.Unset: + Logger.Write($"Unset!", LogLevel.Error); + break; + default: + Logger.Write($"Failed to use {item.ItemId}!", LogLevel.Error); + break; + } + } + } +} diff --git a/PoGo.NecroBot.Logic/Tasks/HumanRandomActionTask.cs b/PoGo.NecroBot.Logic/Tasks/HumanRandomActionTask.cs new file mode 100644 index 000000000..b8eb35a53 --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/HumanRandomActionTask.cs @@ -0,0 +1,85 @@ +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.State; + +namespace PoGo.NecroBot.Logic.Tasks +{ + public class HumanRandomActionTask + { + private static Random ActionRandom = new Random(); + + public static async Task Execute(ISession session, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var randomCommand = Enumerable.Range(1, 8).OrderBy(x => ActionRandom.Next()).Take(8).ToList(); + for (int i = 0; i < 8; i++) + { + cancellationToken.ThrowIfCancellationRequested(); + TinyIoC.TinyIoCContainer.Current.Resolve().ThrowIfSwitchAccountRequested(); + switch (randomCommand[i]) + { + case 1: + // Handling of Eggs and Incubators preferably needs to happen every time. + // While monitoring the previous setting of 50% propability we ended up with + // over an hour of execution without setting any new Eggs for incubation (entering + // UseEggIncubators). + if (session.LogicSettings.UseEggIncubators) + if (ActionRandom.Next(1, 10) > 0) + await UseIncubatorsTask.Execute(session, cancellationToken).ConfigureAwait(false); + break; + case 2: + if (session.LogicSettings.TransferDuplicatePokemon) + if (ActionRandom.Next(1, 10) > 4) + await TransferDuplicatePokemonTask.Execute(session, cancellationToken).ConfigureAwait(false); + if (session.LogicSettings.TransferWeakPokemon) + if (ActionRandom.Next(1, 10) > 4) + await TransferWeakPokemonTask.Execute(session, cancellationToken).ConfigureAwait(false); + if (ActionRandom.Next(1, 10) > 4) + if (EvolvePokemonTask.IsActivated(session)) + await EvolvePokemonTask.Execute(session, cancellationToken).ConfigureAwait(false); + break; + case 3: + if (session.LogicSettings.UseLuckyEggConstantly) + if (ActionRandom.Next(1, 10) > 4) + await UseLuckyEggConstantlyTask.Execute(session, cancellationToken).ConfigureAwait(false); + break; + case 4: + if (session.LogicSettings.UseIncenseConstantly) + if (ActionRandom.Next(1, 10) > 4) + await UseIncenseConstantlyTask.Execute(session, cancellationToken).ConfigureAwait(false); + break; + case 5: + if (session.LogicSettings.RenamePokemon) + if (ActionRandom.Next(1, 10) > 4) + await RenamePokemonTask.Execute(session, cancellationToken).ConfigureAwait(false); + break; + case 6: + if (session.LogicSettings.AutoFavoritePokemon) + if (ActionRandom.Next(1, 10) > 4) + await FavoritePokemonTask.Execute(session, cancellationToken).ConfigureAwait(false); + break; + case 7: + if (ActionRandom.Next(1, 10) > 4) + await RecycleItemsTask.Execute(session, cancellationToken).ConfigureAwait(false); + break; + case 8: + if (session.LogicSettings.AutomaticallyLevelUpPokemon) + if (ActionRandom.Next(1, 10) > 4) + await LevelUpPokemonTask.Execute(session, cancellationToken).ConfigureAwait(false); + break; + } + } + + await GetPokeDexCount.Execute(session, cancellationToken).ConfigureAwait(false); + } + + public static async Task TransferRandom(ISession session, CancellationToken cancellationToken) + { + if (ActionRandom.Next(1, 10) > 4) + await TransferDuplicatePokemonTask.Execute(session, cancellationToken).ConfigureAwait(false); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Tasks/HumanWalkSnipeTask.FastPokemap.cs b/PoGo.NecroBot.Logic/Tasks/HumanWalkSnipeTask.FastPokemap.cs new file mode 100644 index 000000000..2db2de188 --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/HumanWalkSnipeTask.FastPokemap.cs @@ -0,0 +1,267 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using Newtonsoft.Json; +using PoGo.NecroBot.Logic.Logging; +using PoGo.NecroBot.Logic.State; +using POGOProtos.Enums; + +namespace PoGo.NecroBot.Logic.Tasks +{ + public partial class HumanWalkSnipeTask + { + private static string ip; + + private static Task taskDataLive; + + public class FastPokemapItem + { + public class Lnglat + { + public string type { get; set; } + public List coordinates { get; set; } + } + + public string _id { get; set; } + public string pokemon_id { get; set; } + public string encounter_id { get; set; } + public string spawn_id { get; set; } + public DateTime expireAt { get; set; } + public int __v { get; set; } + public Lnglat lnglat { get; set; } + } + + private static string GetIP() + { + string p = @"\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}"; + + if (string.IsNullOrEmpty(ip)) + { + var client = new HttpClient(); + var task = client.GetStringAsync("http://checkip.dyndns.org/"); + task.Wait(); + ip = Regex.Match(task.Result, p).Value; + } + return ip; + } + + public static async Task StartFastPokemapAsync(ISession session, CancellationToken cancellationToken) + { + await Task.Delay(0).ConfigureAwait(false); // Just added to get rid of compiler warning. Remove this if async code is used below. + + return; + /* + double defaultLatitude = session.Settings.DefaultLatitude; + double defaultLongitude = session.Settings.DefaultLongitude; + + while (true) + { + await Task.Delay(30 * 1000).ConfigureAwait(false);//sleep for 30 sec + + Stopwatch sw = new Stopwatch(); + sw.Start(); + var scanOffset = session.LogicSettings.HumanWalkingSnipeSnipingScanOffset; + //scanOffset = 0.065; + var step = scanOffset / 5; + + //Logger.Write($"Execute map scan data Offset: {scanOffset} :))"); + double lat = session.Client.CurrentLatitude; + + double lng = session.Client.CurrentLongitude; + await OffsetScanFPM(scanOffset, step, lat, lng).ConfigureAwait(false); + if(LocationUtils.CalculateDistanceInMeters(session.Client.CurrentLatitude, session.Client.CurrentLongitude, defaultLatitude, defaultLongitude) >1000) + { + await OffsetScanFPM(scanOffset, step, defaultLatitude, defaultLongitude).ConfigureAwait(false); + } + sw.Stop(); + + //Logger.Write($"Map scan finished! Time elapsed {sw.Elapsed.ToString(@"mm\:ss")}"); + } + */ + } + + private static async Task OffsetScanFPM(double scanOffset, double step, double lat, double lng) + { + for (var x = -scanOffset; x <= scanOffset;) + { + for (var y = -scanOffset; y <= scanOffset;) + { + try + { + var scanLat = lat + x; + var scanLng = lng + y; + string scanurl = $"https://cache.fastpokemap.se/?key=2fe7ce70-90b8-460a-bffb-d7f3b4b74cc2&ts=57c9d27c&compute={GetIP()}&lat={scanLat}&lng={scanLng}"; + + var json = await DownloadContent(scanurl).ConfigureAwait(false); + var data = JsonConvert.DeserializeObject>(json); + List chunk = new List(); + foreach (var item in data) + { + var pItem = Map(item); + if (pItem != null && pItem.Id > 0) + { + chunk.Add(pItem); + } + } + // TODO - await is legal here! USE it or use pragma to suppress compilerwarning and write a comment why it is not used + // TODO: Attention - do not touch (add pragma) when you do not know what you are doing ;) + // jjskuld - Ignore CS4014 warning for now. + #pragma warning disable 4014 + PostProcessDataFetched(chunk, false); + #pragma warning restore 4014 + } + catch + { + // TODO Bad practice! Wanna log this? + } + finally + { + y += step; + } + } + x += step; + } + } + + private static void StartAsyncPollingTask(ISession session, CancellationToken cancellationToken) + { + return; + + // TODO unreachable + // jjskuld - Ignore CS0162 warning for now. + #pragma warning disable 0162 + if (!session.LogicSettings.HumanWalkingSnipeUseFastPokemap) return; + + if (taskDataLive != null && !taskDataLive.IsCompleted) return; + taskDataLive = Task.Run(() => + { + while (true) + { + try + { + string key = "d39dbc96-e963-4747-89f6-15b18441b6a5"; + string ts = "6ff886bc"; + //cancellationToken.ThrowIfCancellationRequested(); + string content = ""; + do + { + var lat = _session.Client.CurrentLatitude; + var lng = _session.Client.CurrentLongitude; + var api = $"https://api.fastpokemap.se/?key={key}&ts={ts}&lat={lat}&lng={lng}"; + content = DownloadContent(api).Result; + Task.Delay(2000).Wait(); + } while (content.Contains("error")); + + Task.Delay(1 * 60 * 1000).Wait(); + } + catch + { + } + } + }); + #pragma warning restore 0162 + } + + private static SnipePokemonInfo Map(FastPokemapItem result) + { + return new SnipePokemonInfo() + { + Latitude = result.lnglat.coordinates[1], + Longitude = result.lnglat.coordinates[0], + Id = GetId(result.pokemon_id), + ExpiredTime = result.expireAt.ToLocalTime(), + Source = "Fastpokemap" + }; + } + + public static int GetId(string name) + { + var t = name[0]; + var realName = new StringBuilder(name.ToLower()); + realName[0] = t; + try + { + var p = (PokemonId) Enum.Parse(typeof(PokemonId), realName.ToString()); + return (int) p; + } + + catch (Exception) + { + } + return 0; + } + + private static async Task DownloadContent(string url) + { + var request = new HttpRequestMessage() + { + RequestUri = new Uri(url), + Method = HttpMethod.Get, + }; + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + request.Headers.Add("origin", "https://fastpokemap.se"); + request.Headers.Add("authority", "cache.fastpokemap.se"); + + string result = ""; + + using (HttpClient client = new HttpClient()) + { + try + { + var task = await client.SendAsync(request).ConfigureAwait(false); + result = await task.Content.ReadAsStringAsync().ConfigureAwait(false); + } + catch (Exception) + { + } + } + + return result; + } + + private static async Task> FetchFromFastPokemap(double lat, double lng) + { + return new List(); + + // TODO unreachable + // jjskuld - Ignore CS0162 warning for now. + #pragma warning disable 0162 + List results = new List(); + if (!_setting.HumanWalkingSnipeUseFastPokemap) return results; + + //var startFetchTime = DateTime.Now; + try + { + string key = "20d638c9-c5b3-40cf-bf8b-1c9c52cc2cc2"; //allow-all + string ts = "18503bfe"; + + string url = $"https://cache.fastpokemap.se/?key={key}&ts={ts}&lat={lat}&lng={lng}"; + + var json = await DownloadContent(url).ConfigureAwait(false); + var data = JsonConvert.DeserializeObject>(json); + foreach (var item in data) + { + var pItem = Map(item); + if (pItem != null && pItem.Id > 0) + { + results.Add(pItem); + } + } + } + catch (Exception) + { + Logger.Write("Error loading data fastpokemap", LogLevel.Error, ConsoleColor.DarkRed); + } + + //var endFetchTime = DateTime.Now; + //Logger.Write($"FetchFromFastPokemap spent {(endFetchTime - startFetchTime).TotalSeconds} seconds", LogLevel.Info, ConsoleColor.White); + return results; + #pragma warning restore 0162 + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Tasks/HumanWalkSnipeTask.PokeWatchers.cs b/PoGo.NecroBot.Logic/Tasks/HumanWalkSnipeTask.PokeWatchers.cs new file mode 100644 index 000000000..2a7a671d0 --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/HumanWalkSnipeTask.PokeWatchers.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using Newtonsoft.Json; +using PoGo.NecroBot.Logic.Logging; + +namespace PoGo.NecroBot.Logic.Tasks +{ + public class PokeWatcherItem + { + public double timeend { get; set; } + public string cords { get; set; } + public int pid { get; set; } + public string pokemon { get; set; } + } + + //need refactor this class, move list snipping pokemon to session and split function out to smaller class. + public partial class HumanWalkSnipeTask + { + private static async Task> FetchFromPokeWatcher(double lat, double lng) + { + List results = new List(); + if (!_setting.HumanWalkingSnipeUsePokeWatcher) return results; + + //var startFetchTime = DateTime.Now; + + string url = $"https://pokewatchers.com/grab/"; + + try + { + HttpClient client = new HttpClient(); + + client.DefaultRequestHeaders.Accept.TryParseAdd("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"); + client.DefaultRequestHeaders.AcceptEncoding.ParseAdd("gzip, deflate, sdch, br"); + client.DefaultRequestHeaders.Host = "pokewatchers.com"; + client.DefaultRequestHeaders.UserAgent.TryParseAdd("Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36"); + + var json = await client.GetStringAsync(url).ConfigureAwait(false); + + var list = JsonConvert.DeserializeObject>(json); + results = list.Select(p => Map(p)).ToList(); + } + catch (Exception) + { + Logger.Write("Error loading data from PokeWatcher", LogLevel.Error, ConsoleColor.DarkRed); + } + + //var endFetchTime = DateTime.Now; + //Logger.Write($"FetchFromPokeWatcher spent {(endFetchTime - startFetchTime).TotalSeconds} seconds", LogLevel.Info, ConsoleColor.White); + return results; + } + + public static async Task AddFastPokemapItem(dynamic data) + { + var list = JsonConvert.DeserializeObject>(data.ToString()); + + List result = new List(); + foreach (var item in list) + { + var snipeItem = Map(item); + if (snipeItem != null) + { + result.Add(snipeItem); + } + } + await PostProcessDataFetched(result, false).ConfigureAwait(false); + } + + private static SnipePokemonInfo Map(PokeWatcherItem result) + { + string[] arr = result.cords.Split(','); + return new SnipePokemonInfo() + { + Latitude = Convert.ToDouble(arr[0]), + Longitude = Convert.ToDouble(arr[1]), + Id = result.pid, + ExpiredTime = UnixTimeStampToDateTime(result.timeend), + Source = "Pokewatchers" + }; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Tasks/HumanWalkSnipeTask.PokeZZ.cs b/PoGo.NecroBot.Logic/Tasks/HumanWalkSnipeTask.PokeZZ.cs new file mode 100644 index 000000000..6859eacc3 --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/HumanWalkSnipeTask.PokeZZ.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Logging; +using PoGo.NecroBot.Logic.Utils; +using WebSocket4Net; + +namespace PoGo.NecroBot.Logic.Tasks +{ + public partial class HumanWalkSnipeTask + { + private static async Task> FetchFromPokeZZ(double lat, double lng) + { + List results = new List(); + if (!_setting.HumanWalkingSnipeUsePokeZZ) return results; + + //var startFetchTime = DateTime.Now; + + string url = "ws://pokezz.com/socket.io/?EIO=3&transport=websocket"; + try + { + using (var client = new WebSocket(url, "basic", WebSocketVersion.Rfc6455)) + { + client.MessageReceived += (s, e) => + { + var message = e.Message; + var match = Regex.Match(message, @"^(1?\d+)\[""[a|b]"",""(2?.*)""\]$"); + if (match.Success) + { + if (match.Groups[1].Value == "42") + { + var sniperInfos = Parse(match.Groups[2].Value); + if (sniperInfos != null && sniperInfos.Any()) + { + results.AddRange(sniperInfos + .Where(p => LocationUtils.CalculateDistanceInMeters(lat, lng, p.Latitude, p.Longitude) < 5000) + .ToList()); + } + } + } + }; + client.Open(); + await Task.Delay(1000).ConfigureAwait(false); + client.Close(); + } + } + catch (Exception) + { + Logger.Write("Error loading data from Pokezz", LogLevel.Error, ConsoleColor.DarkRed); + } + + //var endFetchTime = DateTime.Now; + //Logger.Write($"FetchFromPokeZZ spent {(endFetchTime - startFetchTime).TotalSeconds} seconds", LogLevel.Info, ConsoleColor.White); + return results; + } + + private static List Parse(string reader) + { + var lines = reader.Split('~'); + var list = new List(); + + foreach (var line in lines) + { + var sniperInfo = ParseLine(line); + if (sniperInfo != null) + { + list.Add(sniperInfo); + } + } + return list; + } + + private static SnipePokemonInfo ParseLine(string line) + { + var match = Regex.Match(line, + @"(?\d+)\|(?\-?\d+[\,|\.]\d+)\|(?\-?\d+[\,|\.]\d+)\|(?\d+)\|(?[1|0])\|\|"); + if (match.Success) + { + var pokemonId = Convert.ToInt32(match.Groups["id"].Value); + var sniperInfo = new SnipePokemonInfo() + { + Id = pokemonId, + Source = "PokeZZ" + }; + + var lat = Convert.ToDouble(match.Groups["lat"].Value); + var lon = Convert.ToDouble(match.Groups["lon"].Value); + + sniperInfo.Latitude = Math.Round(lat, 7); + sniperInfo.Longitude = Math.Round(lon, 7); + + var expires = Convert.ToInt64(match.Groups["expires"].Value); + if (expires != default(long)) + { + sniperInfo.ExpiredTime = UnixTimeStampToDateTime(expires); + } + return sniperInfo; + } + return null; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Tasks/HumanWalkSnipeTask.Pokecrew.cs b/PoGo.NecroBot.Logic/Tasks/HumanWalkSnipeTask.Pokecrew.cs new file mode 100644 index 000000000..d71bfec66 --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/HumanWalkSnipeTask.Pokecrew.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; +using Newtonsoft.Json; +using PoGo.NecroBot.Logic.Logging; + +namespace PoGo.NecroBot.Logic.Tasks +{ + public partial class HumanWalkSnipeTask + { + public class PokecrewWrap + { + public class PokecrewItem + { + public double latitude { get; set; } + public double longitude { get; set; } + public int pokemon_id { get; set; } + public DateTime expires_at { get; set; } + } + + public List seens { get; set; } + } + + private static SnipePokemonInfo Map(PokecrewWrap.PokecrewItem result) + { + return new SnipePokemonInfo() + { + Latitude = result.latitude, + Longitude = result.longitude, + Id = result.pokemon_id, + ExpiredTime = result.expires_at.ToLocalTime(), + Source = "Pokecrew" + }; + } + + private static async Task> FetchFromPokecrew(double lat, double lng) + { + List results = new List(); + if (!_setting.HumanWalkingSnipeUsePokecrew) return results; + + //var startFetchTime = DateTime.Now; + + try + { + HttpClient client = new HttpClient(); + double offset = _setting.HumanWalkingSnipeSnipingScanOffset; //0.015 + string url = $"https://api.pokecrew.com/api/v1/seens?center_latitude={lat}¢er_longitude={lng}&live=true&minimal=false&northeast_latitude={lat + offset}&northeast_longitude={lng + offset}&pokemon_id=&southwest_latitude={lat - offset}&southwest_longitude={lng - offset}"; + + var task = await client.GetStringAsync(url).ConfigureAwait(false); + + var data = JsonConvert.DeserializeObject(task); + foreach (var item in data.seens) + { + var pItem = Map(item); + if (pItem != null) + { + results.Add(pItem); + } + } + } + catch (Exception) + { + Logger.Write("Error loading data from Pokecrew", LogLevel.Error, ConsoleColor.DarkRed); + } + + //var endFetchTime = DateTime.Now; + //Logger.Write($"FetchFromPokecrew spent {(endFetchTime - startFetchTime).TotalSeconds} seconds", LogLevel.Info, ConsoleColor.White); + return results; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Tasks/HumanWalkSnipeTask.Pokeradar.cs b/PoGo.NecroBot.Logic/Tasks/HumanWalkSnipeTask.Pokeradar.cs new file mode 100644 index 000000000..4d62fb9e9 --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/HumanWalkSnipeTask.Pokeradar.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using Newtonsoft.Json; +using PoGo.NecroBot.Logic.Logging; + +namespace PoGo.NecroBot.Logic.Tasks +{ + public partial class HumanWalkSnipeTask + { + public class PokeradarWrapper + { + public class PokeradarItem + { + public double created { get; set; } + public double latitude { get; set; } + public double longitude { get; set; } + public int pokemonId { get; set; } + } + + //should refactore this model - SnipeInfo + public List data { get; set; } + } + + private static async Task> FetchFromPokeradar(double lat, double lng) + { + List results = new List(); + if (!_setting.HumanWalkingSnipeUsePokeRadar) return results; + + //var startFetchTime = DateTime.Now; + + try + { + HttpClient client = new HttpClient(); + double offset = _setting.HumanWalkingSnipeSnipingScanOffset; //0.015 + string url = $"https://www.pokeradar.io/api/v1/submissions?deviceId=1fd29370661111e6b850a13a2bdc4ebf&minLatitude={lat - offset}&maxLatitude={lat + offset}&minLongitude={lng - offset}&maxLongitude={lng + offset}&pokemonId=0"; + + var task = await client.GetStringAsync(url).ConfigureAwait(false); + + var data = JsonConvert.DeserializeObject(task); + results = data.data.Select(p => Map(p)).ToList(); + } + catch (Exception) + { + Logger.Write("Error loading data from pokeradar", LogLevel.Error, ConsoleColor.DarkRed); + } + + //var endFetchTime = DateTime.Now; + //Logger.Write($"FetchFromPokeradar spent {(endFetchTime - startFetchTime).TotalSeconds} seconds", LogLevel.Info, ConsoleColor.White); + return results; + } + + private static SnipePokemonInfo Map(PokeradarWrapper.PokeradarItem item) + { + DateTime dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); + dtDateTime = dtDateTime.AddSeconds(item.created).ToLocalTime(); + var expiredTime = dtDateTime.AddMinutes(15); + + return new SnipePokemonInfo() + { + Latitude = item.latitude, + Longitude = item.longitude, + Id = item.pokemonId, + ExpiredTime = expiredTime, + Source = "Pokeradar" + }; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Tasks/HumanWalkSnipeTask.Pokesnipers.cs b/PoGo.NecroBot.Logic/Tasks/HumanWalkSnipeTask.Pokesnipers.cs new file mode 100644 index 000000000..2536cbf71 --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/HumanWalkSnipeTask.Pokesnipers.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; +using Newtonsoft.Json; +using PoGo.NecroBot.Logic.Logging; +using POGOProtos.Enums; + +namespace PoGo.NecroBot.Logic.Tasks +{ + public partial class HumanWalkSnipeTask + { + public class PokesniperWrap + { + public class PokesniperItem + { + public string coords { get; set; } + public string name { get; set; } + public DateTime until { get; set; } + } + + public List results { get; set; } + } + + private static SnipePokemonInfo Map(PokesniperWrap.PokesniperItem result) + { + var arr = result.coords.Split(','); + return new SnipePokemonInfo() + { + Latitude = Convert.ToDouble(arr[0]), + Longitude = Convert.ToDouble(arr[1]), + Id = (int) Enum.Parse(typeof(PokemonId), result.name), + ExpiredTime = result.until.ToLocalTime(), + Source = "Pokesnipers" + }; + } + + private static async Task> FetchFromPokesnipers(double lat, double lng) + { + List results = new List(); + if (!_setting.HumanWalkingSnipeUsePokesnipers) return results; + + //var startFetchTime = DateTime.Now; + + try + { + HttpClient client = new HttpClient(); + string url = $"http://pokesnipers.com/api/v1/pokemon.json"; + + var task = await client.GetStringAsync(url).ConfigureAwait(false); + + var data = JsonConvert.DeserializeObject(task); + foreach (var item in data.results) + { + try + { + var pItem = Map(item); + if (pItem != null) + { + results.Add(pItem); + } + } + catch (Exception) + { + //ignore if any data failed. + } + } + } + catch (Exception) + { + Logger.Write("Error loading data from pokesnipers", LogLevel.Error, ConsoleColor.DarkRed); + } + + //var endFetchTime = DateTime.Now; + //Logger.Write($"FetchFromPokesnipers spent {(endFetchTime - startFetchTime).TotalSeconds} seconds", LogLevel.Info, ConsoleColor.White); + return results; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Tasks/HumanWalkSnipeTask.Skiplagged.cs b/PoGo.NecroBot.Logic/Tasks/HumanWalkSnipeTask.Skiplagged.cs new file mode 100644 index 000000000..8cec3790d --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/HumanWalkSnipeTask.Skiplagged.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; +using Newtonsoft.Json; +using PoGo.NecroBot.Logic.Logging; + +namespace PoGo.NecroBot.Logic.Tasks +{ + public class SkiplaggedItem + { + public DateTime UnixTimeStampToDateTime(double unixTimeStamp) + { + // Unix timestamp is seconds past epoch + DateTime dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); + dtDateTime = dtDateTime.AddSeconds(unixTimeStamp).ToLocalTime(); + return dtDateTime; + } + + public DateTime expires_date + { + get { return UnixTimeStampToDateTime(expires); } + } + + public double expires { get; set; } + public double latitude { get; set; } + public double longitude { get; set; } + public int pokemon_id { get; set; } + public string pokemon_name { get; set; } + } + + public class SkiplaggedWrap + { + public double duration { get; set; } + public List pokemons { get; set; } + + public SkiplaggedWrap() + { + pokemons = new List(); + } + } + + //need refactor this class, move list snipping pokemon to session and split function out to smaller class. + public partial class HumanWalkSnipeTask + { + private static async Task> FetchFromSkiplagged(double lat, double lng) + { + List results = new List(); + if (!_setting.HumanWalkingSnipeUseSkiplagged) return results; + + //var startFetchTime = DateTime.Now; + + var lat1 = lat - _setting.HumanWalkingSnipeSnipingScanOffset; + var lat2 = lat + _setting.HumanWalkingSnipeSnipingScanOffset; + var lng1 = lng - _setting.HumanWalkingSnipeSnipingScanOffset; + var lng2 = lng + _setting.HumanWalkingSnipeSnipingScanOffset; + + string url = $"https://skiplagged.com/api/pokemon.php?bounds={lat1},{lng1},{lat2},{lng2}"; + + try + { + HttpClient client = new HttpClient(); + client.DefaultRequestHeaders.Accept.TryParseAdd("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"); + client.DefaultRequestHeaders.AcceptEncoding.ParseAdd("gzip, deflate, sdch, br"); + client.DefaultRequestHeaders.Host = "skiplagged.com"; + client.DefaultRequestHeaders.UserAgent.TryParseAdd("Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36"); + + var json = await client.GetStringAsync(url).ConfigureAwait(false); + + results = GetJsonList(json); + } + catch (Exception) + { + Logger.Write("Error loading data from skiplagged", LogLevel.Error, ConsoleColor.DarkRed); + } + + //var endFetchTime = DateTime.Now; + //Logger.Write($"FetchFromSkiplagged spend {(endFetchTime - startFetchTime).TotalSeconds} seconds", LogLevel.Sniper, ConsoleColor.White); + return results; + } + + private static List GetJsonList(string reader) + { + var wrapper = JsonConvert.DeserializeObject(reader); + var list = new List(); + foreach (var result in wrapper.pokemons) + { + var sniperInfo = Map(result); + if (sniperInfo != null) + { + list.Add(sniperInfo); + } + } + return list; + } + + private static SnipePokemonInfo Map(SkiplaggedItem result) + { + return new SnipePokemonInfo() + { + Latitude = result.latitude, + Longitude = result.longitude, + Id = result.pokemon_id, + ExpiredTime = UnixTimeStampToDateTime(result.expires), + Source = "Skiplagged" + }; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Tasks/HumanWalkSnipeTask.cs b/PoGo.NecroBot.Logic/Tasks/HumanWalkSnipeTask.cs new file mode 100644 index 000000000..3eef28486 --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/HumanWalkSnipeTask.cs @@ -0,0 +1,705 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Newtonsoft.Json; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.Interfaces.Configuration; +using PoGo.NecroBot.Logic.Logging; +using PoGo.NecroBot.Logic.Model; +using PoGo.NecroBot.Logic.Model.Settings; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Utils; +using POGOProtos.Enums; +using POGOProtos.Inventory.Item; +using POGOProtos.Map.Fort; +using POGOProtos.Networking.Responses; +using System.Collections.Concurrent; + +namespace PoGo.NecroBot.Logic.Tasks +{ + //need refactor this class, move list snipping pokemon to session and split function out to smaller class. + public partial class HumanWalkSnipeTask + { + public class SnipePokemonInfo + { + public double Distance { get; set; } + public double EstimatedTime { get; set; } + public bool IsCatching { get; set; } + public double Latitude { get; set; } + public double Longitude { get; set; } + public int Id { get; set; } + public DateTime ExpiredTime { get; set; } + public bool IsFake { get; set; } + public bool IsVisited { get; set; } + public HumanWalkSnipeFilter Setting { get; set; } + + public PokemonId PokemonId + { + get { return (PokemonId)(Id); } + } + + public string UniqueId + { + get { return $"{Id:000}-{Latitude:0.000000}-{Longitude:0.000000}"; } + } + + public string Source { get; set; } + public double IV { get; internal set; } + } + + private static ConcurrentDictionary rarePokemons = new ConcurrentDictionary(); + private static ISession _session; + private static ILogicSettings _setting; + private static int pokestopCount = 0; + private static ConcurrentDictionary pokemonToBeCaughtLocallyIds = new ConcurrentDictionary(); + static bool prioritySnipeFlag = false; + private static DateTime lastUpdated = DateTime.Now.AddMinutes(-10); + + public static async Task AddSnipePokemon(string source, PokemonId id, double latitude, double longitude, + DateTime expirationTimestamp, double iV = 0, ISession session = null) + { + if (session == null) return; + + InitSession(session); + + if (!_session.LogicSettings.EnableHumanWalkingSnipe) + return; + + await PostProcessDataFetched(new List + { + new SnipePokemonInfo() + { + Latitude = latitude, + Longitude = longitude, + Id = (int) id, + ExpiredTime = expirationTimestamp, + Source = source, + IV = iV + } + }, false).ConfigureAwait(false); + } + + public static async Task CheckPokeballsToSnipe(int minPokeballs, ISession session, + CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + // Refresh inventory so that the player stats are fresh + //await session.Inventory.RefreshCachedInventory().ConfigureAwait(false); + var pokeBallsCount = await session.Inventory.GetItemAmountByType(ItemId.ItemPokeBall).ConfigureAwait(false); + pokeBallsCount += await session.Inventory.GetItemAmountByType(ItemId.ItemGreatBall).ConfigureAwait(false); + pokeBallsCount += await session.Inventory.GetItemAmountByType(ItemId.ItemUltraBall).ConfigureAwait(false); + pokeBallsCount += await session.Inventory.GetItemAmountByType(ItemId.ItemMasterBall).ConfigureAwait(false); + + if (pokeBallsCount < minPokeballs) + { + session.EventDispatcher.Send(new HumanWalkSnipeEvent + { + Type = HumanWalkSnipeEventTypes.NotEnoughtPalls, + CurrentBalls = pokeBallsCount, + MinBallsToSnipe = minPokeballs, + }); + return false; + } + return true; + } + + public static async Task ExecuteFetchData(ISession session) + { + InitSession(session); + + await FetchData(_session.Client.CurrentLatitude, _session.Client.CurrentLongitude, true).ConfigureAwait(false); + } + + private static void InitSession(ISession session) + { + _session = session; + _setting = _session.LogicSettings; + pokemonToBeCaughtLocallyIds = new ConcurrentDictionary(); + + if (_setting.HumanWalkingSnipeUseSnipePokemonList) + { + foreach (var pokemonId in _setting.PokemonToCatchLocally.Pokemon) + { + pokemonToBeCaughtLocallyIds[pokemonId] = pokemonId; + } + } + + foreach (var pokemonId in _setting.HumanWalkSnipeFilters + .Where(x => !pokemonToBeCaughtLocallyIds.ContainsKey(x.Key)) + .Select(x => x.Key)) + { + pokemonToBeCaughtLocallyIds[pokemonId] = pokemonId; + } + //this will combine with pokemon snipe filter + } + + public static List ApplyFilter(List> source) + { + return source.Where(p => !p.Value.IsVisited + && !p.Value.IsFake + && p.Value.ExpiredTime > DateTime.Now.AddSeconds(p.Value.EstimatedTime)).Select(p => p.Value) + .ToList(); + } + + public static async Task Execute(ISession session, CancellationToken cancellationToken, + FortData originalPokestop, FortDetailsResponse fortInfo) + { + StartAsyncPollingTask(session, cancellationToken); + + pokestopCount++; + pokestopCount = pokestopCount % 3; + + if (pokestopCount > 0 && !prioritySnipeFlag) return; + + InitSession(session); + if (!_setting.CatchPokemon && !prioritySnipeFlag) return; + + cancellationToken.ThrowIfCancellationRequested(); + + if (_setting.HumanWalkingSnipeTryCatchEmAll) + { + var checkBall = await CheckPokeballsToSnipe(_setting.HumanWalkingSnipeCatchEmAllMinBalls, session, + cancellationToken).ConfigureAwait(false); + if (!checkBall && !prioritySnipeFlag) return; + } + + bool caughtAnyPokemonInThisWalk = false; + SnipePokemonInfo pokemon = null; + do + { + cancellationToken.ThrowIfCancellationRequested(); + TinyIoC.TinyIoCContainer.Current.Resolve().ThrowIfSwitchAccountRequested(); + prioritySnipeFlag = false; + pokemon = await GetNextSnipeablePokemon(session.Client.CurrentLatitude, session.Client.CurrentLongitude, + !caughtAnyPokemonInThisWalk).ConfigureAwait(false); + if (pokemon != null) + { + caughtAnyPokemonInThisWalk = true; + CalculateDistanceAndEstTime(pokemon); + var remainTimes = (pokemon.ExpiredTime - DateTime.Now).TotalSeconds * 0.95; //just use 90% times + + //assume that 100m we catch 1 pokemon and it took 10 second for each. + var catchPokemonTimeEST = + (pokemon.Distance / 100) * 10; + string strPokemon = session.Translation.GetPokemonTranslation(pokemon.PokemonId); + var spinPokestopEST = (pokemon.Distance / 100) * 5; + + bool catchPokemon = (pokemon.EstimatedTime + catchPokemonTimeEST) < remainTimes && + pokemon.Setting.CatchPokemonWhileWalking; + bool spinPokestop = pokemon.Setting.SpinPokestopWhileWalking && + (pokemon.EstimatedTime + catchPokemonTimeEST + spinPokestopEST) < remainTimes; + lock (threadLocker) + { + pokemon.IsCatching = true; + } + session.EventDispatcher.Send(new HumanWalkSnipeEvent() + { + PokemonId = pokemon.PokemonId, + Latitude = pokemon.Latitude, + Longitude = pokemon.Longitude, + Distance = pokemon.Distance, + Expires = (pokemon.ExpiredTime - DateTime.Now).TotalSeconds, + Estimate = (int)pokemon.EstimatedTime, + Setting = pokemon.Setting, + CatchPokemon = catchPokemon, + Pokemons = ApplyFilter(rarePokemons.ToList()), + SpinPokeStop = pokemon.Setting.SpinPokestopWhileWalking, + WalkSpeedApplied = pokemon.Setting.AllowSpeedUp + ? pokemon.Setting.MaxSpeedUpSpeed + : _session.LogicSettings.WalkingSpeedInKilometerPerHour, + Type = HumanWalkSnipeEventTypes.StartWalking, + Rarity = PokemonGradeHelper.GetPokemonGrade(pokemon.PokemonId).ToString() + }); + var snipeTarget = new SnipeLocation(pokemon.Latitude, pokemon.Longitude, + await LocationUtils.GetElevation(session.ElevationService, pokemon.Latitude, pokemon.Longitude).ConfigureAwait(false)); + + await session.Navigation.Move( + snipeTarget, + async () => + { + cancellationToken.ThrowIfCancellationRequested(); + await ActionsWhenTravelToSnipeTarget(session, cancellationToken, pokemon, catchPokemon, spinPokestop).ConfigureAwait(false); + }, + session, + cancellationToken, + pokemon.Setting.AllowSpeedUp ? pokemon.Setting.MaxSpeedUpSpeed : 0 + ).ConfigureAwait(false); + session.EventDispatcher.Send(new HumanWalkSnipeEvent() + { + Latitude = pokemon.Latitude, + Longitude = pokemon.Longitude, + PauseDuration = pokemon.Setting.DelayTimeAtDestination / 1000, + Type = HumanWalkSnipeEventTypes.DestinationReached, + UniqueId = pokemon.UniqueId + }); + + //await Task.Delay(pokemon.Setting.DelayTimeAtDestination, cancellationToken).ConfigureAwait(false); + await CatchNearbyPokemonsTask + .Execute(session, cancellationToken, pokemon.PokemonId, pokemon.Setting.AllowTransferWhileWalking).ConfigureAwait(false); + await Task.Delay(1000, cancellationToken).ConfigureAwait(false); + if (!pokemon.IsVisited) + { + await CatchLurePokemonsTask.Execute(session, cancellationToken).ConfigureAwait(false); + } + + lock (threadLocker) + { + pokemon.IsVisited = true; + pokemon.IsCatching = false; + } + + if (session.LogicSettings.TransferDuplicatePokemon) + await TransferDuplicatePokemonTask.Execute(session, cancellationToken).ConfigureAwait(false); + + if (session.LogicSettings.TransferWeakPokemon) + await TransferWeakPokemonTask.Execute(session, cancellationToken).ConfigureAwait(false); + + if (EvolvePokemonTask.IsActivated(session)) + await EvolvePokemonTask.Execute(session, cancellationToken).ConfigureAwait(false); + } + } while (pokemon != null && _setting.HumanWalkingSnipeTryCatchEmAll); + + if (caughtAnyPokemonInThisWalk && (!_setting.HumanWalkingSnipeAlwaysWalkBack || _setting.UseGpxPathing)) + { + if (session.LogicSettings.UseGpxPathing) + { + await WalkingBackGPXPath(session, cancellationToken, originalPokestop, fortInfo).ConfigureAwait(false); + } + else + await UpdateFarmingPokestop(session, cancellationToken).ConfigureAwait(false); + } + } + + private static async Task WalkingBackGPXPath(ISession session, CancellationToken cancellationToken, + FortData originalPokestop, FortDetailsResponse fortInfo) + { + var destination = new FortLocation( + originalPokestop.Latitude, + originalPokestop.Longitude, + await LocationUtils.GetElevation( + session.ElevationService, + originalPokestop.Latitude, + originalPokestop.Longitude + ).ConfigureAwait(false), + originalPokestop, + fortInfo + ); + await session.Navigation.Move(destination, + async () => + { + await MSniperServiceTask.Execute(session, cancellationToken).ConfigureAwait(false); + await CatchNearbyPokemonsTask.Execute(session, cancellationToken).ConfigureAwait(false); + await UseNearbyPokestopsTask.SpinPokestopNearBy(session, cancellationToken).ConfigureAwait(false); + }, + session, + cancellationToken).ConfigureAwait(false); + } + + private static async Task UpdateFarmingPokestop(ISession session, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var nearestStop = session.VisibleForts.OrderBy(i => + LocationUtils.CalculateDistanceInMeters( + session.Client.CurrentLatitude, + session.Client.CurrentLongitude, + i.Latitude, + i.Longitude + ) + ).FirstOrDefault(); + + if (nearestStop != null) + { + var walkedDistance = LocationUtils.CalculateDistanceInMeters( + nearestStop.Latitude, + nearestStop.Longitude, + session.Client.CurrentLatitude, + session.Client.CurrentLongitude + ); + + if (walkedDistance > session.LogicSettings.HumanWalkingSnipeWalkbackDistanceLimit) + { + await Task.Delay(3000, cancellationToken).ConfigureAwait(false); + var nearbyPokeStops = await UseNearbyPokestopsTask.UpdateFortsData(session).ConfigureAwait(false); + var notexists = nearbyPokeStops + .Where(p => !session.VisibleForts.Exists(x => x.Id == p.Id)) + .ToList(); + session.AddVisibleForts(notexists); + session.EventDispatcher.Send(new PokeStopListEvent(notexists)); + session.EventDispatcher.Send(new HumanWalkSnipeEvent + { + Type = HumanWalkSnipeEventTypes.PokestopUpdated, + Pokestops = notexists, + NearestDistance = walkedDistance + }); + } + } + } + + private static async Task ActionsWhenTravelToSnipeTarget(ISession session, CancellationToken cancellationToken, + SnipePokemonInfo pokemon, bool allowCatchPokemon, bool allowSpinPokeStop) + { + var distance = LocationUtils.CalculateDistanceInMeters( + pokemon.Latitude, + pokemon.Longitude, + session.Client.CurrentLatitude, + session.Client.CurrentLongitude + ); + + if (allowCatchPokemon && distance > 50.0) + { + // Catch normal map Pokemon + await CatchNearbyPokemonsTask.Execute(session, cancellationToken, sessionAllowTransfer: false).ConfigureAwait(false); + } + if (allowSpinPokeStop) + { + //looking for neaby pokestop. spin it + await UseNearbyPokestopsTask.SpinPokestopNearBy(session, cancellationToken, null).ConfigureAwait(false); + } + if (session.LogicSettings.ActivateMSniper) + { + await MSniperServiceTask.Execute(session, cancellationToken).ConfigureAwait(false); + } + } + + static void CalculateDistanceAndEstTime(SnipePokemonInfo p) + { + double speed = p.Setting.AllowSpeedUp ? p.Setting.MaxSpeedUpSpeed : _setting.WalkingSpeedInKilometerPerHour; + var speedInMetersPerSecond = speed / 3.6; + + p.Distance = CalculateDistanceInMeters( + _session.Client.CurrentLatitude, + _session.Client.CurrentLongitude, + p.Latitude, + p.Longitude + ); + p.EstimatedTime = p.Distance / speedInMetersPerSecond + p.Setting.DelayTimeAtDestination / 1000 + + 15; //margin 30 second + } + + private static async Task GetNextSnipeablePokemon(double lat, double lng, bool refreshData = true) + { + if (refreshData) + { + await FetchData(lat, lng).ConfigureAwait(false); + } + //Console.WriteLine("#############GetNextSnipeablePokemon"); + foreach (var k in rarePokemons.Where(p => p.Value.ExpiredTime < DateTime.Now).Select(p => p.Key)) + { + SnipePokemonInfo toRemove; + rarePokemons.TryRemove(k, out toRemove); + } + // Console.WriteLine("#END GetNextSnipeablePokemon"); + foreach (var p in rarePokemons.Select(p => p.Value)) + { + CalculateDistanceAndEstTime(p); + } + + //remove list not reach able (expired) + if (rarePokemons.Count > 0) + { + var ordered = rarePokemons.Where(p => !p.Value.IsVisited && + !p.Value.IsFake && + (p.Value.Setting.Priority == 0 || ( + p.Value.Distance > 10 && + p.Value.Distance < p.Value.Setting.MaxDistance && + p.Value.EstimatedTime < p.Value.Setting.MaxWalkTimes) + && p.Value.ExpiredTime > DateTime.Now.AddSeconds(p.Value.EstimatedTime) + ) + ) + .OrderBy(p => p.Value.Setting.Priority) + .ThenBy(p => p.Value.Distance) + .Select(p => p.Value); + if (ordered != null && ordered.Count() > 0) + { + var first = ordered.First(); + return first; + } + } + return null; + } + + private static async Task FetchData(double lat, double lng, bool silent = false) + { + if (lastUpdated > DateTime.Now.AddSeconds(-30) && !silent) return; + + if (lastUpdated < DateTime.Now.AddSeconds(-30) && silent && rarePokemons != null && rarePokemons.Count > 0) + { + foreach (var p in rarePokemons.Select(p => p.Value)) + { + CalculateDistanceAndEstTime(p); + } + + var ordered = rarePokemons.OrderBy(p => p.Value.Setting.Priority).ThenBy(p => p.Value.Distance); + + _session.EventDispatcher.Send(new HumanWalkSnipeEvent() + { + Type = HumanWalkSnipeEventTypes.ClientRequestUpdate, + Pokemons = ApplyFilter(ordered.ToList()), + }); + } + + List>> allTasks = new List>>() + { + FetchFromPokeradar(lat, lng), + FetchFromSkiplagged(lat, lng), + FetchFromPokecrew(lat, lng), + FetchFromPokesnipers(lat, lng), + FetchFromPokeZZ(lat, lng), + FetchFromFastPokemap(lat, lng), + FetchFromPokeWatcher(lat, lng) + }; + if (_setting.HumanWalkingSnipeIncludeDefaultLocation + && LocationUtils.CalculateDistanceInMeters(lat, lng, _session.Settings.DefaultLatitude, _session.Settings.DefaultLongitude) > 1000) + { + allTasks.Add(FetchFromPokeradar(_session.Settings.DefaultLatitude, _session.Settings.DefaultLongitude)); + allTasks.Add(FetchFromSkiplagged(_session.Settings.DefaultLatitude, _session.Settings.DefaultLongitude)); + allTasks.Add(FetchFromPokecrew(_session.Settings.DefaultLatitude, _session.Settings.DefaultLongitude)); + allTasks.Add(FetchFromPokesnipers(_session.Settings.DefaultLatitude, _session.Settings.DefaultLongitude)); + allTasks.Add(FetchFromPokeZZ(_session.Settings.DefaultLatitude, _session.Settings.DefaultLongitude)); + allTasks.Add(FetchFromFastPokemap(_session.Settings.DefaultLatitude, _session.Settings.DefaultLongitude)); + allTasks.Add(FetchFromPokeWatcher(_session.Settings.DefaultLatitude, _session.Settings.DefaultLongitude)); + } + + Task.WaitAll(allTasks.ToArray()); + lastUpdated = DateTime.Now; + var fetchedPokemons = allTasks.SelectMany(p => p.Result); + + await PostProcessDataFetched(fetchedPokemons, !silent).ConfigureAwait(false); + } + + public static T Clone(object item) + { + if (item != null) + { + string json = JsonConvert.SerializeObject(item); + return JsonConvert.DeserializeObject(json); + } + else + return default(T); + } + + private static object threadLocker = new object(); + + private static async Task PostProcessDataFetched(IEnumerable pokemons, bool displayList = true) + { + // Filter out pokemon with invalid locations. + pokemons = pokemons.Where(p => LocationUtils.IsValidLocation(p.Latitude, p.Longitude)).ToList(); + + var rw = new Random(); + var speedInMetersPerSecond = _setting.WalkingSpeedInKilometerPerHour / 3.6; + int count = 0; + await Task.Run(() => + { + foreach (var item in pokemons) + { + #region ITEM PROCESSING + + //the pokemon data already in the list + if (rarePokemons.Any(x => x.Value.UniqueId == item.UniqueId + || (LocationUtils.CalculateDistanceInMeters(x.Value.Latitude, x.Value.Longitude, item.Latitude, item.Longitude) < 10 && item.Id == x.Value.Id))) + { + continue; + } + //check if pokemon in the snip list + if (!pokemonToBeCaughtLocallyIds.ContainsKey(item.PokemonId)) continue; + + count++; + var snipeSetting = _setting.HumanWalkSnipeFilters.FirstOrDefault(x => x.Key == item.PokemonId); + + HumanWalkSnipeFilter config = new HumanWalkSnipeFilter(_setting.HumanWalkingSnipeMaxDistance, + _setting.HumanWalkingSnipeMaxEstimateTime, + 3, //default priority + _setting.HumanWalkingSnipeTryCatchEmAll, + _setting.HumanWalkingSnipeSpinWhileWalking, + _setting.HumanWalkingSnipeAllowSpeedUp, + _setting.HumanWalkingSnipeMaxSpeedUpSpeed, + _setting.HumanWalkingSnipeDelayTimeAtDestination, + _setting.HumanWalkingSnipeAllowTransferWhileWalking); + + if (_setting.HumanWalkSnipeFilters.Any(x => x.Key == item.PokemonId)) + { + config = _setting.HumanWalkSnipeFilters.First(x => x.Key == item.PokemonId).Value; + } + item.Setting = Clone(config); + + CalculateDistanceAndEstTime(item); + + if (item.Distance < 10000 && item.Distance != 0) //only add if distance <10km + { + rarePokemons[item.GetHashCode()] = item; + } + + #endregion + } + }).ConfigureAwait(false); + + if (count > 0) + { + var orderedRares = rarePokemons.OrderBy(p => p.Value.Setting.Priority).ThenBy(p => p.Value.Distance); + + _session.EventDispatcher.Send(new HumanWalkSnipeEvent() + { + Type = HumanWalkSnipeEventTypes.PokemonScanned, + Pokemons = ApplyFilter(orderedRares.ToList()), + DisplayMessage = displayList + }); + + if (_setting.HumanWalkingSnipeDisplayList) + { + var ordered = rarePokemons.OrderBy(p => p.Value.Setting.Priority).ThenBy(p => p.Value.Distance) + .Where(p => p.Value.ExpiredTime > DateTime.Now.AddSeconds(p.Value.EstimatedTime) && !p.Value.IsVisited) + .ToList(); + + if (ordered.Count > 0 && displayList) + { + Logger.Write( + string.Format( + " Source | Name | Distance | Expires | Travel times | Catchable" + ) + ); + foreach (var pokemon in ordered) + { + string name = _session.Translation.GetPokemonTranslation(pokemon.Value.PokemonId); + name += "".PadLeft(30 - name.Length, ' '); + string source = pokemon.Value.Source; + source += "".PadLeft(30 - source.Length, ' '); + Logger.Write( + string.Format( + " {0} | {1} | {2:0.00}m \t| {3:mm} min {3:ss} sec | {4:00} min {5:00} sec | {6}", + source, + name, + pokemon.Value.Distance, + pokemon.Value.ExpiredTime - DateTime.Now, + pokemon.Value.EstimatedTime / 60, + pokemon.Value.EstimatedTime % 60, + pokemon.Value.ExpiredTime > DateTime.Now.AddSeconds(pokemon.Value.EstimatedTime) + ? "Possible" + : "Missied" + ) + ); + } + } + } + } + } + + public static DateTime UnixTimeStampToDateTime(double unixTimeStamp) + { + // Unix timestamp is seconds past epoch + DateTime dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); + dtDateTime = dtDateTime.AddSeconds(unixTimeStamp).ToLocalTime(); + return dtDateTime; + } + + public static Task PriorityPokemon(ISession session, string id) + { + return Task.Run(() => + { + var pokemonItem = rarePokemons.Where(p => p.Value.UniqueId == id).Select(p => p.Value).FirstOrDefault(); + if (pokemonItem != null) + { + //will be going to catch next check. TODO add code to trigger catch now + pokemonItem.Setting.Priority = 0; + } + }); + } + + public static Task> GetCurrentQueueItems(ISession session) + { + return Task.FromResult(rarePokemons.Select(p => p.Value).ToList()); + } + + public static Task TargetPokemonSnip(ISession session, string id) + { + return Task.Run(() => + { + var ele = rarePokemons.Where(p => p.Value.UniqueId == id).Select(p => p.Value).FirstOrDefault(); + if (ele != null) + { + ele.Setting.Priority = 0; + var ordered = rarePokemons.OrderBy(p => p.Value.Setting.Priority).ThenBy(p => p.Value.Distance).ToList(); + _session.EventDispatcher.Send(new HumanWalkSnipeEvent() + { + Type = HumanWalkSnipeEventTypes.TargetedPokemon, + Pokemons = ApplyFilter(ordered), + }); + } + prioritySnipeFlag = true; + }); + } + + public static double CalculateDistanceInMeters(double sourceLat, double sourceLng, double destinationLat, + double destinationLng) + { + if (LocationUtils.CalculateDistanceInMeters(sourceLat, sourceLng, destinationLat, destinationLng) > 10000) + return 0; + else + return _session.Navigation.WalkStrategy.CalculateDistance(sourceLat, sourceLng, destinationLat, + destinationLng); + } + + public static void UpdateCatchPokemon(double latitude, double longitude, PokemonId id) + { + bool exist = false; + foreach (var p in rarePokemons) + { + if (LocationUtils.CalculateDistanceInMeters(latitude, longitude, p.Value.Latitude, p.Value.Longitude) < 30.0 + && p.Value.PokemonId == id + && !p.Value.IsVisited) + { + p.Value.IsVisited = true; + exist = true; + _session.EventDispatcher.Send(new HumanWalkSnipeEvent() + { + UniqueId = p.Value.UniqueId, + Type = HumanWalkSnipeEventTypes.EncounterSnipePokemon, + PokemonId = id, + Latitude = latitude, + Longitude = longitude, + Pokemons = ApplyFilter(rarePokemons.ToList()), + }); + } + }; + + //in some case, we caught the pokemon before data refresh, we need add a fake pokemon to list to avoid it add back and waste time + if (!exist && pokemonToBeCaughtLocallyIds.ContainsKey(id)) + { + var pokemonInfo = new SnipePokemonInfo() + { + Latitude = latitude, + Longitude = longitude, + Id = (int)id, + IsFake = true, + IsVisited = true, + ExpiredTime = DateTime.Now.AddMinutes(14), + //not being used. just fake to make code valid + Setting = new HumanWalkSnipeFilter(1, 1, 100, false, false, false, 0), + }; + rarePokemons[pokemonInfo.GetHashCode()] = pokemonInfo; + } + } + + public static Task RemovePokemonFromQueue(ISession session, string id) + { + return Task.Run(() => + { + var ele = rarePokemons.Where(p => p.Value.UniqueId == id).Select(p => p.Value).FirstOrDefault(); + if (ele != null) + { + ele.IsVisited = true; //set pokemon to visited, then it won't appear on the list + var ordered = rarePokemons.OrderBy(p => p.Value.Setting.Priority).ThenBy(p => p.Value.Distance); + _session.EventDispatcher.Send(new HumanWalkSnipeEvent() + { + Type = HumanWalkSnipeEventTypes.QueueUpdated, + Pokemons = ApplyFilter(ordered.ToList()), + }); + } + }); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Tasks/InventoryListTask.cs b/PoGo.NecroBot.Logic/Tasks/InventoryListTask.cs new file mode 100644 index 000000000..2bebf16cd --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/InventoryListTask.cs @@ -0,0 +1,27 @@ +using System.Linq; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Utils; + +namespace PoGo.NecroBot.Logic.Tasks +{ + public class InventoryListTask + { + public static async Task Execute(ISession session) + { + // Refresh inventory so that the player stats are fresh + //await session.Inventory.RefreshCachedInventory().ConfigureAwait(false); + + var inventory = await session.Inventory.GetItems().ConfigureAwait(false); + + session.EventDispatcher.Send( + new InventoryListEvent + { + Items = inventory.ToList() + }); + + await DelayingUtils.DelayAsync(session.LogicSettings.DelayBetweenPlayerActions, 0, session.CancellationTokenSource.Token).ConfigureAwait(false); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Tasks/LevelUpPokemonTask.cs b/PoGo.NecroBot.Logic/Tasks/LevelUpPokemonTask.cs new file mode 100644 index 000000000..be2dcdc18 --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/LevelUpPokemonTask.cs @@ -0,0 +1,66 @@ +#region using directives + +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.State; +using POGOProtos.Data; +using POGOProtos.Enums; + +#endregion + +namespace PoGo.NecroBot.Logic.Tasks +{ + internal class LevelUpPokemonTask + { + public static async Task Execute(ISession session, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + //await session.Inventory.RefreshCachedInventory().ConfigureAwait(false); + + if (session.Inventory.GetStarDust() <= session.LogicSettings.GetMinStarDustForLevelUp) + return; + + IEnumerable upgradablePokemon = await session.Inventory.GetPokemonToUpgrade().ConfigureAwait(false); + + if (upgradablePokemon.Count() == 0) + return; + + var upgradedNumber = 0; + List PokemonToLevel = session.LogicSettings.PokemonsToLevelUp.ToList(); + PokemonToLevel.AddRange(session.LogicSettings.PokemonUpgradeFilters.Select(p => p.Key)); + foreach (var pokemon in upgradablePokemon) + { + TinyIoC.TinyIoCContainer.Current.Resolve().ThrowIfSwitchAccountRequested(); + //code seem wrong. need need refactor to cleanup code here. + if (session.LogicSettings.UseLevelUpList && PokemonToLevel != null) + { + for (int i = 0; i < PokemonToLevel.Count; i++) + { + TinyIoC.TinyIoCContainer.Current.Resolve().ThrowIfSwitchAccountRequested(); + //unnessecsarily check, should remove + if (PokemonToLevel.Contains(pokemon.PokemonId)) + { + bool upgradable = await UpgradeSinglePokemonTask.UpgradeSinglePokemon(session, pokemon).ConfigureAwait(false); + if (!upgradable || upgradedNumber >= session.LogicSettings.AmountOfTimesToUpgradeLoop) + break; + await Task.Delay(session.LogicSettings.DelayBetweenPokemonUpgrade).ConfigureAwait(false); + upgradedNumber++; + } + else + { + break; + } + } + } + else + { + await UpgradeSinglePokemonTask.UpgradeSinglePokemon(session, pokemon).ConfigureAwait(false); + await Task.Delay(session.LogicSettings.DelayBetweenPlayerActions).ConfigureAwait(false); + } + } + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Tasks/LevelUpSpecificPokemonTask.cs b/PoGo.NecroBot.Logic/Tasks/LevelUpSpecificPokemonTask.cs new file mode 100644 index 000000000..c61fa5384 --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/LevelUpSpecificPokemonTask.cs @@ -0,0 +1,52 @@ +#region using directives + +using System.Linq; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.Model; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Utils; +using PokemonGo.RocketAPI.Helpers; +using PoGo.NecroBot.Logic.PoGoUtils; + +#endregion + +namespace PoGo.NecroBot.Logic.Tasks +{ + public class LevelUpSpecificPokemonTask + { + //this task is duplicated, may need remove to clean up. + public static async Task Execute(ISession session, ulong pokemonId) + { + using (var blocker = new BlockableScope(session, BotActions.Upgrade)) + { + if (!await blocker.WaitToRun().ConfigureAwait(false)) return; + + var all = await session.Inventory.GetPokemons().ConfigureAwait(false); + var pokemons = all.OrderByDescending(x => x.Cp).ThenBy(n => n.StaminaMax); + var pokemon = pokemons.FirstOrDefault(p => p.Id == pokemonId); + + if (pokemon == null) return; + + var upgradeResult = await session.Inventory.UpgradePokemon(pokemon.Id).ConfigureAwait(false); + + if (upgradeResult.Result.ToString().ToLower().Contains("success")) + { + var stardust = -PokemonCpUtils.GetStardustCostsForPowerup(pokemon.CpMultiplier); //+ pokemon.AdditionalCpMultiplier); + var totalStarDust = session.Inventory.UpdateStarDust(stardust); + + session.EventDispatcher.Send(new PokemonLevelUpEvent + { + Id = upgradeResult.UpgradedPokemon.PokemonId, + Cp = upgradeResult.UpgradedPokemon.Cp, + UniqueId = pokemon.Id, + PSD = stardust, + PCandies = await PokemonInfo.GetCandy(session, pokemon).ConfigureAwait(false), + Lvl = upgradeResult.UpgradedPokemon.Level(), + }); + } + await DelayingUtils.DelayAsync(session.LogicSettings.DelayBetweenPlayerActions, 0, session.CancellationTokenSource.Token).ConfigureAwait(false); + } + } + } +} diff --git a/PoGo.NecroBot.Logic/Tasks/MSniperServiceTask.cs b/PoGo.NecroBot.Logic/Tasks/MSniperServiceTask.cs new file mode 100644 index 000000000..6ba9f6fcd --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/MSniperServiceTask.cs @@ -0,0 +1,1015 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNet.SignalR.Client; +using Newtonsoft.Json; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.Event.Snipe; +using PoGo.NecroBot.Logic.Exceptions; +using PoGo.NecroBot.Logic.Logging; +using PoGo.NecroBot.Logic.Model.Settings; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Utils; +using PokemonGo.RocketAPI.Exceptions; +using POGOProtos.Enums; +using POGOProtos.Map.Pokemon; +using POGOProtos.Networking.Responses; +using System.Runtime.Caching; +using PoGo.NecroBot.Logic.PoGoUtils; +using POGOProtos.Inventory.Item; +using GeoCoordinatePortable; +using PoGo.NecroBot.Logic.Model; + +namespace PoGo.NecroBot.Logic.Tasks +{ + public static class MSniperServiceTask + { + #region Variables + + private const int SNIPE_SAFE_TIME = 180; + public static List LocationQueue = new List(); + public static List VisitedEncounterIds = new List(); + private static List autoSnipePokemons = new List(); + private static List manualSnipePokemons = new List(); + private static List pokedexSnipePokemons = new List(); + private static bool inProgress = false; + private static DateTime OutOffBallBlock = DateTime.MinValue; + public static bool isConnected = false; + public static double minIvPercent = 0.0; //no iv filter + private static string _botIdentiy; +#pragma warning disable 0649 + private static HubConnection _connection; + private static IHubProxy _msniperHub; +#pragma warning restore 0649 +#pragma warning disable 0414 + private static string _msniperServiceUrl = "https://www.msniper.com/signalr"; +#pragma warning restore 0414 + + private static List pokedexList = new List(); + #endregion Variables + + #region signalr msniper service + + public static void ConnectToService() + { + //TODO - remove this line after MSniper.com back to work + return; + /* + while (true) + { + try + { + if (!isConnected) + { + Thread.Sleep(10000); + _connection = new HubConnection(_msniperServiceUrl, useDefaultUrl: false); + X509Certificate2 sertifika = new X509Certificate2(); + sertifika.Import(Properties.Resources.msvc); + _connection.AddClientCertificate(sertifika); + _msniperHub = _connection.CreateHubProxy("msniperHub"); + _msniperHub.On("msvc", p => + { + using (await locker.LockAsync().ConfigureAwait(false)) + { + autoSnipePokemons.Add(p); + } + }); + _connection.Received += Connection_Received; + _connection.Reconnecting += Connection_Reconnecting; + //_connection.Reconnected += Connection_Reconnected; + _connection.Closed += Connection_Closed; + _connection.Start().Wait(); + //Logger.Write("connecting", LogLevel.Service); + _msniperHub.Invoke("Identity"); + isConnected = true; + } + break; + } + catch (CaptchaException cex) + { + throw cex; + } + catch (Exception) + { + //Logger.Write("service: " +e.Message, LogLevel.Error); + Thread.Sleep(500); + } + } + */ + } + + private static void Connection_Closed() + { + //Logger.Write("connection closed, trying to reconnect in 10secs", LogLevel.Service); + ConnectToService(); + } + + private static void Connection_Received(string obj) + { + try + { + HubData xx = _connection.JsonDeserializeObject(obj); + switch (xx.Method) + { + case "sendIdentity": + _botIdentiy = xx.List[0]; + Logger.Write($"(Identity) [ {_botIdentiy} ] connection establisted", LogLevel.Service); + //Console.WriteLine($"[{numb}]now waiting pokemon request (15sec)"); + break; + + case "sendPokemon": + RefreshLocationQueue(); + if (LocationQueue.Count > 0) + { + //Logger.Write($"pokemons are sending.. {LocationQueue.Count} count", LogLevel.Service); + var findingSendables = FindNew(LocationQueue); + AddToVisited(findingSendables); + _msniperHub.Invoke("RecvPokemons", findingSendables); + findingSendables.ForEach(p => { LocationQueue.Remove(p); }); + } + break; + + case "Exceptions": + var defaultc = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Red; + Logger.Write("ERROR: " + xx.List.FirstOrDefault(), LogLevel.Service); + Console.ForegroundColor = defaultc; + break; + } + } + catch (Exception) + { + } + } + + private static void Connection_Reconnecting() + { + isConnected = false; + _connection.Stop(); //removing server cache + ConnectToService(); + //Logger.Write("reconnecting", LogLevel.Service); + } + + //private static void Connection_Reconnected() + //{ + // Logger.Write("reconnected", LogLevel.Service); + //} + + #endregion signalr msniper service + + #region Classes + + public class EncounterInfo : IEvent + { + public string EncounterId { get; set; } + public long Expiration { get; set; } + public double Iv { get; set; } + public string Latitude { get; set; } + public string Longitude { get; set; } + public string Move1 { get; set; } + public string Move2 { get; set; } + public int PokemonId { get; set; } + public string PokemonName { get; set; } + + public string SpawnPointId { get; set; } + //public long LastModifiedTimestampMs { get; set; } + //public int TimeTillHiddenMs { get; set; } + + public ulong GetEncounterId() + { + return Convert.ToUInt64(EncounterId); + } + + public double GetLatitude() + { + return double.Parse(Latitude, CultureInfo.InvariantCulture); + } + + public double GetLongitude() + { + return double.Parse(Longitude, CultureInfo.InvariantCulture); + } + + public PokemonId GetPokemonName() + { + return (PokemonId)PokemonId; + } + } + + private static bool isBlocking = true; //turn it on when account switching, do not add or run snipe + + public static void BlockSnipe() + { + pokedexList = new List(); + isBlocking = true; + } + + public class HubData + { + [JsonProperty("H")] + public string HubName { get; set; } + + [JsonProperty("A")] + public List List { get; set; } + + [JsonProperty("M")] + public string Method { get; set; } + } + + public class MSniperInfo2 + { + public string UniqueIdentifier { get; set; } + public DateTime AddedTime { get; set; } + public ulong EncounterId { get; set; } + public double ExpiredTime { get; set; } + public double Iv { get; set; } + public double Latitude { get; set; } + public double Longitude { get; set; } + public PokemonMove Move1 { get; set; } + public PokemonMove Move2 { get; set; } + public short PokemonId { get; set; } + public string SpawnPointId { get; set; } + public int Priority { get; set; } + public int Level { get; set; } + public bool IsVerified() + { + return EncounterId > 0 && SpawnPointId.IndexOf('-') < 0; + } + } + + #endregion Classes + + #region MSniper Location Feeder + + public static void AddToList(IEvent evt) + { + if (evt is EncounterInfo) + { + var xx = TimeStampToDateTime((evt as EncounterInfo).Expiration); + var ff = DateTime.Now; + if ((ff - xx).TotalMinutes < 1) + { + (evt as EncounterInfo).Expiration += 500000; + LocationQueue.Add(evt as EncounterInfo); + } + else + { + //we need exact expiry time,so here is disabled + } + } + } + + public static void AddToVisited(List encounterIds) + { + encounterIds.ForEach(p => + { + string query = $"{p.EncounterId}-{p.SpawnPointId}"; + if (!VisitedEncounterIds.Contains(query)) + VisitedEncounterIds.Add(query); + }); + } + + public static void UnblockSnipe(bool spinned = true) + { + isBlocking = false; //block release whenever first pokestop looted. + + snipeFailedCount = 0; + waitNextPokestop = spinned; + } + + private static DateTime lastPrintMessageTime = DateTime.Now; + + private static bool CheckSnipeConditions(ISession session) + { + if (session.SaveBallForByPassCatchFlee) return false; + + //if (waitNextPokestop) return false; + if (session.LoggedTime > DateTime.Now.AddMinutes(1)) return false; //only snipe after login 1 min. + + if (snipeFailedCount >= 3) return false; + if (session.Stats.CatchThresholdExceeds(session)) return false; + + if (inProgress || OutOffBallBlock > DateTime.Now) + return false; + + if (!session.LogicSettings.UseSnipeLimit) return true; + + if (lastPrintMessageTime.AddMinutes(1) > DateTime.Now) + { + session.EventDispatcher.Send(new SnipeEvent + { + Message = session.Translation.GetTranslation(TranslationString.SniperCount, session.Stats.SnipeCount) + }); + } + if (session.Stats.LastSnipeTime.AddMilliseconds(session.LogicSettings.MinDelayBetweenSnipes) > DateTime.Now) + return false; + + if (session.Stats.SnipeCount < session.LogicSettings.SnipeCountLimit) + return true; + + if ((DateTime.Now - session.Stats.LastSnipeTime).TotalSeconds > session.LogicSettings.SnipeRestSeconds) + { + session.Stats.SnipeCount = 0; + } + else + { + if (lastPrintMessageTime.AddMinutes(1) > DateTime.Now) + { + lastPrintMessageTime = DateTime.Now; + session.EventDispatcher.Send(new SnipeEvent + { + Message = session.Translation.GetTranslation(TranslationString.SnipeExceeds) + }); + } + return false; + } + return true; + } + + private static MemoryCache expiredCache = new MemoryCache("expired"); + + public static void RemoveExpiredSnipeData(ISession session, string encounterId) + { + lock (expiredCache) + { + expiredCache.Add(encounterId, DateTime.Now, DateTime.Now.AddMinutes(15)); + } + + lock (autoSnipePokemons) + { + var find = autoSnipePokemons.FirstOrDefault(x => x.EncounterId.ToString() == encounterId); + if (find != null) + { + session.EventDispatcher.Send(new SnipePokemonUpdateEvent(encounterId, true, find)); + autoSnipePokemons.RemoveAll(x => x.EncounterId.ToString() == encounterId); + } + } + + lock (manualSnipePokemons) + { + var find = manualSnipePokemons.FirstOrDefault(x => x.EncounterId.ToString() == encounterId); + + if (find != null) + { + session.EventDispatcher.Send(new SnipePokemonUpdateEvent(encounterId, true, find)); + manualSnipePokemons.RemoveAll(x => x.EncounterId.ToString() == encounterId); + } + } + + lock (pokedexSnipePokemons) + { + var find = pokedexSnipePokemons.FirstOrDefault(x => x.EncounterId.ToString() == encounterId); + + if (find != null) + { + session.EventDispatcher.Send(new SnipePokemonUpdateEvent(encounterId, true, find)); + pokedexSnipePokemons.RemoveAll(x => x.EncounterId.ToString() == encounterId); + } + } + } + + private static async Task ActionsWhenTravelToSnipeTarget(ISession session, CancellationToken cancellationToken, + IGeoLocation pokemon, bool allowCatchPokemon, bool allowSpinPokeStop) + { + var distance = LocationUtils.CalculateDistanceInMeters( + pokemon.Latitude, + pokemon.Longitude, + session.Client.CurrentLatitude, + session.Client.CurrentLongitude + ); + + if (allowCatchPokemon && distance > 50.0) + { + // Catch normal map Pokemon + await CatchNearbyPokemonsTask.Execute(session, cancellationToken, sessionAllowTransfer: false).ConfigureAwait(false); + } + if (allowSpinPokeStop) + { + //looking for neaby pokestop. spin it + await UseNearbyPokestopsTask.SpinPokestopNearBy(session, cancellationToken, null).ConfigureAwait(false); + } + } + + public static async Task SnipeUnverifiedPokemon(ISession session, MSniperInfo2 sniperInfo, CancellationToken cancellationToken) + { + var latitude = sniperInfo.Latitude; + var longitude = sniperInfo.Longitude; + + var originalLatitude = session.Client.CurrentLatitude; + var originalLongitude = session.Client.CurrentLongitude; + + var catchedPokemon = false; + + session.EventDispatcher.Send(new SnipeModeEvent { Active = true }); + + MapPokemon catchablePokemon; + int retry = 3; + + bool useWalk = session.LogicSettings.EnableHumanWalkingSnipe; + + try + { + var distance = LocationUtils.CalculateDistanceInMeters(new GeoCoordinate(session.Client.CurrentLatitude, session.Client.CurrentLongitude), new GeoCoordinate(latitude, longitude)); + + if (useWalk) + { + Logger.Write($"Walking to snipe target. Distance: {distance}", LogLevel.Info); + + await session.Navigation.Move( + new MapLocation(latitude, longitude, 0), + async () => + { + cancellationToken.ThrowIfCancellationRequested(); + await ActionsWhenTravelToSnipeTarget(session, cancellationToken, new MapLocation(latitude, longitude, 0), session.LogicSettings.HumanWalkingSnipeCatchPokemonWhileWalking, session.LogicSettings.HumanWalkingSnipeSpinWhileWalking).ConfigureAwait(false); + }, + session, + cancellationToken, + session.LogicSettings.HumanWalkingSnipeAllowSpeedUp ? session.LogicSettings.HumanWalkingSnipeMaxSpeedUpSpeed : 200 + ).ConfigureAwait(false); + } + else + { + Logger.Write($"Jumping to snipe target. Distance: {distance}", LogLevel.Info); + + await LocationUtils.UpdatePlayerLocationWithAltitude(session, new GeoCoordinate(latitude, longitude, 10d), 0).ConfigureAwait(false); // Set speed to 0 for random speed. + + session.EventDispatcher.Send(new UpdatePositionEvent + { + Latitude = latitude, + Longitude = longitude + }); + } + + try + { + do + { + retry--; + + var mapObjects = await session.Client.Map.GetMapObjects(true, false).ConfigureAwait(false); + + catchablePokemon = + mapObjects.MapCells.SelectMany(q => q.CatchablePokemons) + .Where(q => sniperInfo.PokemonId == (short)q.PokemonId) + .OrderByDescending(pokemon => PokemonInfo.CalculateMaxCp(pokemon.PokemonId)) + .FirstOrDefault(); + } while (catchablePokemon == null && retry > 0); + } + catch (HasherException ex) { throw ex; } + catch (CaptchaException ex) + { + throw ex; + } + catch (Exception e) + { + Logger.Write($"Error: {e.Message}", LogLevel.Error); + throw e; + } + + if (catchablePokemon == null) + { + session.EventDispatcher.Send(new SnipeEvent + { + Message = session.Translation.GetTranslation(TranslationString.NoPokemonToSnipe), + }); + + session.EventDispatcher.Send(new SnipeFailedEvent + { + Latitude = latitude, + Longitude = longitude, + PokemonId = (PokemonId)sniperInfo.PokemonId, + EncounterId = sniperInfo.EncounterId + }); + + return false; + } + + if (catchablePokemon != null) + { + EncounterResponse encounter; + try + { + await LocationUtils.UpdatePlayerLocationWithAltitude(session, + new GeoCoordinate(catchablePokemon.Latitude, catchablePokemon.Longitude, session.Client.CurrentAltitude), 0).ConfigureAwait(false); // Set speed to 0 for random speed. + + encounter = + await session.Client.Encounter.EncounterPokemon(catchablePokemon.EncounterId, catchablePokemon.SpawnPointId).ConfigureAwait(false); + } + catch (HasherException ex) { throw ex; } + catch (CaptchaException ex) + { + throw ex; + } + + switch (encounter.Status) + { + case EncounterResponse.Types.Status.EncounterSuccess: + catchedPokemon = await CatchPokemonTask.Execute(session, cancellationToken, encounter, catchablePokemon, + currentFortData: null, sessionAllowTransfer: true).ConfigureAwait(false); + break; + + case EncounterResponse.Types.Status.PokemonInventoryFull: + if (session.LogicSettings.TransferDuplicatePokemon) + { + await TransferDuplicatePokemonTask.Execute(session, cancellationToken).ConfigureAwait(false); + } + else + { + session.EventDispatcher.Send(new WarnEvent + { + Message = session.Translation.GetTranslation(TranslationString.InvFullTransferManually) + }); + } + return false; + + default: + session.EventDispatcher.Send(new WarnEvent + { + Message = + session.Translation.GetTranslation( + TranslationString.EncounterProblem, encounter.Status) + }); + break; + } + + await Task.Delay(session.LogicSettings.DelayBetweenPokemonCatch, cancellationToken).ConfigureAwait(false); + } + + if (catchedPokemon) + { + session.Stats.SnipeCount++; + } + session.EventDispatcher.Send(new SnipeModeEvent { Active = false }); + return true; + } + finally + { + if (useWalk) + { + Logger.Write($"Walking back to original location.", LogLevel.Info); + + await session.Navigation.Move( + new MapLocation(originalLatitude, originalLongitude, 0), + async () => + { + cancellationToken.ThrowIfCancellationRequested(); + await ActionsWhenTravelToSnipeTarget(session, cancellationToken, new MapLocation(latitude, longitude, 0), session.LogicSettings.HumanWalkingSnipeCatchPokemonWhileWalking, session.LogicSettings.HumanWalkingSnipeSpinWhileWalking).ConfigureAwait(false); + }, + session, + cancellationToken, + session.LogicSettings.HumanWalkingSnipeAllowSpeedUp ? session.LogicSettings.HumanWalkingSnipeMaxSpeedUpSpeed : 200 + ).ConfigureAwait(false); + } + else + { + Logger.Write($"Jumping back to original location.", LogLevel.Info); + + await LocationUtils.UpdatePlayerLocationWithAltitude(session, new GeoCoordinate(originalLatitude, originalLongitude), 0).ConfigureAwait(false); // Set speed to 0 for random speed. + + session.EventDispatcher.Send(new UpdatePositionEvent + { + Latitude = originalLatitude, + Longitude = originalLongitude + }); + + await session.Client.Map.GetMapObjects(true).ConfigureAwait(false); + } + } + } + + // CatchFromService no longer works. + /* + public static async Task CatchFromService(ISession session, + CancellationToken cancellationToken, MSniperInfo2 encounterId) + { + cancellationToken.ThrowIfCancellationRequested(); + double originalLat = session.Client.CurrentLatitude; + double originalLng = session.Client.CurrentLongitude; + + EncounterResponse encounter; + try + { + // Speed set to 0 for random speed. + await LocationUtils.UpdatePlayerLocationWithAltitude( + session, + new GeoCoordinate(encounterId.Latitude, encounterId.Longitude, session.Client.CurrentAltitude), + 0 + ).ConfigureAwait(false); + + + await session.Client.Misc.RandomAPICall().ConfigureAwait(false); + + encounter = await session.Client.Encounter.EncounterPokemon(encounterId.EncounterId, encounterId.SpawnPointId).ConfigureAwait(false); + + if (encounter != null && encounter.Status != EncounterResponse.Types.Status.EncounterSuccess) + { + Logger.Debug($"{encounter}"); + } + //pokemon has expired, send event to remove it. + if (encounter != null && (encounter.Status == EncounterResponse.Types.Status.EncounterClosed || + encounter.Status == EncounterResponse.Types.Status.EncounterNotFound)) + { + session.EventDispatcher.Send(new SnipePokemonUpdateEvent(encounterId.EncounterId.ToString(), false, null)); + } + } + catch (CaptchaException ex) + { + throw ex; + } + catch (Exception) + { + return false; + } + finally + { + session.Client.Player.SetCoordinates(originalLat, originalLng, session.Client.CurrentAltitude); //only reset d + } + + if (encounter.Status == EncounterResponse.Types.Status.PokemonInventoryFull) + { + Logger.Write("Pokemon bag full, snipe cancel"); + await TransferDuplicatePokemonTask.Execute(session, cancellationToken).ConfigureAwait(false); + return false; + } + + if (encounter.Status == EncounterResponse.Types.Status.EncounterClosed) + { + Logger.Write("This pokemon has been expired"); + return true; + } + PokemonData encounteredPokemon; + + // Catch if it's a WildPokemon (MSniping not allowed for Incense pokemons) + if (encounter?.Status == EncounterResponse.Types.Status.EncounterSuccess) + { + encounteredPokemon = encounter.WildPokemon?.PokemonData; + } + else + { + Logger.Write($"Pokemon despawned or wrong link format!", LogLevel.Service, ConsoleColor.Gray); + return false; + //return await CatchWithSnipe(session, encounterId, cancellationToken).ConfigureAwait(false);// No success to work with + } + + var pokemon = new MapPokemon + { + EncounterId = encounterId.EncounterId, + Latitude = encounterId.Latitude, + Longitude = encounterId.Longitude, + PokemonId = encounteredPokemon.PokemonId, + SpawnPointId = encounterId.SpawnPointId + }; + + return await CatchPokemonTask.Execute( + session, cancellationToken, encounter, pokemon, currentFortData: null, sessionAllowTransfer: true + ).ConfigureAwait(false); + } + */ + + public static List FindNew(List received) + { + List newOne = new List(); + received.ForEach(p => + { + if (!VisitedEncounterIds.Contains($"{p.EncounterId}-{p.SpawnPointId}")) + newOne.Add(p); + }); + return newOne; + } + + public static DateTime TimeStampToDateTime(double timeStamp) + { + // Java timestamp is millisecods past epoch + var dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); + dtDateTime = dtDateTime.AddSeconds(Math.Round(timeStamp / 1000)).ToLocalTime(); + return dtDateTime; + } + + private static void RefreshLocationQueue() + { + var pkmns = LocationQueue + .Where(p => TimeStampToDateTime(p.Expiration) > DateTime.Now) + .ToList(); + LocationQueue.Clear(); + LocationQueue.AddRange(pkmns); + } + + #endregion MSniper Location Feeder + + private static AsyncLock locker = new AsyncLock(); + + public static async Task AddSnipeItem(ISession session, MSniperInfo2 item, bool byPassValidation = false) + { + if (isBlocking) return false; + //this pokemon has been recorded as expires + if (item.EncounterId > 0 && expiredCache.Get(item.EncounterId.ToString()) != null) return false; + + //fake & annoy data + if (Math.Abs(item.Latitude) > 90 || Math.Abs(item.Longitude) > 180 || item.Iv > 100) return false; + + using (await locker.LockAsync().ConfigureAwait(false)) + { + Func checkExisting = (MSniperInfo2 x) => + { + return (x.EncounterId > 0 && x.EncounterId == item.EncounterId) || + (x.EncounterId == 0 && Math.Round(x.Latitude, 6) == Math.Round(item.Latitude, 6) + && Math.Round(x.Longitude, 6) == Math.Round(item.Longitude, 6) + && x.PokemonId == item.PokemonId); + }; + + //remove existing item that + autoSnipePokemons.RemoveAll(x => checkExisting(x)); + pokedexSnipePokemons.RemoveAll(x => checkExisting(x)); + manualSnipePokemons.RemoveAll(x => checkExisting(x)); + } + + if (!byPassValidation && + session.LogicSettings.AutoSnipeMaxDistance > 0 && + LocationUtils.CalculateDistanceInMeters(session.Settings.DefaultLatitude, session.Settings.DefaultLongitude, item.Latitude, item.Longitude) > session.LogicSettings.AutoSnipeMaxDistance * 1000) return false; + + using (await locker.LockAsync().ConfigureAwait(false)) + { + item.AddedTime = DateTime.Now; + //just keep pokemon in last 2 min + autoSnipePokemons.RemoveAll(x => x.AddedTime.AddSeconds(SNIPE_SAFE_TIME) < DateTime.Now); + pokedexSnipePokemons.RemoveAll(x => x.AddedTime.AddMinutes(SNIPE_SAFE_TIME) < DateTime.Now); + } + if (OutOffBallBlock > DateTime.Now || + autoSnipePokemons.Exists(x => x.EncounterId == item.EncounterId && item.EncounterId > 0) || + (item.EncounterId > 0 && session.Cache[CatchPokemonTask.GetEncounterCacheKey(item.EncounterId)] != null)) return false; + + item.Iv = Math.Round(item.Iv, 2); + if (session.LogicSettings.SnipePokemonNotInPokedex) + { + //sometime the API return pokedex not correct, we need cahe this list, need lean everyetime peopellogi + var pokedex = (await session.Inventory.GetPokeDexItems().ConfigureAwait(false)).Select(x => x.InventoryItemData?.PokedexEntry?.PokemonId).Where(x => x != null).ToList(); + var update = pokedex.Where(x => !pokedexList.Contains(x.Value)).ToList(); + + pokedexList.AddRange(update.Select(x => x.Value)); + + //Logger.Debug($"Pokedex Entry : {pokedexList.Count()}"); + + if (pokedexList.Count > 0 && + !pokedexList.Exists(x => x == (PokemonId)item.PokemonId) && + !pokedexSnipePokemons.Exists(p => p.PokemonId == item.PokemonId) && + (!session.LogicSettings.AutosnipeVerifiedOnly || + (session.LogicSettings.AutosnipeVerifiedOnly && item.IsVerified()))) + { + session.EventDispatcher.Send(new WarnEvent() + { + Message = session.Translation.GetTranslation(TranslationString.SnipePokemonNotInPokedex, + session.Translation.GetPokemonTranslation((PokemonId)item.PokemonId)) + }); + item.Priority = 0; + pokedexSnipePokemons.Add(item); //Add as hight priority snipe entry + return true; + } + } + var pokemonId = (PokemonId)item.PokemonId; + SnipeFilter filter = session.LogicSettings.PokemonSnipeFilters.GetFilter(pokemonId); + + using (await locker.LockAsync().ConfigureAwait(false)) + { + if (byPassValidation) + { + item.Priority = -1; + manualSnipePokemons.Add(item); + + Logger.Write($"(MANUAL SNIPER) Pokemon added | {(PokemonId)item.PokemonId} [{item.Latitude},{item.Longitude}] IV {item.Iv}%"); + return true; + } + + item.Priority = filter.Priority; + + if (filter.VerifiedOnly && item.EncounterId == 0) return false; + + //check candy + int candy = await session.Inventory.GetCandyCount(pokemonId).ConfigureAwait(false); + if (candy < filter.AutoSnipeCandy) + { + autoSnipePokemons.Add(item); + return true; + } + + if (filter.IsMatch(item.Iv, item.Move1, item.Move2, item.Level, item.EncounterId > 0)) + { + autoSnipePokemons.Add(item); + return true; + } + } + return false; + } + + public static async Task CatchWithSnipe(ISession session, MSniperInfo2 sniperInfo, + CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + TinyIoC.TinyIoCContainer.Current.Resolve().ThrowIfSwitchAccountRequested(); + return await SnipeUnverifiedPokemon(session, sniperInfo, cancellationToken).ConfigureAwait(false); + } + + private static int snipeFailedCount = 0; + private static bool waitNextPokestop = true; + + public static async Task CheckPokeballsToSnipe(int minPokeballs, ISession session, + CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + TinyIoC.TinyIoCContainer.Current.Resolve().ThrowIfSwitchAccountRequested(); + + var pokeBallsCount = await session.Inventory.GetItemAmountByType(ItemId.ItemPokeBall).ConfigureAwait(false); + pokeBallsCount += await session.Inventory.GetItemAmountByType(ItemId.ItemGreatBall).ConfigureAwait(false); + pokeBallsCount += await session.Inventory.GetItemAmountByType(ItemId.ItemUltraBall).ConfigureAwait(false); + pokeBallsCount += await session.Inventory.GetItemAmountByType(ItemId.ItemMasterBall).ConfigureAwait(false); + + if (pokeBallsCount >= minPokeballs) + return true; + + session.EventDispatcher.Send(new SnipeEvent + { + Message = + session.Translation.GetTranslation(TranslationString.NotEnoughPokeballsToSnipe, pokeBallsCount, + minPokeballs) + }); + + return false; + } + + public static async Task Execute(ISession session, CancellationToken cancellationToken) + { + if (!CheckSnipeConditions(session)) return; + + inProgress = true; + double originalLatitude = session.Client.CurrentLatitude; + double originalLongitude = session.Client.CurrentLongitude; + session.KnownLatitudeBeforeSnipe = originalLatitude; + session.KnownLongitudeBeforeSnipe = originalLongitude; + + //Logger.Write($"DEBUG : Location before snipe : {originalLatitude},{originalLongitude}"); + + var pth = Path.Combine(Directory.GetCurrentDirectory(), "SnipeMS.json"); + try + { + if (OutOffBallBlock > DateTime.Now || ( + File.Exists(pth) && autoSnipePokemons.Count == 0 && manualSnipePokemons.Count == 0 && + pokedexSnipePokemons.Count == 0)) + { + return; + } + + if (autoSnipePokemons.Count > 0 && !(await CheckPokeballsToSnipe( + session.LogicSettings.MinPokeballsToSnipe + 1, session, cancellationToken).ConfigureAwait(false))) + { + session.EventDispatcher.Send(new WarnEvent() + { + Message = session.Translation.GetTranslation(TranslationString.AutoSnipeDisabled, + session.LogicSettings.SnipePauseOnOutOfBallTime) + }); + + OutOffBallBlock = DateTime.Now.AddMinutes(session.LogicSettings.SnipePauseOnOutOfBallTime); + return; + } + List mSniperLocation2 = new List(); + if (File.Exists(pth)) + { + var sr = new StreamReader(pth, Encoding.UTF8); + var jsn = sr.ReadToEnd(); + sr.Close(); + + mSniperLocation2 = JsonConvert.DeserializeObject>(jsn); + File.Delete(pth); + if (mSniperLocation2 == null) mSniperLocation2 = new List(); + } + using (await locker.LockAsync().ConfigureAwait(false)) + { + if (pokedexSnipePokemons.Count > 0) + { + mSniperLocation2.Add(pokedexSnipePokemons.OrderByDescending(x => x.PokemonId).FirstOrDefault()); + pokedexSnipePokemons.Clear(); + } + if (manualSnipePokemons.Count > 0) + { + mSniperLocation2.AddRange(manualSnipePokemons); + manualSnipePokemons.Clear(); + } + else + { + autoSnipePokemons.RemoveAll(x => x.AddedTime.AddSeconds(SNIPE_SAFE_TIME) < DateTime.Now); + // || ( x.ExpiredTime >0 && x.ExpiredTime < DateTime.Now.ToUnixTime())); + autoSnipePokemons.OrderBy(x => x.Priority) + .ThenByDescending(x => PokemonGradeHelper.GetPokemonGrade((PokemonId)x.PokemonId)) + .ThenByDescending(x => x.Iv) + .ThenByDescending(x => x.PokemonId) + .ThenByDescending(x => x.AddedTime); + + var batch = autoSnipePokemons.Take(session.LogicSettings.AutoSnipeBatchSize); + if (batch != null && batch.Count() > 0) + { + mSniperLocation2.AddRange(batch); + autoSnipePokemons.RemoveAll(x => batch.Contains(x)); + } + } + } + foreach (var location in mSniperLocation2) + { + if (session.Stats.CatchThresholdExceeds(session) || isBlocking) break; + using (await locker.LockAsync().ConfigureAwait(false)) + { + if (location.EncounterId > 0 && expiredCache.Get(location.EncounterId.ToString()) != null) continue; + + if (pokedexSnipePokemons.Count > 0 || manualSnipePokemons.Count > 0) + { + break; + //should return item back to snipe list + } + } + session.EventDispatcher.Send(new SnipePokemonStarted(location)); + + if (location.EncounterId > 0 && session.Cache[CatchPokemonTask.GetEncounterCacheKey(location.EncounterId)] != null) continue; + + if (!(await CheckPokeballsToSnipe(session.LogicSettings.MinPokeballsWhileSnipe + 1, session, cancellationToken).ConfigureAwait(false))) + { + session.EventDispatcher.Send(new WarnEvent() + { + Message = session.Translation.GetTranslation(TranslationString.AutoSnipeDisabled) + }); + + OutOffBallBlock = DateTime.Now.AddMinutes(session.LogicSettings.SnipePauseOnOutOfBallTime); + break; + } + + if (location.AddedTime.AddSeconds(SNIPE_SAFE_TIME) < DateTime.Now) continue; + + //If bot already catch the same pokemon, and very close this location. + if (session.Cache.Get(CatchPokemonTask.GetUsernameGeoLocationCacheKey(session.Settings.Username, (PokemonId)location.PokemonId, location.Latitude, location.Longitude)) != null) continue; + + session.Cache.Add(CatchPokemonTask.GetEncounterCacheKey(location.EncounterId), true, DateTime.Now.AddMinutes(15)); + + cancellationToken.ThrowIfCancellationRequested(); + TinyIoC.TinyIoCContainer.Current.Resolve().ThrowIfSwitchAccountRequested(); + session.EventDispatcher.Send(new SnipeScanEvent + { + Bounds = new Location(location.Latitude, location.Longitude), + PokemonId = (PokemonId)location.PokemonId, + Source = "InternalSnipe", + Iv = location.Iv + }); + + session.Stats.IsSnipping = true; + var result = await CatchWithSnipe(session, location, cancellationToken).ConfigureAwait(false); + + if (result) + { + snipeFailedCount = 0; + } + else + { + snipeFailedCount++; + if (snipeFailedCount >= 3) break; //maybe softban, stop snipe wait until verify it not been + } + //await Task.Delay(1000, cancellationToken).ConfigureAwait(false); + session.Stats.LastSnipeTime = DateTime.Now; + session.Stats.SnipeCount++; + waitNextPokestop = true; + } + } + catch (ActiveSwitchByPokemonException ex) { throw ex; } + catch (ActiveSwitchAccountManualException ex) + { + throw ex; + } + catch (ActiveSwitchByRuleException ex) + { + throw ex; + } + catch (CaptchaException cex) + { + throw cex; + } + catch (Exception ex) + { + if (ex.InnerException != null && ex.InnerException is CaptchaException) throw ex.InnerException; + + File.Delete(pth); + var ee = new ErrorEvent { Message = ex.Message }; + if (ex.InnerException != null) ee.Message = ex.InnerException.Message; + session.EventDispatcher.Send(ee); + } + finally + { + inProgress = false; + session.Stats.IsSnipping = false; + //Logger.Write($"DEBUG : Back to home location: {originalLatitude},{originalLongitude}"); + + await LocationUtils.UpdatePlayerLocationWithAltitude(session,new GeoCoordinate(originalLatitude, originalLongitude),0).ConfigureAwait(false); + + } + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Tasks/PokemonListTask.cs b/PoGo.NecroBot.Logic/Tasks/PokemonListTask.cs new file mode 100644 index 000000000..454987eff --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/PokemonListTask.cs @@ -0,0 +1,49 @@ +#region using directives + +using System; +using System.Linq; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.PoGoUtils; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Utils; + +#endregion + +namespace PoGo.NecroBot.Logic.Tasks +{ + public class PokemonListTask + { + public static async Task Execute(ISession session) + { + // Refresh inventory so that the player stats are fresh + //await session.Inventory.RefreshCachedInventory().ConfigureAwait(false); + + var myPokemonSettings = await session.Inventory.GetPokemonSettings().ConfigureAwait(false); + var pokemonSettings = myPokemonSettings.ToList(); + + var myPokemonFamilies = await session.Inventory.GetPokemonFamilies().ConfigureAwait(false); + var pokemonFamilies = myPokemonFamilies.ToArray(); + + var allPokemonInBag = await session.Inventory.GetHighestsCp(1000).ConfigureAwait(false); + + var pkmWithIv = allPokemonInBag.Select(p => + { + var settings = pokemonSettings.Single(x => x.PokemonId == p.PokemonId); + return Tuple.Create( + p, + PokemonInfo.CalculatePokemonPerfection(p), + pokemonFamilies.Single(x => settings.FamilyId == x.FamilyId).Candy_ + ); + }); + + session.EventDispatcher.Send( + new PokemonListEvent + { + PokemonList = pkmWithIv.ToList() + }); + + await DelayingUtils.DelayAsync(session.LogicSettings.DelayBetweenPlayerActions, 0, session.CancellationTokenSource.Token).ConfigureAwait(false); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Tasks/RecycleItemsTask.cs b/PoGo.NecroBot.Logic/Tasks/RecycleItemsTask.cs new file mode 100644 index 000000000..1e8d93d01 --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/RecycleItemsTask.cs @@ -0,0 +1,456 @@ +#region using directives + +using System; +using System.Threading; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.Logging; +using PoGo.NecroBot.Logic.Model; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Utils; +using POGOProtos.Inventory.Item; + +#endregion + +namespace PoGo.NecroBot.Logic.Tasks +{ + public class RecycleItemsTask + { + private static int _diff; + private static Random rnd = new Random(); + + public static async Task Execute(ISession session, CancellationToken cancellationToken) + { + + cancellationToken.ThrowIfCancellationRequested(); + TinyIoC.TinyIoCContainer.Current.Resolve().ThrowIfSwitchAccountRequested(); + + var currentTotalItems = await session.Inventory.GetTotalItemCount().ConfigureAwait(false); + if ((session.Profile.PlayerData.MaxItemStorage * session.LogicSettings.RecycleInventoryAtUsagePercentage / 100.0f) > currentTotalItems) + return; + + var currentAmountOfPokeballs = await session.Inventory.GetItemAmountByType(ItemId.ItemPokeBall).ConfigureAwait(false); + var currentAmountOfGreatballs = await session.Inventory.GetItemAmountByType(ItemId.ItemGreatBall).ConfigureAwait(false); + var currentAmountOfUltraballs = await session.Inventory.GetItemAmountByType(ItemId.ItemUltraBall).ConfigureAwait(false); + var currentAmountOfMasterballs = await session.Inventory.GetItemAmountByType(ItemId.ItemMasterBall).ConfigureAwait(false); + + if (session.LogicSettings.DetailedCountsBeforeRecycling) + Logger.Write(session.Translation.GetTranslation(TranslationString.CurrentPokeballInv, + currentAmountOfPokeballs.ToString("0").PadLeft(3, ' '), currentAmountOfGreatballs.ToString("0").PadLeft(3, ' '), currentAmountOfUltraballs.ToString("0").PadLeft(3, ' '), + currentAmountOfMasterballs.ToString("0").PadLeft(3, ' '))); + + var currentPotions = await session.Inventory.GetItemAmountByType(ItemId.ItemPotion).ConfigureAwait(false); + var currentSuperPotions = await session.Inventory.GetItemAmountByType(ItemId.ItemSuperPotion).ConfigureAwait(false); + var currentHyperPotions = await session.Inventory.GetItemAmountByType(ItemId.ItemHyperPotion).ConfigureAwait(false); + var currentMaxPotions = await session.Inventory.GetItemAmountByType(ItemId.ItemMaxPotion).ConfigureAwait(false); + + var currentAmountOfPotions = currentPotions + currentSuperPotions + currentHyperPotions + currentMaxPotions; + + if (session.LogicSettings.DetailedCountsBeforeRecycling) + Logger.Write(session.Translation.GetTranslation(TranslationString.CurrentPotionInv, + currentPotions.ToString("0").PadLeft(3, ' '), currentSuperPotions.ToString("0").PadLeft(3, ' '), currentHyperPotions.ToString("0").PadLeft(3, ' '), currentMaxPotions.ToString("0").PadLeft(3, ' '))); + + var currentRevives = await session.Inventory.GetItemAmountByType(ItemId.ItemRevive).ConfigureAwait(false); + var currentMaxRevives = await session.Inventory.GetItemAmountByType(ItemId.ItemMaxRevive).ConfigureAwait(false); + + var currentAmountOfRevives = currentRevives + currentMaxRevives; + + var currentAmountOfEvoItems = await session.Inventory.GetItemAmountByType(ItemId.ItemUpGrade).ConfigureAwait(false) + + await session.Inventory.GetItemAmountByType(ItemId.ItemDragonScale).ConfigureAwait(false) + + await session.Inventory.GetItemAmountByType(ItemId.ItemKingsRock).ConfigureAwait(false) + + await session.Inventory.GetItemAmountByType(ItemId.ItemMetalCoat).ConfigureAwait(false) + + await session.Inventory.GetItemAmountByType(ItemId.ItemSunStone).ConfigureAwait(false); + + var currentAmountOfRAIDPasses = await session.Inventory.GetItemAmountByType(ItemId.ItemFreeRaidTicket).ConfigureAwait(false) + + await session.Inventory.GetItemAmountByType(ItemId.ItemPaidRaidTicket).ConfigureAwait(false); + + if (session.LogicSettings.DetailedCountsBeforeRecycling) + Logger.Write(session.Translation.GetTranslation(TranslationString.CurrentReviveInv, + currentRevives.ToString("0").PadLeft(3, ' '), currentMaxRevives.ToString("0").PadLeft(3, ' '), currentAmountOfEvoItems.ToString("0").PadLeft(3, ' '), currentAmountOfRAIDPasses.ToString("0").PadLeft(3, ' '))); + + var currentAmountOfBerries = await session.Inventory.GetItemAmountByType(ItemId.ItemRazzBerry).ConfigureAwait(false) + + await session.Inventory.GetItemAmountByType(ItemId.ItemBlukBerry).ConfigureAwait(false) + + await session.Inventory.GetItemAmountByType(ItemId.ItemNanabBerry).ConfigureAwait(false) + + await session.Inventory.GetItemAmountByType(ItemId.ItemWeparBerry).ConfigureAwait(false) + + await session.Inventory.GetItemAmountByType(ItemId.ItemPinapBerry).ConfigureAwait(false) + + await session.Inventory.GetItemAmountByType(ItemId.ItemGoldenPinapBerry).ConfigureAwait(false) + + await session.Inventory.GetItemAmountByType(ItemId.ItemGoldenRazzBerry).ConfigureAwait(false) + + await session.Inventory.GetItemAmountByType(ItemId.ItemGoldenNanabBerry).ConfigureAwait(false); + var currentAmountOfIncense = await session.Inventory.GetItemAmountByType(ItemId.ItemIncenseOrdinary).ConfigureAwait(false) + + await session.Inventory.GetItemAmountByType(ItemId.ItemIncenseSpicy).ConfigureAwait(false) + + await session.Inventory.GetItemAmountByType(ItemId.ItemIncenseCool).ConfigureAwait(false) + + await session.Inventory.GetItemAmountByType(ItemId.ItemIncenseFloral).ConfigureAwait(false); + var currentAmountOfLuckyEggs = await session.Inventory.GetItemAmountByType(ItemId.ItemLuckyEgg).ConfigureAwait(false); + var currentAmountOfLures = await session.Inventory.GetItemAmountByType(ItemId.ItemTroyDisk).ConfigureAwait(false); + + if (session.LogicSettings.DetailedCountsBeforeRecycling) + Logger.Write(session.Translation.GetTranslation(TranslationString.CurrentMiscItemInv, + currentAmountOfBerries.ToString("0").PadLeft(3,' '), currentAmountOfIncense.ToString("0").PadLeft(3, ' '), currentAmountOfLuckyEggs.ToString("0").PadLeft(3, ' '), currentAmountOfLures.ToString("0").PadLeft(3, ' '))); + + if (!session.LogicSettings.VerboseRecycling) + Logger.Write(session.Translation.GetTranslation(TranslationString.RecyclingQuietly), LogLevel.Recycling); + + await OptimizedRecycleBalls(session, cancellationToken).ConfigureAwait(false); + await OptimizedRecyclePotions(session, cancellationToken).ConfigureAwait(false); + await OptimizedRecycleRevives(session, cancellationToken).ConfigureAwait(false); + await OptimizedRecycleBerries(session, cancellationToken).ConfigureAwait(false); + await OptimizedRecycleEvoItems(session, cancellationToken).ConfigureAwait(false); + + //await session.Inventory.RefreshCachedInventory().ConfigureAwait(false); + currentTotalItems = await session.Inventory.GetTotalItemCount().ConfigureAwait(false); + if ((session.Profile.PlayerData.MaxItemStorage * session.LogicSettings.RecycleInventoryAtUsagePercentage / 100.0f) > currentTotalItems) + return; + + var items = await session.Inventory.GetItemsToRecycle(session).ConfigureAwait(false); + + foreach (var item in items) + { + if (item.Count <= 1 || + (session.SaveBallForByPassCatchFlee && + (item.ItemId == ItemId.ItemPokeBall || + item.ItemId == ItemId.ItemGreatBall || + item.ItemId == ItemId.ItemUltraBall)) + + ) continue; + + cancellationToken.ThrowIfCancellationRequested(); + TinyIoC.TinyIoCContainer.Current.Resolve().ThrowIfSwitchAccountRequested(); + await session.Client.Inventory.RecycleItem(item.ItemId, item.Count).ConfigureAwait(false); + await session.Inventory.UpdateInventoryItem(item.ItemId).ConfigureAwait(false); + + if (session.LogicSettings.VerboseRecycling) + session.EventDispatcher.Send(new ItemRecycledEvent { Id = item.ItemId, Count = item.Count }); + + await DelayingUtils.DelayAsync(session.LogicSettings.RecycleActionDelay, 500, session.CancellationTokenSource.Token).ConfigureAwait(false); + } + //await session.Inventory.RefreshCachedInventory(); + } + + private static async Task RecycleItems(ISession session, CancellationToken cancellationToken, int itemCount, ItemId item, int maxItemToKeep = 1000) + { + int itemsToRecycle = 0; + int itemsToKeep = itemCount - _diff; + if (itemsToKeep < 0) + itemsToKeep = 0; + + if (maxItemToKeep > 0) + { + itemsToKeep = Math.Min(itemsToKeep, maxItemToKeep); + } + itemsToRecycle = itemCount - itemsToKeep; + if (itemsToRecycle > 0) + { + _diff -= itemsToRecycle; + cancellationToken.ThrowIfCancellationRequested(); + TinyIoC.TinyIoCContainer.Current.Resolve().ThrowIfSwitchAccountRequested(); + await session.Client.Inventory.RecycleItem(item, itemsToRecycle).ConfigureAwait(false); + await session.Inventory.UpdateInventoryItem(item).ConfigureAwait(false); + if (session.LogicSettings.VerboseRecycling) + session.EventDispatcher.Send(new ItemRecycledEvent { Id = item, Count = itemsToRecycle }); + + await DelayingUtils.DelayAsync(session.LogicSettings.RecycleActionDelay, 500, session.CancellationTokenSource.Token).ConfigureAwait(false); + } + } + + private static async Task OptimizedRecycleBalls(ISession session, CancellationToken cancellationToken) + { + var pokeBallsCount = await session.Inventory.GetItemAmountByType(ItemId.ItemPokeBall).ConfigureAwait(false); + var greatBallsCount = await session.Inventory.GetItemAmountByType(ItemId.ItemGreatBall).ConfigureAwait(false); + var ultraBallsCount = await session.Inventory.GetItemAmountByType(ItemId.ItemUltraBall).ConfigureAwait(false); + var masterBallsCount = await session.Inventory.GetItemAmountByType(ItemId.ItemMasterBall).ConfigureAwait(false); + + int totalBallsCount = pokeBallsCount + greatBallsCount + ultraBallsCount + masterBallsCount; + + if (session.SaveBallForByPassCatchFlee) return; + + int random = rnd.Next(-1 * session.LogicSettings.RandomRecycleValue, session.LogicSettings.RandomRecycleValue + 1); + + int totalPokeballsToKeep; + + if (session.LogicSettings.UseRecyclePercentsInsteadOfTotals) + { + totalPokeballsToKeep = (int)Math.Floor(session.LogicSettings.PercentOfInventoryPokeballsToKeep / 100.0 * session.Profile.PlayerData.MaxItemStorage); + } + else + { + totalPokeballsToKeep = session.LogicSettings.TotalAmountOfPokeballsToKeep; + } + + if (totalBallsCount > totalPokeballsToKeep) + { + if (session.LogicSettings.RandomizeRecycle) + { + _diff = totalBallsCount - totalPokeballsToKeep + random; + } + else + { + _diff = totalBallsCount - totalPokeballsToKeep; + } + + if (_diff > 0) + { + await RecycleItems(session, cancellationToken, pokeBallsCount, ItemId.ItemPokeBall).ConfigureAwait(false); + } + if (_diff > 0) + { + await RecycleItems(session, cancellationToken, greatBallsCount, ItemId.ItemGreatBall).ConfigureAwait(false); + } + if (_diff > 0) + { + await RecycleItems(session, cancellationToken, ultraBallsCount, ItemId.ItemUltraBall).ConfigureAwait(false); + } + if (_diff > 0) + { + await RecycleItems(session, cancellationToken, masterBallsCount, ItemId.ItemMasterBall).ConfigureAwait(false); + } + } + } + + private static async Task OptimizedRecyclePotions(ISession session, CancellationToken cancellationToken) + { + var potionCount = await session.Inventory.GetItemAmountByType(ItemId.ItemPotion).ConfigureAwait(false); + var superPotionCount = await session.Inventory.GetItemAmountByType(ItemId.ItemSuperPotion).ConfigureAwait(false); + var hyperPotionsCount = await session.Inventory.GetItemAmountByType(ItemId.ItemHyperPotion).ConfigureAwait(false); + var maxPotionCount = await session.Inventory.GetItemAmountByType(ItemId.ItemMaxPotion).ConfigureAwait(false); + + int totalPotionsCount = potionCount + superPotionCount + hyperPotionsCount + maxPotionCount; + int random = rnd.Next(-1 * session.LogicSettings.RandomRecycleValue, session.LogicSettings.RandomRecycleValue + 1); + + int totalPotionsToKeep; + if (session.LogicSettings.UseRecyclePercentsInsteadOfTotals) + { + totalPotionsToKeep = (int)Math.Floor(session.LogicSettings.PercentOfInventoryPotionsToKeep / 100.0 * session.Profile.PlayerData.MaxItemStorage); + } + else + { + totalPotionsToKeep = session.LogicSettings.TotalAmountOfPotionsToKeep; + } + + if (totalPotionsCount > totalPotionsToKeep) + { + if (session.LogicSettings.RandomizeRecycle) + { + _diff = totalPotionsCount - totalPotionsToKeep + random; + } + else + { + _diff = totalPotionsCount - totalPotionsToKeep; + } + + if (_diff > 0) + { + await RecycleItems(session, cancellationToken, potionCount, ItemId.ItemPotion).ConfigureAwait(false); + } + + if (_diff > 0) + { + await RecycleItems(session, cancellationToken, superPotionCount, ItemId.ItemSuperPotion).ConfigureAwait(false); + } + + if (_diff > 0) + { + await RecycleItems(session, cancellationToken, hyperPotionsCount, ItemId.ItemHyperPotion).ConfigureAwait(false); + } + + if (_diff > 0) + { + await RecycleItems(session, cancellationToken, maxPotionCount, ItemId.ItemMaxPotion).ConfigureAwait(false); + } + } + } + + private static async Task OptimizedRecycleRevives(ISession session, CancellationToken cancellationToken) + { + var reviveCount = await session.Inventory.GetItemAmountByType(ItemId.ItemRevive).ConfigureAwait(false); + var maxReviveCount = await session.Inventory.GetItemAmountByType(ItemId.ItemMaxRevive).ConfigureAwait(false); + + int totalRevivesCount = reviveCount + maxReviveCount; + int random = rnd.Next(-1 * session.LogicSettings.RandomRecycleValue, session.LogicSettings.RandomRecycleValue + 1); + + int totalRevivesToKeep; + if (session.LogicSettings.UseRecyclePercentsInsteadOfTotals) + { + totalRevivesToKeep = (int)Math.Floor(session.LogicSettings.PercentOfInventoryRevivesToKeep / 100.0 * session.Profile.PlayerData.MaxItemStorage); + } + else + { + totalRevivesToKeep = session.LogicSettings.TotalAmountOfRevivesToKeep; + } + + if (totalRevivesCount > totalRevivesToKeep) + { + if (session.LogicSettings.RandomizeRecycle) + { + _diff = totalRevivesCount - totalRevivesToKeep + random; + } + else + { + _diff = totalRevivesCount - totalRevivesToKeep; + } + if (_diff > 0) + { + await RecycleItems(session, cancellationToken, reviveCount, ItemId.ItemRevive).ConfigureAwait(false); + } + + if (_diff > 0) + { + await RecycleItems(session, cancellationToken, maxReviveCount, ItemId.ItemMaxRevive).ConfigureAwait(false); + } + } + } + + private static async Task OptimizedRecycleBerries(ISession session, CancellationToken cancellationToken) + { + var razz = await session.Inventory.GetItemAmountByType(ItemId.ItemRazzBerry).ConfigureAwait(false); + var bluk = await session.Inventory.GetItemAmountByType(ItemId.ItemBlukBerry).ConfigureAwait(false); + var nanab = await session.Inventory.GetItemAmountByType(ItemId.ItemNanabBerry).ConfigureAwait(false); + var pinap = await session.Inventory.GetItemAmountByType(ItemId.ItemPinapBerry).ConfigureAwait(false); + var wepar = await session.Inventory.GetItemAmountByType(ItemId.ItemWeparBerry).ConfigureAwait(false); + var goldnana = await session.Inventory.GetItemAmountByType(ItemId.ItemGoldenNanabBerry).ConfigureAwait(false); + var goldpinap = await session.Inventory.GetItemAmountByType(ItemId.ItemGoldenPinapBerry).ConfigureAwait(false); + var goldrazz = await session.Inventory.GetItemAmountByType(ItemId.ItemGoldenRazzBerry).ConfigureAwait(false); + + int totalBerryCount = razz + bluk + nanab + pinap + wepar + goldnana + goldpinap + goldrazz; + int random = rnd.Next(-1 * session.LogicSettings.RandomRecycleValue, session.LogicSettings.RandomRecycleValue + 1); + + int totalBerriesToKeep; + if (session.LogicSettings.UseRecyclePercentsInsteadOfTotals) + { + totalBerriesToKeep = (int)Math.Floor(session.LogicSettings.PercentOfInventoryBerriesToKeep / 100.0 * session.Profile.PlayerData.MaxItemStorage); + } + else + { + totalBerriesToKeep = session.LogicSettings.TotalAmountOfBerriesToKeep; + } + + if (totalBerryCount > totalBerriesToKeep) + { + if (session.LogicSettings.RandomizeRecycle) + { + _diff = totalBerryCount - totalBerriesToKeep + random; + } + else + { + _diff = totalBerryCount - totalBerriesToKeep; + } + + if (_diff > 0) + { + await RecycleItems(session, cancellationToken, razz, ItemId.ItemRazzBerry).ConfigureAwait(false); + } + + if (_diff > 0) + { + await RecycleItems(session, cancellationToken, bluk, ItemId.ItemBlukBerry).ConfigureAwait(false); + } + + if (_diff > 0) + { + await RecycleItems(session, cancellationToken, nanab, ItemId.ItemNanabBerry).ConfigureAwait(false); + } + + if (_diff > 0) + { + await RecycleItems(session, cancellationToken, pinap, ItemId.ItemPinapBerry).ConfigureAwait(false); + } + + if (_diff > 0) + { + await RecycleItems(session, cancellationToken, wepar, ItemId.ItemWeparBerry).ConfigureAwait(false); + } + + if (_diff > 0) + { + await RecycleItems(session, cancellationToken, goldnana, ItemId.ItemGoldenNanabBerry).ConfigureAwait(false); + } + if (_diff > 0) + { + await RecycleItems(session, cancellationToken, goldpinap, ItemId.ItemGoldenPinapBerry).ConfigureAwait(false); + } + if (_diff > 0) + { + await RecycleItems(session, cancellationToken, goldrazz, ItemId.ItemGoldenRazzBerry).ConfigureAwait(false); + } + } + } + + private static async Task OptimizedRecycleEvoItems(ISession session, CancellationToken cancellationToken) + { + var upgrade = await session.Inventory.GetItemAmountByType(ItemId.ItemUpGrade).ConfigureAwait(false); + var dragon = await session.Inventory.GetItemAmountByType(ItemId.ItemDragonScale).ConfigureAwait(false); + var kings = await session.Inventory.GetItemAmountByType(ItemId.ItemKingsRock).ConfigureAwait(false); + var metal = await session.Inventory.GetItemAmountByType(ItemId.ItemMetalCoat).ConfigureAwait(false); + var sun = await session.Inventory.GetItemAmountByType(ItemId.ItemSunStone).ConfigureAwait(false); + + int totalEvolutionCount = upgrade + dragon + kings + metal + sun; + int random = rnd.Next(-1 * session.LogicSettings.RandomRecycleValue, session.LogicSettings.RandomRecycleValue + 1); + + int totalEvolutionToKeep; + if (session.LogicSettings.UseRecyclePercentsInsteadOfTotals) + { + totalEvolutionToKeep = (int)Math.Floor(session.LogicSettings.PercentOfInventoryEvolutionToKeep / 100.0 * session.Profile.PlayerData.MaxItemStorage); + } + else + { + totalEvolutionToKeep = session.LogicSettings.TotalAmountOfEvolutionToKeep; + } + + if (totalEvolutionCount > totalEvolutionToKeep) + { + if (session.LogicSettings.RandomizeRecycle) + { + _diff = totalEvolutionCount - totalEvolutionToKeep + random; + } + else + { + _diff = totalEvolutionCount - totalEvolutionToKeep; + } + + if (_diff > 0) + { + await RecycleItems(session, cancellationToken, upgrade, ItemId.ItemUpGrade).ConfigureAwait(false); + } + + if (_diff > 0) + { + await RecycleItems(session, cancellationToken, dragon, ItemId.ItemDragonScale).ConfigureAwait(false); + } + + if (_diff > 0) + { + await RecycleItems(session, cancellationToken, kings, ItemId.ItemKingsRock).ConfigureAwait(false); + } + + if (_diff > 0) + { + await RecycleItems(session, cancellationToken, metal, ItemId.ItemMetalCoat).ConfigureAwait(false); + } + + if (_diff > 0) + { + await RecycleItems(session, cancellationToken, sun, ItemId.ItemSunStone).ConfigureAwait(false); + } + } + } + + public static async Task DropItem(ISession session, ItemId item, int count) + { + using (var blocker = new BlockableScope(session, BotActions.RecycleItem)) + { + if (!await blocker.WaitToRun().ConfigureAwait(false)) return; + + if (count > 0) + { + await session.Client.Inventory.RecycleItem(item, count).ConfigureAwait(false); + await session.Inventory.UpdateInventoryItem(item).ConfigureAwait(false); + + if (session.LogicSettings.VerboseRecycling) + session.EventDispatcher.Send(new ItemRecycledEvent { Id = item, Count = count }); + + await DelayingUtils.DelayAsync(session.LogicSettings.RecycleActionDelay, 500, session.CancellationTokenSource.Token).ConfigureAwait(false); + } + } + } + } +} diff --git a/PoGo.NecroBot.Logic/Tasks/RenamePokemonTask.cs b/PoGo.NecroBot.Logic/Tasks/RenamePokemonTask.cs new file mode 100644 index 000000000..6ab4bdc63 --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/RenamePokemonTask.cs @@ -0,0 +1,104 @@ +#region using directives + +using System; +using System.Threading; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.PoGoUtils; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Utils; +using POGOProtos.Networking.Responses; +using PoGo.NecroBot.Logic.Logging; +using System.Linq; + +#endregion + +namespace PoGo.NecroBot.Logic.Tasks +{ + public class RenamePokemonTask + { + public static async Task Execute(ISession session, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + TinyIoC.TinyIoCContainer.Current.Resolve().ThrowIfSwitchAccountRequested(); + var pokemons = await session.Inventory.GetPokemons().ConfigureAwait(false); + + if (session.LogicSettings.TransferDuplicatePokemon && session.LogicSettings.RenamePokemonRespectTransferRule) + { + var duplicatePokemons = await + session.Inventory.GetDuplicatePokemonToTransfer( + session.LogicSettings.PokemonsNotToTransfer, + session.LogicSettings.PokemonEvolveFilters, + session.LogicSettings.KeepPokemonsToBeEvolved, + session.LogicSettings.PrioritizeIvOverCp).ConfigureAwait(false); + + pokemons = pokemons.Where(x => !duplicatePokemons.Any(p => p.Id == x.Id)); + } + + if (session.LogicSettings.TransferWeakPokemon && session.LogicSettings.RenamePokemonRespectTransferRule) + { + var weakPokemons = await + session.Inventory.GetWeakPokemonToTransfer( + session.LogicSettings.PokemonsNotToTransfer, + session.LogicSettings.PokemonEvolveFilters, + session.LogicSettings.KeepPokemonsToBeEvolved).ConfigureAwait(false); + + pokemons = pokemons.Where(x => !weakPokemons.Any(p => p.Id == x.Id)); + } + foreach (var pokemon in pokemons) + { + cancellationToken.ThrowIfCancellationRequested(); + TinyIoC.TinyIoCContainer.Current.Resolve().ThrowIfSwitchAccountRequested(); + var perfection = Math.Round(PokemonInfo.CalculatePokemonPerfection(pokemon)); + var level = PokemonInfo.GetLevel(pokemon); + var pokemonName = session.Translation.GetPokemonTranslation(pokemon.PokemonId); + var cp = PokemonInfo.CalculateCp(pokemon); + // iv number + templating part + pokemonName <= 12 + + var newNickname = session.LogicSettings.RenameTemplate.ToUpper(); + newNickname = newNickname.Replace("{IV}", Math.Round(perfection, 0).ToString()); + newNickname = newNickname.Replace("{LEVEL}", Math.Round(level, 0).ToString()); + newNickname = newNickname.Replace("{CP}", cp.ToString()); + + var nameLength = 18 - newNickname.Length; + if (pokemonName.Length > nameLength && nameLength > 0) + { + pokemonName = pokemonName.Substring(0, nameLength); + } + + newNickname = newNickname.Replace("{NAME}", pokemonName); + + //verify + if (nameLength <= 0) + { + Logger.Write($"Your rename template : {session.LogicSettings.RenameTemplate} incorrect. : {pokemonName} / {newNickname}"); + continue; + } + var oldNickname = pokemon.Nickname.Length != 0 ? pokemon.Nickname : pokemon.PokemonId.ToString(); + + // If "RenameOnlyAboveIv" = true only rename pokemon with IV over "KeepMinIvPercentage" + if ((!session.LogicSettings.RenameOnlyAboveIv || + perfection >= session.LogicSettings.KeepMinIvPercentage) && + newNickname != oldNickname) + { + var result = await session.Client.Inventory.NicknamePokemon(pokemon.Id, newNickname).ConfigureAwait(false); + + if (result.Result == NicknamePokemonResponse.Types.Result.Success) + { + pokemon.Nickname = newNickname; + + session.EventDispatcher.Send(new RenamePokemonEvent + { + Id = pokemon.Id, + PokemonId = pokemon.PokemonId, + OldNickname = oldNickname, + NewNickname = newNickname + }); + } + //Delay only if the pokemon was really renamed! + await DelayingUtils.DelayAsync(session.LogicSettings.RenamePokemonActionDelay, 500, session.CancellationTokenSource.Token).ConfigureAwait(false); + } + } + } + } +} diff --git a/PoGo.NecroBot.Logic/Tasks/RenameSinglePokemonTask.cs b/PoGo.NecroBot.Logic/Tasks/RenameSinglePokemonTask.cs new file mode 100644 index 000000000..c86b882fd --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/RenameSinglePokemonTask.cs @@ -0,0 +1,46 @@ +#region using directives + +using System.Threading; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.State; +using POGOProtos.Networking.Responses; +using System.Linq; + +#endregion + +namespace PoGo.NecroBot.Logic.Tasks +{ + public class RenameSinglePokemonTask + { + public static async Task Execute(ISession session, ulong pokemonId, string newNickname, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + TinyIoC.TinyIoCContainer.Current.Resolve().ThrowIfSwitchAccountRequested(); + var pokemon = (await session.Inventory.GetPokemons().ConfigureAwait(false)).Where(x => x.Id == pokemonId).FirstOrDefault(); + + if (pokemon == null || pokemon.Nickname == newNickname) + return; + + if (newNickname.Length > 12) + newNickname = newNickname.Substring(0, 12); + + var oldNickname = string.IsNullOrEmpty(pokemon.Nickname) ? pokemon.PokemonId.ToString() : pokemon.Nickname; + + var result = await session.Client.Inventory.NicknamePokemon(pokemon.Id, newNickname).ConfigureAwait(false); + + if (result.Result == NicknamePokemonResponse.Types.Result.Success) + { + pokemon.Nickname = newNickname; + + session.EventDispatcher.Send(new RenamePokemonEvent + { + Id = pokemon.Id, + PokemonId = pokemon.PokemonId, + OldNickname = oldNickname, + NewNickname = newNickname + }); + } + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Tasks/SelectBuddyPokemonTask.cs b/PoGo.NecroBot.Logic/Tasks/SelectBuddyPokemonTask.cs new file mode 100644 index 000000000..d61ab13a8 --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/SelectBuddyPokemonTask.cs @@ -0,0 +1,73 @@ +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Event.Player; +using PoGo.NecroBot.Logic.State; +using POGOProtos.Networking.Responses; +using PoGo.NecroBot.Logic.PoGoUtils; +using PoGo.NecroBot.Logic.Logging; +using POGOProtos.Data; +using POGOProtos.Enums; +using System; + +namespace PoGo.NecroBot.Logic.Tasks +{ + public class SelectBuddyPokemonTask + { + public static async Task Execute(ISession session, CancellationToken cancellationToken, ulong pokemonId = 0) + { + PokemonData newBuddy = null; + if (pokemonId == 0) + { + if (string.IsNullOrEmpty(session.LogicSettings.DefaultBuddyPokemon)) + return; + + PokemonId buddyPokemonId; + bool success = Enum.TryParse(session.LogicSettings.DefaultBuddyPokemon, out buddyPokemonId); + if (!success) + { + // Invalid buddy pokemon type + Logger.Write($"The DefaultBuddyPokemon ({session.LogicSettings.DefaultBuddyPokemon}) is not a valid pokemon.", LogLevel.Error); + return; + } + + if (session.Profile.PlayerData.BuddyPokemon?.Id > 0) + { + var currentBuddy = (await session.Inventory.GetPokemons().ConfigureAwait(false)).FirstOrDefault(x => x.Id == session.Profile.PlayerData.BuddyPokemon.Id); + if (currentBuddy.PokemonId == buddyPokemonId) + { + //dont change same buddy + return; + } + } + + var buddy = (await session.Inventory.GetPokemons().ConfigureAwait(false)).Where(x => x.PokemonId == buddyPokemonId) + .OrderByDescending(x => PokemonInfo.CalculateCp(x)); + + if (session.LogicSettings.PrioritizeIvOverCp) + { + buddy = buddy.OrderByDescending(x => PokemonInfo.CalculatePokemonPerfection(x)); + } + newBuddy = buddy.FirstOrDefault(); + + if (newBuddy == null) + { + Logger.Write($"You don't have the pokemon {session.LogicSettings.DefaultBuddyPokemon} to set as your buddy"); + return; + } + } + if (newBuddy == null) + { + newBuddy = (await session.Inventory.GetPokemons().ConfigureAwait(false)).FirstOrDefault(x => x.Id == pokemonId); + } + if (newBuddy == null) return; + + var response = await session.Client.Player.SelectBuddy(newBuddy.Id).ConfigureAwait(false); + + if (response.Result == SetBuddyPokemonResponse.Types.Result.Success) + { + session.EventDispatcher.Send(new BuddyUpdateEvent(response.UpdatedBuddy, newBuddy)); + } + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Tasks/SetMoveToTargetTask.cs b/PoGo.NecroBot.Logic/Tasks/SetMoveToTargetTask.cs new file mode 100644 index 000000000..2fdcb38dd --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/SetMoveToTargetTask.cs @@ -0,0 +1,100 @@ +#region using directives + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Event.Player; +using PoGo.NecroBot.Logic.State; +using PokemonGo.RocketAPI.Extensions; +using POGOProtos.Map.Fort; +using POGOProtos.Networking.Responses; +using TinyIoC; + +#endregion + +namespace PoGo.NecroBot.Logic.Tasks +{ + public class SetMoveToTargetTask + { + public static string TARGET_ID = "NECRO2_FORT"; + + private static FortData _targetStop; + + static Queue queue = new Queue(); + + public static async Task Execute(double lat, double lng, string fortId = "") + { + ISession session = TinyIoCContainer.Current.Resolve(); + await Task.Run(() => + { + if (!string.IsNullOrEmpty(fortId)) + { + var knownFort = session.Forts.FirstOrDefault(x => x.Id == fortId); + if (knownFort != null) + { + queue.Enqueue(knownFort); + return; + } + } + //at this time only allow one target, can't be cancel + //if (_targetStop == null || _targetStop.CooldownCompleteTimestampMs == 0) + { + _targetStop = new FortData() + { + Latitude = lat, + Longitude = lng, + Id = TARGET_ID + DateTime.Now.Ticks.ToString(), + Type = FortType.Checkpoint, + //make sure bot not try to spin this fake pokestop + CooldownCompleteTimestampMs = DateTime.UtcNow.AddHours(1).ToUnixTime() + }; + } + queue.Enqueue(_targetStop); + }).ConfigureAwait(false); + + session.EventDispatcher.Send(new TargetLocationEvent(lat, lng)); + } + + public static FortDetailsResponse FakeFortInfo(FortData data) + { + return new FortDetailsResponse() + { + Latitude = data.Latitude, + Longitude = data.Longitude, + Name = "User selected", + Type = FortType.Checkpoint + }; + } + + public static async Task IsReachedDestination(FortData destination, ISession session, + CancellationToken cancellationToken) + { + if (_targetStop != null && destination.Id == _targetStop.Id) + { + _targetStop = null; + queue.Dequeue(); + //looking for pokemon + await CatchNearbyPokemonsTask.Execute(session, cancellationToken); + //TODO - maybe looking for lure pokestop and try catch lure pokestop task + return true; + } + return false; + } + + internal static async Task GetTarget(ISession session) + { + return await Task.Run(() => + { + if (queue.Count > 0 && + !session.LogicSettings.UseGpxPathing) + { + _targetStop = queue.Peek(); + return _targetStop; + } + return null; + }).ConfigureAwait(false); + } + } +} diff --git a/PoGo.NecroBot.Logic/Tasks/SnipePokemonTask.cs b/PoGo.NecroBot.Logic/Tasks/SnipePokemonTask.cs new file mode 100644 index 000000000..c4233c90f --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/SnipePokemonTask.cs @@ -0,0 +1,507 @@ +#region using directives + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; +using Newtonsoft.Json; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.Model.Settings; +using PoGo.NecroBot.Logic.PoGoUtils; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Utils; +using PokemonGo.RocketAPI.Exceptions; +using POGOProtos.Enums; +using POGOProtos.Inventory.Item; +using POGOProtos.Map.Pokemon; +using POGOProtos.Networking.Responses; +using PoGo.NecroBot.Logic.Logging; +using GeoCoordinatePortable; + +#endregion + +namespace PoGo.NecroBot.Logic.Tasks +{ + public class SniperInfo + { + public ulong EncounterId { get; set; } + public DateTime ExpirationTimestamp { get; set; } + public double Latitude { get; set; } + public double Longitude { get; set; } + public PokemonId Id { get; set; } + public string SpawnPointId { get; set; } + public PokemonMove Move1 { get; set; } + public PokemonMove Move2 { get; set; } + public double IV { get; set; } + + [JsonIgnore] + public DateTime TimeStampAdded { get; set; } = DateTime.Now; + } + + public class PokemonLocation + { + public PokemonLocation(double lat, double lon) + { + latitude = lat; + longitude = lon; + } + + public long Id { get; set; } + public double expires { get; set; } + public double latitude { get; set; } + public double longitude { get; set; } + public int pokemon_id { get; set; } + public PokemonId pokemon_name { get; set; } + + [JsonIgnore] + public DateTime TimeStampAdded { get; set; } = DateTime.Now; + + public bool Equals(PokemonLocation obj) + { + return Math.Abs(latitude - obj.latitude) < 0.0001 && Math.Abs(longitude - obj.longitude) < 0.0001; + } + + public override bool Equals(object obj) // contains calls this here + { + var p = obj as PokemonLocation; + if (p == null) // no cast available + { + return false; + } + + return Math.Abs(latitude - p.latitude) < 0.0001 && Math.Abs(longitude - p.longitude) < 0.0001; + } + + public override int GetHashCode() + { + return ToString().GetHashCode(); + } + + public override string ToString() + { + return latitude.ToString("0.00000000000") + ", " + longitude.ToString("0.00000000000"); + } + } + + public class PokemonLocationPokezz + { + public double time { get; set; } + public double lat { get; set; } + public double lng { get; set; } + public string iv { get; set; } + + public double _iv + { + get + { + try + { + return Convert.ToDouble(iv, CultureInfo.InvariantCulture); + } + catch + { + return 0; + } + } + } + + public PokemonId name { get; set; } + public bool verified { get; set; } + } + + public class PokemonLocationPokesnipers + { + public int id { get; set; } + public double iv { get; set; } + public PokemonId name { get; set; } + public string until { get; set; } + public string coords { get; set; } + } + + public class PokemonLocationPokewatchers + { + public PokemonId pokemon { get; set; } + public double timeadded { get; set; } + public double timeend { get; set; } + public string cords { get; set; } + } + + public class ScanResult + { + public string Status { get; set; } + public List pokemons { get; set; } + } + + public class ScanResultPokesnipers + { + public string Status { get; set; } + + [JsonProperty("results")] + public List pokemons { get; set; } + } + + public class ScanResultPokewatchers + { + public string Status { get; set; } + public List pokemons { get; set; } + } + + public static class SnipePokemonTask + { + public static List LocsVisited = new List(); + private static readonly List SnipeLocations = new List(); + private static DateTime _lastSnipe = DateTime.MinValue; + + public static Task AsyncStart(Session session, CancellationToken cancellationToken = default(CancellationToken)) + { + return Task.Run(() => Start(session, cancellationToken), cancellationToken); + } + + public static async Task CheckPokeballsToSnipe(int minPokeballs, ISession session, + CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + TinyIoC.TinyIoCContainer.Current.Resolve().ThrowIfSwitchAccountRequested(); + + var pokeBallsCount = await session.Inventory.GetItemAmountByType(ItemId.ItemPokeBall).ConfigureAwait(false); + pokeBallsCount += await session.Inventory.GetItemAmountByType(ItemId.ItemGreatBall).ConfigureAwait(false); + pokeBallsCount += await session.Inventory.GetItemAmountByType(ItemId.ItemUltraBall).ConfigureAwait(false); + pokeBallsCount += await session.Inventory.GetItemAmountByType(ItemId.ItemMasterBall).ConfigureAwait(false); + + if (pokeBallsCount >= minPokeballs) + return true; + + session.EventDispatcher.Send(new SnipeEvent + { + Message = + session.Translation.GetTranslation(TranslationString.NotEnoughPokeballsToSnipe, pokeBallsCount, + minPokeballs) + }); + + return false; + } + + private static bool CheckSnipeConditions(ISession session) + { + if (!session.LogicSettings.UseSnipeLimit) return true; + + session.EventDispatcher.Send(new SnipeEvent + { + Message = session.Translation.GetTranslation(TranslationString.SniperCount, session.Stats.SnipeCount) + }); + + if (session.Stats.SnipeCount < session.LogicSettings.SnipeCountLimit) + return true; + + if ((DateTime.Now - session.Stats.LastSnipeTime).TotalSeconds > session.LogicSettings.SnipeRestSeconds) + { + session.Stats.SnipeCount = 0; + } + else + { + session.EventDispatcher.Send(new SnipeEvent + { + Message = session.Translation.GetTranslation(TranslationString.SnipeExceeds) + }); + return false; + } + return true; + } + + public static async Task Execute(ISession session, CancellationToken cancellationToken) + { + if (_lastSnipe.AddMilliseconds(session.LogicSettings.MinDelayBetweenSnipes) > DateTime.Now) + return; + + LocsVisited.RemoveAll(q => DateTime.Now > q.TimeStampAdded.AddMinutes(15)); + SnipeLocations.RemoveAll(x => DateTime.Now > x.TimeStampAdded.AddMinutes(15)); + + if (await CheckPokeballsToSnipe(session.LogicSettings.MinPokeballsToSnipe, session, cancellationToken).ConfigureAwait(false)) + { + var listPokemonToSnipe = new List(); + foreach (var item in session.LogicSettings.PokemonSnipeFilters) + { + var f = item.Value; + listPokemonToSnipe.Add(item.Key); + listPokemonToSnipe.AddRange(f.AffectToPokemons); + + } + if (listPokemonToSnipe.Count > 0) + { + List pokemonIds; + if (session.LogicSettings.SnipePokemonNotInPokedex) + { + var pokeDex = await session.Inventory.GetPokeDexItems().ConfigureAwait(false); + var pokemonOnlyList = listPokemonToSnipe; + var capturedPokemon = + pokeDex.Where(i => i.InventoryItemData.PokedexEntry.TimesCaptured >= 1) + .Select(i => i.InventoryItemData.PokedexEntry.PokemonId); + var pokemonToCapture = + Enum.GetValues(typeof(PokemonId)).Cast().Except(capturedPokemon); + pokemonIds = pokemonOnlyList.Union(pokemonToCapture).ToList(); + } + else + { + pokemonIds = listPokemonToSnipe; + } + + if (session.LogicSettings.UseSnipeLocationServer) + { + var locationsToSnipe = SnipeLocations?.Where(q => + q.IV >= session.LogicSettings.PokemonSnipeFilters.GetFilter(q.Id).SnipeIV && + !LocsVisited.Contains(new PokemonLocation(q.Latitude, q.Longitude)) + && !(q.ExpirationTimestamp != default(DateTime) && + q.ExpirationTimestamp > new DateTime(2016) && + // make absolutely sure that the server sent a correct datetime + q.ExpirationTimestamp < DateTime.Now) && + (q.Id == PokemonId.Missingno || pokemonIds.Contains(q.Id))) + .ToList(); + + var _locationsToSnipe = locationsToSnipe.OrderBy(q => q.ExpirationTimestamp).ToList(); + if (_locationsToSnipe.Any()) + { + foreach (var location in _locationsToSnipe) + { + if (LocsVisited.Contains(new PokemonLocation(location.Latitude, location.Longitude))) + continue; + + session.EventDispatcher.Send(new SnipeScanEvent + { + Bounds = new Location(location.Latitude, location.Longitude), + PokemonId = location.Id, + Source = session.LogicSettings.SnipeLocationServer, + Iv = location.IV + }); + + if ( + !(await CheckPokeballsToSnipe(session.LogicSettings.MinPokeballsWhileSnipe + 1, session, + cancellationToken).ConfigureAwait(false))) + return; + if (!CheckSnipeConditions(session)) return; + await + Snipe(session, pokemonIds, location.Latitude, location.Longitude, cancellationToken).ConfigureAwait(false); + } + } + } + } + } + } + + public static async Task Snipe(ISession session, IEnumerable pokemonIds, double latitude, + double longitude, CancellationToken cancellationToken) + { + //if (LocsVisited.Contains(new PokemonLocation(latitude, longitude))) + // return; + + var originalLatitude = session.Client.CurrentLatitude; + var originalLongitude = session.Client.CurrentLongitude; + var catchedPokemon = false; + + session.EventDispatcher.Send(new SnipeModeEvent { Active = true }); + + List catchablePokemon; + int retry = 3; + + try + { + await LocationUtils.UpdatePlayerLocationWithAltitude(session, new GeoCoordinate(latitude, longitude), 0).ConfigureAwait(false); // Set speed to 0 for random speed. + + session.EventDispatcher.Send(new UpdatePositionEvent + { + Latitude = latitude, + Longitude = longitude + }); + + try + { + do + { + retry--; + var mapObjects = await session.Client.Map.GetMapObjects(true, false).ConfigureAwait(false); + catchablePokemon = + mapObjects.MapCells.SelectMany(q => q.CatchablePokemons) + .Where(q => pokemonIds.Contains(q.PokemonId)) + .OrderByDescending(pokemon => PokemonInfo.CalculateMaxCp(pokemon.PokemonId)) + .ToList(); + } while (catchablePokemon.Count == 0 && retry > 0); + } + catch (HasherException ex) { throw ex; } + catch (CaptchaException ex) + { + throw ex; + } + catch (Exception e) + { + Logger.Write($"Error: {e.Message}", LogLevel.Error); + throw e; + } + + if (catchablePokemon.Count == 0) + { + // Pokemon not found but we still add to the locations visited, so we don't keep sniping + // locations with no pokemon. + if (!LocsVisited.Contains(new PokemonLocation(latitude, longitude))) + LocsVisited.Add(new PokemonLocation(latitude, longitude)); + + session.EventDispatcher.Send(new SnipeEvent + { + Message = session.Translation.GetTranslation(TranslationString.NoPokemonToSnipe), + }); + + session.EventDispatcher.Send(new SnipeFailedEvent + { + Latitude = latitude, + Longitude = longitude, + PokemonId = pokemonIds.FirstOrDefault() + }); + + return false; + } + + foreach (var pokemon in catchablePokemon) + { + EncounterResponse encounter; + try + { + await LocationUtils.UpdatePlayerLocationWithAltitude(session, + new GeoCoordinate(pokemon.Latitude, pokemon.Longitude), 0).ConfigureAwait(false); // Set speed to 0 for random speed. + + encounter = + await session.Client.Encounter.EncounterPokemon(pokemon.EncounterId, pokemon.SpawnPointId).ConfigureAwait(false); + } + catch (HasherException ex) { throw ex; } + catch (CaptchaException ex) + { + throw ex; + } + + switch (encounter.Status) + { + case EncounterResponse.Types.Status.EncounterSuccess: + if (!LocsVisited.Contains(new PokemonLocation(latitude, longitude))) + LocsVisited.Add(new PokemonLocation(latitude, longitude)); + + //Also add exact pokemon location to LocsVisited, some times the server one differ a little. + if (!LocsVisited.Contains(new PokemonLocation(pokemon.Latitude, pokemon.Longitude))) + LocsVisited.Add(new PokemonLocation(pokemon.Latitude, pokemon.Longitude)); + + catchedPokemon = await CatchPokemonTask.Execute(session, cancellationToken, encounter, pokemon, + currentFortData: null, sessionAllowTransfer: true).ConfigureAwait(false); + break; + + case EncounterResponse.Types.Status.PokemonInventoryFull: + if (session.LogicSettings.TransferDuplicatePokemon) + { + await TransferDuplicatePokemonTask.Execute(session, cancellationToken).ConfigureAwait(false); + } + else + { + session.EventDispatcher.Send(new WarnEvent + { + Message = session.Translation.GetTranslation(TranslationString.InvFullTransferManually) + }); + } + return false; + + default: + session.EventDispatcher.Send(new WarnEvent + { + Message = + session.Translation.GetTranslation( + TranslationString.EncounterProblem, encounter.Status) + }); + break; + } + + if (!Equals(catchablePokemon.ElementAtOrDefault(catchablePokemon.Count - 1), pokemon)) + await Task.Delay(session.LogicSettings.DelayBetweenPokemonCatch, cancellationToken).ConfigureAwait(false); + } + + _lastSnipe = DateTime.Now; + + if (catchedPokemon) + { + session.Stats.SnipeCount++; + session.Stats.LastSnipeTime = _lastSnipe; + } + session.EventDispatcher.Send(new SnipeModeEvent { Active = false }); + return true; + } + finally + { + await LocationUtils.UpdatePlayerLocationWithAltitude(session, new GeoCoordinate(originalLatitude, originalLongitude), 0).ConfigureAwait(false); // Set speed to 0 for random speed. + + session.EventDispatcher.Send(new UpdatePositionEvent + { + Latitude = originalLatitude, + Longitude = originalLongitude + }); + + await session.Client.Map.GetMapObjects(true).ConfigureAwait(false); + } + //await Task.Delay(session.LogicSettings.DelayBetweenPlayerActions, cancellationToken).ConfigureAwait(false); + } + + public static async Task Start(Session session, CancellationToken cancellationToken) + { + while (true) + { + cancellationToken.ThrowIfCancellationRequested(); + TinyIoC.TinyIoCContainer.Current.Resolve().ThrowIfSwitchAccountRequested(); + try + { // May rem out due to 'Unhandled Error' on lines 460 & 461 - TheWizard1328 + var lClient = new TcpClient(); + lClient.Connect(session.LogicSettings.SnipeLocationServer, + session.LogicSettings.SnipeLocationServerPort); + + var sr = new StreamReader(lClient.GetStream()); + + while (lClient.Connected) + { + try + { + var line = sr.ReadLine(); + if (line == null) + throw new Exception("Unable to ReadLine from sniper socket"); + + var info = JsonConvert.DeserializeObject(line); + + if (SnipeLocations.Any(x => + Math.Abs(x.Latitude - info.Latitude) < 0.0001 && + Math.Abs(x.Longitude - info.Longitude) < 0.0001)) + // we might have different precisions from other sources + continue; + + SnipeLocations.RemoveAll(x => _lastSnipe > x.TimeStampAdded); + SnipeLocations.RemoveAll(x => DateTime.Now > x.TimeStampAdded.AddMinutes(15)); + SnipeLocations.Add(info); + session.EventDispatcher.Send(new SnipePokemonFoundEvent { PokemonFound = info }); + } + catch (IOException) + { + session.EventDispatcher.Send(new ErrorEvent + { + Message = "The connection to the sniping location server has been lost." + }); + } + } + } + catch (SocketException) + { + } + catch (Exception ex) + { + // most likely System.IO.IOException + session.EventDispatcher.Send(new ErrorEvent { Message = ex.ToString() }); + } + + await Task.Delay(100, cancellationToken).ConfigureAwait(false); + } + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Tasks/TransferDuplicatePokemonTask.cs b/PoGo.NecroBot.Logic/Tasks/TransferDuplicatePokemonTask.cs new file mode 100644 index 000000000..6e8af146d --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/TransferDuplicatePokemonTask.cs @@ -0,0 +1,72 @@ +#region using directives + +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Model.Settings; + +#endregion + +namespace PoGo.NecroBot.Logic.Tasks +{ + public class TransferDuplicatePokemonTask : BaseTransferPokemonTask + { + public static GlobalSettings _settings; + + public static async Task Execute(ISession session, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + TinyIoC.TinyIoCContainer.Current.Resolve().ThrowIfSwitchAccountRequested(); + if (!session.LogicSettings.TransferDuplicatePokemon) return; + if (session.LogicSettings.UseBulkTransferPokemon) + { + int buff = session.LogicSettings.BulkTransferStogareBuffer; + //check for bag, if bag is nearly full, then process bulk transfer. + var maxStorage = session.Profile.PlayerData.MaxPokemonStorage; + var totalPokemon = await session.Inventory.GetPokemons().ConfigureAwait(false); + var totalEggs = await session.Inventory.GetEggs().ConfigureAwait(false); + if ((maxStorage - totalEggs.Count() - buff) > totalPokemon.Count()) return; + } + + if (session.LogicSettings.AutoFavoritePokemon) + await FavoritePokemonTask.Execute(session, cancellationToken).ConfigureAwait(false); + + var duplicatePokemons = await + session.Inventory.GetDuplicatePokemonToTransfer( + session.LogicSettings.PokemonsNotToTransfer, + session.LogicSettings.PokemonEvolveFilters, + session.LogicSettings.KeepPokemonsToBeEvolved, + session.LogicSettings.PrioritizeIvOverCp).ConfigureAwait(false); + + if (duplicatePokemons.Count() > 0) + { + Logging.Logger.Write($"Transferring {duplicatePokemons.Count()} Duplicate pokemon.",Logging.LogLevel.Info, System.ConsoleColor.Yellow); + await Execute(session, duplicatePokemons, cancellationToken).ConfigureAwait(false); + } + + var maxPokemonsToTransfer = await + session.Inventory.GetMaxPokemonToTransfer( + session.LogicSettings.PokemonsNotToTransfer, + session.LogicSettings.PrioritizeIvOverCp).ConfigureAwait(false); + + if (maxPokemonsToTransfer.Count() > 0) + { + //Logging.Logger.Write($"Max Duplicate Pokemon Allowed: {_settings.PokemonConfig.KeepMinDuplicatePokemon}. Transferring {maxPokemonsToTransfer.Count()} pokemon over max limit.", Logging.LogLevel.Info, System.ConsoleColor.Yellow); + await Execute(session, maxPokemonsToTransfer, cancellationToken).ConfigureAwait(false); + } + + var SlashedPokemonsToTransfer = await + session.Inventory.GetSlashedPokemonToTransfer().ConfigureAwait(false); + + if (SlashedPokemonsToTransfer.Count() > 0) + { + Logging.Logger.Write($"Transferring {SlashedPokemonsToTransfer.Count()} Slashed pokemon.", Logging.LogLevel.Info, System.ConsoleColor.Yellow); + await Execute(session, SlashedPokemonsToTransfer, cancellationToken).ConfigureAwait(false); + } + + // Evolve after transfer + await EvolvePokemonTask.Execute(session, cancellationToken).ConfigureAwait(false); + } + } +} diff --git a/PoGo.NecroBot.Logic/Tasks/TransferPokemonTask.cs b/PoGo.NecroBot.Logic/Tasks/TransferPokemonTask.cs new file mode 100644 index 000000000..1f7c47669 --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/TransferPokemonTask.cs @@ -0,0 +1,76 @@ +#region using directives + +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.Model; +using PoGo.NecroBot.Logic.PoGoUtils; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Utils; +using POGOProtos.Data; + +#endregion + +namespace PoGo.NecroBot.Logic.Tasks +{ + public class TransferPokemonTask + { + public static async Task Execute(ISession session, CancellationToken cancellationToken, List pokemonIds) + { + using (var blocker = new BlockableScope(session, BotActions.Transfer)) + { + if (!await blocker.WaitToRun().ConfigureAwait(false)) return; + + var all = await session.Inventory.GetPokemons().ConfigureAwait(false); + List pokemonToTransfer = new List(); + var pokemons = all.OrderBy(x => x.Cp).ThenBy(n => n.StaminaMax); + + foreach (var item in pokemonIds) + { + var pokemon = pokemons.FirstOrDefault(p => p.Id == item); + + if (pokemon == null) return; + pokemonToTransfer.Add(pokemon); + } + + var pokemonSettings = await session.Inventory.GetPokemonSettings().ConfigureAwait(false); + var pokemonFamilies = await session.Inventory.GetPokemonFamilies().ConfigureAwait(false); + + await session.Client.Inventory.TransferPokemons(pokemonIds).ConfigureAwait(false); + + foreach (var pokemon in pokemonToTransfer) + { + var bestPokemonOfType = (session.LogicSettings.PrioritizeIvOverCp + ? await session.Inventory.GetHighestPokemonOfTypeByIv(pokemon).ConfigureAwait(false) + : await session.Inventory.GetHighestPokemonOfTypeByCp(pokemon).ConfigureAwait(false)) ?? + pokemon; + + // Broadcast event as everyone would benefit + var ev = new TransferPokemonEvent + { + Id = pokemon.Id, + PokemonId = pokemon.PokemonId, //session.Translation.GetPokemonTranslation(pokemon.PokemonId), + Slashed = pokemon.IsBad, + Perfection = PokemonInfo.CalculatePokemonPerfection(pokemon), + Cp = pokemon.Cp, + BestCp = bestPokemonOfType.Cp, + BestPerfection = PokemonInfo.CalculatePokemonPerfection(bestPokemonOfType), + Candy = await session.Inventory.GetCandyCount(pokemon.PokemonId).ConfigureAwait(false), + Level = PokemonInfo.GetLevel(pokemon) + }; + + if ((await session.Inventory.GetCandyFamily(pokemon.PokemonId).ConfigureAwait(false)) != null) + { + ev.FamilyId = (await session.Inventory.GetCandyFamily(pokemon.PokemonId).ConfigureAwait(false)).FamilyId; + } + + session.EventDispatcher.Send(ev); + } + + await DelayingUtils.DelayAsync(session.LogicSettings.TransferActionDelay, 0, cancellationToken).ConfigureAwait(false); + } + } + } +} diff --git a/PoGo.NecroBot.Logic/Tasks/TransferWeakPokemonTask.cs b/PoGo.NecroBot.Logic/Tasks/TransferWeakPokemonTask.cs new file mode 100644 index 000000000..49383de71 --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/TransferWeakPokemonTask.cs @@ -0,0 +1,47 @@ +#region using directives + +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.State; + +#endregion + +namespace PoGo.NecroBot.Logic.Tasks +{ + public class TransferWeakPokemonTask : BaseTransferPokemonTask + { + public static async Task Execute(ISession session, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + TinyIoC.TinyIoCContainer.Current.Resolve().ThrowIfSwitchAccountRequested(); + if (!session.LogicSettings.TransferWeakPokemon) return; + if (session.LogicSettings.UseBulkTransferPokemon) + { + int buff = session.LogicSettings.BulkTransferStogareBuffer; + //check for bag, if bag is nearly full, then process bulk transfer. + var maxStorage = session.Profile.PlayerData.MaxPokemonStorage; + var totalPokemon = await session.Inventory.GetPokemons().ConfigureAwait(false); + var totalEggs = await session.Inventory.GetEggs().ConfigureAwait(false); + if ((maxStorage - totalEggs.Count() - buff) > totalPokemon.Count()) return; + } + + if (session.LogicSettings.AutoFavoritePokemon) + await FavoritePokemonTask.Execute(session, cancellationToken).ConfigureAwait(false); + + var weakPokemon = await + session.Inventory.GetWeakPokemonToTransfer( + session.LogicSettings.PokemonsNotToTransfer, + session.LogicSettings.PokemonEvolveFilters, + session.LogicSettings.KeepPokemonsToBeEvolved).ConfigureAwait(false); + + if (weakPokemon.Count() > 0) + { + Logging.Logger.Write($"Transferring {weakPokemon.Count()} Weak pokemon.", Logging.LogLevel.Info, System.ConsoleColor.Yellow); + await Execute(session, weakPokemon, cancellationToken).ConfigureAwait(false); + } + // Evolve after transfer. + await EvolvePokemonTask.Execute(session, cancellationToken).ConfigureAwait(false); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Tasks/UpgradeSinglePokemonTask.cs b/PoGo.NecroBot.Logic/Tasks/UpgradeSinglePokemonTask.cs new file mode 100644 index 000000000..4db508a60 --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/UpgradeSinglePokemonTask.cs @@ -0,0 +1,117 @@ +#region using directives + +using System; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.Event.Inventory; +using PoGo.NecroBot.Logic.Model; +using PoGo.NecroBot.Logic.PoGoUtils; +using PoGo.NecroBot.Logic.State; +using PokemonGo.RocketAPI.Exceptions; +using POGOProtos.Data; +using POGOProtos.Networking.Responses; +using PokemonGo.RocketAPI.Helpers; + +#endregion + +namespace PoGo.NecroBot.Logic.Tasks +{ + public class UpgradeSinglePokemonTask + { + public static async Task UpgradeSinglePokemon(ISession session, PokemonData pokemon) + { + if (!(await session.Inventory.CanUpgradePokemon(pokemon).ConfigureAwait(false))) + return false; + + var upgradeResult = await session.Inventory.UpgradePokemon(pokemon.Id).ConfigureAwait(false); + + if (upgradeResult.Result == UpgradePokemonResponse.Types.Result.Success && upgradeResult.UpgradedPokemon != null) + { + var bestPokemonOfType = (session.LogicSettings.PrioritizeIvOverCp + ? await session.Inventory.GetHighestPokemonOfTypeByIv(upgradeResult + .UpgradedPokemon).ConfigureAwait(false) + : await session.Inventory.GetHighestPokemonOfTypeByCp(upgradeResult + .UpgradedPokemon).ConfigureAwait(false)) ?? upgradeResult.UpgradedPokemon; + + //stardust from what I've gathered is supposed to be - not + for AdditionalCpMultiplier + var stardust = -PokemonCpUtils.GetStardustCostsForPowerup(upgradeResult.UpgradedPokemon.CpMultiplier); + var stardust2 = -PokemonCpUtils.GetStardustCostsForPowerup(upgradeResult.UpgradedPokemon.CpMultiplier - upgradeResult.UpgradedPokemon.AdditionalCpMultiplier); + var totalStarDust = session.Inventory.UpdateStarDust(stardust); + Logging.Logger.Write($"SD1: {stardust,5:0} | SD2: {stardust2,5:0} | TotalSD: {totalStarDust,5:0}", Logging.LogLevel.Error); + + session.EventDispatcher.Send(new UpgradePokemonEvent() + { + Candy = await session.Inventory.GetCandyCount(pokemon.PokemonId).ConfigureAwait(false), + Pokemon = upgradeResult.UpgradedPokemon, + PokemonId = upgradeResult.UpgradedPokemon.PokemonId, + Cp = upgradeResult.UpgradedPokemon.Cp, + Id = upgradeResult.UpgradedPokemon.Id, + BestCp = bestPokemonOfType.Cp, + BestPerfection = PokemonInfo.CalculatePokemonPerfection(bestPokemonOfType), + Perfection = PokemonInfo.CalculatePokemonPerfection(upgradeResult.UpgradedPokemon), + USD = stardust, + Lvl = upgradeResult.UpgradedPokemon.Level(), + }); + return true; + } + return false; + } + + public static async Task Execute(ISession session, ulong pokemonId, bool isMax = false, int numUpgrades = -1) + { + using (var block = new BlockableScope(session, BotActions.Upgrade)) + { + if (numUpgrades == -1) + numUpgrades = session.LogicSettings.AmountOfTimesToUpgradeLoop; + + PokemonData pokemonToUpgrade = null; + try + { + if (await block.WaitToRun().ConfigureAwait(false)) + { + if (session.Inventory.GetStarDust() <= session.LogicSettings.GetMinStarDustForLevelUp) + return; + + pokemonToUpgrade = await session.Inventory.GetSinglePokemon(pokemonId).ConfigureAwait(false); + if (pokemonToUpgrade == null) + return; + + bool upgradable = false; + int upgradeTimes = 0; + do + { + try + { + upgradable = await UpgradeSinglePokemon(session, pokemonToUpgrade).ConfigureAwait(false); + + if (upgradable) + { + await Task.Delay(session.LogicSettings.DelayBetweenPokemonUpgrade).ConfigureAwait(false); + } + upgradeTimes++; + } + catch (CaptchaException cex) + { + throw cex; + } + catch (Exception) + { + //make sure no exception happen + } + } while (upgradable && (isMax || upgradeTimes < numUpgrades)); + } + } + finally + { + // Reload pokemon after upgrade. + var upgradedPokemon = await session.Inventory.GetSinglePokemon(pokemonId).ConfigureAwait(false); + session.EventDispatcher.Send(new FinishUpgradeEvent() + { + PokemonId = pokemonId, + Pokemon = upgradedPokemon + }); + } + } + } + } +} diff --git a/PoGo.NecroBot.Logic/Tasks/UseFortItemsTask.cs b/PoGo.NecroBot.Logic/Tasks/UseFortItemsTask.cs new file mode 100644 index 000000000..f138cfb43 --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/UseFortItemsTask.cs @@ -0,0 +1,42 @@ +using PoGo.NecroBot.Logic.Logging; +using PoGo.NecroBot.Logic.State; +using POGOProtos.Inventory.Item; +using POGOProtos.Map.Fort; +using POGOProtos.Networking.Responses; +using System; +using System.Threading.Tasks; + +namespace PoGo.NecroBot.Logic.Tasks +{ + public class UseFortItemsTask + { + public static async Task Execute(ISession session, FortData fort, ItemData item) + { + var response = await session.Client.Fort.AddFortModifier(fort.Id, item.ItemId).ConfigureAwait(false); + switch (response.Result) + { + case AddFortModifierResponse.Types.Result.Success: + Logger.Write($"{item.ItemId} is valid until: {DateTime.Now.AddMinutes(30)}"); + break; + case AddFortModifierResponse.Types.Result.FortAlreadyHasModifier: + Logger.Write($"An {item.ItemId} is already active!", LogLevel.Warning); + break; + case AddFortModifierResponse.Types.Result.NoItemInInventory: + Logger.Write($"{item.ItemId} no found!", LogLevel.Error); + break; + case AddFortModifierResponse.Types.Result.NoResultSet: + Logger.Write($"{item.ItemId} no result set!", LogLevel.Error); + break; + case AddFortModifierResponse.Types.Result.PoiInaccessible: + Logger.Write($"Pokestop poi inaccessible!", LogLevel.Error); + break; + case AddFortModifierResponse.Types.Result.TooFarAway: + Logger.Write($"Pokestop too far away!", LogLevel.Error); + break; + default: + Logger.Write($"Failed to use an {item.ItemId}!", LogLevel.Error); + break; + } + } + } +} diff --git a/PoGo.NecroBot.Logic/Tasks/UseGymBattleTask.cs b/PoGo.NecroBot.Logic/Tasks/UseGymBattleTask.cs new file mode 100644 index 000000000..82c3d8bbb --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/UseGymBattleTask.cs @@ -0,0 +1,1581 @@ +#region using directives + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.PoGoUtils; +using PoGo.NecroBot.Logic.State; +using POGOProtos.Inventory.Item; +using POGOProtos.Map.Fort; +using PoGo.NecroBot.Logic.Logging; +using POGOProtos.Networking.Responses; +using POGOProtos.Enums; +using PoGo.NecroBot.Logic.Event.Gym; +using PokemonGo.RocketAPI.Extensions; +using POGOProtos.Data; +using POGOProtos.Data.Battle; +using PokemonGo.RocketAPI.Exceptions; +using PoGo.NecroBot.Logic.Utils; + +#endregion + +namespace PoGo.NecroBot.Logic.Tasks +{ + public class UseGymBattleTask + { + private static DateTime AttackStart { get; set; } + private static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + private static FortDetailsResponse GymInfo { get; set; } + private static GymGetInfoResponse GymDetails { get; set; } + private static IEnumerable DeployedPokemons { get; set; } + private static FortData Gym { get; set; } + private static ISession Session; + + public const int MaxPlayers = 6; + + public static async Task Execute(ISession session, CancellationToken cancellationToken, FortData gym, FortDetailsResponse gymInfo, GymGetInfoResponse gymDetails) + { + cancellationToken.ThrowIfCancellationRequested(); + TinyIoC.TinyIoCContainer.Current.Resolve().ThrowIfSwitchAccountRequested(); + + if (!session.LogicSettings.GymConfig.Enable || gym.Type != FortType.Gym) return; + + Session = session; + + GymInfo = gymInfo; + Gym = gym; + GymDetails = gymDetails; + + DeployedPokemons = await Session.Inventory.GetDeployedPokemons().ConfigureAwait(false); + + if (Session.GymState.MoveSettings == null) + { + Session.GymState.MoveSettings = await Session.Inventory.GetMoveSettings().ConfigureAwait(false); + } + + await Session.GymState.LoadMyPokemons(Session).ConfigureAwait(false); + + var distance = Session.Navigation.WalkStrategy.CalculateDistance(Session.Client.CurrentLatitude, Session.Client.CurrentLongitude, Gym.Latitude, Gym.Longitude); + var player = Session.Profile.PlayerData; + + if (player.Team == TeamColor.Neutral && session.LogicSettings.GymConfig.DefaultTeam == "Neutral") + { + Logger.Write($"No team selected yet.. Gym battle functions are disabled.", LogLevel.Gym, ConsoleColor.White); + return; + } + + if (GymInfo != null) + { + Session.EventDispatcher.Send(new GymWalkToTargetEvent() + { + Name = GymInfo.Name, + Distance = distance, + Latitude = GymInfo.Latitude, + Longitude = GymInfo.Longitude + }); + + await EnsureJoinTeam(player).ConfigureAwait(false); + + Session.EventDispatcher.Send(new GymDetailInfoEvent() + { + Team = Gym.OwnedByTeam, + Players = GymDetails.GymStatusAndDefenders.GymDefender.Count(), + Name = GymDetails.Name, + }); + + if (Gym.OwnedByTeam == player.Team || Gym.OwnedByTeam == TeamColor.Neutral) + { + if (CanDeployToGym()) + await DeployPokemonToGym().ConfigureAwait(false); + + if (CanBerrieGym()) + SendBerriesLogic(); + } + else + { + if (CanAttackGym()) + await StartGymAttackLogic().ConfigureAwait(false); + } + + if (CanAttackRaid()) + await StartRaidAttackLogic().ConfigureAwait(false); + } + else + { + Logger.Write($"You are not level 5 yet, come back later...", LogLevel.Gym, ConsoleColor.White); + } + } + + private /*async*/ static void SendBerriesLogic() + { + /* + * Dev Mode + * + + ItemData item = new ItemData(); + PokemonData pokemon = new PokemonData(); + int startingQuantity = 1; + + var response = await _session.Client.Fort.GymFeedPokemon(_gym.Id, item.ItemId, pokemon.Id, startingQuantity).ConfigureAwait(false); + switch (response.Result) + { + case GymFeedPokemonResponse.Types.Result.Success: + Logger.Write($"Succes", LogLevel.Info); + break; + case GymFeedPokemonResponse.Types.Result.ErrorCannotUse: + Logger.Write($"Error Cannot Use {item.ItemId}!", LogLevel.Error); + break; + case GymFeedPokemonResponse.Types.Result.ErrorGymBusy: + Logger.Write($"Error Gym Busy!", LogLevel.Error); + break; + case GymFeedPokemonResponse.Types.Result.ErrorGymClosed: + Logger.Write($"Error Gym Closed!", LogLevel.Error); + break; + case GymFeedPokemonResponse.Types.Result.ErrorNoBerriesLeft: + Logger.Write($"Error No Berries Left!", LogLevel.Error); + break; + case GymFeedPokemonResponse.Types.Result.ErrorNotInRange: + Logger.Write($"Error Not In Range!", LogLevel.Error); + break; + case GymFeedPokemonResponse.Types.Result.ErrorPokemonFull: + Logger.Write($"Error Pokemon Full!", LogLevel.Error); + break; + case GymFeedPokemonResponse.Types.Result.ErrorPokemonNotThere: + Logger.Write($"Error Pokemon Not There!", LogLevel.Error); + break; + case GymFeedPokemonResponse.Types.Result.ErrorRaidActive: + Logger.Write($"Error Raid Active!", LogLevel.Error); + break; + case GymFeedPokemonResponse.Types.Result.ErrorTooFast: + Logger.Write($"Error Too Fast!", LogLevel.Error); + break; + case GymFeedPokemonResponse.Types.Result.ErrorTooFrequent: + Logger.Write($"Error Too Frequent!", LogLevel.Error); + break; + case GymFeedPokemonResponse.Types.Result.ErrorWrongCount: + Logger.Write($"Error Wrong Count!", LogLevel.Error); + break; + case GymFeedPokemonResponse.Types.Result.ErrorWrongTeam: + Logger.Write($"Error Wrong Team!", LogLevel.Error); + break; + case GymFeedPokemonResponse.Types.Result.Unset: + Logger.Write($"Unset!", LogLevel.Error); + break; + default: + Logger.Write($"Failed to use {item.ItemId}!", LogLevel.Error); + break; + } + */ + + Logger.Write("Send Berries not yet released.", LogLevel.Gym, ConsoleColor.Red); + } + + private async static Task StartRaidAttackLogic() + { + GetRaidDetailsResponse RaidDetails = await Session.Client.Fort.GetRaidDetails(Gym.Id, Gym.RaidInfo.RaidSeed).ConfigureAwait(false); + switch (RaidDetails.Result) + { + case GetRaidDetailsResponse.Types.Result.ErrorNotInRange: + Logger.Write("Raid Error Not In Range...", LogLevel.Gym, ConsoleColor.Green); + break; + case GetRaidDetailsResponse.Types.Result.ErrorPlayerBelowMinimumLevel: + Logger.Write("Raid Error Player Below Minimum Level...", LogLevel.Gym, ConsoleColor.Green); + break; + case GetRaidDetailsResponse.Types.Result.ErrorPoiInaccessible: + Logger.Write("Raid Error Poi Inaccessible...", LogLevel.Gym, ConsoleColor.Green); + break; + case GetRaidDetailsResponse.Types.Result.ErrorRaidCompleted: + Logger.Write("Raid Error Raid Completed...", LogLevel.Gym, ConsoleColor.Green); + break; + case GetRaidDetailsResponse.Types.Result.ErrorRaidUnavailable: + Logger.Write("Raid Error Raid Unavailable...", LogLevel.Gym, ConsoleColor.Green); + break; + case GetRaidDetailsResponse.Types.Result.Success: + DateTime expires = new DateTime(0); + TimeSpan time = new TimeSpan(0); + + if (RaidDetails.RaidInfo.RaidBattleMs > DateTime.UtcNow.ToUnixTime()) + { + expires = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(RaidDetails.RaidInfo.RaidBattleMs); + time = expires - DateTime.UtcNow; + if (!(expires.Ticks == 0 || time.TotalSeconds < 0)) + { + string str = $"Next RAID starts in: {time.Hours:00}h:{time.Minutes:00}m at: {(DateTime.Now + time).Hour:00}:{(DateTime.Now + time).Minute:00} Local time"; + Logger.Write($"{str}.", LogLevel.Gym); + } + } + + if (RaidDetails.RaidInfo.RaidPokemon.PokemonId != PokemonId.Missingno) + { + //Raid modes + expires = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(RaidDetails.RaidInfo.RaidEndMs); + time = expires - DateTime.UtcNow; + if (!(expires.Ticks == 0 || time.TotalSeconds < 0)) + { + /* + * Dev Mode + * + */ + Logger.Write("Raid boos is present. Raids battle not yet released.", LogLevel.Gym, ConsoleColor.Red); + string boss = $"Boss: {Session.Translation.GetPokemonTranslation(RaidDetails.RaidInfo.RaidPokemon.PokemonId)} CP: {RaidDetails.RaidInfo.RaidPokemon.Cp}"; + string str = $"Local RAID ends in: {time.Hours:00}h:{time.Minutes:00}m at: {(DateTime.Now + time).Hour:00}:{(DateTime.Now + time).Minute:00} Local time {boss}"; + Logger.Write($"{str}.", LogLevel.Gym); + + /* + IEnumerable raidBoss = new List + { + RaidDetails.RaidInfo.RaidPokemon + }; + + _defenders = raidBoss; + JoinLobbyResponse joinLobbyResult = await _session.Client.Fort.JoinLobby(_gym.Id, raidDetails.RaidInfo.RaidSeed, false).ConfigureAwait(false); + SetLobbyVisibilityResponse setLobbyVisibility = await _session.Client.Fort.SetLobbyVisibility(_gym.Id, raidDetails.RaidInfo.RaidSeed); + SetLobbyPokemonResponse setLobbyPokemon = await _session.Client.Fort.SetLobbyPokemon(_gym.Id, raidDetails.RaidInfo.RaidSeed); + StartRaidBattleResponse startRaidBattle = await _session.Client.Fort.StartRaidBattle(_gym.Id, raidDetails.RaidInfo.RaidSeed).ConfigureAwait(false); + AttackRaidBattleResponse attackRaid = await _session.Client.Fort.AttackRaidBattle(_gym.Id, raidDetails.RaidInfo.RaidSeed).ConfigureAwait(false); + LeaveLobbyResponse leaveLobbyResult = await _session.Client.Fort.LeaveLobby(_gym.Id, raidDetails.RaidInfo.RaidSeed); + */ + } + } + + if (RaidDetails.RaidInfo.RaidSpawnMs > DateTime.UtcNow.ToUnixTime()) + { + expires = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(RaidDetails.RaidInfo.RaidSpawnMs); + time = expires - DateTime.UtcNow; + if (!(expires.Ticks == 0 || time.TotalSeconds < 0)) + { + Logger.Write("Raid battle is runing...", LogLevel.Gym); + } + } + Logger.Write("Raid Success...", LogLevel.Gym, ConsoleColor.Green); + break; + case GetRaidDetailsResponse.Types.Result.Unset: + Logger.Write("Raid Unset...", LogLevel.Gym); + break; + default: + Logger.Write("Raid Unset...", LogLevel.Gym); + break; + } + } + + private static async Task StartGymAttackLogic() + { + int currentDefender = 0; + var _defenders = GymDetails.GymStatusAndDefenders.GymDefender.Select(x => x.MotivatedPokemon.Pokemon).ToList(); + + if (_defenders.Count() < 1 || Gym.OwnedByTeam == Session.Client.Player.PlayerData.Team) + { + return; + } + + var badassPokemon = await CompleteAttackTeam(_defenders).ConfigureAwait(false); + if (badassPokemon == null) + { + Logger.Write("Check gym settings, we can't compete against attackers team. Exiting.", LogLevel.Warning, ConsoleColor.Magenta); + return; + } + var pokemonDatas = badassPokemon as PokemonData[] ?? badassPokemon.ToArray(); + int allCp = 0; + foreach (var x in _defenders) + allCp = allCp + x.Cp; + + Logger.Write($"Gym global CP: {allCp}", LogLevel.Gym); + Logger.Write("Starting battle with: " + string.Join(", ", _defenders.Select(x => x.PokemonId.ToString())), LogLevel.Gym); + + foreach (var pokemon in pokemonDatas) + { + if (pokemon.Stamina <= 0) + await RevivePokemon(pokemon).ConfigureAwait(false); + + if (pokemon.Stamina <= 0) + { + Logger.Write("You are out of revive potions! Can't revive attacker", LogLevel.Gym, ConsoleColor.Magenta); + return; + } + + if (pokemon.Stamina < pokemon.StaminaMax) + await HealPokemon(pokemon).ConfigureAwait(false); + + if (pokemon.Stamina < pokemon.StaminaMax) + Logger.Write($"You are out of healing potions! {pokemon.PokemonId.ToString()} ({pokemon.Cp} CP) was not fully healed", LogLevel.Gym, ConsoleColor.Magenta); + } + + while (currentDefender < _defenders.Count()) + { + var defender = GymDetails.GymStatusAndDefenders.GymDefender[currentDefender].MotivatedPokemon.Pokemon.Id; + GymStartSessionResponse result = await GymStartSession(pokemonDatas, defender).ConfigureAwait(false); + + if (result.Result != GymStartSessionResponse.Types.Result.Success) + return; + + + + + + + + + Logger.Write("Attacking Team consists of:" + string.Join(", ", + Session.GymState.MyTeam.Select(s => string.Format("{0} ({1} HP / {2} CP)", + s.Attacker.PokemonId.ToString(), + s.HpState, + s.Attacker.Cp))), LogLevel.Gym, ConsoleColor.Yellow); + + + + //await Task.Delay(2000).ConfigureAwait(false); + List battleActions = new List(); + + switch (result.Battle.BattleLog.State) + { + case BattleState.Active: + AttackStart = DateTime.Now.AddSeconds(120); + Logger.Write($"Time to start Attack Mode", LogLevel.Gym, ConsoleColor.DarkYellow); + List thisAttackActions = new List(); + thisAttackActions = await AttackGym(result).ConfigureAwait(false); + //exit if gyms is disabled into config + if (thisAttackActions.Count < 1 && !Session.LogicSettings.GymConfig.Enable) + return; + battleActions.AddRange(thisAttackActions); + break; + case BattleState.Defeated: + //Logger.Write("Defeat to try again (10 sec)"); + //await Task.Delay(10000).ConfigureAwait(false); + //await Execute(Session, Session.CancellationTokenSource.Token, Gym, GymInfo, GymDetails).ConfigureAwait(false); + break; + case BattleState.StateUnset: + //Logger.Write("Gym Unset to try again (10 sec)"); + //await Task.Delay(10000).ConfigureAwait(false); + //await Execute(Session, Session.CancellationTokenSource.Token, Gym, GymInfo, GymDetails).ConfigureAwait(false); + break; + case BattleState.TimedOut: + Logger.Write("TimeOut to try again (10 sec)"); + if (Session.LogicSettings.NotificationConfig.EnablePushBulletNotification == true) + await PushNotificationClient.SendNotification(Session, "Gym Battle", $"Our attack timed out...:", true).ConfigureAwait(false); + await Task.Delay(10000).ConfigureAwait(false); + await Execute(Session, Session.CancellationTokenSource.Token, Gym, GymInfo, GymDetails).ConfigureAwait(false); + break; + case BattleState.Victory: + currentDefender++; + var lastAction = battleActions.LastOrDefault(); + var exp = lastAction.BattleResults.PlayerXpAwarded; + var defenderPokemonId = unchecked((ulong)lastAction.BattleResults.NextDefenderPokemonId); + + Logger.Write($"(Battle) XP: {exp} | Players: {_defenders.Count(),2:#0} | Next defender Id: {defenderPokemonId.ToString()}", LogLevel.Gym, ConsoleColor.Magenta); + + if (Session.LogicSettings.NotificationConfig.EnablePushBulletNotification == true) + await PushNotificationClient.SendNotification(Session, $"Gym Battle", + $"We were victorious!\n" + + $"XP: {exp}" + + $"Players: {_defenders.Count(),2:#0}", true).ConfigureAwait(false); // + + break; + default: + continue; + } + + var rewarded = battleActions.Select(x => x.BattleResults?.PlayerXpAwarded).Where(x => x != null); + var faintedPKM = battleActions.Where(x => x != null && x.Type == BattleActionType.ActionFaint).Select(x => x.ActivePokemonId).Distinct(); + var livePokemons = pokemonDatas.Where(x => !faintedPKM.Any(y => y == x.Id)); + var faintedPokemons = pokemonDatas.Where(x => faintedPKM.Any(y => y == x.Id)); + pokemonDatas = livePokemons.Concat(faintedPokemons).ToArray(); + } + + //Logger.Write(string.Join(Environment.NewLine, battleActions.OrderBy(o => o.ActionStartMs).Select(s => s).Distinct()), LogLevel.Gym, ConsoleColor.White); + } + + private static async Task DeployPokemonToGym() + { + try + { + var availableSlots = MaxPlayers - GymDetails.GymStatusAndDefenders.GymDefender.Count(); + + if (availableSlots > 0) + { + var deployed = await Session.Inventory.GetDeployedPokemons().ConfigureAwait(false); + if (!deployed.Any(a => a.DeployedFortId == GymInfo.FortId)) + { + PokemonData pokemon = await GetDeployablePokemon().ConfigureAwait(false); + if (pokemon != null) + { + GymDeployResponse response = new GymDeployResponse(await Session.Client.Fort.GymDeploy(GymInfo.FortId, pokemon.Id).ConfigureAwait(false)); + switch (response.Result) + { + case GymDeployResponse.Types.Result.ErrorAlreadyHasPokemonOnFort: + Session.EventDispatcher.Send(new GymEventMessages() { Message = "Failed to deploy pokemon. Result: Error Already Has Pokemon On Fort", consoleColor = ConsoleColor.Red }); + break; + case GymDeployResponse.Types.Result.ErrorFortDeployLockout: + Session.EventDispatcher.Send(new GymEventMessages() { Message = "Failed to deploy pokemon. Result: Error For tDeploy Lockout", consoleColor = ConsoleColor.Red }); + break; + case GymDeployResponse.Types.Result.ErrorFortIsFull: + Session.EventDispatcher.Send(new GymEventMessages() { Message = "Failed to deploy pokemon. Result: Error Fort Is Full", consoleColor = ConsoleColor.Red }); + break; + case GymDeployResponse.Types.Result.ErrorInvalidPokemon: + Session.EventDispatcher.Send(new GymEventMessages() { Message = "Failed to deploy pokemon. Result: Error Invalid Pokemon", consoleColor = ConsoleColor.Red }); + break; + case GymDeployResponse.Types.Result.ErrorLegendaryPokemon: + Session.EventDispatcher.Send(new GymEventMessages() { Message = "Failed to deploy pokemon. Result: Error Legendary Pokemon", consoleColor = ConsoleColor.Red }); + break; + case GymDeployResponse.Types.Result.ErrorNotAPokemon: + Session.EventDispatcher.Send(new GymEventMessages() { Message = "Failed to deploy pokemon. Result: Error Not A Pokemon", consoleColor = ConsoleColor.Red }); + break; + case GymDeployResponse.Types.Result.ErrorNotInRange: + Session.EventDispatcher.Send(new GymEventMessages() { Message = "Failed to deploy pokemon. Result: Error Not In Range", consoleColor = ConsoleColor.Red }); + break; + case GymDeployResponse.Types.Result.ErrorOpposingTeamOwnsFort: + Session.EventDispatcher.Send(new GymEventMessages() { Message = "Failed to deploy pokemon. Result: Error Opposing Team Owns Fort", consoleColor = ConsoleColor.Red }); + break; + case GymDeployResponse.Types.Result.ErrorPlayerBelowMinimumLevel: + Session.EventDispatcher.Send(new GymEventMessages() { Message = "Failed to deploy pokemon. Result: Error Player Below Minimum Level", consoleColor = ConsoleColor.Red }); + break; + case GymDeployResponse.Types.Result.ErrorPlayerHasNoNickname: + Session.EventDispatcher.Send(new GymEventMessages() { Message = "Failed to deploy pokemon. Result: Error Player Has No Nickname", consoleColor = ConsoleColor.Red }); + break; + case GymDeployResponse.Types.Result.ErrorPlayerHasNoTeam: + Session.EventDispatcher.Send(new GymEventMessages() { Message = "Failed to deploy pokemon. Result: Error Player Has No Team", consoleColor = ConsoleColor.Red }); + break; + case GymDeployResponse.Types.Result.ErrorPoiInaccessible: + Session.EventDispatcher.Send(new GymEventMessages() { Message = "Failed to deploy pokemon. Result: Error Poi Inaccessible", consoleColor = ConsoleColor.Red }); + break; + case GymDeployResponse.Types.Result.ErrorPokemonIsBuddy: + Session.EventDispatcher.Send(new GymEventMessages() { Message = "Failed to deploy pokemon. Result: Error Pokemon Is Buddy", consoleColor = ConsoleColor.Red }); + break; + case GymDeployResponse.Types.Result.ErrorPokemonNotFullHp: + Session.EventDispatcher.Send(new GymEventMessages() { Message = "Failed to deploy pokemon. Result: Error Pokemon Not Full Hp", consoleColor = ConsoleColor.Red }); + break; + case GymDeployResponse.Types.Result.ErrorRaidActive: + Session.EventDispatcher.Send(new GymEventMessages() { Message = "Failed to deploy pokemon. Result: Error Raid Active", consoleColor = ConsoleColor.Red }); + break; + case GymDeployResponse.Types.Result.ErrorTeamDeployLockout: + Session.EventDispatcher.Send(new GymEventMessages() { Message = "Failed to deploy pokemon. Result: Error Team Deploy Lockout", consoleColor = ConsoleColor.Red }); + break; + case GymDeployResponse.Types.Result.ErrorTooManyDeployed: + Session.EventDispatcher.Send(new GymEventMessages() { Message = "Failed to deploy pokemon. Result: Error Too Many Deployed", consoleColor = ConsoleColor.Red }); + break; + case GymDeployResponse.Types.Result.ErrorTooManyOfSameKind: + Session.EventDispatcher.Send(new GymEventMessages() { Message = "Failed to deploy pokemon. Result: Error Too Many Of Same Kind", consoleColor = ConsoleColor.Red }); + break; + case GymDeployResponse.Types.Result.NoResultSet: + Session.EventDispatcher.Send(new GymEventMessages() { Message = "Failed to deploy pokemon.Result: No Result Set", consoleColor = ConsoleColor.Red }); + break; + case GymDeployResponse.Types.Result.Success: + Session.EventDispatcher.Send(new GymDeployEvent() + { + PokemonId = pokemon.PokemonId, + GymGetInfo = GymDetails + }); + + Session.GymState.CapturedGymId = Gym.Id; + break; + } + } + else + Logger.Write($"You don't have any pokemon to be deployed!", LogLevel.Gym); + } + else + Logger.Write($"You already have pokemon deployed here", LogLevel.Gym); + } + else + { + int allCp = 0; + foreach (var x in GymDetails.GymStatusAndDefenders.GymDefender.Select(p => p.MotivatedPokemon.Pokemon).ToList()) + allCp = allCp + x.Cp; + + string message = string.Format("No FREE slots in GYM: {0}/{1} (All Cp: {2})", GymDetails.GymStatusAndDefenders.GymDefender.Count(), MaxPlayers, allCp); + Logger.Write(message, LogLevel.Gym, ConsoleColor.White); + } + } + catch (NullReferenceException e) + { + e.Data.Clear(); + Logger.Write("Error Null Reference Exception", LogLevel.Gym, ConsoleColor.Red); + } + } + + private static async Task> CompleteAttackTeam(IEnumerable defenders) + { + /* + * While i'm trying to make this gym attack i've made an error and complete team with the same one pokemon 6 times. + * Guess what, it was no error. More, fight in gym was successfull and this one pokemon didn't died once but after faint got max hp again and fight again. + * So after all we used only one pokemon. + * Maybe we can use it somehow. + */ + Session.GymState.MyTeam.Clear(); + + List attackers = new List(); + + if (Session.LogicSettings.GymConfig.UsePokemonToAttackOnlyByCp && defenders.Count() > 1) + { + attackers.AddRange(GetBestToTeam(attackers)); + attackers.ForEach(attacker => + { + Session.GymState.AddToTeam(Session, attacker); + }); + } + else + { + while (attackers.Count() < MaxPlayers) + { + foreach (var defender in defenders) + { + var attacker = await GetBestAgainst(attackers, defender).ConfigureAwait(false); + if (attacker != null) + { + //Trying to make bot only select pokemon that are more than 75% of full CP to battle. Still needs some work(The Wizard1328) + //if (attacker.Cp >= attacker.Cp * 0.75) + //{ + attackers.Add(attacker); + Session.GymState.AddToTeam(Session, attacker); + if (attackers.Count == MaxPlayers) + break; + //} + } + else return null; + } + } + } + return attackers; + } + + private static async Task GetBestAgainst(List myTeam, PokemonData defender) + { + Logger.Write($"Checking pokemon for {defender.PokemonId.ToString()} ({defender.Cp} CP).", LogLevel.Gym, ConsoleColor.White); + Session.GymState.AddPokemon(Session, defender, false); + AnyPokemonStat defenderStat = Session.GymState.OtherDefenders.FirstOrDefault(f => f.Data.Id == defender.Id); + + if (Session.LogicSettings.GymConfig.Attackers != null && Session.LogicSettings.GymConfig.Attackers.Count > 0) + { + var allPokemons = await Session.Inventory.GetPokemons().ConfigureAwait(false); + foreach (var def in Session.LogicSettings.GymConfig.Attackers.OrderByDescending(o => o.Priority)) + { + var attackersFromConfig = allPokemons.Where(w => + w.PokemonId == def.Pokemon && + w.Id != Session.Profile.PlayerData.BuddyPokemon?.Id && + !myTeam.Any(a => a.Id == w.Id) && + string.IsNullOrEmpty(w.DeployedFortId) && + w.Cp >= (def.MinCP ?? 0) && + w.Cp <= (def.MaxCP ?? 5000) && + def.IsMoveMatch(w.Move1, w.Move2) + ).ToList(); + + if (attackersFromConfig != null && attackersFromConfig.Count > 0) + return attackersFromConfig.OrderByDescending(o => o.Cp).FirstOrDefault(); + } + } + + MyPokemonStat myAttacker = Session.GymState.MyPokemons + .Where(w => + !myTeam.Any(a => a.Id == w.Data.Id) && //not already in team + string.IsNullOrEmpty(w.Data.DeployedFortId) && //not already deployed + Session.Profile.PlayerData.BuddyPokemon?.Id != w.Data.Id //not a buddy + ) + .OrderByDescending(o => o.TypeFactor[defenderStat.MainType] + o.TypeFactor[defenderStat.ExtraType]) + .ThenByDescending(o => o.Data.Cp) + .FirstOrDefault(); + + if (myAttacker == null || myAttacker.Data.Cp < (defender.Cp * Session.LogicSettings.GymConfig.ButNotLessThanDefenderPercent)) + { + var other = GetBestToTeam(myTeam).FirstOrDefault(); + Logger.Write($"Best against {defender.PokemonId.ToString()} {defender.Cp} CP with is {defenderStat.MainType} {defenderStat.ExtraType} can't be found, will use top by CP instead: {other?.PokemonId.ToString()} ({other?.Cp} CP) with attacks {other?.Move1} and {other?.Move2}", LogLevel.Gym, ConsoleColor.Cyan); + return other; + } + else + Logger.Write($"Best against {defender.PokemonId.ToString()} {defender.Cp} CP with is {defenderStat.MainType} {defenderStat.ExtraType} type will be {myAttacker.Data.PokemonId.ToString()} ({myAttacker.Data.Cp} CP) with attacks {myAttacker.Data.Move1} and {myAttacker.Data.Move2} (Factor for main type {myAttacker.TypeFactor[defenderStat.MainType]}, second {myAttacker.TypeFactor[defenderStat.ExtraType]}", LogLevel.Gym, ConsoleColor.Cyan); + return myAttacker.Data; + } + + private static PokemonData GetBestInBattle(PokemonData defender) + { + Session.GymState.AddPokemon(Session, defender, false); + AnyPokemonStat defenderStat = Session.GymState.OtherDefenders.FirstOrDefault(f => f.Data.Id == defender.Id); + List attacks = new List(GetBestTypes(defenderStat.MainType)); + + Logger.Write(string.Format("Searching for a new attacker against {0} ({1})", defender.PokemonId.ToString(), defenderStat.MainType), LogLevel.Gym, ConsoleColor.Blue); + + var moves = Session.GymState.MoveSettings.Where(w => attacks.Any(a => a == w.PokemonType)); + + PokemonData newAttacker = Session.GymState.MyTeam.Where(w => + moves.Any(a => a.MovementId == w.Attacker.Move1 || a.MovementId == w.Attacker.Move2) && //by move + w.HpState > 0 + ) + .OrderByDescending(o => o.Attacker.Cp) + .Select(s => s.Attacker) + .FirstOrDefault(); + + if (newAttacker == null) + { + Logger.Write("No best found, takeing by CP", LogLevel.Gym, ConsoleColor.Green); + newAttacker = Session.GymState.MyTeam.Where(w => w.HpState > 0) + .OrderByDescending(o => o.Attacker.Cp) + .Select(s => s.Attacker) + .FirstOrDefault(); + } + + if (newAttacker != null) + Logger.Write(string.Format("New atacker to switch will be {0} CP {1}", newAttacker.PokemonId.ToString(), newAttacker.Cp), LogLevel.Gym, ConsoleColor.Green); + + return newAttacker; + } + + private static IEnumerable GetBestToTeam(List myTeam) + { + var data = Session.GymState.MyPokemons.Where(w => + !myTeam.Any(a => a.Id == w.Data.Id) && //not already in team + string.IsNullOrEmpty(w.Data.DeployedFortId) && //not already deployed + Session.Profile.PlayerData.BuddyPokemon?.Id != w.Data.Id //not a buddy + ) + .Select(s => s.Data) + .OrderByDescending(o => o.Cp) + .Take(MaxPlayers - myTeam.Count()); + Logger.Write("Best others are: " + string.Join(", ", data.Select(s => s.PokemonId.ToString())), LogLevel.Gym, ConsoleColor.Green); + + return data; + } + + public static IEnumerable GetBestTypes(PokemonType defencTeype) + { + switch (defencTeype) + { + case PokemonType.Bug: + return new PokemonType[] { PokemonType.Rock, PokemonType.Fire, PokemonType.Flying }; + case PokemonType.Dark: + return new PokemonType[] { PokemonType.Bug, PokemonType.Fairy, PokemonType.Fighting }; + case PokemonType.Dragon: + return new PokemonType[] { PokemonType.Dragon, PokemonType.Fire, PokemonType.Ice }; + case PokemonType.Electric: + return new PokemonType[] { PokemonType.Ground }; + case PokemonType.Fairy: + return new PokemonType[] { PokemonType.Poison, PokemonType.Steel }; + case PokemonType.Fighting: + return new PokemonType[] { PokemonType.Fairy, PokemonType.Flying, PokemonType.Psychic }; + case PokemonType.Fire: + return new PokemonType[] { PokemonType.Ground, PokemonType.Rock, PokemonType.Water }; + case PokemonType.Flying: + return new PokemonType[] { PokemonType.Electric, PokemonType.Ice, PokemonType.Rock }; + case PokemonType.Ghost: + return new PokemonType[] { PokemonType.Dark, PokemonType.Ghost }; + case PokemonType.Grass: + return new PokemonType[] { PokemonType.Bug, PokemonType.Fire, PokemonType.Flying, PokemonType.Ice, PokemonType.Poison }; + case PokemonType.Ground: + return new PokemonType[] { PokemonType.Grass, PokemonType.Ice, PokemonType.Water }; + case PokemonType.Ice: + return new PokemonType[] { PokemonType.Fighting, PokemonType.Fire, PokemonType.Rock, PokemonType.Steel }; + case PokemonType.None: + return new PokemonType[] { }; + case PokemonType.Normal: + return new PokemonType[] { PokemonType.Fighting }; + case PokemonType.Poison: + return new PokemonType[] { PokemonType.Ground, PokemonType.Psychic }; + case PokemonType.Psychic: + return new PokemonType[] { PokemonType.Bug, PokemonType.Dark, PokemonType.Ghost }; + case PokemonType.Rock: + return new PokemonType[] { PokemonType.Fighting, PokemonType.Grass, PokemonType.Ground, PokemonType.Steel, PokemonType.Water }; + case PokemonType.Steel: + return new PokemonType[] { PokemonType.Fighting, PokemonType.Fire, PokemonType.Ground }; + case PokemonType.Water: + return new PokemonType[] { PokemonType.Electric, PokemonType.Grass }; + + default: + return null; + } + } + + public static IEnumerable GetWorstTypes(PokemonType defencTeype) + { + switch (defencTeype) + { + case PokemonType.Bug: + return new PokemonType[] { PokemonType.Fighting, PokemonType.Grass, PokemonType.Ground }; + case PokemonType.Dark: + return new PokemonType[] { PokemonType.Dark, PokemonType.Ghost }; + case PokemonType.Dragon: + return new PokemonType[] { PokemonType.Electric, PokemonType.Fire, PokemonType.Grass, PokemonType.Water }; + case PokemonType.Electric: + return new PokemonType[] { PokemonType.Electric, PokemonType.Flying, PokemonType.Steel }; + case PokemonType.Fairy: + return new PokemonType[] { PokemonType.Bug, PokemonType.Dark, PokemonType.Dragon, PokemonType.Fighting }; + case PokemonType.Fighting: + return new PokemonType[] { PokemonType.Bug, PokemonType.Dark, PokemonType.Rock }; + case PokemonType.Fire: + return new PokemonType[] { PokemonType.Bug, PokemonType.Fire, PokemonType.Fairy, PokemonType.Grass, PokemonType.Ice, PokemonType.Steel }; + case PokemonType.Flying: + return new PokemonType[] { PokemonType.Bug, PokemonType.Fighting, PokemonType.Grass }; + case PokemonType.Ghost: + return new PokemonType[] { PokemonType.Bug, PokemonType.Poison }; + case PokemonType.Grass: + return new PokemonType[] { PokemonType.Electric, PokemonType.Grass, PokemonType.Ground, PokemonType.Water }; + case PokemonType.Ground: + return new PokemonType[] { PokemonType.Poison, PokemonType.Rock }; + case PokemonType.Ice: + return new PokemonType[] { PokemonType.Ice }; + case PokemonType.None: + return new PokemonType[] { }; + case PokemonType.Normal: + return new PokemonType[] { }; + case PokemonType.Poison: + return new PokemonType[] { PokemonType.Bug, PokemonType.Fairy, PokemonType.Fighting, PokemonType.Grass, PokemonType.Poison }; + case PokemonType.Psychic: + return new PokemonType[] { PokemonType.Fighting, PokemonType.Psychic }; + case PokemonType.Rock: + return new PokemonType[] { PokemonType.Fire, PokemonType.Flying, PokemonType.Normal, PokemonType.Poison }; + case PokemonType.Steel: + return new PokemonType[] { PokemonType.Bug, PokemonType.Dragon, PokemonType.Fairy, PokemonType.Flying, PokemonType.Grass, PokemonType.Ice, PokemonType.Normal, PokemonType.Psychic, PokemonType.Rock, PokemonType.Steel }; + case PokemonType.Water: + return new PokemonType[] { PokemonType.Fire, PokemonType.Ice, PokemonType.Steel, PokemonType.Water }; + + default: + return null; + } + } + + private static async Task RevivePokemon(PokemonData pokemon) + { + int healPower = 0; + + if (Session.LogicSettings.GymConfig.SaveMaxRevives && await Session.Inventory.GetItemAmountByType(ItemId.ItemMaxPotion).ConfigureAwait(false) > 0) + healPower = Int32.MaxValue; + else + { + var normalPotions = await Session.Inventory.GetItemAmountByType(ItemId.ItemPotion).ConfigureAwait(false); + var superPotions = await Session.Inventory.GetItemAmountByType(ItemId.ItemSuperPotion).ConfigureAwait(false); + var hyperPotions = await Session.Inventory.GetItemAmountByType(ItemId.ItemHyperPotion).ConfigureAwait(false); + + healPower = normalPotions * 20 + superPotions * 50 + hyperPotions * 200; + } + + var normalRevives = await Session.Inventory.GetItemAmountByType(ItemId.ItemRevive).ConfigureAwait(false); + var maxRevives = await Session.Inventory.GetItemAmountByType(ItemId.ItemMaxRevive).ConfigureAwait(false); + + if ((healPower >= pokemon.StaminaMax / 2 || maxRevives == 0) && normalRevives > 0 && pokemon.Stamina <= 0) + { + var ret = await Session.Client.Inventory.UseItemRevive(ItemId.ItemRevive, pokemon.Id).ConfigureAwait(false); + switch (ret.Result) + { + case UseItemReviveResponse.Types.Result.Success: + await Session.Inventory.UpdateInventoryItem(ItemId.ItemRevive).ConfigureAwait(false); + pokemon.Stamina = ret.Stamina; + Session.EventDispatcher.Send(new EventUsedRevive + { + Type = "normal", + PokemonCp = pokemon.Cp, + PokemonId = pokemon.PokemonId.ToString(), + Remaining = (normalRevives - 1) + }); + break; + case UseItemReviveResponse.Types.Result.ErrorDeployedToFort: + Logger.Write( + $"Pokemon: {pokemon.PokemonId.ToString()} (CP: {pokemon.Cp}) is already deployed to a gym..."); + return; + case UseItemReviveResponse.Types.Result.ErrorCannotUse: + return; + default: + return; + } + return; + } + + if (maxRevives > 0 && pokemon.Stamina <= 0) + { + var ret = await Session.Client.Inventory.UseItemRevive(ItemId.ItemMaxRevive, pokemon.Id).ConfigureAwait(false); + switch (ret.Result) + { + case UseItemReviveResponse.Types.Result.Success: + await Session.Inventory.UpdateInventoryItem(ItemId.ItemMaxRevive).ConfigureAwait(false); + pokemon.Stamina = ret.Stamina; + Session.EventDispatcher.Send(new EventUsedRevive + { + Type = "max", + PokemonCp = pokemon.Cp, + PokemonId = pokemon.PokemonId.ToString(), + Remaining = (maxRevives - 1) + }); + break; + + case UseItemReviveResponse.Types.Result.ErrorDeployedToFort: + Logger.Write($"Pokemon: {pokemon.PokemonId.ToString()} (CP: {pokemon.Cp}) is already deployed to a gym..."); + return; + + case UseItemReviveResponse.Types.Result.ErrorCannotUse: + return; + + default: + return; + } + } + } + + private static async Task UsePotion(PokemonData pokemon, int normalPotions) + { + var ret = await Session.Client.Inventory.UseItemPotion(ItemId.ItemPotion, pokemon.Id).ConfigureAwait(false); + switch (ret.Result) + { + case UseItemPotionResponse.Types.Result.Success: + pokemon.Stamina = ret.Stamina; + Session.EventDispatcher.Send(new EventUsedPotion + { + Type = "normal", + PokemonCp = pokemon.Cp, + PokemonId = pokemon.PokemonId.ToString(), + Remaining = (normalPotions - 1) + }); + break; + + case UseItemPotionResponse.Types.Result.ErrorDeployedToFort: + Logger.Write($"Pokemon: {pokemon.PokemonId.ToString()} (CP: {pokemon.Cp}) is already deployed to a gym..."); + return false; + + case UseItemPotionResponse.Types.Result.ErrorCannotUse: + return false; + + default: + return false; + } + return true; + } + + private static async Task UseSuperPotion(PokemonData pokemon, int superPotions) + { + var ret = await Session.Client.Inventory.UseItemPotion(ItemId.ItemSuperPotion, pokemon.Id).ConfigureAwait(false); + switch (ret.Result) + { + case UseItemPotionResponse.Types.Result.Success: + pokemon.Stamina = ret.Stamina; + Session.EventDispatcher.Send(new EventUsedPotion + { + Type = "super", + PokemonCp = pokemon.Cp, + + PokemonId = pokemon.PokemonId.ToString(), + Remaining = (superPotions - 1) + }); + break; + + case UseItemPotionResponse.Types.Result.ErrorDeployedToFort: + Logger.Write($"Pokemon: {pokemon.PokemonId.ToString()} (CP: {pokemon.Cp}) is already deployed to a gym..."); + return false; + + case UseItemPotionResponse.Types.Result.ErrorCannotUse: + return false; + + default: + return false; + } + return true; + } + + private static async Task UseHyperPotion(PokemonData pokemon, int hyperPotions) + { + var ret = await Session.Client.Inventory.UseItemPotion(ItemId.ItemHyperPotion, pokemon.Id).ConfigureAwait(false); + switch (ret.Result) + { + case UseItemPotionResponse.Types.Result.Success: + pokemon.Stamina = ret.Stamina; + Session.EventDispatcher.Send(new EventUsedPotion + { + Type = "hyper", + PokemonCp = pokemon.Cp, + PokemonId = pokemon.PokemonId.ToString(), + Remaining = (hyperPotions - 1) + }); + break; + + case UseItemPotionResponse.Types.Result.ErrorDeployedToFort: + Logger.Write($"Pokemon: {pokemon.PokemonId.ToString()} (CP: {pokemon.Cp}) is already deployed to a gym..."); + return false; + + case UseItemPotionResponse.Types.Result.ErrorCannotUse: + return false; + + default: + return false; + } + return true; + } + + private static async Task UseMaxPotion(PokemonData pokemon, int maxPotions) + { + var ret = await Session.Client.Inventory.UseItemPotion(ItemId.ItemMaxPotion, pokemon.Id).ConfigureAwait(false); + switch (ret.Result) + { + case UseItemPotionResponse.Types.Result.Success: + pokemon.Stamina = ret.Stamina; + Session.EventDispatcher.Send(new EventUsedPotion + { + Type = "max", + PokemonCp = pokemon.Cp, + PokemonId = pokemon.PokemonId.ToString(), + Remaining = maxPotions + }); + break; + + case UseItemPotionResponse.Types.Result.ErrorDeployedToFort: + Logger.Write($"Pokemon: {pokemon.PokemonId.ToString()} (CP: {pokemon.Cp}) is already deployed to a gym..."); + return false; + + case UseItemPotionResponse.Types.Result.ErrorCannotUse: + return false; + + default: + return false; + } + return true; + } + + private static async Task HealPokemon(PokemonData pokemon) + { + var normalPotions = await Session.Inventory.GetItemAmountByType(ItemId.ItemPotion).ConfigureAwait(false); + var superPotions = await Session.Inventory.GetItemAmountByType(ItemId.ItemSuperPotion).ConfigureAwait(false); + var hyperPotions = await Session.Inventory.GetItemAmountByType(ItemId.ItemHyperPotion).ConfigureAwait(false); + var maxPotions = await Session.Inventory.GetItemAmountByType(ItemId.ItemMaxPotion).ConfigureAwait(false); + + var healPower = normalPotions * 20 + superPotions * 50 + hyperPotions * 200; + + if (healPower < (pokemon.StaminaMax - pokemon.Stamina) && maxPotions > 0) + { + try + { + if (await UseMaxPotion(pokemon, maxPotions).ConfigureAwait(false)) + { + await Session.Inventory.UpdateInventoryItem(ItemId.ItemMaxPotion).ConfigureAwait(false); + return true; + } + } + catch (APIBadRequestException) + { + Logger.Write(string.Format("Heal problem with max potions ({0}) on pokemon: {1}", maxPotions, pokemon.PokemonId.ToString()), LogLevel.Error, ConsoleColor.Magenta); + return false; + } + } + + while (normalPotions + superPotions + hyperPotions > 0 && (pokemon.Stamina < pokemon.StaminaMax)) + { + if (((pokemon.StaminaMax - pokemon.Stamina) > 200 || ((normalPotions * 20 + superPotions * 50) < (pokemon.StaminaMax - pokemon.Stamina))) && hyperPotions > 0) + { + if (!await UseHyperPotion(pokemon, hyperPotions).ConfigureAwait(false)) + return false; + hyperPotions--; + await Session.Inventory.UpdateInventoryItem(ItemId.ItemHyperPotion).ConfigureAwait(false); + } + else + if (((pokemon.StaminaMax - pokemon.Stamina) > 50 || normalPotions * 20 < (pokemon.StaminaMax - pokemon.Stamina)) && superPotions > 0) + { + if (!await UseSuperPotion(pokemon, superPotions).ConfigureAwait(false)) + return false; + superPotions--; + await Session.Inventory.UpdateInventoryItem(ItemId.ItemSuperPotion).ConfigureAwait(false); + } + else + { + if (!await UsePotion(pokemon, normalPotions).ConfigureAwait(false)) + return false; + normalPotions--; + await Session.Inventory.UpdateInventoryItem(ItemId.ItemPotion).ConfigureAwait(false); + } + } + + return pokemon.Stamina == pokemon.StaminaMax; + } + + private static async Task> AttackGym(GymStartSessionResponse startResponse) + { + PokemonData ActiveAttacker = startResponse.Battle.Attacker.ActivePokemon?.PokemonData; + PokemonData ActiveDefender = startResponse.Battle.Defender.ActivePokemon?.PokemonData; + List LastActions = startResponse.Battle.BattleLog.BattleActions.ToList(); + long ServerMs = startResponse.Battle.BattleLog.BattleStartTimestampMs; + List EmptyActions = new List(); + BattleAction EmptyAction = new BattleAction(); + int CurrentAttackerEnergy = 0; + + if (ActiveAttacker == null || ActiveDefender == null) + { + Logger.Write("Attacker or defender is NULL!!", LogLevel.Gym, ConsoleColor.Red); + return EmptyActions; + } + + Logger.Write($"Gym battle started; fighting trainer: {startResponse.Battle.Defender.TrainerPublicProfile.Name}", LogLevel.Gym, ConsoleColor.Green); + Logger.Write($"We are attacking: {startResponse.Battle.Defender.ActivePokemon.PokemonData.PokemonId.ToString()} ({startResponse.Battle.Defender.ActivePokemon.PokemonData.Cp} CP), Lvl: {startResponse.Battle.Defender.ActivePokemon.PokemonData.Level():0.0}", LogLevel.Gym, ConsoleColor.White); + Console.WriteLine(Environment.NewLine); + + if (Session.LogicSettings.NotificationConfig.EnablePushBulletNotification == true) + await PushNotificationClient.SendNotification(Session, $"Gym battle started", $"Trainer: {startResponse.Battle.Defender.TrainerPublicProfile.Name}\n" + + $"We are attacking: {startResponse.Battle.Defender.ActivePokemon.PokemonData.PokemonId.ToString()} ({startResponse.Battle.Defender.ActivePokemon.PokemonData.Cp} CP)\n" + + $"Lvl: {startResponse.Battle.Defender.ActivePokemon.PokemonData.Level():0.0}", true).ConfigureAwait(false); + + while (true) + { + //exit battle if gyms is disabled into config + if (!Session.LogicSettings.GymConfig.Enable) + return EmptyActions; + + Logger.Write("Starts loop", LogLevel.Gym); + var last = LastActions.Where(w => !Session.GymState.MyTeam.Any(a => a.Attacker.Id.Equals(w.ActivePokemonId))).LastOrDefault(); + BattleAction lastSpecialAttack = LastActions.Where(w => !Session.GymState.MyTeam.Any(a => a.Attacker.Id.Equals(w.ActivePokemonId)) && w.Type == BattleActionType.ActionSpecialAttack).LastOrDefault(); + + Logger.Write("Getting actions", LogLevel.Gym, ConsoleColor.White); + var attackActionz = (last == null || last.Type == BattleActionType.ActionVictory || last.Type == BattleActionType.ActionDefeat ? EmptyActions : GetActions(ServerMs, ActiveAttacker, ActiveDefender, CurrentAttackerEnergy, last, lastSpecialAttack)); + + Logger.Write(string.Format("Going to make attack : {0}", + string.Join(", ", attackActionz.Select(s => string.Format("{0} -> {1}", s.Type, s.DurationMs)))), LogLevel.Gym, ConsoleColor.Yellow); + + BattleAction a2 = (last == null || last.Type == BattleActionType.ActionVictory || last.Type == BattleActionType.ActionDefeat ? EmptyAction : last); + + Logger.Write("Start making attack", LogLevel.Gym, ConsoleColor.Green); + long timeBefore = DateTime.UtcNow.ToUnixTime(); + GymBattleAttackResponse attackResult = await Session.Client.Fort.GymBattleAttak(Gym.Id, startResponse.Battle.BattleId, attackActionz, a2, ServerMs).ConfigureAwait(false); + long timeAfter = DateTime.UtcNow.ToUnixTime(); + Logger.Write(string.Format("Finished making attack call: {0}", timeAfter - timeBefore), LogLevel.Gym, ConsoleColor.White); + + var attackTime = attackActionz.Sum(x => x.DurationMs); + int attackTimeCorrected = attackTime; + + if (attackTimeCorrected > 0) + await Task.Delay(attackTimeCorrected).ConfigureAwait(false); + + Logger.Write(string.Format("Waiting for attack to be prepared: {0} (last call was {1}, after correction {2})", + attackTime, timeAfter, attackTimeCorrected > 0 ? attackTimeCorrected : 0), LogLevel.Gym, ConsoleColor.Yellow); + + if (attackActionz.Any(a => a.Type != BattleActionType.ActionSpecialAttack)) + attackTimeCorrected = attackTime - (int)(timeAfter - timeBefore); + + if (attackActionz.Any(a => a.Type == BattleActionType.ActionSwapPokemon)) + { + Logger.Write("Extra wait after SWAP call", LogLevel.Gym); + await Task.Delay(3000).ConfigureAwait(false); + } + + if (attackResult.Result == GymBattleAttackResponse.Types.Result.Success) + { + if (attackResult.BattleUpdate.BattleLog.BattleActions.Count > 0) + { + var result = attackResult.BattleUpdate.BattleLog.BattleActions.OrderBy(o => o.ActionStartMs).Distinct(); + LastActions.AddRange(result); + } + + ServerMs = attackResult.BattleUpdate.BattleLog.ServerMs; + bool wasSwithed = false; + + switch (attackResult.BattleUpdate.BattleLog.State) + { + case BattleState.Active: + CurrentAttackerEnergy = attackResult.BattleUpdate.ActiveAttacker.CurrentEnergy; + int currentDefenderEnergy = attackResult.BattleUpdate.ActiveDefender.CurrentEnergy; + PokemonData attacker = attackResult.BattleUpdate.ActiveAttacker?.PokemonData; + PokemonData defender = attackResult.BattleUpdate.ActiveDefender?.PokemonData; + + if (ActiveAttacker == null) + ActiveAttacker = attacker; + else if (ActiveAttacker != null && ActiveAttacker.Id != attacker.Id) + { + bool extraWait = true; + bool informDie = true; + if (!wasSwithed && GymDetails.GymStatusAndDefenders.GymDefender.Count() > 1) + { + var newAttacker = GetBestInBattle(defender); + if (newAttacker != null && newAttacker.Id != attacker.Id) + { + if (!Session.LogicSettings.GymConfig.UsePokemonToAttackOnlyByCp) //we should manually switch pokemon to best one + Session.GymState.MyTeam.Where(w => w.Attacker.Id == attacker.Id).FirstOrDefault().HpState = 0; + + Session.GymState.SwithAttacker = new SwitchPokemonData(attacker.Id, newAttacker.Id); + wasSwithed = true; + informDie = false; //don't inform, we just prepared swap call... + extraWait = false; //don't wait, this is in swap call + } + } + else + { + wasSwithed = false; + informDie = false; //don't inform, we just prepared swap call... + extraWait = false; //don't wait, this is in swap call + } + if (informDie) + + Logger.Write(string.Format("Our Pokemon has fainted in battle, our new attacker is: {0} ({1} CP)", + attacker.PokemonId.ToString(), attacker.Cp), LogLevel.Gym, ConsoleColor.Red); + + + if (extraWait) + Logger.Write("Death penalty applied.", LogLevel.Gym, ConsoleColor.Red); + await Task.Delay(1000).ConfigureAwait(false); + } + + ActiveAttacker = attacker; + ActiveDefender = defender; + + var player = Session.Profile.PlayerData; + await EnsureJoinTeam(player).ConfigureAwait(false); + var ev = Gym.OwnedByTeam; + if (AttackStart > DateTime.Now) { AttackStart = DateTime.Now; } + + Logger.Write($"(DEFENDER): {defender.PokemonId.ToString(),-12} | HP: {attackResult.BattleUpdate.ActiveDefender.CurrentHealth,3:##0} | Sta: {currentDefenderEnergy,3:##0} | Lvl: {attackResult.BattleUpdate.ActiveDefender.PokemonData.Level(),4:#0.0}", LogLevel.Gym, + (ev == TeamColor.Red) + ? ConsoleColor.Red + : (ev == TeamColor.Yellow ? ConsoleColor.Yellow : ConsoleColor.Blue)); + + + + + + Logger.Write($"(ATTACKER): {attacker.PokemonId.ToString(),-12} | HP: {attackResult.BattleUpdate.ActiveAttacker.CurrentHealth,3:##0} | Sta: {CurrentAttackerEnergy,3:##0} | Lvl: {attackResult.BattleUpdate.ActiveAttacker.PokemonData.Level(),4:#0.0}", LogLevel.Gym, + (player.Team == TeamColor.Red) + ? ConsoleColor.Red + : (player.Team == TeamColor.Yellow ? ConsoleColor.Yellow : ConsoleColor.Blue)); + + + + + + + TimeSpan BattleTimer = DateTime.Now.Subtract(AttackStart); + + + + + + + + + + + + + + + + + Logger.Write($"Battle Timer: {100 - BattleTimer.TotalSeconds,3:##0} Sec remaining.", LogLevel.Info, ConsoleColor.White); + + if (attackResult != null && attackResult.BattleUpdate.ActiveAttacker != null) + Session.GymState.MyTeam.Where(w => w.Attacker.Id == attacker.Id).FirstOrDefault().HpState = attackResult.BattleUpdate.ActiveAttacker.CurrentHealth; + Logger.Write("Attack success... (AttackGym)", LogLevel.Gym, ConsoleColor.Green); + continue; + case BattleState.Defeated: + Logger.Write($"We have been defeated. Trying again in 10 sec... (AttackGym)", LogLevel.Gym, ConsoleColor.DarkYellow); + await Task.Delay(10000).ConfigureAwait(false); + await Execute(Session, Session.CancellationTokenSource.Token, Gym, GymInfo, GymDetails).ConfigureAwait(false); + return EmptyActions; + case BattleState.TimedOut: + Logger.Write($"Our attack timed out to try again (10 sec)... (AttackGym)", LogLevel.Gym, ConsoleColor.DarkYellow); + if (Session.LogicSettings.NotificationConfig.EnablePushBulletNotification == true) + await PushNotificationClient.SendNotification(Session, "Gym Battle", $"Our attack timed out...:", true).ConfigureAwait(false); + await Task.Delay(10000).ConfigureAwait(false); + await Execute(Session, Session.CancellationTokenSource.Token, Gym, GymInfo, GymDetails).ConfigureAwait(false); + return EmptyActions; + case BattleState.StateUnset: + Logger.Write($"State was unset... (AttackGym)", LogLevel.Gym, ConsoleColor.DarkYellow); + return EmptyActions; + case BattleState.Victory: + var defenderPokemonId = LastActions.LastOrDefault().BattleResults.NextDefenderPokemonId; + Logger.Write($"We were victorious... (AttackGym) XP: {LastActions.LastOrDefault().BattleResults.PlayerXpAwarded} | Players: {GymDetails.GymStatusAndDefenders.GymDefender.Count(),2:#0} | Next defender Id: {defenderPokemonId.ToString()}", LogLevel.Gym, ConsoleColor.Green); + await Task.Delay(2000).ConfigureAwait(false); + return LastActions; + default: + Logger.Write($"Unhandled attack response... (AttackGym)", LogLevel.Gym, ConsoleColor.DarkYellow); + return LastActions; + } + } + else + { + switch (attackResult.Result) + { + case GymBattleAttackResponse.Types.Result.ErrorInvalidAttackActions: + Logger.Write("Attack Error Invalid Attack Actions... (AttackGym)", LogLevel.Gym, ConsoleColor.Red); + break; + case GymBattleAttackResponse.Types.Result.ErrorNotInRange: + Logger.Write("Attack Error Not In Range... (AttackGym)", LogLevel.Gym, ConsoleColor.Red); + break; + case GymBattleAttackResponse.Types.Result.ErrorRaidActive: + Logger.Write("Attack Error Raid Active... (AttackGym)", LogLevel.Gym, ConsoleColor.Red); + break; + case GymBattleAttackResponse.Types.Result.ErrorWrongBattleType: + Logger.Write("Attack Error Wrong Battle Type... (AttackGym)", LogLevel.Gym, ConsoleColor.Red); + break; + case GymBattleAttackResponse.Types.Result.Unset: + Logger.Write("Attack Unset... (AttackGym)", LogLevel.Gym, ConsoleColor.Red); + break; + default: + Logger.Write("Attack Default... (AttackGym)", LogLevel.Gym, ConsoleColor.Red); + break; + } + return EmptyActions; + } + } + } + + private static DateTime DateTimeFromUnixTimestampMillis(long millis) + { + return UnixEpoch.AddMilliseconds(millis); + } + + private static List GetActions(long serverMs, PokemonData attacker, PokemonData defender, int energy, BattleAction lastAction, BattleAction lastSpecialAttack) + { + List actions = new List(); + DateTime now = DateTimeFromUnixTimestampMillis(serverMs); + const int beforeDodge = 200; + + if (Session.GymState.SwithAttacker != null) + { + actions.Add(new BattleAction() + { + Type = BattleActionType.ActionSwapPokemon, + DurationMs = Session.GymState.SwithAttacker.AttackDuration, + ActionStartMs = serverMs, + ActivePokemonId = Session.GymState.SwithAttacker.OldAttacker, + TargetPokemonId = Session.GymState.SwithAttacker.NewAttacker, + TargetIndex = -1, + }); + Logger.Write(string.Format("Trying to switch pokemon: {0} to: {1}, serverMs: {2}", Session.GymState.SwithAttacker.OldAttacker, Session.GymState.SwithAttacker.NewAttacker, serverMs), LogLevel.Gym, ConsoleColor.Yellow); + Session.GymState.SwithAttacker = null; + return actions; + } + + if (lastSpecialAttack != null && lastSpecialAttack.DamageWindowsStartTimestampMs > serverMs) + { + long dodgeTime = lastSpecialAttack.DamageWindowsStartTimestampMs - beforeDodge; + if (Session.GymState.TimeToDodge < dodgeTime) + Session.GymState.TimeToDodge = dodgeTime; + } + + if (attacker != null && defender != null) + { + var normalMove = Session.GymState.MyPokemons.FirstOrDefault(f => f.Data.Id == attacker.Id).Attack; + var specialMove = Session.GymState.MyPokemons.FirstOrDefault(f => f.Data.Id == attacker.Id).SpecialAttack; + bool skipDodge = ((lastSpecialAttack?.DurationMs ?? 0) < normalMove.DurationMs + 550) || Session.LogicSettings.GymConfig.UseDodge; //if our normal attack is too slow and defender special is too fast so we should to only do dodge all the time then we totally skip dodge + bool canDoSpecialAttack = Math.Abs(specialMove.EnergyDelta) <= energy && (!(Session.GymState.TimeToDodge > now.ToUnixTime() && Session.GymState.TimeToDodge < now.ToUnixTime() + specialMove.DurationMs) || skipDodge); + bool canDoAttack = !canDoSpecialAttack && (!(Session.GymState.TimeToDodge > now.ToUnixTime() && Session.GymState.TimeToDodge < now.ToUnixTime() + normalMove.DurationMs) || skipDodge); + + if (Session.GymState.TimeToDodge > now.ToUnixTime() && !canDoAttack && !canDoSpecialAttack && !skipDodge) + { + Session.GymState.LastWentDodge = now.ToUnixTime(); + + BattleAction dodge = new BattleAction() + { + Type = BattleActionType.ActionDodge, + ActionStartMs = now.ToUnixTime(), + DurationMs = 500, + TargetIndex = -1, + ActivePokemonId = attacker.Id, + }; + + Logger.Write(string.Format("Trying to dodge an attack {0}, lastSpecialAttack.DamageWindowsStartTimestampMs: {1}, serverMs: {2}", + dodge, lastSpecialAttack.DamageWindowsStartTimestampMs, serverMs), LogLevel.Gym, ConsoleColor.Cyan); + actions.Add(dodge); + } + else + { + BattleAction action2 = new BattleAction(); + if (canDoSpecialAttack) + { + action2.Type = BattleActionType.ActionSpecialAttack; + action2.DurationMs = specialMove.DurationMs; + action2.DamageWindowsStartTimestampMs = specialMove.DamageWindowStartMs; + action2.DamageWindowsEndTimestampMs = specialMove.DamageWindowEndMs; + Logger.Write(string.Format("Trying to make a special attack {0}, on: {1}, duration: {2}" + , specialMove.MovementId, GymInfo.Name, specialMove.DurationMs), LogLevel.Gym, ConsoleColor.Blue); + } + else if (canDoAttack) + { + action2.Type = BattleActionType.ActionAttack; + action2.DurationMs = normalMove.DurationMs; + action2.DamageWindowsStartTimestampMs = normalMove.DamageWindowStartMs; + action2.DamageWindowsEndTimestampMs = normalMove.DamageWindowEndMs; + Logger.Write(string.Format("Trying to make a normal attack {0}, on: {1}, duration: {2}" + , normalMove.MovementId, GymInfo.Name, normalMove.DurationMs), LogLevel.Gym, ConsoleColor.White); + } + else + { + Logger.Write("SHIT", LogLevel.Gym, ConsoleColor.Red); + } + action2.ActionStartMs = now.ToUnixTime(); + action2.TargetIndex = -1; + if (attacker.Stamina > 0) + action2.ActivePokemonId = attacker.Id; + action2.TargetPokemonId = defender.Id; + actions.Add(action2); + } + return actions; + } + BattleAction action1 = new BattleAction() + { + Type = BattleActionType.ActionDodge, + DurationMs = 500, + ActionStartMs = now.ToUnixTime(), + TargetIndex = -1 + }; + if (defender != null && attacker != null) + action1.ActivePokemonId = attacker.Id; + + actions.Add(action1); + return actions; + } + + private static async Task GymStartSession(IEnumerable attackers, ulong defenderId) + { + IEnumerable currentPokemons = attackers; + var pokemonDatas = currentPokemons as PokemonData[] ?? currentPokemons.ToArray(); + var attackerPokemons = pokemonDatas.Select(pokemon => pokemon.Id); + var attackingPokemonIds = attackerPokemons as ulong[] ?? attackerPokemons.ToArray(); + + await Task.Delay(2000).ConfigureAwait(false); + + var numTries = 3; + GymStartSessionResponse result = null; + + do + { + try + { + result = await Session.Client.Fort.GymStartSession(Gym.Id, defenderId, attackingPokemonIds).ConfigureAwait(false); + } + catch (Exception ex) + { + Logger.Write("Exception [GymStartSession]:" + ex.Message); + break; + } + switch (result.Result) + { + case GymStartSessionResponse.Types.Result.Unset: + Logger.Write("Failed with error UNSET", LogLevel.Gym, ConsoleColor.Red); + return result; + case GymStartSessionResponse.Types.Result.Success: + return result; + case GymStartSessionResponse.Types.Result.ErrorGymNotFound: + Logger.Write("Failed with error ERROR_GYM_NOT_FOUND", LogLevel.Gym, ConsoleColor.Red); + return result; + case GymStartSessionResponse.Types.Result.ErrorGymNeutral: + // Call to DeployPokemon + Logger.Write("Try to deploy", LogLevel.Gym, ConsoleColor.Blue); + await DeployPokemonToGym().ConfigureAwait(false); + return result; + case GymStartSessionResponse.Types.Result.ErrorGymWrongTeam: + Logger.Write("Failed with error ERROR_GYM_WRONG_TEAM", LogLevel.Gym, ConsoleColor.Red); + return result; + case GymStartSessionResponse.Types.Result.ErrorGymEmpty: + // Call to DeployPokemon + Logger.Write("Try to deploy", LogLevel.Gym, ConsoleColor.Blue); + await DeployPokemonToGym().ConfigureAwait(false); + return result; + case GymStartSessionResponse.Types.Result.ErrorInvalidDefender: + Logger.Write("Failed with error ERROR_INVALID_DEFENDER", LogLevel.Gym, ConsoleColor.Red); + // Call to DeployPokemon + //Logger.Write("Try to deploy", LogLevel.Gym, ConsoleColor.Blue); + //await DeployPokemonToGym().ConfigureAwait(false); + return result; + case GymStartSessionResponse.Types.Result.ErrorTrainingInvalidAttackerCount: + Logger.Write("Failed with error ERROR_TRAINING_INVALID_ATTACKER_COUNT", LogLevel.Gym, ConsoleColor.Red); + return result; + case GymStartSessionResponse.Types.Result.ErrorAllPokemonFainted: + Logger.Write("Failed with error ERROR_ALL_POKEMON_FAINTED", LogLevel.Gym, ConsoleColor.Red); + return result; + case GymStartSessionResponse.Types.Result.ErrorTooManyBattles: + Logger.Write("Failed with error ERROR_TOO_MANY_BATTLES", LogLevel.Gym, ConsoleColor.Red); + // Set to try later + return result; + case GymStartSessionResponse.Types.Result.ErrorTooManyPlayers: + Logger.Write("Failed with error ERROR_TOO_MANY_PLAYERS", LogLevel.Gym, ConsoleColor.Red); + return result; + case GymStartSessionResponse.Types.Result.ErrorGymBattleLockout: + Logger.Write("Failed with error ERROR_GYM_BATTLE_LOCKOUT", LogLevel.Gym, ConsoleColor.Red); + return result; + case GymStartSessionResponse.Types.Result.ErrorPlayerBelowMinimumLevel: + Logger.Write("Failed with error ERROR_PLAYER_BELOW_MINIMUM_LEVEL", LogLevel.Gym, ConsoleColor.Red); + return result; + case GymStartSessionResponse.Types.Result.ErrorNotInRange: + Logger.Write("Failed with error ERROR_NOT_IN_RANGE", LogLevel.Gym, ConsoleColor.Red); + return result; + case GymStartSessionResponse.Types.Result.ErrorPoiInaccessible: + Logger.Write("Failed with error ERROR_POI_INACCESSIBLE", LogLevel.Gym, ConsoleColor.Red); + return result; + case GymStartSessionResponse.Types.Result.ErrorRaidActive: + Logger.Write("Failed with error ERROR_RAID_ACTIVE", LogLevel.Gym, ConsoleColor.Red); + // Go to Battle Raid + return result; + } + Logger.Write("Start Gym Failed (" + numTries + "): " + new GymStartSessionResponse(), LogLevel.Gym, ConsoleColor.Red); + numTries--; + } while (numTries > 0 && result != null); + return new GymStartSessionResponse(); + } + + private static async Task EnsureJoinTeam(PlayerData player) + { + if (Session.Profile.PlayerData.Team == TeamColor.Neutral) + { + var defaultTeam = (TeamColor)Enum.Parse(typeof(TeamColor), Session.LogicSettings.GymConfig.DefaultTeam); + var teamResponse = await Session.Client.Player.SetPlayerTeam(defaultTeam).ConfigureAwait(false); + if (teamResponse.Status == SetPlayerTeamResponse.Types.Status.Success) + { + player.Team = defaultTeam; + } + + Session.EventDispatcher.Send(new GymTeamJoinEvent() + { + Team = defaultTeam, + Status = teamResponse.Status + }); + } + } + + private static bool CanAttackGym() + { + if (!Session.LogicSettings.GymConfig.EnableAttackGym || !Session.LogicSettings.GymConfig.Enable) + return false; + + try + { + if (Gym.RaidInfo.RaidPokemon.PokemonId != PokemonId.Missingno) + return false; + } + catch + { + // + } + + return true; + } + + private static bool CanAttackRaid() + { + if (!Session.LogicSettings.GymConfig.EnableAttackRaid || !Session.LogicSettings.GymConfig.Enable) + return false; + + try + { + if (Gym.RaidInfo.RaidPokemon.PokemonId != PokemonId.Missingno) + return true; + } + catch + { + // + } + + return false; + } + + private static bool CanBerrieGym() + { + if (!Session.LogicSettings.GymConfig.EnableGymBerries || !Session.LogicSettings.GymConfig.Enable) + return false; + + //Only berries if my pokemon is into gym + if (DeployedPokemons.Any(a => a.DeployedFortId.Equals(Gym.Id))) + return true; + + try + { + if (Gym.RaidInfo.RaidPokemon.PokemonId != PokemonId.Missingno) + return false; + } + catch + { + // + } + + return false; + } + + private static bool CanDeployToGym() + { + if (!Session.LogicSettings.GymConfig.EnableDeployPokemon || !Session.LogicSettings.GymConfig.Enable) + return false; + + if (!(GymDetails.GymStatusAndDefenders.GymDefender.Count() < MaxPlayers)) + return false; + + try + { + if (Gym.RaidInfo.RaidPokemon.PokemonId != PokemonId.Missingno) + return false; + } + catch + { + // + } + + return true; + } + + private static async Task GetDeployablePokemon() + { + List excluded = new List(); + var pokemonList = (await Session.Inventory.GetPokemons().ConfigureAwait(false)).ToList(); + PokemonData pokemon = null; + + if (Session.LogicSettings.GymConfig.Defenders != null && Session.LogicSettings.GymConfig.Defenders.Count > 0) + { + foreach (var def in Session.LogicSettings.GymConfig.Defenders.OrderByDescending(o => o.Priority)) + { + var defendersFromConfig = pokemonList.Where(w => + w.PokemonId == def.Pokemon && + w.Id != Session.Profile.PlayerData.BuddyPokemon?.Id && + string.IsNullOrEmpty(w.DeployedFortId) && + w.Cp >= (def.MinCP ?? 0) && + w.Cp <= (def.MaxCP ?? 5000) && + def.IsMoveMatch(w.Move1, w.Move2) + ).ToList(); + + if (defendersFromConfig != null && defendersFromConfig.Count > 0) + foreach (var _pokemon in defendersFromConfig.OrderByDescending(o => o.Cp)) + { + if (Session.LogicSettings.GymConfig.HealDefendersBeforeApplyToGym) + { + if (_pokemon.Stamina <= 0) + await RevivePokemon(_pokemon).ConfigureAwait(false); + + if (_pokemon.Stamina < _pokemon.StaminaMax && _pokemon.Stamina > 0) + await HealPokemon(_pokemon).ConfigureAwait(false); + } + + if (_pokemon.Stamina < _pokemon.StaminaMax) + excluded.Add(_pokemon.Id); + else + return _pokemon; + } + + } + + while (pokemon == null) + { + pokemonList = pokemonList + .Where(w => !excluded.Contains(w.Id) && w.Id != Session.Profile.PlayerData.BuddyPokemon?.Id) + .OrderByDescending(p => p.Cp) + .ToList(); + + if (pokemonList.Count == 0) + return null; + + if (pokemonList.Count == 1) + pokemon = pokemonList.FirstOrDefault(); + + if (Session.LogicSettings.GymConfig.UseRandomPokemon && pokemon == null) + pokemon = pokemonList.ElementAt(new Random().Next(0, pokemonList.Count - 1)); + + pokemon = pokemonList.FirstOrDefault(p => + p.Cp <= Session.LogicSettings.GymConfig.MaxCPToDeploy && + PokemonInfo.GetLevel(p) <= Session.LogicSettings.GymConfig.MaxLevelToDeploy && + string.IsNullOrEmpty(p.DeployedFortId) + ); + + if (Session.LogicSettings.GymConfig.HealDefendersBeforeApplyToGym) + { + if (pokemon.Stamina <= 0) + await RevivePokemon(pokemon).ConfigureAwait(false); + + if (pokemon.Stamina < pokemon.StaminaMax && pokemon.Stamina > 0) + await HealPokemon(pokemon).ConfigureAwait(false); + } + + if (pokemon.Stamina < pokemon.StaminaMax) + { + excluded.Add(pokemon.Id); + pokemon = null; + } + } + } + return pokemon; + } + } +} diff --git a/PoGo.NecroBot.Logic/Tasks/UseIncenseConstantlyTask.cs b/PoGo.NecroBot.Logic/Tasks/UseIncenseConstantlyTask.cs new file mode 100644 index 000000000..801df8cf5 --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/UseIncenseConstantlyTask.cs @@ -0,0 +1,50 @@ +using System.Threading; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.Logging; +using PoGo.NecroBot.Logic.State; +using POGOProtos.Inventory.Item; +using POGOProtos.Networking.Responses; +using TinyIoC; +using System; + +namespace PoGo.NecroBot.Logic.Tasks +{ + public class UseIncenseConstantlyTask + { + public static async Task Execute(ISession session, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + TinyIoC.TinyIoCContainer.Current.Resolve().ThrowIfSwitchAccountRequested(); + + var currentAmountOfIncense = await session.Inventory.GetItemAmountByType(ItemId.ItemIncenseOrdinary).ConfigureAwait(false); + if (currentAmountOfIncense == 0) + { + Logger.Write(session.Translation.GetTranslation(TranslationString.NoIncenseAvailable)); + return; + } + else + { + Logger.Write(session.Translation.GetTranslation(TranslationString.UseIncenseAmount, currentAmountOfIncense)); + } + + var UseIncense = await session.Inventory.UseIncenseConstantly().ConfigureAwait(false); + + if (UseIncense.Result == UseIncenseResponse.Types.Result.Success) + { + var totalMS = UseIncense.AppliedIncense.ExpireMs - UseIncense.AppliedIncense.AppliedMs; + + TinyIoCContainer.Current.Resolve().DisableSwitchAccountUntil(DateTime.Now.AddMilliseconds(totalMS)); + Logger.Write(session.Translation.GetTranslation(TranslationString.UsedIncense)); + } + else if (UseIncense.Result == UseIncenseResponse.Types.Result.NoneInInventory) + { + Logger.Write(session.Translation.GetTranslation(TranslationString.NoIncenseAvailable)); + } + else if (UseIncense.Result == UseIncenseResponse.Types.Result.IncenseAlreadyActive || (UseIncense.AppliedIncense == null)) + { + Logger.Write(session.Translation.GetTranslation(TranslationString.UseIncenseActive)); + } + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Tasks/UseIncenseTask.cs b/PoGo.NecroBot.Logic/Tasks/UseIncenseTask.cs new file mode 100644 index 000000000..89a7b8567 --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/UseIncenseTask.cs @@ -0,0 +1,36 @@ +using System; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Logging; +using PoGo.NecroBot.Logic.State; +using POGOProtos.Inventory.Item; +using POGOProtos.Networking.Responses; + +namespace PoGo.NecroBot.Logic.Tasks +{ + public class UseIncenseTask + { + public static async Task Execute(ISession session) + { + var response = await session.Client.Inventory.UseIncense(ItemId.ItemIncenseOrdinary).ConfigureAwait(false); + switch (response.Result) + { + case UseIncenseResponse.Types.Result.Success: + Logger.Write($"Incense is Valid until: {DateTime.Now.AddMinutes(30)}"); + break; + case UseIncenseResponse.Types.Result.IncenseAlreadyActive: + Logger.Write($"An incense is already active!", LogLevel.Warning); + break; + case UseIncenseResponse.Types.Result.LocationUnset: + Logger.Write($"Bot must be running first!", LogLevel.Error); + break; + case UseIncenseResponse.Types.Result.Unknown: + break; + case UseIncenseResponse.Types.Result.NoneInInventory: + break; + default: + Logger.Write($"Failed to use an incense!", LogLevel.Error); + break; + } + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Tasks/UseIncubatorsTask.cs b/PoGo.NecroBot.Logic/Tasks/UseIncubatorsTask.cs new file mode 100644 index 000000000..3fa34944b --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/UseIncubatorsTask.cs @@ -0,0 +1,229 @@ +#region using directives + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Newtonsoft.Json; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.PoGoUtils; +using PoGo.NecroBot.Logic.State; +using POGOProtos.Inventory.Item; +using POGOProtos.Networking.Responses; +using PoGo.NecroBot.Logic.Logging; +using PoGo.NecroBot.Logic.Utils; + +#endregion + +namespace PoGo.NecroBot.Logic.Tasks +{ + public class UseIncubatorsTask + { // Still working on this-for NecroBot window console to update egg KM for egg with least KM remaining TheWizard1328 + public static double KmToWalk { get; set; } + public static long Exp { get; set; } + public static long Stardust { get; set; } + public static object eggIncubatorStatusEvent { get; private set; } + + public static async Task Execute(ISession session, CancellationToken cancellationToken, + ulong eggId, string incubatorId) + { + var incubators = (await session.Inventory.GetEggIncubators().ConfigureAwait(false)) + .Where(x => x.UsesRemaining > 0 || x.ItemId == ItemId.ItemIncubatorBasicUnlimited) + .FirstOrDefault(x => x.Id == incubatorId); + + var unusedEggs = (await session.Inventory.GetEggs().ConfigureAwait(false)) + .Where(x => string.IsNullOrEmpty(x.EggIncubatorId)) + .FirstOrDefault(x => x.Id == eggId); + + if (incubators == null || unusedEggs == null) return; + + var rememberedIncubatorsFilePath = Path.Combine(session.LogicSettings.ProfilePath, "temp", "incubators.json"); + var rememberedIncubators = GetRememberedIncubators(rememberedIncubatorsFilePath); + var response = await session.Client.Inventory.UseItemEggIncubator(incubators.Id, unusedEggs.Id).ConfigureAwait(false); + var newRememberedIncubators = new List(); + + if (response.Result == UseItemEggIncubatorResponse.Types.Result.Success) + { + newRememberedIncubators.Add(new IncubatorUsage { IncubatorId = incubators.Id, PokemonId = unusedEggs.Id }); + + session.EventDispatcher.Send(new EggIncubatorStatusEvent + { + IncubatorId = incubators.Id, + WasAddedNow = true, + PokemonId = unusedEggs.Id, + KmToWalk = unusedEggs.EggKmWalkedTarget, + KmRemaining = response.EggIncubator.TargetKmWalked + }); + + if (!newRememberedIncubators.SequenceEqual(rememberedIncubators)) + SaveRememberedIncubators(newRememberedIncubators, rememberedIncubatorsFilePath); + } + else + { + //error output + } + } + + public static async Task Execute(ISession session, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + TinyIoC.TinyIoCContainer.Current.Resolve().ThrowIfSwitchAccountRequested(); + + try + { + var playerStats = (await session.Inventory.GetPlayerStats().ConfigureAwait(false)).FirstOrDefault(); + if (playerStats == null) + return; + + var kmWalked = playerStats.KmWalked; + + var incubators = (await session.Inventory.GetEggIncubators().ConfigureAwait(false)) + .Where(x => x.UsesRemaining > 0 || x.ItemId == ItemId.ItemIncubatorBasicUnlimited) + .OrderByDescending(x => x.ItemId == ItemId.ItemIncubatorBasicUnlimited) + .ToList(); + + var unusedEggs = (await session.Inventory.GetEggs().ConfigureAwait(false)) + .Where(x => string.IsNullOrEmpty(x.EggIncubatorId)) + .OrderBy(x => x.EggKmWalkedTarget - x.EggKmWalkedStart) + .ToList(); + + var rememberedIncubatorsFilePath = Path.Combine(session.LogicSettings.ProfilePath, "temp", "incubators.json"); + var rememberedIncubators = GetRememberedIncubators(rememberedIncubatorsFilePath); + var pokemons = (await session.Inventory.GetPokemons().ConfigureAwait(false)).ToList(); + var eggIncubatorStatusEvent = new EggIncubatorStatusEvent(); + + // Check if eggs in remembered incubator usages have since hatched + // (instead of calling session.Client.Inventory.GetHatchedEgg(), which doesn't seem to work properly) + foreach (var incubator in rememberedIncubators) + { + var hatched = pokemons.FirstOrDefault(x => !x.IsEgg && x.Id == incubator.PokemonId); + if (hatched == null) continue; + + //Still Needs some work - TheWizard1328 + var stats = session.RuntimeStatistics; // Total Km walked + var KMs = eggIncubatorStatusEvent.KmToWalk; //playerStats.KmWalked - hatched.EggKmWalkedStart; // Total Km Walked(hatched.EggKmWalkedStart=0) + var stardust1 = session.Inventory.GetStarDust(); // Total trainer Stardust + var stardust2 = stats.TotalStardust; // Total trainer Stardust + var ExpAwarded1 = playerStats.Experience; // Total Player Exp - TheWizard1328 + var ExpAwarded2 = stats.TotalExperience; // Session Exp - TheWizard1328 + var TotCandy = session.Inventory.GetCandyCount(hatched.PokemonId); + //Temp logger line personal testing info - TheWizard1328 +#if DEBUG + Logger.Write($"Hatch: eISE.KmWalked: {eggIncubatorStatusEvent.KmWalked:0.00} | eISE.KmToWalk: {eggIncubatorStatusEvent.KmToWalk:0.00} | " + + $"XP1: {ExpAwarded1} | XP2: {ExpAwarded2} | SD1: {stardust1} | SD2: {stardust2}", LogLevel.Egg, ConsoleColor.DarkYellow); +#endif + if (session.LogicSettings.NotificationConfig.EnablePushBulletNotification) + await PushNotificationClient.SendNotification(session, $"Egg has hatched.", $"Pokemon: {hatched.PokemonId}\n" + + $"Lvl: {PokemonInfo.GetLevel(hatched)}\n" + + $"CP: {hatched.Cp}\n" + + $"IV: {Math.Round(PokemonInfo.CalculatePokemonPerfection(hatched), 2)}\n", true).ConfigureAwait(false); + + session.EventDispatcher.Send(new EggHatchedEvent + { + Dist = KMs, //Still working on this - TheWizard1328 + Id = hatched.Id, + PokemonId = hatched.PokemonId, + Level = PokemonInfo.GetLevel(hatched), + Cp = hatched.Cp, + MaxCp = PokemonInfo.CalculateMaxCp(hatched.PokemonId), + Perfection = Math.Round(PokemonInfo.CalculatePokemonPerfection(hatched), 2), + HXP = ExpAwarded1, + HSD = stardust2, // This still needs work too to display the total SD after hatching - TheWizard1328 + HCandy = await TotCandy, + }); + } + + var newRememberedIncubators = new List(); + + foreach (var incubator in incubators) + { + cancellationToken.ThrowIfCancellationRequested(); + TinyIoC.TinyIoCContainer.Current.Resolve().ThrowIfSwitchAccountRequested(); + if (incubator.PokemonId == 0) + { + // Unlimited incubators prefer short eggs, limited incubators prefer long eggs + // Special case: If only one incubator is available at all, it will prefer long eggs + var egg = (incubator.ItemId == ItemId.ItemIncubatorBasicUnlimited && incubators.Count > 1) + ? unusedEggs.FirstOrDefault() + : unusedEggs.LastOrDefault(); + + if (egg == null) + continue; + + // Skip (save) limited incubators depending on user choice in config + if (!session.LogicSettings.UseLimitedEggIncubators + && incubator.ItemId != ItemId.ItemIncubatorBasicUnlimited) + continue; + + var response = await session.Client.Inventory.UseItemEggIncubator(incubator.Id, egg.Id).ConfigureAwait(false); + unusedEggs.Remove(egg); + + newRememberedIncubators.Add(new IncubatorUsage { IncubatorId = incubator.Id, PokemonId = egg.Id }); + + session.EventDispatcher.Send(new EggIncubatorStatusEvent + { + IncubatorId = incubator.Id, + WasAddedNow = true, + PokemonId = egg.Id, + KmToWalk = egg.EggKmWalkedTarget, + KmRemaining = response.EggIncubator.TargetKmWalked - kmWalked + }); + } + else + { + newRememberedIncubators.Add(new IncubatorUsage + { + IncubatorId = incubator.Id, + PokemonId = incubator.PokemonId + }); + + session.EventDispatcher.Send(new EggIncubatorStatusEvent + { + IncubatorId = incubator.Id, + PokemonId = incubator.PokemonId, + KmToWalk = incubator.TargetKmWalked - incubator.StartKmWalked, + KmRemaining = incubator.TargetKmWalked - kmWalked + }); + } + } + + if (!newRememberedIncubators.SequenceEqual(rememberedIncubators)) + SaveRememberedIncubators(newRememberedIncubators, rememberedIncubatorsFilePath); + } + catch (Exception) + { + } + } + + private static List GetRememberedIncubators(string filePath) + { + Directory.CreateDirectory(Path.GetDirectoryName(filePath)); + + if (File.Exists(filePath)) + return JsonConvert.DeserializeObject>(File.ReadAllText(filePath, Encoding.UTF8)); + + return new List(0); + } + + private static void SaveRememberedIncubators(List incubators, string filePath) + { + Directory.CreateDirectory(Path.GetDirectoryName(filePath)); + + File.WriteAllText(filePath, JsonConvert.SerializeObject(incubators), Encoding.UTF8); + } + + private class IncubatorUsage : IEquatable + { + public string IncubatorId; + public ulong PokemonId; + + public bool Equals(IncubatorUsage other) + { + return other != null && other.IncubatorId == IncubatorId && other.PokemonId == PokemonId; + } + } + } +} diff --git a/PoGo.NecroBot.Logic/Tasks/UseItemMoveRerollTask.cs b/PoGo.NecroBot.Logic/Tasks/UseItemMoveRerollTask.cs new file mode 100644 index 000000000..2ef275413 --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/UseItemMoveRerollTask.cs @@ -0,0 +1,47 @@ +using PoGo.NecroBot.Logic.Logging; +using PoGo.NecroBot.Logic.State; +using POGOProtos.Data; +using POGOProtos.Inventory.Item; +using POGOProtos.Networking.Responses; +using System.Threading.Tasks; + +namespace PoGo.NecroBot.Logic.Tasks +{ + public class UseItemMoveRerollTask + { + public static async Task Execute(ISession session, ItemData item, PokemonData pokemondata) + { + var response = await session.Client.Inventory.UseItemMoveReroll(item.ItemId, pokemondata.Id).ConfigureAwait(false); + switch (response.Result) + { + case UseItemMoveRerollResponse.Types.Result.Success: + Logger.Write($"Success to use {item.ItemId}", LogLevel.Info); + break; + case UseItemMoveRerollResponse.Types.Result.InvalidPokemon: + Logger.Write($"Failed Invalid Pokemon!", LogLevel.Error); + break; + case UseItemMoveRerollResponse.Types.Result.ItemNotInInventory: + Logger.Write($"Error Item Not In Inventory!", LogLevel.Error); + break; + case UseItemMoveRerollResponse.Types.Result.NoOtherMoves: + Logger.Write($"Error No Other Moves!", LogLevel.Error); + break; + case UseItemMoveRerollResponse.Types.Result.NoPlayer: + Logger.Write($"No Player!", LogLevel.Error); + break; + case UseItemMoveRerollResponse.Types.Result.NoPokemon: + Logger.Write($"No Pokemon!", LogLevel.Error); + break; + case UseItemMoveRerollResponse.Types.Result.WrongItemType: + Logger.Write($"Wrong Item Type!", LogLevel.Error); + break; + case UseItemMoveRerollResponse.Types.Result.Unset: + Logger.Write($"Unset!", LogLevel.Warning); + break; + default: + Logger.Write($"Failed to use {item.ItemId}!", LogLevel.Warning); + break; + } + } + } +} diff --git a/PoGo.NecroBot.Logic/Tasks/UseLuckyEggConstantlyTask.cs b/PoGo.NecroBot.Logic/Tasks/UseLuckyEggConstantlyTask.cs new file mode 100644 index 000000000..cf4ef1004 --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/UseLuckyEggConstantlyTask.cs @@ -0,0 +1,31 @@ +using System.Threading; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.Logging; +using PoGo.NecroBot.Logic.State; +using POGOProtos.Inventory.Item; + +namespace PoGo.NecroBot.Logic.Tasks +{ + public class UseLuckyEggConstantlyTask + { + public static async Task Execute(ISession session, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + TinyIoC.TinyIoCContainer.Current.Resolve().ThrowIfSwitchAccountRequested(); + + var currentAmountOfLuckyEggs = await session.Inventory.GetItemAmountByType(ItemId.ItemLuckyEgg).ConfigureAwait(false); + if (currentAmountOfLuckyEggs == 0) + { + Logger.Write(session.Translation.GetTranslation(TranslationString.NoEggsAvailable)); + return; + } + else + { + Logger.Write(session.Translation.GetTranslation(TranslationString.UseLuckyEggAmount, currentAmountOfLuckyEggs)); + } + + await session.Inventory.UseLuckyEgg().ConfigureAwait(false); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Tasks/UseLuckyEggTask.cs b/PoGo.NecroBot.Logic/Tasks/UseLuckyEggTask.cs new file mode 100644 index 000000000..f20ad48f6 --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/UseLuckyEggTask.cs @@ -0,0 +1,13 @@ +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.State; + +namespace PoGo.NecroBot.Logic.Tasks +{ + public class UseLuckyEggTask + { + public static async Task Execute(ISession session) + { + await session.Inventory.UseLuckyEgg().ConfigureAwait(false); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Tasks/UseNearbyPokestopsTask.cs b/PoGo.NecroBot.Logic/Tasks/UseNearbyPokestopsTask.cs new file mode 100644 index 000000000..0592544fd --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/UseNearbyPokestopsTask.cs @@ -0,0 +1,649 @@ +#region using directives + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.Logging; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Utils; +using PokemonGo.RocketAPI.Extensions; +using POGOProtos.Map.Fort; +using POGOProtos.Networking.Responses; +using PoGo.NecroBot.Logic.Model; +using PoGo.NecroBot.Logic.Exceptions; +using PoGo.NecroBot.Logic.Model.Settings; +using TinyIoC; +using PokemonGo.RocketAPI.Util; +using POGOProtos.Inventory.Item; +using GeoCoordinatePortable; + +#endregion + +namespace PoGo.NecroBot.Logic.Tasks +{ + public class UseNearbyPokestopsTask + { + //add delegate + public delegate void LootPokestopDelegate(FortData pokestop); + + private static int _stopsHit; + private static int _randomStop; + private static Random _rc; //initialize pokestop random cleanup counter first time + private static int _storeRi; + private static int _randomNumber; + public static bool _pokestopLimitReached; + public static bool _pokestopTimerReached; + //private static double lastPokestopLat =0; + //private static double lastPokestopLng = 0; + + internal static void Initialize() + { + _stopsHit = 0; + _randomStop = 0; + _rc = new Random(); + _storeRi = _rc.Next(8, 15); + _randomNumber = _rc.Next(4, 11); + _pokestopLimitReached = false; + _pokestopTimerReached = false; + } + + public static async Task Execute(ISession session, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + TinyIoC.TinyIoCContainer.Current.Resolve().ThrowIfSwitchAccountRequested(); + //request map objects to referesh data. keep all fort in session + + var mapObjectTupe = await GetPokeStops(session).ConfigureAwait(false); + var pokeStop = await GetNextPokeStop(session).ConfigureAwait(false); + + while (pokeStop != null) + { + cancellationToken.ThrowIfCancellationRequested(); + // Exit this task if both catching and looting has reached its limits + await CheckLimit(session).ConfigureAwait(false); + + var fortInfo = pokeStop.Id.StartsWith(SetMoveToTargetTask.TARGET_ID) ? SetMoveToTargetTask.FakeFortInfo(pokeStop) : await session.Client.Fort.GetFort(pokeStop.Id, pokeStop.Latitude, pokeStop.Longitude).ConfigureAwait(false); + await WalkingToPokeStop(session, cancellationToken, pokeStop, fortInfo).ConfigureAwait(false); + await DoActionAtPokeStop(session, cancellationToken, pokeStop, fortInfo).ConfigureAwait(false); + + if (pokeStop.Type == FortType.Gym) + { + var fortDetails = await session.Client.Fort.GymGetInfo(pokeStop.Id, pokeStop.Latitude, pokeStop.Longitude).ConfigureAwait(false); + if (fortDetails.Result == GymGetInfoResponse.Types.Result.Success) + { + await UseGymBattleTask.Execute(session, cancellationToken, pokeStop, fortInfo, fortDetails).ConfigureAwait(false); + } + } + + if (!await SetMoveToTargetTask.IsReachedDestination(pokeStop, session, cancellationToken).ConfigureAwait(false)) + { + pokeStop.CooldownCompleteTimestampMs = DateTime.UtcNow.ToUnixTime() + 5 * 60 * 1000; //5 minutes to cooldown + session.AddForts(new List() { pokeStop }); //replace object in memory. + } + + if (session.LogicSettings.EnableHumanWalkingSnipe) + { + await MSniperServiceTask.Execute(session, cancellationToken).ConfigureAwait(false); + await HumanWalkSnipeTask.Execute(session, cancellationToken, pokeStop, fortInfo).ConfigureAwait(false); + } + + pokeStop = await GetNextPokeStop(session).ConfigureAwait(false); + } + } + + private static async Task CheckLimit(ISession session) + { + var manager = TinyIoCContainer.Current.Resolve(); + bool allowSwitch = manager.AllowSwitch(); + var multiConfig = session.LogicSettings.MultipleBotConfig; + + if (session.Stats.CatchThresholdExceeds(session, false) && + multiConfig.SwitchOnCatchLimit && + manager.AllowMultipleBot() && + allowSwitch) + { + throw new ActiveSwitchByRuleException(SwitchRules.CatchLimitReached, session.LogicSettings.CatchPokemonLimit); + } + if (session.Stats.SearchThresholdExceeds(session, false) && + multiConfig.SwitchOnPokestopLimit && + manager.AllowMultipleBot() && + allowSwitch) + { + throw new ActiveSwitchByRuleException(SwitchRules.SpinPokestopReached, session.LogicSettings.PokeStopLimit); + } + + if (session.Stats.CatchThresholdExceeds(session, false) && + session.Stats.SearchThresholdExceeds(session, false) + ) + { + if (manager.AllowMultipleBot() && allowSwitch) + { + throw new ActiveSwitchByRuleException(SwitchRules.SpinPokestopReached, session.LogicSettings.PokeStopLimit); + } + else + { + await Task.Delay(15 * 60 * 1000).ConfigureAwait(false); + } + } + } + + private static async Task WalkingToPokeStop(ISession session, CancellationToken cancellationToken, FortData pokeStop, FortDetailsResponse fortInfo) + { + var distance = LocationUtils.CalculateDistanceInMeters(session.Client.CurrentLatitude, + session.Client.CurrentLongitude, pokeStop.Latitude, pokeStop.Longitude); + + // we only move to the PokeStop, and send the associated FortTargetEvent, when not using GPX + // also, GPX pathing uses its own EggWalker and calls the CatchPokemon tasks internally. + if (!session.LogicSettings.UseGpxPathing) + { + var eggWalker = new EggWalker(1000, session); + + cancellationToken.ThrowIfCancellationRequested(); + TinyIoC.TinyIoCContainer.Current.Resolve().ThrowIfSwitchAccountRequested(); + // Always set the fort info in base walk strategy. + + var pokeStopDestination = new FortLocation(pokeStop.Latitude, pokeStop.Longitude, + await LocationUtils.GetElevation(session.ElevationService, pokeStop.Latitude, pokeStop.Longitude).ConfigureAwait(false), pokeStop, fortInfo); + + await session.Navigation.Move(pokeStopDestination, + async () => + { + await OnWalkingToPokeStopOrGym(session, pokeStop, cancellationToken).ConfigureAwait(false); + }, + session, + cancellationToken).ConfigureAwait(false); + + // we have moved this distance, so apply it immediately to the egg walker. + await eggWalker.ApplyDistance(distance, cancellationToken).ConfigureAwait(false); + } + } + private static DateTime lastCatch = DateTime.Now; + private static async Task OnWalkingToPokeStopOrGym(ISession session, FortData pokeStop, CancellationToken cancellationToken) + { + await MSniperServiceTask.Execute(session, cancellationToken).ConfigureAwait(false); + + //to avoid api call when walking. + //if (lastCatch < DateTime.Now.AddSeconds(-2)) + //{ + // Catch normal map Pokemon + await CatchNearbyPokemonsTask.Execute(session, cancellationToken).ConfigureAwait(false); + //Catch Incense Pokemon + await CatchIncensePokemonsTask.Execute(session, cancellationToken).ConfigureAwait(false); + lastCatch = DateTime.Now; + //} + + if (!session.LogicSettings.UseGpxPathing) + { + // Spin as long as we haven't reached the user defined limits + if (!_pokestopLimitReached && !_pokestopTimerReached) + { + await SpinPokestopNearBy(session, cancellationToken, pokeStop).ConfigureAwait(false); + } + } + } + public static async Task GetNextPokeStop(ISession session) + { + var priorityTarget = await SetMoveToTargetTask.GetTarget(session).ConfigureAwait(false); + if (priorityTarget != null) return priorityTarget; + + if (session.Forts == null || + session.Forts.Count == 0 || + session.Forts.Count(p => p.CooldownCompleteTimestampMs < DateTime.UtcNow.ToUnixTime()) == 0) + { + //TODO : A logic need to be add for handle this case? + }; + + //NOTE : This code is killing perfomance of BOT if GYM is turn on, need to refactor to avoid this hummer call API + + var forts = session.Forts.Where(p => p.CooldownCompleteTimestampMs < DateTime.UtcNow.ToUnixTime()) + .Where(x => x.Id != string.Empty).ToList(); + + forts = forts.OrderBy( + p => + session.Navigation.WalkStrategy.CalculateDistance( + session.Client.CurrentLatitude, + session.Client.CurrentLongitude, + p.Latitude, + p.Longitude, + session) + ).ToList(); + + if (session.LogicSettings.UseGpxPathing) + { + forts = forts.Where(p => LocationUtils.CalculateDistanceInMeters(p.Latitude, p.Longitude, session.Client.CurrentLatitude, session.Client.CurrentLongitude) < 40).ToList(); + } + + var reviveCount = (await session.Inventory.GetItems().ConfigureAwait(false)).Where(w => w.ItemId == POGOProtos.Inventory.Item.ItemId.ItemRevive || w.ItemId == POGOProtos.Inventory.Item.ItemId.ItemMaxRevive).Select(s => s.Count).Sum(); + if (session.LogicSettings.GymConfig.Enable && session.LogicSettings.GymConfig.MinRevivePotions > reviveCount) + { + // Filter out the gyms + forts = forts//Favorise battles .Where(p => p.CooldownCompleteTimestampMs < DateTime.UtcNow.ToUnixTime()) + .Where(x => x.Type == FortType.Gym).ToList(); + } + + if (session.LogicSettings.GymConfig.PrioritizeGymOverPokestop) + { + // Prioritize gyms over pokestops + var gyms = forts.Where(x => x.Type == FortType.Gym && + LocationUtils.CalculateDistanceInMeters(x.Latitude, x.Longitude, session.Client.CurrentLatitude, session.Client.CurrentLongitude) < session.LogicSettings.GymConfig.MaxDistance); + //.OrderBy(x => LocationUtils.CalculateDistanceInMeters(x.Latitude, x.Longitude, session.Client.CurrentLatitude, session.Client.CurrentLongitude)); + + if (session.LogicSettings.GymConfig.PrioritizeGymWithFreeSlot) + { + var freeSlots = new List(); + foreach (var gym in gyms) + { + if (gym.OwnedByTeam == session.Profile.PlayerData.Team) + { + var task = await session.Client.Fort.GymGetInfo(gym.Id, gym.Latitude, gym.Longitude).ConfigureAwait(false); + if (task.Result == GymGetInfoResponse.Types.Result.Success) + { + if (task.GymStatusAndDefenders.GymDefender.Count() < UseGymBattleTask.MaxPlayers) + freeSlots.Add(gym); + } + } + } + + if (freeSlots.Count() > 0) + return freeSlots.FirstOrDefault(); + } + + // Return the first gym in range. + if (gyms.Count() > 0) + return gyms.FirstOrDefault(); + } + + return forts.FirstOrDefault(); + } + + public static async Task SpinPokestopNearBy(ISession session, CancellationToken cancellationToken, FortData destinationFort = null) + { + if (session.Forts.Count() > 0) + { + var spinablePokestops = session.Forts.Where( + i => + ( + LocationUtils.CalculateDistanceInMeters( + session.Client.CurrentLatitude, session.Client.CurrentLongitude, + i.Latitude, i.Longitude) < 40 && + i.CooldownCompleteTimestampMs < DateTime.UtcNow.ToUnixTime() && + (destinationFort == null || destinationFort.Id != i.Id)) + ).ToList(); + + if (spinablePokestops.Count() > 0) + { + foreach (var pokeStop in spinablePokestops) + { + var fortInfo = await session.Client.Fort.GetFort(pokeStop.Id, pokeStop.Latitude, pokeStop.Longitude).ConfigureAwait(false); + await FarmPokestop(session, pokeStop, fortInfo, cancellationToken, true).ConfigureAwait(false); + } + } + session.AddForts(spinablePokestops); + } + } + + private static async Task DoActionAtPokeStop(ISession session, CancellationToken cancellationToken, FortData pokeStop, FortDetailsResponse fortInfo, bool doNotTrySpin = false) + { + //Catch Lure Pokemon + if (pokeStop.LureInfo != null) + { + // added for cooldowns + await Task.Delay(Math.Min(session.LogicSettings.DelayBetweenPlayerActions, 3000)).ConfigureAwait(false); + await CatchLurePokemonsTask.Execute(session, pokeStop, cancellationToken).ConfigureAwait(false); + } + + // Spin as long as we haven't reached the user defined limits + if (!_pokestopLimitReached && !_pokestopTimerReached) + { + await FarmPokestop(session, pokeStop, fortInfo, cancellationToken, doNotTrySpin).ConfigureAwait(false); + } + else + { + // We hit the pokestop limit but not the pokemon limit. So we want to set the cooldown on the pokestop so that + // we keep moving and don't walk back and forth between 2 pokestops. + pokeStop.CooldownCompleteTimestampMs = DateTime.UtcNow.ToUnixTime() + 5 * 60 * 1000; // 5 minutes to cooldown for pokestop. + } + + if (++_stopsHit >= _storeRi) //TODO: OR item/pokemon bag is full //check stopsHit against storeRI random without dividing. + { + _storeRi = _rc.Next(6, 12); //set new storeRI for new random value + _stopsHit = 0; + + if (session.LogicSettings.UseNearActionRandom) + { + await HumanRandomActionTask.Execute(session, cancellationToken).ConfigureAwait(false); + } + else + { + await RecycleItemsTask.Execute(session, cancellationToken).ConfigureAwait(false); + + if (session.LogicSettings.UseLuckyEggConstantly) + await UseLuckyEggConstantlyTask.Execute(session, cancellationToken).ConfigureAwait(false); + if (session.LogicSettings.UseIncenseConstantly) + await UseIncenseConstantlyTask.Execute(session, cancellationToken).ConfigureAwait(false); + if (session.LogicSettings.TransferDuplicatePokemon) + await TransferDuplicatePokemonTask.Execute(session, cancellationToken).ConfigureAwait(false); + if (session.LogicSettings.TransferWeakPokemon) + await TransferWeakPokemonTask.Execute(session, cancellationToken).ConfigureAwait(false); + if (EvolvePokemonTask.IsActivated(session)) + await EvolvePokemonTask.Execute(session, cancellationToken).ConfigureAwait(false); + if (session.LogicSettings.AutomaticallyLevelUpPokemon) + await LevelUpPokemonTask.Execute(session, cancellationToken).ConfigureAwait(false); + if (session.LogicSettings.RenamePokemon) + await RenamePokemonTask.Execute(session, cancellationToken).ConfigureAwait(false); + + await GetPokeDexCount.Execute(session, cancellationToken).ConfigureAwait(false); + } + } + } + private static int softbanCount = 0; + + public static async Task FarmPokestop(ISession session, FortData pokeStop, FortDetailsResponse fortInfo, CancellationToken cancellationToken, bool doNotRetry = false) + { + var manager = TinyIoC.TinyIoCContainer.Current.Resolve(); + + // If the cooldown is in the future than don't farm the pokestop. + if (pokeStop.CooldownCompleteTimestampMs > DateTime.UtcNow.ToUnixTime()) + return; + + if (session.Stats.SearchThresholdExceeds(session, true)) + { + if (manager.AllowMultipleBot() && session.LogicSettings.MultipleBotConfig.SwitchOnPokestopLimit) + { + throw new Exceptions.ActiveSwitchByRuleException(SwitchRules.SpinPokestopReached, session.LogicSettings.PokeStopLimit); + } + return; + } + + //await session.Client.Map.GetMapObjects().ConfigureAwait(false); + FortSearchResponse fortSearch; + var timesZeroXPawarded = 0; + var fortTry = 0; //Current check + int retryNumber = session.LogicSettings.ByPassSpinCount; //How many times it needs to check to clear softban + int zeroCheck = Math.Min(5, retryNumber); //How many times it checks fort before it thinks it's softban + + var distance = LocationUtils.CalculateDistanceInMeters(pokeStop.Latitude, pokeStop.Longitude, session.Client.CurrentLatitude, session.Client.CurrentLongitude); + //This should be < ## not > ##. > makes bot jump to pokestop if < then when in range will just spin. + if (distance < 50) //if (distance > 30) + { + await LocationUtils.UpdatePlayerLocationWithAltitude(session, new GeoCoordinate(pokeStop.Latitude, pokeStop.Longitude), 0).ConfigureAwait(false); + await session.Client.Misc.RandomAPICall().ConfigureAwait(false); + } + + do + { + cancellationToken.ThrowIfCancellationRequested(); + TinyIoC.TinyIoCContainer.Current.Resolve().ThrowIfSwitchAccountRequested(); + int retry = 3; + double latitude = pokeStop.Latitude; + double longitude = pokeStop.Longitude; + do + { + fortSearch = await session.Client.Fort.SearchFort(pokeStop.Id, pokeStop.Latitude, pokeStop.Longitude).ConfigureAwait(false); + if (fortSearch.Result == FortSearchResponse.Types.Result.OutOfRange) + { + if (retry > 2) + await Task.Delay(500).ConfigureAwait(false); + else + await session.Client.Map.GetMapObjects(true).ConfigureAwait(false); + + Logger.Debug($"Loot pokestop result: {fortSearch.Result}, distance to pokestop:[{pokeStop.Latitude}, {pokeStop.Longitude}] {distance:0.00}m, retry: #{4 - retry}"); + + latitude += 0.000003; + longitude += 0.000005; + await LocationUtils.UpdatePlayerLocationWithAltitude(session, new GeoCoordinate(latitude, longitude), 0).ConfigureAwait(false); + + retry--; + } + } + while (fortSearch.Result == FortSearchResponse.Types.Result.OutOfRange && retry > 0); + + if (fortSearch.Result == FortSearchResponse.Types.Result.PoiInaccessible) + { + Logger.Debug($"Loot {fortInfo.Type} result: {fortSearch.Result} trainer level must be >= 5 to access gym disk."); //, LogLevel.Gym, ConsoleColor.White); + break; + } + + if (fortSearch.ExperienceAwarded > 0 && timesZeroXPawarded > 0) timesZeroXPawarded = 0; + if (fortSearch.ExperienceAwarded == 0 && fortSearch.Result != FortSearchResponse.Types.Result.InventoryFull) + { + timesZeroXPawarded++; + + if (timesZeroXPawarded > zeroCheck) + { + if ((int)fortSearch.CooldownCompleteTimestampMs != 0) + { + break; // Check if successfully looted, if so program can continue as this was "false alarm". + } + + fortTry += 1; + + session.EventDispatcher.Send(new FortFailedEvent + { + Name = fortInfo.Name, + Try = fortTry, + Max = retryNumber - zeroCheck, + Looted = false + }); + if (doNotRetry) + { + break; + } + if (!session.LogicSettings.FastSoftBanBypass) + { + await DelayingUtils.DelayAsync(session.LogicSettings.DelayBetweenPlayerActions, 0, session.CancellationTokenSource.Token).ConfigureAwait(false); + } + } + } + else + { + softbanCount = 0; + if (fortTry != 0) + { + session.EventDispatcher.Send(new FortFailedEvent + { + Name = fortInfo.Name, + Try = fortTry + 1, + Max = retryNumber - zeroCheck, + Looted = true + }); + } + + session.EventDispatcher.Send(new FortUsedEvent + { + Id = pokeStop.Id, + Name = fortInfo.Name, + Exp = fortSearch.ExperienceAwarded, + Gems = fortSearch.GemsAwarded > 0 ? $"Yes {fortSearch.GemsAwarded}" : "No", + Items = StringUtils.GetSummedFriendlyNameOfItemAwardList(fortSearch.ItemsAwarded), + Badges = fortSearch.AwardedGymBadge != null ? fortSearch.AwardedGymBadge.GymBadgeType.ToString() : "No", + BonusLoot = fortSearch.BonusLoot != null ? StringUtils.GetSummedFriendlyNameOfGetLootList(fortSearch.BonusLoot.LootItem) : "No", + RaidTickets = fortSearch.RaidTickets > 0 ? $"{fortSearch.RaidTickets} tickets" : "No", + TeamBonusLoot = fortSearch.TeamBonusLoot != null ? StringUtils.GetSummedFriendlyNameOfGetLootList(fortSearch.TeamBonusLoot.LootItem) : "No", + PokemonDataEgg = fortSearch.PokemonDataEgg != null ? fortSearch.PokemonDataEgg : null, + Latitude = pokeStop.Latitude, + Longitude = pokeStop.Longitude, + Altitude = session.Client.CurrentAltitude, + InventoryFull = fortSearch.Result == FortSearchResponse.Types.Result.InventoryFull, + Fort = pokeStop + }); + + if (fortSearch.Result == FortSearchResponse.Types.Result.Success) + { + mapEmptyCount = 0; + foreach (var item in fortSearch.ItemsAwarded) + { + await session.Inventory.UpdateInventoryItem(item.ItemId).ConfigureAwait(false); + } + if (fortSearch.PokemonDataEgg != null) + { + fortSearch.PokemonDataEgg.IsEgg = true; + } + + // Update the cache + var fortFromCache = session.Client.Map.LastGetMapObjectResponse.MapCells.SelectMany(x => x.Forts).FirstOrDefault(f => f.Id == pokeStop.Id); + + long newCooldown = TimeUtil.GetCurrentTimestampInMilliseconds() + (5 * 60 * 1000); /* 5 min */ + fortFromCache.CooldownCompleteTimestampMs = newCooldown; + pokeStop.CooldownCompleteTimestampMs = newCooldown; + + if (session.SaveBallForByPassCatchFlee) + { + var totalBalls = (await session.Inventory.GetItems().ConfigureAwait(false)).Where(x => x.ItemId == ItemId.ItemPokeBall || x.ItemId == ItemId.ItemGreatBall || x.ItemId == ItemId.ItemUltraBall).Sum(x => x.Count); + Logger.Write($"Ball requires for by pass catch flee {totalBalls}/{CatchPokemonTask.BALL_REQUIRED_TO_BYPASS_CATCHFLEE}"); + } + else + MSniperServiceTask.UnblockSnipe(false); + } + if (fortSearch.Result == FortSearchResponse.Types.Result.InventoryFull) + { + await RecycleItemsTask.Execute(session, cancellationToken).ConfigureAwait(false); + _storeRi = 1; + } + + if (session.LogicSettings.UsePokeStopLimit) + { + session.Stats.AddPokestopTimestamp(DateTime.Now.Ticks); + session.EventDispatcher.Send(new PokestopLimitUpdate(session.Stats.GetNumPokestopsInLast24Hours(), session.LogicSettings.PokeStopLimit)); + } + //add pokeStops to Map + OnLootPokestopEvent(pokeStop); + //end pokeStop to Map + + break; //Continue with program as loot was succesfull. + } + } while (fortTry < retryNumber - zeroCheck); + //Stop trying if softban is cleaned earlier or if 40 times fort looting failed. + + if (manager.AllowMultipleBot()) + { + if (fortTry >= retryNumber - zeroCheck) + { + softbanCount++; + + //only check if PokestopSoftbanCount > 0 + if (MultipleBotConfig.IsMultiBotActive(session.LogicSettings, manager) && + session.LogicSettings.MultipleBotConfig.PokestopSoftbanCount > 0 && + session.LogicSettings.MultipleBotConfig.PokestopSoftbanCount <= softbanCount && + TinyIoCContainer.Current.Resolve().AllowSwitch()) + { + softbanCount = 0; + + //Activate switcher by pokestop + throw new ActiveSwitchByRuleException() + { + MatchedRule = SwitchRules.PokestopSoftban, + ReachedValue = session.LogicSettings.MultipleBotConfig.PokestopSoftbanCount + }; + } + } + } + else + { + softbanCount = 0; //reset softban count + } + + if (session.LogicSettings.RandomlyPauseAtStops && !doNotRetry) + { + if (++_randomStop >= _randomNumber) + { + _randomNumber = _rc.Next(4, 11); + _randomStop = 0; + int randomWaitTime = _rc.Next(30, 120); + await Task.Delay(randomWaitTime, cancellationToken).ConfigureAwait(false); + } + } + } + + private static int mapEmptyCount = 0; + //Please do not change GetPokeStops() in this file, it's specifically set + //to only find stops within 40 meters for GPX pathing, as we are not going to the pokestops, + //so do not make it more than 40 because it will never get close to those stops. + //For non GPX pathing, it returns all pokestops in range. + private static async Task, List>> GetPokeStops(ISession session) + { + var manager = TinyIoC.TinyIoCContainer.Current.Resolve(); + List mapObjects = await UpdateFortsData(session).ConfigureAwait(false); + session.AddForts(mapObjects); + + if (!session.LogicSettings.UseGpxPathing) + { + if (mapObjects.Count <= 0) + { + // only send this for non GPX because otherwise we generate false positives + session.EventDispatcher.Send(new WarnEvent + { + Message = session.Translation.GetTranslation(TranslationString.FarmPokestopsNoUsableFound) + }); + mapEmptyCount++; + if (mapEmptyCount == 30 && + manager.AllowMultipleBot() && + TinyIoCContainer.Current.Resolve().AllowSwitch()) + { + throw new ActiveSwitchByRuleException() { MatchedRule = SwitchRules.EmptyMap, ReachedValue = 30 }; + } + } + else + { + mapEmptyCount = 0; + } + + var pokeStops = mapObjects.Where(p => p.Type == FortType.Checkpoint).ToList(); + session.AddVisibleForts(pokeStops); + session.EventDispatcher.Send(new PokeStopListEvent(mapObjects)); + + var gyms = mapObjects.Where(p => p.Type == FortType.Gym).ToList(); + return Tuple.Create(pokeStops, gyms); + } + + if (mapObjects.Count > 0) + { + // only send when there are stops for GPX because otherwise we send empty arrays often + session.EventDispatcher.Send(new PokeStopListEvent(mapObjects)); + } + // Wasn't sure how to make this pretty. Edit as needed. + return Tuple.Create( + mapObjects.Where( + i => i.Type == FortType.Checkpoint && + ( // Make sure PokeStop is within 40 meters or else it is pointless to hit it + LocationUtils.CalculateDistanceInMeters( + session.Client.CurrentLatitude, session.Client.CurrentLongitude, + i.Latitude, i.Longitude) <= 40) + ).ToList(), + mapObjects.Where(p => p.Type == FortType.Gym && LocationUtils.CalculateDistanceInMeters( + session.Client.CurrentLatitude, session.Client.CurrentLongitude, + p.Latitude, p.Longitude) <= 40).ToList() + ); + } + + public static async Task> UpdateFortsData(ISession session) + { + var mapObjects = await session.Client.Map.GetMapObjects().ConfigureAwait(false); + + session.AddForts(mapObjects.MapCells.SelectMany(p => p.Forts).ToList()); + + var pokeStops = mapObjects.MapCells.SelectMany(i => i.Forts) + .Where( + i => + (i.Type == FortType.Checkpoint || i.Type == FortType.Gym) && + i.CooldownCompleteTimestampMs < DateTime.UtcNow.ToUnixTime() && + ( + LocationUtils.CalculateDistanceInMeters( + session.Settings.DefaultLatitude, session.Settings.DefaultLongitude, + i.Latitude, i.Longitude) <= session.LogicSettings.MaxTravelDistanceInMeters) + ); + + return pokeStops.ToList(); + } + //add delegate event + private static void OnLootPokestopEvent(FortData pokestop) + { + LootPokestopEvent?.Invoke(pokestop); + } + public static event LootPokestopDelegate LootPokestopEvent; + } +} diff --git a/PoGo.NecroBot.Logic/Tasks/UseRareCandyTask.cs b/PoGo.NecroBot.Logic/Tasks/UseRareCandyTask.cs new file mode 100644 index 000000000..ebd1ff6d9 --- /dev/null +++ b/PoGo.NecroBot.Logic/Tasks/UseRareCandyTask.cs @@ -0,0 +1,42 @@ +using PoGo.NecroBot.Logic.Logging; +using PoGo.NecroBot.Logic.State; +using POGOProtos.Data; +using POGOProtos.Inventory.Item; +using POGOProtos.Networking.Responses; +using System.Threading.Tasks; + +namespace PoGo.NecroBot.Logic.Tasks +{ + public class UseRareCandyTask + { + public static async Task Execute(ISession session, ItemData item, PokemonData pokemondata) + { + int candy = await session.Inventory.GetCandyCount(pokemondata.PokemonId).ConfigureAwait(false); + var response = await session.Client.Inventory.UseRareCandy(item.ItemId, pokemondata.PokemonId).ConfigureAwait(false); + switch (response.Result) + { + case UseItemRareCandyResponse.Types.Result.Success: + Logger.Write($"Success {candy} ===> {candy + 1}", LogLevel.Info); + break; + case UseItemRareCandyResponse.Types.Result.InvalidPokemonId: + Logger.Write($"Failed Invalid Pokemon!", LogLevel.Error); + break; + case UseItemRareCandyResponse.Types.Result.ItemNotInInventory: + Logger.Write($"Error Item Not In Inventory!", LogLevel.Error); + break; + case UseItemRareCandyResponse.Types.Result.NoPlayer: + Logger.Write($"No Player!", LogLevel.Error); + break; + case UseItemRareCandyResponse.Types.Result.WrongItemType: + Logger.Write($"Wrong Item Type!", LogLevel.Error); + break; + case UseItemRareCandyResponse.Types.Result.Unset: + Logger.Write($"Unset!", LogLevel.Warning); + break; + default: + Logger.Write($"Failed to use {item.ItemId}!", LogLevel.Warning); + break; + } + } + } +} diff --git a/PoGo.NecroBot.Logic/TinyIoC.cs b/PoGo.NecroBot.Logic/TinyIoC.cs new file mode 100644 index 000000000..093fbaf7d --- /dev/null +++ b/PoGo.NecroBot.Logic/TinyIoC.cs @@ -0,0 +1,4365 @@ +//=============================================================================== +// TinyIoC +// +// An easy to use, hassle free, Inversion of Control Container for small projects +// and beginners alike. +// +// https://github.com/grumpydev/TinyIoC +//=============================================================================== +// Copyright © Steven Robbins. All rights reserved. +// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY +// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT +// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS FOR A PARTICULAR PURPOSE. +//=============================================================================== + +#region Preprocessor Directives +// Uncomment this line if you want the container to automatically +// register the TinyMessenger messenger/event aggregator +//#define TINYMESSENGER + +// Uncomment this line if you want to internalize this library +//#define TINYIOC_INTERNAL + +// Uncomment this line if you want to target PCL. +//#define PORTABLE + +// Preprocessor directives for enabling/disabling functionality +// depending on platform features. If the platform has an appropriate +// #DEFINE then these should be set automatically below. +#define EXPRESSIONS + +// Platform supports System.Linq.Expressions +#define COMPILED_EXPRESSIONS // Platform supports compiling expressions +#define APPDOMAIN_GETASSEMBLIES // Platform supports getting all assemblies from the AppDomain object +#define UNBOUND_GENERICS_GETCONSTRUCTORS // Platform supports GetConstructors on unbound generic types +#define GETPARAMETERS_OPEN_GENERICS // Platform supports GetParameters on open generics +#define RESOLVE_OPEN_GENERICS // Platform supports resolving open generics +#define READER_WRITER_LOCK_SLIM // Platform supports ReaderWriterLockSlim +#define SERIALIZABLE // Platform supports SerializableAttribute/SerializationInfo/StreamingContext + +#if PORTABLE +#undef APPDOMAIN_GETASSEMBLIES +#undef COMPILED_EXPRESSIONS +#undef READER_WRITER_LOCK_SLIM +#undef SERIALIZABLE +#endif + +#if NETSTANDARD1_0 || NETSTANDARD1_1 || NETSTANDARD1_2 +#undef COMPILED_EXPRESSIONS +#undef READER_WRITER_LOCK_SLIM +#endif + +#if NETSTANDARD1_0 || NETSTANDARD1_1 || NETSTANDARD1_2 +#undef APPDOMAIN_GETASSEMBLIES +#endif + +#if NETSTANDARD1_0 || NETSTANDARD1_1 || NETSTANDARD1_2 || NETSTANDARD1_3 || NETSTANDARD1_4 || NETSTANDARD1_5 || NETSTANDARD1_6 +#undef SERIALIZABLE +#endif + +// CompactFramework / Windows Phone 7 +// By default does not support System.Linq.Expressions. +// AppDomain object does not support enumerating all assemblies in the app domain. +#if PocketPC || WINDOWS_PHONE +#undef EXPRESSIONS +#undef COMPILED_EXPRESSIONS +#undef APPDOMAIN_GETASSEMBLIES +#undef UNBOUND_GENERICS_GETCONSTRUCTORS +#endif + +// PocketPC has a bizarre limitation on enumerating parameters on unbound generic methods. +// We need to use a slower workaround in that case. +#if PocketPC +#undef GETPARAMETERS_OPEN_GENERICS +#undef RESOLVE_OPEN_GENERICS +#undef READER_WRITER_LOCK_SLIM +#endif + +#if SILVERLIGHT +#undef APPDOMAIN_GETASSEMBLIES +#endif + +#if NETFX_CORE +#undef APPDOMAIN_GETASSEMBLIES +#undef RESOLVE_OPEN_GENERICS +#endif + +#if COMPILED_EXPRESSIONS +#define USE_OBJECT_CONSTRUCTOR +#endif + +#endregion +#if SERIALIZABLE +using System.Runtime.Serialization; +#endif + +namespace TinyIoC +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + +#if EXPRESSIONS + using System.Linq.Expressions; + using System.Threading; + +#endif + +#if NETFX_CORE + using System.Threading.Tasks; + using Windows.Storage.Search; + using Windows.Storage; + using Windows.UI.Xaml.Shapes; +#endif + + #region SafeDictionary +#if READER_WRITER_LOCK_SLIM +#if TINYIOC_INTERNAL + internal +#else + public +#endif + class SafeDictionary : IDisposable + { + private readonly ReaderWriterLockSlim _padlock = new ReaderWriterLockSlim(); + private readonly Dictionary _Dictionary = new Dictionary(); + + public TValue this[TKey key] + { +#pragma warning disable IDE0018 // Inline variable declaration - Build.Bat Error Happens if We Do + set + { + _padlock.EnterWriteLock(); + + try + { + TValue current; + if (_Dictionary.TryGetValue(key, out current)) + { +#pragma warning disable IDE0019 // Use pattern matching - Build.Bat Error Happens if We Do + var disposable = current as IDisposable; + + if (disposable != null) + disposable.Dispose(); +#pragma warning restore IDE0019 // Use pattern matching - Build.Bat Error Happens if We Do + } + + _Dictionary[key] = value; + } + finally + { + _padlock.ExitWriteLock(); + } + } +#pragma warning restore IDE0018 // Inline variable declaration - Build.Bat Error Happens if We Do + } + + public bool TryGetValue(TKey key, out TValue value) + { + _padlock.EnterReadLock(); + try + { + return _Dictionary.TryGetValue(key, out value); + } + finally + { + _padlock.ExitReadLock(); + } + } + + public bool Remove(TKey key) + { + _padlock.EnterWriteLock(); + try + { + return _Dictionary.Remove(key); + } + finally + { + _padlock.ExitWriteLock(); + } + } + + public void Clear() + { + _padlock.EnterWriteLock(); + try + { + _Dictionary.Clear(); + } + finally + { + _padlock.ExitWriteLock(); + } + } + + public IEnumerable Keys + { + get + { + _padlock.EnterReadLock(); + try + { + return new List(_Dictionary.Keys); + } + finally + { + _padlock.ExitReadLock(); + } + } + } + + #region IDisposable Members + + public void Dispose() + { + _padlock.EnterWriteLock(); + + try + { + var disposableItems = from item in _Dictionary.Values + where item is IDisposable + select item as IDisposable; + + foreach (var item in disposableItems) + { + item.Dispose(); + } + } + finally + { + _padlock.ExitWriteLock(); + } + + GC.SuppressFinalize(this); + } + + #endregion + } +#else +#if TINYIOC_INTERNAL + internal +#else + public +#endif + class SafeDictionary : IDisposable + { + private readonly object _Padlock = new object(); + private readonly Dictionary _Dictionary = new Dictionary(); + + public TValue this[TKey key] + { + set + { + lock (_Padlock) + { + TValue current; + if (_Dictionary.TryGetValue(key, out current)) + { + var disposable = current as IDisposable; + + if (disposable != null) + disposable.Dispose(); + } + + _Dictionary[key] = value; + } + } + } + + public bool TryGetValue(TKey key, out TValue value) + { + lock (_Padlock) + { + return _Dictionary.TryGetValue(key, out value); + } + } + + public bool Remove(TKey key) + { + lock (_Padlock) + { + return _Dictionary.Remove(key); + } + } + + public void Clear() + { + lock (_Padlock) + { + _Dictionary.Clear(); + } + } + + public IEnumerable Keys + { + get + { + return _Dictionary.Keys; + } + } + #region IDisposable Members + + public void Dispose() + { + lock (_Padlock) + { + var disposableItems = from item in _Dictionary.Values + where item is IDisposable + select item as IDisposable; + + foreach (var item in disposableItems) + { + item.Dispose(); + } + } + + GC.SuppressFinalize(this); + } + + #endregion + } +#endif + #endregion + + #region Extensions +#if TINYIOC_INTERNAL + internal +#else + public +#endif + static class AssemblyExtensions + { + public static Type[] SafeGetTypes(this Assembly assembly) + { + Type[] assemblies; + + try + { +#if PORTABLE || NETSTANDARD1_0 || NETSTANDARD1_1 || NETSTANDARD1_2 + assemblies = assembly.ExportedTypes.ToArray(); +#else + assemblies = assembly.GetTypes(); +#endif + } + catch (System.IO.FileNotFoundException) + { + assemblies = new Type[] { }; + } + catch (NotSupportedException) + { + assemblies = new Type[] { }; + } +#if !NETFX_CORE + catch (ReflectionTypeLoadException e) + { + assemblies = e.Types.Where(t => t != null).ToArray(); + } +#endif + return assemblies; + } + } + +#if PORTABLE || NETSTANDARD1_0 || NETSTANDARD1_1 || NETSTANDARD1_2 + [Flags] + public enum BindingFlags { + Default = 0, + IgnoreCase = 1, + DeclaredOnly = 2, + Instance = 4, + Static = 8, + Public = 16, + NonPublic = 32, + FlattenHierarchy = 64, + InvokeMethod = 256, + CreateInstance = 512, + GetField = 1024, + SetField = 2048, + GetProperty = 4096, + SetProperty = 8192, + PutDispProperty = 16384, + ExactBinding = 65536, + PutRefDispProperty = 32768, + SuppressChangeType = 131072, + OptionalParamBinding = 262144, + IgnoreReturn = 16777216 + } +#endif + +#if TINYIOC_INTERNAL + internal +#else + public +#endif + static class TypeExtensions + { + private static SafeDictionary _genericMethodCache; + + static TypeExtensions() + { + _genericMethodCache = new SafeDictionary(); + } + +#if PORTABLE || NETSTANDARD1_0 || NETSTANDARD1_1 || NETSTANDARD1_2 + private static BindingFlags DefaultFlags = BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance; + + public static ConstructorInfo[] GetConstructors(this Type type) + { + return type.GetConstructors(DefaultFlags); + } + + public static ConstructorInfo[] GetConstructors(this Type type, BindingFlags bindingFlags) + { + return type.GetConstructors(bindingFlags, null); + } + + private static ConstructorInfo[] GetConstructors(this Type type, BindingFlags bindingFlags, IList parameterTypes) + { + return type.GetTypeInfo().DeclaredConstructors.Where( + c => + { + if (!TestAccessibility(c, bindingFlags)) + { + return false; + } + + if (parameterTypes != null && !c.GetParameters().Select(p => p.ParameterType).SequenceEqual(parameterTypes)) + { + return false; + } + + return true; + }).ToArray(); + } + + public static MethodInfo GetGetMethod(this PropertyInfo propertyInfo) { + return propertyInfo.GetGetMethod(false); + } + + public static MethodInfo GetGetMethod(this PropertyInfo propertyInfo, bool nonPublic) { + MethodInfo getMethod = propertyInfo.GetMethod; + if (getMethod != null && (getMethod.IsPublic || nonPublic)) { + return getMethod; + } + + return null; + } + + public static MethodInfo GetSetMethod(this PropertyInfo propertyInfo) { + return propertyInfo.GetSetMethod(false); + } + + public static MethodInfo GetSetMethod(this PropertyInfo propertyInfo, bool nonPublic) { + MethodInfo setMethod = propertyInfo.SetMethod; + if (setMethod != null && (setMethod.IsPublic || nonPublic)) { + return setMethod; + } + + return null; + } + + public static Type[] GetGenericArguments(this Type type) + { + return type.GetTypeInfo().GenericTypeArguments; + } + + public static IEnumerable GetProperties(this Type type) + { + TypeInfo t = type.GetTypeInfo(); + IList properties = new List(); + while (t != null) + { + foreach (PropertyInfo member in t.DeclaredProperties) + { + if (!properties.Any(p => p.Name == member.Name)) + { + properties.Add(member); + } + } + t = (t.BaseType != null) ? t.BaseType.GetTypeInfo() : null; + } + + return properties; + } + + public static IEnumerable GetInterfaces(this Type type) + { + return type.GetTypeInfo().ImplementedInterfaces; + } + + public static MethodInfo GetMethod(this Type type, string name, IList parameterTypes) + { + return type.GetMethod(name, DefaultFlags, null, parameterTypes, null); + } + + public static MethodInfo GetMethod(this Type type, string name, BindingFlags bindingFlags, object placeHolder1, IList parameterTypes, object placeHolder2) + { + return type.GetTypeInfo().DeclaredMethods.Where( + m => + { + if (name != null && m.Name != name) + { + return false; + } + + if (!TestAccessibility(m, bindingFlags)) + { + return false; + } + + return m.GetParameters().Select(p => p.ParameterType).SequenceEqual(parameterTypes); + }).SingleOrDefault(); + } + + public static IEnumerable GetMethods(this Type type, BindingFlags bindingFlags) + { + return type.GetTypeInfo().DeclaredMethods; + } + + public static bool IsAssignableFrom(this Type type, Type c) + { + return type.GetTypeInfo().IsAssignableFrom(c.GetTypeInfo()); + } + + private static bool TestAccessibility(MethodBase member, BindingFlags bindingFlags) + { + bool visibility = (member.IsPublic && bindingFlags.HasFlag(BindingFlags.Public)) || + (!member.IsPublic && bindingFlags.HasFlag(BindingFlags.NonPublic)); + + bool instance = (member.IsStatic && bindingFlags.HasFlag(BindingFlags.Static)) || + (!member.IsStatic && bindingFlags.HasFlag(BindingFlags.Instance)); + + return visibility && instance; + } +#endif + + /// + /// Gets a generic method from a type given the method name, binding flags, generic types and parameter types + /// + /// Source type + /// Binding flags + /// Name of the method + /// Generic types to use to make the method generic + /// Method parameters + /// MethodInfo or null if no matches found + /// + /// + public static MethodInfo GetGenericMethod(this Type sourceType, BindingFlags bindingFlags, string methodName, Type[] genericTypes, Type[] parameterTypes) + { +#pragma warning disable IDE0018 // Inline variable declaration - Build.Bat Error Happens if We Do + MethodInfo method; + var cacheKey = new GenericMethodCacheKey(sourceType, methodName, genericTypes, parameterTypes); + + // Shouldn't need any additional locking + // we don't care if we do the method info generation + // more than once before it gets cached. + if (!_genericMethodCache.TryGetValue(cacheKey, out method)) + { + method = GetMethod(sourceType, bindingFlags, methodName, genericTypes, parameterTypes); + _genericMethodCache[cacheKey] = method; + } + return method; +#pragma warning restore IDE0018 // Inline variable declaration - Build.Bat Error Happens if We Do + } + //#endif + +#if NETFX_CORE + private static MethodInfo GetMethod(Type sourceType, BindingFlags flags, string methodName, Type[] genericTypes, Type[] parameterTypes) + { + var methods = + sourceType.GetMethods(flags).Where( + mi => string.Equals(methodName, mi.Name, StringComparison.Ordinal)).Where( + mi => mi.ContainsGenericParameters).Where(mi => mi.GetGenericArguments().Length == genericTypes.Length). + Where(mi => mi.GetParameters().Length == parameterTypes.Length).Select( + mi => mi.MakeGenericMethod(genericTypes)).Where( + mi => mi.GetParameters().Select(pi => pi.ParameterType).SequenceEqual(parameterTypes)).ToList(); + + if (methods.Count > 1) + { + throw new AmbiguousMatchException(); + } + + return methods.FirstOrDefault(); + } +#else + private static MethodInfo GetMethod(Type sourceType, BindingFlags bindingFlags, string methodName, Type[] genericTypes, Type[] parameterTypes) + { +#if GETPARAMETERS_OPEN_GENERICS + var methods = + sourceType.GetMethods(bindingFlags).Where( + mi => string.Equals(methodName, mi.Name, StringComparison.Ordinal)).Where( + mi => mi.ContainsGenericParameters).Where(mi => mi.GetGenericArguments().Length == genericTypes.Length). + Where(mi => mi.GetParameters().Length == parameterTypes.Length).Select( + mi => mi.MakeGenericMethod(genericTypes)).Where( + mi => mi.GetParameters().Select(pi => pi.ParameterType).SequenceEqual(parameterTypes)).ToList(); +#else + var validMethods = from method in sourceType.GetMethods(bindingFlags) + where method.Name == methodName + where method.IsGenericMethod + where method.GetGenericArguments().Length == genericTypes.Length + let genericMethod = method.MakeGenericMethod(genericTypes) + where genericMethod.GetParameters().Count() == parameterTypes.Length + where genericMethod.GetParameters().Select(pi => pi.ParameterType).SequenceEqual(parameterTypes) + select genericMethod; + + var methods = validMethods.ToList(); +#endif + if (methods.Count > 1) + { + throw new AmbiguousMatchException(); + } + + return methods.FirstOrDefault(); + } +#endif + + private sealed class GenericMethodCacheKey + { + private readonly Type _sourceType; + + private readonly string _methodName; + + private readonly Type[] _genericTypes; + + private readonly Type[] _parameterTypes; + + private readonly int _hashCode; + + public GenericMethodCacheKey(Type sourceType, string methodName, Type[] genericTypes, Type[] parameterTypes) + { + _sourceType = sourceType; + _methodName = methodName; + _genericTypes = genericTypes; + _parameterTypes = parameterTypes; + _hashCode = GenerateHashCode(); + } + + public override bool Equals(object obj) + { + var cacheKey = obj as GenericMethodCacheKey; + if (cacheKey == null) + return false; + + if (_sourceType != cacheKey._sourceType) + return false; + + if (!String.Equals(_methodName, cacheKey._methodName, StringComparison.Ordinal)) + return false; + + if (_genericTypes.Length != cacheKey._genericTypes.Length) + return false; + + if (_parameterTypes.Length != cacheKey._parameterTypes.Length) + return false; + + for (int i = 0; i < _genericTypes.Length; ++i) + { + if (_genericTypes[i] != cacheKey._genericTypes[i]) + return false; + } + + for (int i = 0; i < _parameterTypes.Length; ++i) + { + if (_parameterTypes[i] != cacheKey._parameterTypes[i]) + return false; + } + + return true; + } + + public override int GetHashCode() + { + return _hashCode; + } + + private int GenerateHashCode() + { + unchecked + { + var result = _sourceType.GetHashCode(); + + result = (result * 397) ^ _methodName.GetHashCode(); + + for (int i = 0; i < _genericTypes.Length; ++i) + { + result = (result * 397) ^ _genericTypes[i].GetHashCode(); + } + + for (int i = 0; i < _parameterTypes.Length; ++i) + { + result = (result * 397) ^ _parameterTypes[i].GetHashCode(); + } + + return result; + } + } + } + + } + + // @mbrit - 2012-05-22 - shim for ForEach call on List... +#if NETFX_CORE + internal static class ListExtender + { + internal static void ForEach(this List list, Action callback) + { + foreach (T obj in list) + callback(obj); + } + } +#endif + + #endregion + + #region TinyIoC Exception Types +#if SERIALIZABLE + [Serializable] +#endif +#if TINYIOC_INTERNAL + internal +#else + public +#endif + class TinyIoCResolutionException : Exception + { + private const string ERROR_TEXT = "Unable to resolve type: {0}"; + + public TinyIoCResolutionException(Type type) + : base(String.Format(ERROR_TEXT, type.FullName)) + { + } + + public TinyIoCResolutionException(Type type, Exception innerException) + : base(String.Format(ERROR_TEXT, type.FullName), innerException) + { + } +#if SERIALIZABLE + protected TinyIoCResolutionException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } +#endif + } +#if SERIALIZABLE + [Serializable] +#endif +#if TINYIOC_INTERNAL + internal +#else + public +#endif + class TinyIoCRegistrationTypeException : Exception + { + private const string REGISTER_ERROR_TEXT = "Cannot register type {0} - abstract classes or interfaces are not valid implementation types for {1}."; + + public TinyIoCRegistrationTypeException(Type type, string factory) + : base(String.Format(REGISTER_ERROR_TEXT, type.FullName, factory)) + { + } + + public TinyIoCRegistrationTypeException(Type type, string factory, Exception innerException) + : base(String.Format(REGISTER_ERROR_TEXT, type.FullName, factory), innerException) + { + } +#if SERIALIZABLE + protected TinyIoCRegistrationTypeException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } +#endif + } +#if SERIALIZABLE + [Serializable] +#endif +#if TINYIOC_INTERNAL + internal +#else + public +#endif + class TinyIoCRegistrationException : Exception + { + private const string CONVERT_ERROR_TEXT = "Cannot convert current registration of {0} to {1}"; + private const string GENERIC_CONSTRAINT_ERROR_TEXT = "Type {1} is not valid for a registration of type {0}"; + + public TinyIoCRegistrationException(Type type, string method) + : base(String.Format(CONVERT_ERROR_TEXT, type.FullName, method)) + { + } + + public TinyIoCRegistrationException(Type type, string method, Exception innerException) + : base(String.Format(CONVERT_ERROR_TEXT, type.FullName, method), innerException) + { + } + + public TinyIoCRegistrationException(Type registerType, Type implementationType) + : base(String.Format(GENERIC_CONSTRAINT_ERROR_TEXT, registerType.FullName, implementationType.FullName)) + { + } + + public TinyIoCRegistrationException(Type registerType, Type implementationType, Exception innerException) + : base(String.Format(GENERIC_CONSTRAINT_ERROR_TEXT, registerType.FullName, implementationType.FullName), innerException) + { + } +#if SERIALIZABLE + protected TinyIoCRegistrationException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } +#endif + } +#if SERIALIZABLE + [Serializable] +#endif +#if TINYIOC_INTERNAL + internal +#else + public +#endif + class TinyIoCWeakReferenceException : Exception + { + private const string ERROR_TEXT = "Unable to instantiate {0} - referenced object has been reclaimed"; + + public TinyIoCWeakReferenceException(Type type) + : base(String.Format(ERROR_TEXT, type.FullName)) + { + } + + public TinyIoCWeakReferenceException(Type type, Exception innerException) + : base(String.Format(ERROR_TEXT, type.FullName), innerException) + { + } +#if SERIALIZABLE + protected TinyIoCWeakReferenceException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } +#endif + } +#if SERIALIZABLE + [Serializable] +#endif +#if TINYIOC_INTERNAL + internal +#else + public +#endif + class TinyIoCConstructorResolutionException : Exception + { + private const string ERROR_TEXT = "Unable to resolve constructor for {0} using provided Expression."; + + public TinyIoCConstructorResolutionException(Type type) + : base(String.Format(ERROR_TEXT, type.FullName)) + { + } + + public TinyIoCConstructorResolutionException(Type type, Exception innerException) + : base(String.Format(ERROR_TEXT, type.FullName), innerException) + { + } + + public TinyIoCConstructorResolutionException(string message, Exception innerException) + : base(message, innerException) + { + } + + public TinyIoCConstructorResolutionException(string message) + : base(message) + { + } +#if SERIALIZABLE + protected TinyIoCConstructorResolutionException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } +#endif + } +#if SERIALIZABLE + [Serializable] +#endif +#if TINYIOC_INTERNAL + internal +#else + public +#endif + class TinyIoCAutoRegistrationException : Exception + { + private const string ERROR_TEXT = "Duplicate implementation of type {0} found ({1})."; + + public TinyIoCAutoRegistrationException(Type registerType, IEnumerable types) + : base(String.Format(ERROR_TEXT, registerType, GetTypesString(types))) + { + } + + public TinyIoCAutoRegistrationException(Type registerType, IEnumerable types, Exception innerException) + : base(String.Format(ERROR_TEXT, registerType, GetTypesString(types)), innerException) + { + } +#if SERIALIZABLE + protected TinyIoCAutoRegistrationException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } +#endif + + private static string GetTypesString(IEnumerable types) + { + var typeNames = from type in types + select type.FullName; + + return string.Join(",", typeNames.ToArray()); + } + } + #endregion + + #region Public Setup / Settings Classes + /// + /// Name/Value pairs for specifying "user" parameters when resolving + /// +#if TINYIOC_INTERNAL + internal +#else + public +#endif + sealed class NamedParameterOverloads : Dictionary + { + public static NamedParameterOverloads FromIDictionary(IDictionary data) + { + return data as NamedParameterOverloads ?? new NamedParameterOverloads(data); + } + + public NamedParameterOverloads() + { + } + + public NamedParameterOverloads(IDictionary data) + : base(data) + { + } + + private static readonly NamedParameterOverloads _Default = new NamedParameterOverloads(); + + public static NamedParameterOverloads Default + { + get + { + return _Default; + } + } + } + +#if TINYIOC_INTERNAL + internal +#else + public +#endif + enum UnregisteredResolutionActions + { + /// + /// Attempt to resolve type, even if the type isn't registered. + /// + /// Registered types/options will always take precedence. + /// + AttemptResolve, + + /// + /// Fail resolution if type not explicitly registered + /// + Fail, + + /// + /// Attempt to resolve unregistered type if requested type is generic + /// and no registration exists for the specific generic parameters used. + /// + /// Registered types/options will always take precedence. + /// + GenericsOnly + } + +#if TINYIOC_INTERNAL + internal +#else + public +#endif + enum NamedResolutionFailureActions + { + AttemptUnnamedResolution, + Fail + } + +#if TINYIOC_INTERNAL + internal +#else + public +#endif + enum DuplicateImplementationActions + { + RegisterSingle, + RegisterMultiple, + Fail + } + + /// + /// Resolution settings + /// +#if TINYIOC_INTERNAL + internal +#else + public +#endif + sealed class ResolveOptions + { + private static readonly ResolveOptions _Default = new ResolveOptions(); + private static readonly ResolveOptions _FailUnregisteredAndNameNotFound = new ResolveOptions() { NamedResolutionFailureAction = NamedResolutionFailureActions.Fail, UnregisteredResolutionAction = UnregisteredResolutionActions.Fail }; + private static readonly ResolveOptions _FailUnregisteredOnly = new ResolveOptions() { NamedResolutionFailureAction = NamedResolutionFailureActions.AttemptUnnamedResolution, UnregisteredResolutionAction = UnregisteredResolutionActions.Fail }; + private static readonly ResolveOptions _FailNameNotFoundOnly = new ResolveOptions() { NamedResolutionFailureAction = NamedResolutionFailureActions.Fail, UnregisteredResolutionAction = UnregisteredResolutionActions.AttemptResolve }; + + private UnregisteredResolutionActions _UnregisteredResolutionAction = UnregisteredResolutionActions.AttemptResolve; + public UnregisteredResolutionActions UnregisteredResolutionAction + { + get { return _UnregisteredResolutionAction; } + set { _UnregisteredResolutionAction = value; } + } + + private NamedResolutionFailureActions _NamedResolutionFailureAction = NamedResolutionFailureActions.Fail; + public NamedResolutionFailureActions NamedResolutionFailureAction + { + get { return _NamedResolutionFailureAction; } + set { _NamedResolutionFailureAction = value; } + } + + /// + /// Gets the default options (attempt resolution of unregistered types, fail on named resolution if name not found) + /// + public static ResolveOptions Default + { + get + { + return _Default; + } + } + + /// + /// Preconfigured option for attempting resolution of unregistered types and failing on named resolution if name not found + /// + public static ResolveOptions FailNameNotFoundOnly + { + get + { + return _FailNameNotFoundOnly; + } + } + + /// + /// Preconfigured option for failing on resolving unregistered types and on named resolution if name not found + /// + public static ResolveOptions FailUnregisteredAndNameNotFound + { + get + { + return _FailUnregisteredAndNameNotFound; + } + } + + /// + /// Preconfigured option for failing on resolving unregistered types, but attempting unnamed resolution if name not found + /// + public static ResolveOptions FailUnregisteredOnly + { + get + { + return _FailUnregisteredOnly; + } + } + } + #endregion + +#if TINYIOC_INTERNAL + internal +#else + public +#endif + sealed partial class TinyIoCContainer : IDisposable + { + #region Fake NETFX_CORE Classes +#if NETFX_CORE + private sealed class MethodAccessException : Exception + { + } + + private sealed class AppDomain + { + public static AppDomain CurrentDomain { get; private set; } + + static AppDomain() + { + CurrentDomain = new AppDomain(); + } + + // @mbrit - 2012-05-30 - in WinRT, this should be done async... + public async Task> GetAssembliesAsync() + { + var folder = Windows.ApplicationModel.Package.Current.InstalledLocation; + + List assemblies = new List(); + + var files = await folder.GetFilesAsync(); + + foreach (StorageFile file in files) + { + if (file.FileType == ".dll" || file.FileType == ".exe") + { + AssemblyName name = new AssemblyName() { Name = System.IO.Path.GetFileNameWithoutExtension(file.Name) }; + try + { + var asm = Assembly.Load(name); + assemblies.Add(asm); + } + catch + { + // ignore exceptions here... + } + } + } + + return assemblies; + } + } +#endif + #endregion + + #region "Fluent" API + /// + /// Registration options for "fluent" API + /// + public sealed class RegisterOptions + { + private TinyIoCContainer _Container; + private TypeRegistration _Registration; + + public RegisterOptions(TinyIoCContainer container, TypeRegistration registration) + { + _Container = container; + _Registration = registration; + } + + /// + /// Make registration a singleton (single instance) if possible + /// + /// RegisterOptions + /// + public RegisterOptions AsSingleton() + { + var currentFactory = _Container.GetCurrentFactory(_Registration); + + if (currentFactory == null) + throw new TinyIoCRegistrationException(_Registration.Type, "singleton"); + + return _Container.AddUpdateRegistration(_Registration, currentFactory.SingletonVariant); + } + + /// + /// Make registration multi-instance if possible + /// + /// RegisterOptions + /// + public RegisterOptions AsMultiInstance() + { + var currentFactory = _Container.GetCurrentFactory(_Registration); + + if (currentFactory == null) + throw new TinyIoCRegistrationException(_Registration.Type, "multi-instance"); + + return _Container.AddUpdateRegistration(_Registration, currentFactory.MultiInstanceVariant); + } + + /// + /// Make registration hold a weak reference if possible + /// + /// RegisterOptions + /// + public RegisterOptions WithWeakReference() + { + var currentFactory = _Container.GetCurrentFactory(_Registration); + + if (currentFactory == null) + throw new TinyIoCRegistrationException(_Registration.Type, "weak reference"); + + return _Container.AddUpdateRegistration(_Registration, currentFactory.WeakReferenceVariant); + } + + /// + /// Make registration hold a strong reference if possible + /// + /// RegisterOptions + /// + public RegisterOptions WithStrongReference() + { + var currentFactory = _Container.GetCurrentFactory(_Registration); + + if (currentFactory == null) + throw new TinyIoCRegistrationException(_Registration.Type, "strong reference"); + + return _Container.AddUpdateRegistration(_Registration, currentFactory.StrongReferenceVariant); + } + +#if EXPRESSIONS + public RegisterOptions UsingConstructor(Expression> constructor) + { + var lambda = constructor as LambdaExpression; + if (lambda == null) + throw new TinyIoCConstructorResolutionException(typeof(RegisterType)); + + var newExpression = lambda.Body as NewExpression; + if (newExpression == null) + throw new TinyIoCConstructorResolutionException(typeof(RegisterType)); + + var constructorInfo = newExpression.Constructor; + if (constructorInfo == null) + throw new TinyIoCConstructorResolutionException(typeof(RegisterType)); + + var currentFactory = _Container.GetCurrentFactory(_Registration); + if (currentFactory == null) + throw new TinyIoCConstructorResolutionException(typeof(RegisterType)); + + currentFactory.SetConstructor(constructorInfo); + + return this; + } +#endif + /// + /// Switches to a custom lifetime manager factory if possible. + /// + /// Usually used for RegisterOptions "To*" extension methods such as the ASP.Net per-request one. + /// + /// RegisterOptions instance + /// Custom lifetime manager + /// Error string to display if switch fails + /// RegisterOptions + public static RegisterOptions ToCustomLifetimeManager(RegisterOptions instance, ITinyIoCObjectLifetimeProvider lifetimeProvider, string errorString) + { + if (instance == null) + throw new ArgumentNullException("instance", "instance is null."); + + if (lifetimeProvider == null) + throw new ArgumentNullException("lifetimeProvider", "lifetimeProvider is null."); + + if (string.IsNullOrEmpty(errorString)) + throw new ArgumentException("errorString is null or empty.", "errorString"); + + var currentFactory = instance._Container.GetCurrentFactory(instance._Registration); + + if (currentFactory == null) + throw new TinyIoCRegistrationException(instance._Registration.Type, errorString); + + return instance._Container.AddUpdateRegistration(instance._Registration, currentFactory.GetCustomObjectLifetimeVariant(lifetimeProvider, errorString)); + } + } + + /// + /// Registration options for "fluent" API when registering multiple implementations + /// + public sealed class MultiRegisterOptions + { + private IEnumerable _RegisterOptions; + + /// + /// Initializes a new instance of the MultiRegisterOptions class. + /// + /// Registration options + public MultiRegisterOptions(IEnumerable registerOptions) + { + _RegisterOptions = registerOptions; + } + + /// + /// Make registration a singleton (single instance) if possible + /// + /// RegisterOptions + /// + public MultiRegisterOptions AsSingleton() + { + _RegisterOptions = ExecuteOnAllRegisterOptions(ro => ro.AsSingleton()); + return this; + } + + /// + /// Make registration multi-instance if possible + /// + /// MultiRegisterOptions + /// + public MultiRegisterOptions AsMultiInstance() + { + _RegisterOptions = ExecuteOnAllRegisterOptions(ro => ro.AsMultiInstance()); + return this; + } + + /// + /// Switches to a custom lifetime manager factory if possible. + /// + /// Usually used for RegisterOptions "To*" extension methods such as the ASP.Net per-request one. + /// + /// MultiRegisterOptions instance + /// Custom lifetime manager + /// Error string to display if switch fails + /// MultiRegisterOptions + public static MultiRegisterOptions ToCustomLifetimeManager( + MultiRegisterOptions instance, + ITinyIoCObjectLifetimeProvider lifetimeProvider, + string errorString) + { + if (instance == null) + throw new ArgumentNullException("instance", "instance is null."); + + if (lifetimeProvider == null) + throw new ArgumentNullException("lifetimeProvider", "lifetimeProvider is null."); + + if (string.IsNullOrEmpty(errorString)) + throw new ArgumentException("errorString is null or empty.", "errorString"); + + instance._RegisterOptions = instance.ExecuteOnAllRegisterOptions(ro => RegisterOptions.ToCustomLifetimeManager(ro, lifetimeProvider, errorString)); + + return instance; + } + + private IEnumerable ExecuteOnAllRegisterOptions(Func action) + { + var newRegisterOptions = new List(); + + foreach (var registerOption in _RegisterOptions) + { + newRegisterOptions.Add(action(registerOption)); + } + + return newRegisterOptions; + } + } + #endregion + + #region Public API + #region Child Containers + public TinyIoCContainer GetChildContainer() + { + return new TinyIoCContainer(this); + } + #endregion + + #region Registration + /// + /// Attempt to automatically register all non-generic classes and interfaces in the current app domain. + /// + /// If more than one class implements an interface then only one implementation will be registered + /// although no error will be thrown. + /// + public void AutoRegister() + { +#if APPDOMAIN_GETASSEMBLIES + AutoRegisterInternal(AppDomain.CurrentDomain.GetAssemblies().Where(a => !IsIgnoredAssembly(a)), DuplicateImplementationActions.RegisterSingle, null); +#else + AutoRegisterInternal(new Assembly[] { this.GetType().Assembly() }, DuplicateImplementationActions.RegisterSingle, null); +#endif + } + + /// + /// Attempt to automatically register all non-generic classes and interfaces in the current app domain. + /// Types will only be registered if they pass the supplied registration predicate. + /// + /// If more than one class implements an interface then only one implementation will be registered + /// although no error will be thrown. + /// + /// Predicate to determine if a particular type should be registered + public void AutoRegister(Func registrationPredicate) + { +#if APPDOMAIN_GETASSEMBLIES + AutoRegisterInternal(AppDomain.CurrentDomain.GetAssemblies().Where(a => !IsIgnoredAssembly(a)), DuplicateImplementationActions.RegisterSingle, registrationPredicate); +#else + AutoRegisterInternal(new Assembly[] { this.GetType().Assembly() }, DuplicateImplementationActions.RegisterSingle, registrationPredicate); +#endif + } + + /// + /// Attempt to automatically register all non-generic classes and interfaces in the current app domain. + /// + /// What action to take when encountering duplicate implementations of an interface/base class. + /// + public void AutoRegister(DuplicateImplementationActions duplicateAction) + { +#if APPDOMAIN_GETASSEMBLIES + AutoRegisterInternal(AppDomain.CurrentDomain.GetAssemblies().Where(a => !IsIgnoredAssembly(a)), duplicateAction, null); +#else + AutoRegisterInternal(new Assembly[] { this.GetType().Assembly() }, duplicateAction, null); +#endif + } + + /// + /// Attempt to automatically register all non-generic classes and interfaces in the current app domain. + /// Types will only be registered if they pass the supplied registration predicate. + /// + /// What action to take when encountering duplicate implementations of an interface/base class. + /// Predicate to determine if a particular type should be registered + /// + public void AutoRegister(DuplicateImplementationActions duplicateAction, Func registrationPredicate) + { +#if APPDOMAIN_GETASSEMBLIES + AutoRegisterInternal(AppDomain.CurrentDomain.GetAssemblies().Where(a => !IsIgnoredAssembly(a)), duplicateAction, registrationPredicate); +#else + AutoRegisterInternal(new Assembly[] { this.GetType().Assembly() }, duplicateAction, registrationPredicate); +#endif + } + + /// + /// Attempt to automatically register all non-generic classes and interfaces in the specified assemblies + /// + /// If more than one class implements an interface then only one implementation will be registered + /// although no error will be thrown. + /// + /// Assemblies to process + public void AutoRegister(IEnumerable assemblies) + { + AutoRegisterInternal(assemblies, DuplicateImplementationActions.RegisterSingle, null); + } + + /// + /// Attempt to automatically register all non-generic classes and interfaces in the specified assemblies + /// Types will only be registered if they pass the supplied registration predicate. + /// + /// If more than one class implements an interface then only one implementation will be registered + /// although no error will be thrown. + /// + /// Assemblies to process + /// Predicate to determine if a particular type should be registered + public void AutoRegister(IEnumerable assemblies, Func registrationPredicate) + { + AutoRegisterInternal(assemblies, DuplicateImplementationActions.RegisterSingle, registrationPredicate); + } + + /// + /// Attempt to automatically register all non-generic classes and interfaces in the specified assemblies + /// + /// Assemblies to process + /// What action to take when encountering duplicate implementations of an interface/base class. + /// + public void AutoRegister(IEnumerable assemblies, DuplicateImplementationActions duplicateAction) + { + AutoRegisterInternal(assemblies, duplicateAction, null); + } + + /// + /// Attempt to automatically register all non-generic classes and interfaces in the specified assemblies + /// Types will only be registered if they pass the supplied registration predicate. + /// + /// Assemblies to process + /// What action to take when encountering duplicate implementations of an interface/base class. + /// Predicate to determine if a particular type should be registered + /// + public void AutoRegister(IEnumerable assemblies, DuplicateImplementationActions duplicateAction, Func registrationPredicate) + { + AutoRegisterInternal(assemblies, duplicateAction, registrationPredicate); + } + + /// + /// Creates/replaces a container class registration with default options. + /// + /// Type to register + /// RegisterOptions for fluent API + public RegisterOptions Register(Type registerType) + { + return RegisterInternal(registerType, string.Empty, GetDefaultObjectFactory(registerType, registerType)); + } + + /// + /// Creates/replaces a named container class registration with default options. + /// + /// Type to register + /// Name of registration + /// RegisterOptions for fluent API + public RegisterOptions Register(Type registerType, string name) + { + return RegisterInternal(registerType, name, GetDefaultObjectFactory(registerType, registerType)); + + } + + /// + /// Creates/replaces a container class registration with a given implementation and default options. + /// + /// Type to register + /// Type to instantiate that implements RegisterType + /// RegisterOptions for fluent API + public RegisterOptions Register(Type registerType, Type registerImplementation) + { + return RegisterInternal(registerType, string.Empty, GetDefaultObjectFactory(registerType, registerImplementation)); + } + + /// + /// Creates/replaces a named container class registration with a given implementation and default options. + /// + /// Type to register + /// Type to instantiate that implements RegisterType + /// Name of registration + /// RegisterOptions for fluent API + public RegisterOptions Register(Type registerType, Type registerImplementation, string name) + { + return RegisterInternal(registerType, name, GetDefaultObjectFactory(registerType, registerImplementation)); + } + + /// + /// Creates/replaces a container class registration with a specific, strong referenced, instance. + /// + /// Type to register + /// Instance of RegisterType to register + /// RegisterOptions for fluent API + public RegisterOptions Register(Type registerType, object instance) + { + return RegisterInternal(registerType, string.Empty, new InstanceFactory(registerType, registerType, instance)); + } + + /// + /// Creates/replaces a named container class registration with a specific, strong referenced, instance. + /// + /// Type to register + /// Instance of RegisterType to register + /// Name of registration + /// RegisterOptions for fluent API + public RegisterOptions Register(Type registerType, object instance, string name) + { + return RegisterInternal(registerType, name, new InstanceFactory(registerType, registerType, instance)); + } + + /// + /// Creates/replaces a container class registration with a specific, strong referenced, instance. + /// + /// Type to register + /// Type of instance to register that implements RegisterType + /// Instance of RegisterImplementation to register + /// RegisterOptions for fluent API + public RegisterOptions Register(Type registerType, Type registerImplementation, object instance) + { + return RegisterInternal(registerType, string.Empty, new InstanceFactory(registerType, registerImplementation, instance)); + } + + /// + /// Creates/replaces a named container class registration with a specific, strong referenced, instance. + /// + /// Type to register + /// Type of instance to register that implements RegisterType + /// Instance of RegisterImplementation to register + /// Name of registration + /// RegisterOptions for fluent API + public RegisterOptions Register(Type registerType, Type registerImplementation, object instance, string name) + { + return RegisterInternal(registerType, name, new InstanceFactory(registerType, registerImplementation, instance)); + } + + /// + /// Creates/replaces a container class registration with a user specified factory + /// + /// Type to register + /// Factory/lambda that returns an instance of RegisterType + /// RegisterOptions for fluent API + public RegisterOptions Register(Type registerType, Func factory) + { + return RegisterInternal(registerType, string.Empty, new DelegateFactory(registerType, factory)); + } + + /// + /// Creates/replaces a container class registration with a user specified factory + /// + /// Type to register + /// Factory/lambda that returns an instance of RegisterType + /// Name of registation + /// RegisterOptions for fluent API + public RegisterOptions Register(Type registerType, Func factory, string name) + { + return RegisterInternal(registerType, name, new DelegateFactory(registerType, factory)); + } + + /// + /// Creates/replaces a container class registration with default options. + /// + /// Type to register + /// RegisterOptions for fluent API + public RegisterOptions Register() + where RegisterType : class + { + return Register(typeof(RegisterType)); + } + + /// + /// Creates/replaces a named container class registration with default options. + /// + /// Type to register + /// Name of registration + /// RegisterOptions for fluent API + public RegisterOptions Register(string name) + where RegisterType : class + { + return Register(typeof(RegisterType), name); + } + + /// + /// Creates/replaces a container class registration with a given implementation and default options. + /// + /// Type to register + /// Type to instantiate that implements RegisterType + /// RegisterOptions for fluent API + public RegisterOptions Register() + where RegisterType : class + where RegisterImplementation : class, RegisterType + { + return Register(typeof(RegisterType), typeof(RegisterImplementation)); + } + + /// + /// Creates/replaces a named container class registration with a given implementation and default options. + /// + /// Type to register + /// Type to instantiate that implements RegisterType + /// Name of registration + /// RegisterOptions for fluent API + public RegisterOptions Register(string name) + where RegisterType : class + where RegisterImplementation : class, RegisterType + { + return Register(typeof(RegisterType), typeof(RegisterImplementation), name); + } + + /// + /// Creates/replaces a container class registration with a specific, strong referenced, instance. + /// + /// Type to register + /// Instance of RegisterType to register + /// RegisterOptions for fluent API + public RegisterOptions Register(RegisterType instance) + where RegisterType : class + { + return Register(typeof(RegisterType), instance); + } + + /// + /// Creates/replaces a named container class registration with a specific, strong referenced, instance. + /// + /// Type to register + /// Instance of RegisterType to register + /// Name of registration + /// RegisterOptions for fluent API + public RegisterOptions Register(RegisterType instance, string name) + where RegisterType : class + { + return Register(typeof(RegisterType), instance, name); + } + + /// + /// Creates/replaces a container class registration with a specific, strong referenced, instance. + /// + /// Type to register + /// Type of instance to register that implements RegisterType + /// Instance of RegisterImplementation to register + /// RegisterOptions for fluent API + public RegisterOptions Register(RegisterImplementation instance) + where RegisterType : class + where RegisterImplementation : class, RegisterType + { + return Register(typeof(RegisterType), typeof(RegisterImplementation), instance); + } + + /// + /// Creates/replaces a named container class registration with a specific, strong referenced, instance. + /// + /// Type to register + /// Type of instance to register that implements RegisterType + /// Instance of RegisterImplementation to register + /// Name of registration + /// RegisterOptions for fluent API + public RegisterOptions Register(RegisterImplementation instance, string name) + where RegisterType : class + where RegisterImplementation : class, RegisterType + { + return Register(typeof(RegisterType), typeof(RegisterImplementation), instance, name); + } + + /// + /// Creates/replaces a container class registration with a user specified factory + /// + /// Type to register + /// Factory/lambda that returns an instance of RegisterType + /// RegisterOptions for fluent API + public RegisterOptions Register(Func factory) + where RegisterType : class + { + if (factory == null) + { + throw new ArgumentNullException("factory"); + } + + return Register(typeof(RegisterType), (c, o) => factory(c, o)); + } + + /// + /// Creates/replaces a named container class registration with a user specified factory + /// + /// Type to register + /// Factory/lambda that returns an instance of RegisterType + /// Name of registation + /// RegisterOptions for fluent API + public RegisterOptions Register(Func factory, string name) + where RegisterType : class + { + if (factory == null) + { + throw new ArgumentNullException("factory"); + } + + return Register(typeof(RegisterType), (c, o) => factory(c, o), name); + } + + /// + /// Register multiple implementations of a type. + /// + /// Internally this registers each implementation using the full name of the class as its registration name. + /// + /// Type that each implementation implements + /// Types that implement RegisterType + /// MultiRegisterOptions for the fluent API + public MultiRegisterOptions RegisterMultiple(IEnumerable implementationTypes) + { + return RegisterMultiple(typeof(RegisterType), implementationTypes); + } + + /// + /// Register multiple implementations of a type. + /// + /// Internally this registers each implementation using the full name of the class as its registration name. + /// + /// Type that each implementation implements + /// Types that implement RegisterType + /// MultiRegisterOptions for the fluent API + public MultiRegisterOptions RegisterMultiple(Type registrationType, IEnumerable implementationTypes) + { + if (implementationTypes == null) + throw new ArgumentNullException("types", "types is null."); + + foreach (var type in implementationTypes) + //#if NETFX_CORE + // if (!registrationType.GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) + //#else + if (!registrationType.IsAssignableFrom(type)) + //#endif + throw new ArgumentException(String.Format("types: The type {0} is not assignable from {1}", registrationType.FullName, type.FullName)); + + if (implementationTypes.Count() != implementationTypes.Distinct().Count()) + { + var queryForDuplicatedTypes = from i in implementationTypes + group i by i + into j + where j.Count() > 1 + select j.Key.FullName; + + var fullNamesOfDuplicatedTypes = string.Join(",\n", queryForDuplicatedTypes.ToArray()); + var multipleRegMessage = string.Format("types: The same implementation type cannot be specified multiple times for {0}\n\n{1}", registrationType.FullName, fullNamesOfDuplicatedTypes); + throw new ArgumentException(multipleRegMessage); + } + + var registerOptions = new List(); + + foreach (var type in implementationTypes) + { + registerOptions.Add(Register(registrationType, type, type.FullName)); + } + + return new MultiRegisterOptions(registerOptions); + } + #endregion + + #region Unregistration + + /// + /// Remove a container class registration. + /// + /// Type to unregister + /// true if the registration is successfully found and removed; otherwise, false. + public bool Unregister() + { + return Unregister(typeof(RegisterType), string.Empty); + } + + /// + /// Remove a named container class registration. + /// + /// Type to unregister + /// Name of registration + /// true if the registration is successfully found and removed; otherwise, false. + public bool Unregister(string name) + { + return Unregister(typeof(RegisterType), name); + } + + /// + /// Remove a container class registration. + /// + /// Type to unregister + /// true if the registration is successfully found and removed; otherwise, false. + public bool Unregister(Type registerType) + { + return Unregister(registerType, string.Empty); + } + + /// + /// Remove a named container class registration. + /// + /// Type to unregister + /// Name of registration + /// true if the registration is successfully found and removed; otherwise, false. + public bool Unregister(Type registerType, string name) + { + var typeRegistration = new TypeRegistration(registerType, name); + + return RemoveRegistration(typeRegistration); + } + + #endregion + + #region Resolution + /// + /// Attempts to resolve a type using default options. + /// + /// Type to resolve + /// Instance of type + /// Unable to resolve the type. + public object Resolve(Type resolveType) + { + return ResolveInternal(new TypeRegistration(resolveType), NamedParameterOverloads.Default, ResolveOptions.Default); + } + + /// + /// Attempts to resolve a type using specified options. + /// + /// Type to resolve + /// Resolution options + /// Instance of type + /// Unable to resolve the type. + public object Resolve(Type resolveType, ResolveOptions options) + { + return ResolveInternal(new TypeRegistration(resolveType), NamedParameterOverloads.Default, options); + } + + /// + /// Attempts to resolve a type using default options and the supplied name. + /// + /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). + /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. + /// + /// Type to resolve + /// Name of registration + /// Instance of type + /// Unable to resolve the type. + public object Resolve(Type resolveType, string name) + { + return ResolveInternal(new TypeRegistration(resolveType, name), NamedParameterOverloads.Default, ResolveOptions.Default); + } + + /// + /// Attempts to resolve a type using supplied options and name. + /// + /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). + /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. + /// + /// Type to resolve + /// Name of registration + /// Resolution options + /// Instance of type + /// Unable to resolve the type. + public object Resolve(Type resolveType, string name, ResolveOptions options) + { + return ResolveInternal(new TypeRegistration(resolveType, name), NamedParameterOverloads.Default, options); + } + + /// + /// Attempts to resolve a type using default options and the supplied constructor parameters. + /// + /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). + /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. + /// + /// Type to resolve + /// User specified constructor parameters + /// Instance of type + /// Unable to resolve the type. + public object Resolve(Type resolveType, NamedParameterOverloads parameters) + { + return ResolveInternal(new TypeRegistration(resolveType), parameters, ResolveOptions.Default); + } + + /// + /// Attempts to resolve a type using specified options and the supplied constructor parameters. + /// + /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). + /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. + /// + /// Type to resolve + /// User specified constructor parameters + /// Resolution options + /// Instance of type + /// Unable to resolve the type. + public object Resolve(Type resolveType, NamedParameterOverloads parameters, ResolveOptions options) + { + return ResolveInternal(new TypeRegistration(resolveType), parameters, options); + } + + /// + /// Attempts to resolve a type using default options and the supplied constructor parameters and name. + /// + /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). + /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. + /// + /// Type to resolve + /// User specified constructor parameters + /// Name of registration + /// Instance of type + /// Unable to resolve the type. + public object Resolve(Type resolveType, string name, NamedParameterOverloads parameters) + { + return ResolveInternal(new TypeRegistration(resolveType, name), parameters, ResolveOptions.Default); + } + + /// + /// Attempts to resolve a named type using specified options and the supplied constructor parameters. + /// + /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). + /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. + /// + /// Type to resolve + /// Name of registration + /// User specified constructor parameters + /// Resolution options + /// Instance of type + /// Unable to resolve the type. + public object Resolve(Type resolveType, string name, NamedParameterOverloads parameters, ResolveOptions options) + { + return ResolveInternal(new TypeRegistration(resolveType, name), parameters, options); + } + + /// + /// Attempts to resolve a type using default options. + /// + /// Type to resolve + /// Instance of type + /// Unable to resolve the type. + public ResolveType Resolve() + where ResolveType : class + { + return (ResolveType)Resolve(typeof(ResolveType)); + } + + /// + /// Attempts to resolve a type using specified options. + /// + /// Type to resolve + /// Resolution options + /// Instance of type + /// Unable to resolve the type. + public ResolveType Resolve(ResolveOptions options) + where ResolveType : class + { + return (ResolveType)Resolve(typeof(ResolveType), options); + } + + /// + /// Attempts to resolve a type using default options and the supplied name. + /// + /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). + /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. + /// + /// Type to resolve + /// Name of registration + /// Instance of type + /// Unable to resolve the type. + public ResolveType Resolve(string name) + where ResolveType : class + { + return (ResolveType)Resolve(typeof(ResolveType), name); + } + + /// + /// Attempts to resolve a type using supplied options and name. + /// + /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). + /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. + /// + /// Type to resolve + /// Name of registration + /// Resolution options + /// Instance of type + /// Unable to resolve the type. + public ResolveType Resolve(string name, ResolveOptions options) + where ResolveType : class + { + return (ResolveType)Resolve(typeof(ResolveType), name, options); + } + + /// + /// Attempts to resolve a type using default options and the supplied constructor parameters. + /// + /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). + /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. + /// + /// Type to resolve + /// User specified constructor parameters + /// Instance of type + /// Unable to resolve the type. + public ResolveType Resolve(NamedParameterOverloads parameters) + where ResolveType : class + { + return (ResolveType)Resolve(typeof(ResolveType), parameters); + } + + /// + /// Attempts to resolve a type using specified options and the supplied constructor parameters. + /// + /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). + /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. + /// + /// Type to resolve + /// User specified constructor parameters + /// Resolution options + /// Instance of type + /// Unable to resolve the type. + public ResolveType Resolve(NamedParameterOverloads parameters, ResolveOptions options) + where ResolveType : class + { + return (ResolveType)Resolve(typeof(ResolveType), parameters, options); + } + + /// + /// Attempts to resolve a type using default options and the supplied constructor parameters and name. + /// + /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). + /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. + /// + /// Type to resolve + /// User specified constructor parameters + /// Name of registration + /// Instance of type + /// Unable to resolve the type. + public ResolveType Resolve(string name, NamedParameterOverloads parameters) + where ResolveType : class + { + return (ResolveType)Resolve(typeof(ResolveType), name, parameters); + } + + /// + /// Attempts to resolve a named type using specified options and the supplied constructor parameters. + /// + /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). + /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. + /// + /// Type to resolve + /// Name of registration + /// User specified constructor parameters + /// Resolution options + /// Instance of type + /// Unable to resolve the type. + public ResolveType Resolve(string name, NamedParameterOverloads parameters, ResolveOptions options) + where ResolveType : class + { + return (ResolveType)Resolve(typeof(ResolveType), name, parameters, options); + } + + /// + /// Attempts to predict whether a given type can be resolved with default options. + /// + /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. + /// + /// Type to resolve + /// Bool indicating whether the type can be resolved + public bool CanResolve(Type resolveType) + { + return CanResolveInternal(new TypeRegistration(resolveType), NamedParameterOverloads.Default, ResolveOptions.Default); + } + + /// + /// Attempts to predict whether a given named type can be resolved with default options. + /// + /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. + /// + /// Type to resolve + /// Name of registration + /// Bool indicating whether the type can be resolved + private bool CanResolve(Type resolveType, string name) + { + return CanResolveInternal(new TypeRegistration(resolveType, name), NamedParameterOverloads.Default, ResolveOptions.Default); + } + + /// + /// Attempts to predict whether a given type can be resolved with the specified options. + /// + /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. + /// + /// Type to resolve + /// Resolution options + /// Bool indicating whether the type can be resolved + public bool CanResolve(Type resolveType, ResolveOptions options) + { + return CanResolveInternal(new TypeRegistration(resolveType), NamedParameterOverloads.Default, options); + } + + /// + /// Attempts to predict whether a given named type can be resolved with the specified options. + /// + /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. + /// + /// Type to resolve + /// Name of registration + /// Resolution options + /// Bool indicating whether the type can be resolved + public bool CanResolve(Type resolveType, string name, ResolveOptions options) + { + return CanResolveInternal(new TypeRegistration(resolveType, name), NamedParameterOverloads.Default, options); + } + + /// + /// Attempts to predict whether a given type can be resolved with the supplied constructor parameters and default options. + /// + /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). + /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. + /// + /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. + /// + /// Type to resolve + /// User supplied named parameter overloads + /// Bool indicating whether the type can be resolved + public bool CanResolve(Type resolveType, NamedParameterOverloads parameters) + { + return CanResolveInternal(new TypeRegistration(resolveType), parameters, ResolveOptions.Default); + } + + /// + /// Attempts to predict whether a given named type can be resolved with the supplied constructor parameters and default options. + /// + /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). + /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. + /// + /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. + /// + /// Type to resolve + /// Name of registration + /// User supplied named parameter overloads + /// Bool indicating whether the type can be resolved + public bool CanResolve(Type resolveType, string name, NamedParameterOverloads parameters) + { + return CanResolveInternal(new TypeRegistration(resolveType, name), parameters, ResolveOptions.Default); + } + + /// + /// Attempts to predict whether a given type can be resolved with the supplied constructor parameters options. + /// + /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). + /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. + /// + /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. + /// + /// Type to resolve + /// User supplied named parameter overloads + /// Resolution options + /// Bool indicating whether the type can be resolved + public bool CanResolve(Type resolveType, NamedParameterOverloads parameters, ResolveOptions options) + { + return CanResolveInternal(new TypeRegistration(resolveType), parameters, options); + } + + /// + /// Attempts to predict whether a given named type can be resolved with the supplied constructor parameters options. + /// + /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). + /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. + /// + /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. + /// + /// Type to resolve + /// Name of registration + /// User supplied named parameter overloads + /// Resolution options + /// Bool indicating whether the type can be resolved + public bool CanResolve(Type resolveType, string name, NamedParameterOverloads parameters, ResolveOptions options) + { + return CanResolveInternal(new TypeRegistration(resolveType, name), parameters, options); + } + + /// + /// Attempts to predict whether a given type can be resolved with default options. + /// + /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. + /// + /// Type to resolve + /// Bool indicating whether the type can be resolved + public bool CanResolve() + where ResolveType : class + { + return CanResolve(typeof(ResolveType)); + } + + /// + /// Attempts to predict whether a given named type can be resolved with default options. + /// + /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. + /// + /// Type to resolve + /// Bool indicating whether the type can be resolved + public bool CanResolve(string name) + where ResolveType : class + { + return CanResolve(typeof(ResolveType), name); + } + + /// + /// Attempts to predict whether a given type can be resolved with the specified options. + /// + /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. + /// + /// Type to resolve + /// Resolution options + /// Bool indicating whether the type can be resolved + public bool CanResolve(ResolveOptions options) + where ResolveType : class + { + return CanResolve(typeof(ResolveType), options); + } + + /// + /// Attempts to predict whether a given named type can be resolved with the specified options. + /// + /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. + /// + /// Type to resolve + /// Name of registration + /// Resolution options + /// Bool indicating whether the type can be resolved + public bool CanResolve(string name, ResolveOptions options) + where ResolveType : class + { + return CanResolve(typeof(ResolveType), name, options); + } + + /// + /// Attempts to predict whether a given type can be resolved with the supplied constructor parameters and default options. + /// + /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). + /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. + /// + /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. + /// + /// Type to resolve + /// User supplied named parameter overloads + /// Bool indicating whether the type can be resolved + public bool CanResolve(NamedParameterOverloads parameters) + where ResolveType : class + { + return CanResolve(typeof(ResolveType), parameters); + } + + /// + /// Attempts to predict whether a given named type can be resolved with the supplied constructor parameters and default options. + /// + /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). + /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. + /// + /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. + /// + /// Type to resolve + /// Name of registration + /// User supplied named parameter overloads + /// Bool indicating whether the type can be resolved + public bool CanResolve(string name, NamedParameterOverloads parameters) + where ResolveType : class + { + return CanResolve(typeof(ResolveType), name, parameters); + } + + /// + /// Attempts to predict whether a given type can be resolved with the supplied constructor parameters options. + /// + /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). + /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. + /// + /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. + /// + /// Type to resolve + /// User supplied named parameter overloads + /// Resolution options + /// Bool indicating whether the type can be resolved + public bool CanResolve(NamedParameterOverloads parameters, ResolveOptions options) + where ResolveType : class + { + return CanResolve(typeof(ResolveType), parameters, options); + } + + /// + /// Attempts to predict whether a given named type can be resolved with the supplied constructor parameters options. + /// + /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). + /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. + /// + /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. + /// + /// Type to resolve + /// Name of registration + /// User supplied named parameter overloads + /// Resolution options + /// Bool indicating whether the type can be resolved + public bool CanResolve(string name, NamedParameterOverloads parameters, ResolveOptions options) + where ResolveType : class + { + return CanResolve(typeof(ResolveType), name, parameters, options); + } + + /// + /// Attemps to resolve a type using the default options + /// + /// Type to resolve + /// Resolved type or default if resolve fails + /// True if resolved sucessfully, false otherwise + public bool TryResolve(Type resolveType, out object resolvedType) + { + try + { + resolvedType = Resolve(resolveType); + return true; + } + catch (TinyIoCResolutionException) + { + resolvedType = null; + return false; + } + } + + /// + /// Attemps to resolve a type using the given options + /// + /// Type to resolve + /// Resolution options + /// Resolved type or default if resolve fails + /// True if resolved sucessfully, false otherwise + public bool TryResolve(Type resolveType, ResolveOptions options, out object resolvedType) + { + try + { + resolvedType = Resolve(resolveType, options); + return true; + } + catch (TinyIoCResolutionException) + { + resolvedType = null; + return false; + } + } + + /// + /// Attemps to resolve a type using the default options and given name + /// + /// Type to resolve + /// Name of registration + /// Resolved type or default if resolve fails + /// True if resolved sucessfully, false otherwise + public bool TryResolve(Type resolveType, string name, out object resolvedType) + { + try + { + resolvedType = Resolve(resolveType, name); + return true; + } + catch (TinyIoCResolutionException) + { + resolvedType = null; + return false; + } + } + + /// + /// Attemps to resolve a type using the given options and name + /// + /// Type to resolve + /// Name of registration + /// Resolution options + /// Resolved type or default if resolve fails + /// True if resolved sucessfully, false otherwise + public bool TryResolve(Type resolveType, string name, ResolveOptions options, out object resolvedType) + { + try + { + resolvedType = Resolve(resolveType, name, options); + return true; + } + catch (TinyIoCResolutionException) + { + resolvedType = null; + return false; + } + } + + /// + /// Attemps to resolve a type using the default options and supplied constructor parameters + /// + /// Type to resolve + /// User specified constructor parameters + /// Resolved type or default if resolve fails + /// True if resolved sucessfully, false otherwise + public bool TryResolve(Type resolveType, NamedParameterOverloads parameters, out object resolvedType) + { + try + { + resolvedType = Resolve(resolveType, parameters); + return true; + } + catch (TinyIoCResolutionException) + { + resolvedType = null; + return false; + } + } + + /// + /// Attemps to resolve a type using the default options and supplied name and constructor parameters + /// + /// Type to resolve + /// Name of registration + /// User specified constructor parameters + /// Resolved type or default if resolve fails + /// True if resolved sucessfully, false otherwise + public bool TryResolve(Type resolveType, string name, NamedParameterOverloads parameters, out object resolvedType) + { + try + { + resolvedType = Resolve(resolveType, name, parameters); + return true; + } + catch (TinyIoCResolutionException) + { + resolvedType = null; + return false; + } + } + + /// + /// Attemps to resolve a type using the supplied options and constructor parameters + /// + /// Type to resolve + /// User specified constructor parameters + /// Resolution options + /// Resolved type or default if resolve fails + /// True if resolved sucessfully, false otherwise + public bool TryResolve(Type resolveType, NamedParameterOverloads parameters, ResolveOptions options, out object resolvedType) + { + try + { + resolvedType = Resolve(resolveType, parameters, options); + return true; + } + catch (TinyIoCResolutionException) + { + resolvedType = null; + return false; + } + } + + /// + /// Attemps to resolve a type using the supplied name, options and constructor parameters + /// + /// Type to resolve + /// Name of registration + /// User specified constructor parameters + /// Resolution options + /// Resolved type or default if resolve fails + /// True if resolved sucessfully, false otherwise + public bool TryResolve(Type resolveType, string name, NamedParameterOverloads parameters, ResolveOptions options, out object resolvedType) + { + try + { + resolvedType = Resolve(resolveType, name, parameters, options); + return true; + } + catch (TinyIoCResolutionException) + { + resolvedType = null; + return false; + } + } + + /// + /// Attemps to resolve a type using the default options + /// + /// Type to resolve + /// Resolved type or default if resolve fails + /// True if resolved sucessfully, false otherwise + public bool TryResolve(out ResolveType resolvedType) + where ResolveType : class + { + try + { + resolvedType = Resolve(); + return true; + } + catch (TinyIoCResolutionException) + { + resolvedType = default(ResolveType); + return false; + } + } + + /// + /// Attemps to resolve a type using the given options + /// + /// Type to resolve + /// Resolution options + /// Resolved type or default if resolve fails + /// True if resolved sucessfully, false otherwise + public bool TryResolve(ResolveOptions options, out ResolveType resolvedType) + where ResolveType : class + { + try + { + resolvedType = Resolve(options); + return true; + } + catch (TinyIoCResolutionException) + { + resolvedType = default(ResolveType); + return false; + } + } + + /// + /// Attemps to resolve a type using the default options and given name + /// + /// Type to resolve + /// Name of registration + /// Resolved type or default if resolve fails + /// True if resolved sucessfully, false otherwise + public bool TryResolve(string name, out ResolveType resolvedType) + where ResolveType : class + { + try + { + resolvedType = Resolve(name); + return true; + } + catch (TinyIoCResolutionException) + { + resolvedType = default(ResolveType); + return false; + } + } + + /// + /// Attemps to resolve a type using the given options and name + /// + /// Type to resolve + /// Name of registration + /// Resolution options + /// Resolved type or default if resolve fails + /// True if resolved sucessfully, false otherwise + public bool TryResolve(string name, ResolveOptions options, out ResolveType resolvedType) + where ResolveType : class + { + try + { + resolvedType = Resolve(name, options); + return true; + } + catch (TinyIoCResolutionException) + { + resolvedType = default(ResolveType); + return false; + } + } + + /// + /// Attemps to resolve a type using the default options and supplied constructor parameters + /// + /// Type to resolve + /// User specified constructor parameters + /// Resolved type or default if resolve fails + /// True if resolved sucessfully, false otherwise + public bool TryResolve(NamedParameterOverloads parameters, out ResolveType resolvedType) + where ResolveType : class + { + try + { + resolvedType = Resolve(parameters); + return true; + } + catch (TinyIoCResolutionException) + { + resolvedType = default(ResolveType); + return false; + } + } + + /// + /// Attemps to resolve a type using the default options and supplied name and constructor parameters + /// + /// Type to resolve + /// Name of registration + /// User specified constructor parameters + /// Resolved type or default if resolve fails + /// True if resolved sucessfully, false otherwise + public bool TryResolve(string name, NamedParameterOverloads parameters, out ResolveType resolvedType) + where ResolveType : class + { + try + { + resolvedType = Resolve(name, parameters); + return true; + } + catch (TinyIoCResolutionException) + { + resolvedType = default(ResolveType); + return false; + } + } + + /// + /// Attemps to resolve a type using the supplied options and constructor parameters + /// + /// Type to resolve + /// User specified constructor parameters + /// Resolution options + /// Resolved type or default if resolve fails + /// True if resolved sucessfully, false otherwise + public bool TryResolve(NamedParameterOverloads parameters, ResolveOptions options, out ResolveType resolvedType) + where ResolveType : class + { + try + { + resolvedType = Resolve(parameters, options); + return true; + } + catch (TinyIoCResolutionException) + { + resolvedType = default(ResolveType); + return false; + } + } + + /// + /// Attemps to resolve a type using the supplied name, options and constructor parameters + /// + /// Type to resolve + /// Name of registration + /// User specified constructor parameters + /// Resolution options + /// Resolved type or default if resolve fails + /// True if resolved sucessfully, false otherwise + public bool TryResolve(string name, NamedParameterOverloads parameters, ResolveOptions options, out ResolveType resolvedType) + where ResolveType : class + { + try + { + resolvedType = Resolve(name, parameters, options); + return true; + } + catch (TinyIoCResolutionException) + { + resolvedType = default(ResolveType); + return false; + } + } + + /// + /// Returns all registrations of a type + /// + /// Type to resolveAll + /// Whether to include un-named (default) registrations + /// IEnumerable + public IEnumerable ResolveAll(Type resolveType, bool includeUnnamed) + { + return ResolveAllInternal(resolveType, includeUnnamed); + } + + /// + /// Returns all registrations of a type, both named and unnamed + /// + /// Type to resolveAll + /// IEnumerable + public IEnumerable ResolveAll(Type resolveType) + { + return ResolveAll(resolveType, true); + } + + /// + /// Returns all registrations of a type + /// + /// Type to resolveAll + /// Whether to include un-named (default) registrations + /// IEnumerable + public IEnumerable ResolveAll(bool includeUnnamed) + where ResolveType : class + { + return ResolveAll(typeof(ResolveType), includeUnnamed).Cast(); + } + + /// + /// Returns all registrations of a type, both named and unnamed + /// + /// Type to resolveAll + /// IEnumerable + public IEnumerable ResolveAll() + where ResolveType : class + { + return ResolveAll(true); + } + + /// + /// Attempts to resolve all public property dependencies on the given object. + /// + /// Object to "build up" + public void BuildUp(object input) + { + BuildUpInternal(input, ResolveOptions.Default); + } + + /// + /// Attempts to resolve all public property dependencies on the given object using the given resolve options. + /// + /// Object to "build up" + /// Resolve options to use + public void BuildUp(object input, ResolveOptions resolveOptions) + { + BuildUpInternal(input, resolveOptions); + } + #endregion + #endregion + + #region Object Factories + /// + /// Provides custom lifetime management for ASP.Net per-request lifetimes etc. + /// + public interface ITinyIoCObjectLifetimeProvider + { + /// + /// Gets the stored object if it exists, or null if not + /// + /// Object instance or null + object GetObject(); + + /// + /// Store the object + /// + /// Object to store + void SetObject(object value); + + /// + /// Release the object + /// + void ReleaseObject(); + } + + private abstract class ObjectFactoryBase + { + /// + /// Whether to assume this factory sucessfully constructs its objects + /// + /// Generally set to true for delegate style factories as CanResolve cannot delve + /// into the delegates they contain. + /// + public virtual bool AssumeConstruction { get { return false; } } + + /// + /// The type the factory instantiates + /// + public abstract Type CreatesType { get; } + + /// + /// Constructor to use, if specified + /// + public ConstructorInfo Constructor { get; protected set; } + + /// + /// Create the type + /// + /// Type user requested to be resolved + /// Container that requested the creation + /// Any user parameters passed + /// + /// + public abstract object GetObject(Type requestedType, TinyIoCContainer container, NamedParameterOverloads parameters, ResolveOptions options); + + public virtual ObjectFactoryBase SingletonVariant + { + get + { + throw new TinyIoCRegistrationException(GetType(), "singleton"); + } + } + + public virtual ObjectFactoryBase MultiInstanceVariant + { + get + { + throw new TinyIoCRegistrationException(GetType(), "multi-instance"); + } + } + + public virtual ObjectFactoryBase StrongReferenceVariant + { + get + { + throw new TinyIoCRegistrationException(GetType(), "strong reference"); + } + } + + public virtual ObjectFactoryBase WeakReferenceVariant + { + get + { + throw new TinyIoCRegistrationException(GetType(), "weak reference"); + } + } + + public virtual ObjectFactoryBase GetCustomObjectLifetimeVariant(ITinyIoCObjectLifetimeProvider lifetimeProvider, string errorString) + { + throw new TinyIoCRegistrationException(GetType(), errorString); + } + + public virtual void SetConstructor(ConstructorInfo constructor) + { + Constructor = constructor; + } + + public virtual ObjectFactoryBase GetFactoryForChildContainer(Type type, TinyIoCContainer parent, TinyIoCContainer child) + { + return this; + } + } + + /// + /// IObjectFactory that creates new instances of types for each resolution + /// + private class MultiInstanceFactory : ObjectFactoryBase + { + private readonly Type registerType; + private readonly Type registerImplementation; + public override Type CreatesType { get { return registerImplementation; } } + + public MultiInstanceFactory(Type registerType, Type registerImplementation) + { + //#if NETFX_CORE + // if (registerImplementation.GetTypeInfo().IsAbstract() || registerImplementation.GetTypeInfo().IsInterface()) + // throw new TinyIoCRegistrationTypeException(registerImplementation, "MultiInstanceFactory"); + //#else + if (registerImplementation.IsAbstract() || registerImplementation.IsInterface()) + throw new TinyIoCRegistrationTypeException(registerImplementation, "MultiInstanceFactory"); + //#endif + if (!IsValidAssignment(registerType, registerImplementation)) + throw new TinyIoCRegistrationTypeException(registerImplementation, "MultiInstanceFactory"); + + this.registerType = registerType; + this.registerImplementation = registerImplementation; + } + + public override object GetObject(Type requestedType, TinyIoCContainer container, NamedParameterOverloads parameters, ResolveOptions options) + { + try + { + return container.ConstructType(requestedType, registerImplementation, Constructor, parameters, options); + } + catch (TinyIoCResolutionException ex) + { + throw new TinyIoCResolutionException(registerType, ex); + } + } + + public override ObjectFactoryBase SingletonVariant + { + get + { + return new SingletonFactory(registerType, registerImplementation); + } + } + + public override ObjectFactoryBase GetCustomObjectLifetimeVariant(ITinyIoCObjectLifetimeProvider lifetimeProvider, string errorString) + { + return new CustomObjectLifetimeFactory(registerType, registerImplementation, lifetimeProvider, errorString); + } + + public override ObjectFactoryBase MultiInstanceVariant + { + get + { + return this; + } + } + } + + /// + /// IObjectFactory that invokes a specified delegate to construct the object + /// + private class DelegateFactory : ObjectFactoryBase + { + private readonly Type registerType; + + private Func _factory; + + public override bool AssumeConstruction { get { return true; } } + + public override Type CreatesType { get { return registerType; } } + + public override object GetObject(Type requestedType, TinyIoCContainer container, NamedParameterOverloads parameters, ResolveOptions options) + { + try + { + return _factory.Invoke(container, parameters); + } + catch (Exception ex) + { + throw new TinyIoCResolutionException(registerType, ex); + } + } + + public DelegateFactory(Type registerType, Func factory) + { +#pragma warning disable IDE0016 // Null check can be simplified - Build.Bat Error Happens if We Do + if (factory == null) + throw new ArgumentNullException("factory"); + + _factory = factory; + + this.registerType = registerType; +#pragma warning restore IDE0016 // Null check can be simplified - Build.Bat Error Happens if We Do + } + + public override ObjectFactoryBase WeakReferenceVariant + { + get + { + return new WeakDelegateFactory(registerType, _factory); + } + } + + public override ObjectFactoryBase StrongReferenceVariant + { + get + { + return this; + } + } + + public override void SetConstructor(ConstructorInfo constructor) + { + throw new TinyIoCConstructorResolutionException("Constructor selection is not possible for delegate factory registrations"); + } + } + + /// + /// IObjectFactory that invokes a specified delegate to construct the object + /// Holds the delegate using a weak reference + /// + private class WeakDelegateFactory : ObjectFactoryBase + { + private readonly Type registerType; + + private WeakReference _factory; + + public override bool AssumeConstruction { get { return true; } } + + public override Type CreatesType { get { return registerType; } } + + public override object GetObject(Type requestedType, TinyIoCContainer container, NamedParameterOverloads parameters, ResolveOptions options) + { + var factory = _factory.Target as Func; + + if (factory == null) + throw new TinyIoCWeakReferenceException(registerType); + + try + { + return factory.Invoke(container, parameters); + } + catch (Exception ex) + { + throw new TinyIoCResolutionException(registerType, ex); + } + } + + public WeakDelegateFactory(Type registerType, Func factory) + { + if (factory == null) + throw new ArgumentNullException("factory"); + + _factory = new WeakReference(factory); + + this.registerType = registerType; + } + + public override ObjectFactoryBase StrongReferenceVariant + { + get + { + var factory = _factory.Target as Func; + + if (factory == null) + throw new TinyIoCWeakReferenceException(registerType); + + return new DelegateFactory(registerType, factory); + } + } + + public override ObjectFactoryBase WeakReferenceVariant + { + get + { + return this; + } + } + + public override void SetConstructor(ConstructorInfo constructor) + { + throw new TinyIoCConstructorResolutionException("Constructor selection is not possible for delegate factory registrations"); + } + } + + /// + /// Stores an particular instance to return for a type + /// + private class InstanceFactory : ObjectFactoryBase, IDisposable + { + private readonly Type registerType; + private readonly Type registerImplementation; + private object _instance; + + public override bool AssumeConstruction { get { return true; } } + + public InstanceFactory(Type registerType, Type registerImplementation, object instance) + { + if (!IsValidAssignment(registerType, registerImplementation)) + throw new TinyIoCRegistrationTypeException(registerImplementation, "InstanceFactory"); + + this.registerType = registerType; + this.registerImplementation = registerImplementation; + _instance = instance; + } + + public override Type CreatesType + { + get { return registerImplementation; } + } + + public override object GetObject(Type requestedType, TinyIoCContainer container, NamedParameterOverloads parameters, ResolveOptions options) + { + return _instance; + } + + public override ObjectFactoryBase MultiInstanceVariant + { + get { return new MultiInstanceFactory(registerType, registerImplementation); } + } + + public override ObjectFactoryBase WeakReferenceVariant + { + get + { + return new WeakInstanceFactory(registerType, registerImplementation, _instance); + } + } + + public override ObjectFactoryBase StrongReferenceVariant + { + get + { + return this; + } + } + + public override void SetConstructor(ConstructorInfo constructor) + { + throw new TinyIoCConstructorResolutionException("Constructor selection is not possible for instance factory registrations"); + } + + public void Dispose() + { +#pragma warning disable IDE0019 // Use pattern matching - Build.Bat Error Happens if We Do + var disposable = _instance as IDisposable; + + if (disposable != null) + disposable.Dispose(); +#pragma warning restore IDE0019 // Use pattern matching - Build.Bat Error Happens if We Do + } + } + + /// + /// Stores an particular instance to return for a type + /// + /// Stores the instance with a weak reference + /// + private class WeakInstanceFactory : ObjectFactoryBase, IDisposable + { + private readonly Type registerType; + private readonly Type registerImplementation; + private readonly WeakReference _instance; + + public WeakInstanceFactory(Type registerType, Type registerImplementation, object instance) + { + if (!IsValidAssignment(registerType, registerImplementation)) + throw new TinyIoCRegistrationTypeException(registerImplementation, "WeakInstanceFactory"); + + this.registerType = registerType; + this.registerImplementation = registerImplementation; + _instance = new WeakReference(instance); + } + + public override Type CreatesType + { + get { return registerImplementation; } + } + + public override object GetObject(Type requestedType, TinyIoCContainer container, NamedParameterOverloads parameters, ResolveOptions options) + { + var instance = _instance.Target; + + if (instance == null) + throw new TinyIoCWeakReferenceException(registerType); + + return instance; + } + + public override ObjectFactoryBase MultiInstanceVariant + { + get + { + return new MultiInstanceFactory(registerType, registerImplementation); + } + } + + public override ObjectFactoryBase WeakReferenceVariant + { + get + { + return this; + } + } + + public override ObjectFactoryBase StrongReferenceVariant + { + get + { + var instance = _instance.Target; + + if (instance == null) + throw new TinyIoCWeakReferenceException(registerType); + + return new InstanceFactory(registerType, registerImplementation, instance); + } + } + + public override void SetConstructor(ConstructorInfo constructor) + { + throw new TinyIoCConstructorResolutionException("Constructor selection is not possible for instance factory registrations"); + } + + public void Dispose() + { +#pragma warning disable IDE0019 // Use pattern matching - Build.Bat Error Happens if We Do + var disposable = _instance.Target as IDisposable; + + if (disposable != null) + disposable.Dispose(); +#pragma warning restore IDE0019 // Use pattern matching - Build.Bat Error Happens if We Do + } + } + + /// + /// A factory that lazy instantiates a type and always returns the same instance + /// + private class SingletonFactory : ObjectFactoryBase, IDisposable + { + private readonly Type registerType; + private readonly Type registerImplementation; + private readonly object SingletonLock = new object(); + private object _Current; + + public SingletonFactory(Type registerType, Type registerImplementation) + { + //#if NETFX_CORE + // if (registerImplementation.GetTypeInfo().IsAbstract() || registerImplementation.GetTypeInfo().IsInterface()) + //#else + if (registerImplementation.IsAbstract() || registerImplementation.IsInterface()) + //#endif + throw new TinyIoCRegistrationTypeException(registerImplementation, "SingletonFactory"); + + if (!IsValidAssignment(registerType, registerImplementation)) + throw new TinyIoCRegistrationTypeException(registerImplementation, "SingletonFactory"); + + this.registerType = registerType; + this.registerImplementation = registerImplementation; + } + + public override Type CreatesType + { + get { return registerImplementation; } + } + + public override object GetObject(Type requestedType, TinyIoCContainer container, NamedParameterOverloads parameters, ResolveOptions options) + { + if (parameters.Count != 0) + throw new ArgumentException("Cannot specify parameters for singleton types"); + + lock (SingletonLock) + if (_Current == null) + _Current = container.ConstructType(requestedType, registerImplementation, Constructor, options); + + return _Current; + } + + public override ObjectFactoryBase SingletonVariant + { + get + { + return this; + } + } + + public override ObjectFactoryBase GetCustomObjectLifetimeVariant(ITinyIoCObjectLifetimeProvider lifetimeProvider, string errorString) + { + return new CustomObjectLifetimeFactory(registerType, registerImplementation, lifetimeProvider, errorString); + } + + public override ObjectFactoryBase MultiInstanceVariant + { + get + { + return new MultiInstanceFactory(registerType, registerImplementation); + } + } + + public override ObjectFactoryBase GetFactoryForChildContainer(Type type, TinyIoCContainer parent, TinyIoCContainer child) + { + // We make sure that the singleton is constructed before the child container takes the factory. + // Otherwise the results would vary depending on whether or not the parent container had resolved + // the type before the child container does. + GetObject(type, parent, NamedParameterOverloads.Default, ResolveOptions.Default); + return this; + } + + public void Dispose() + { + var disposable = _Current as IDisposable; + if (_Current == null) + return; + + if (disposable != null) + disposable.Dispose(); + } + } + + /// + /// A factory that offloads lifetime to an external lifetime provider + /// + private class CustomObjectLifetimeFactory : ObjectFactoryBase, IDisposable + { + private readonly object SingletonLock = new object(); + private readonly Type registerType; + private readonly Type registerImplementation; + private readonly ITinyIoCObjectLifetimeProvider _LifetimeProvider; + + public CustomObjectLifetimeFactory(Type registerType, Type registerImplementation, ITinyIoCObjectLifetimeProvider lifetimeProvider, string errorMessage) + { +#pragma warning disable IDE0016 // Null check can be simplified - Build.Bat Error Happens if We Do + if (lifetimeProvider == null) + throw new ArgumentNullException("lifetimeProvider", "lifetimeProvider is null."); + + if (!IsValidAssignment(registerType, registerImplementation)) + throw new TinyIoCRegistrationTypeException(registerImplementation, "SingletonFactory"); + + //#if NETFX_CORE + // if (registerImplementation.GetTypeInfo().IsAbstract() || registerImplementation.GetTypeInfo().IsInterface()) + //#else + if (registerImplementation.IsAbstract() || registerImplementation.IsInterface()) + //#endif + throw new TinyIoCRegistrationTypeException(registerImplementation, errorMessage); + + this.registerType = registerType; + this.registerImplementation = registerImplementation; + _LifetimeProvider = lifetimeProvider; +#pragma warning restore IDE0016 // Null check can be simplified - Build.Bat Error Happens if We Do + } + + public override Type CreatesType + { + get { return registerImplementation; } + } + + public override object GetObject(Type requestedType, TinyIoCContainer container, NamedParameterOverloads parameters, ResolveOptions options) + { + object current; + + lock (SingletonLock) + { + current = _LifetimeProvider.GetObject(); + if (current == null) + { + current = container.ConstructType(requestedType, registerImplementation, Constructor, options); + _LifetimeProvider.SetObject(current); + } + } + + return current; + } + + public override ObjectFactoryBase SingletonVariant + { + get + { + _LifetimeProvider.ReleaseObject(); + return new SingletonFactory(registerType, registerImplementation); + } + } + + public override ObjectFactoryBase MultiInstanceVariant + { + get + { + _LifetimeProvider.ReleaseObject(); + return new MultiInstanceFactory(registerType, registerImplementation); + } + } + + public override ObjectFactoryBase GetCustomObjectLifetimeVariant(ITinyIoCObjectLifetimeProvider lifetimeProvider, string errorString) + { + _LifetimeProvider.ReleaseObject(); + return new CustomObjectLifetimeFactory(registerType, registerImplementation, lifetimeProvider, errorString); + } + + public override ObjectFactoryBase GetFactoryForChildContainer(Type type, TinyIoCContainer parent, TinyIoCContainer child) + { + // We make sure that the singleton is constructed before the child container takes the factory. + // Otherwise the results would vary depending on whether or not the parent container had resolved + // the type before the child container does. + GetObject(type, parent, NamedParameterOverloads.Default, ResolveOptions.Default); + return this; + } + + public void Dispose() + { + _LifetimeProvider.ReleaseObject(); + } + } + #endregion + + #region Singleton Container + private static readonly TinyIoCContainer _Current = new TinyIoCContainer(); + + static TinyIoCContainer() + { + } + + /// + /// Lazy created Singleton instance of the container for simple scenarios + /// + public static TinyIoCContainer Current + { + get + { + return _Current; + } + } + #endregion + + #region Type Registrations + public sealed class TypeRegistration + { + private int _hashCode; + + public Type Type { get; private set; } + public string Name { get; private set; } + + public TypeRegistration(Type type) + : this(type, string.Empty) + { + } + + public TypeRegistration(Type type, string name) + { + Type = type; + Name = name; + + _hashCode = String.Concat(Type.FullName, "|", Name).GetHashCode(); + } + + public override bool Equals(object obj) + { + var typeRegistration = obj as TypeRegistration; + + if (typeRegistration == null) + return false; + + if (Type != typeRegistration.Type) + return false; + + if (String.Compare(Name, typeRegistration.Name, StringComparison.Ordinal) != 0) + return false; + + return true; + } + + public override int GetHashCode() + { + return _hashCode; + } + } + private readonly SafeDictionary _RegisteredTypes; +#if USE_OBJECT_CONSTRUCTOR + private delegate object ObjectConstructor(params object[] parameters); + private static readonly SafeDictionary _ObjectConstructorCache = new SafeDictionary(); +#endif + #endregion + + #region Constructors + public TinyIoCContainer() + { + _RegisteredTypes = new SafeDictionary(); + + RegisterDefaultTypes(); + } + + TinyIoCContainer _Parent; + private TinyIoCContainer(TinyIoCContainer parent) + : this() + { + _Parent = parent; + } + #endregion + + #region Internal Methods + private readonly object _AutoRegisterLock = new object(); + private void AutoRegisterInternal(IEnumerable assemblies, DuplicateImplementationActions duplicateAction, Func registrationPredicate) + { + lock (_AutoRegisterLock) + { + var types = assemblies.SelectMany(a => a.SafeGetTypes()).Where(t => !IsIgnoredType(t, registrationPredicate)).ToList(); + + var concreteTypes = types + .Where(type => type.IsClass() && (type.IsAbstract() == false) && (type != GetType() && (type.DeclaringType != GetType()) && (!type.IsGenericTypeDefinition()))) + .ToList(); + + foreach (var type in concreteTypes) + { + try + { + RegisterInternal(type, string.Empty, GetDefaultObjectFactory(type, type)); + } +#if PORTABLE || NETSTANDARD1_0 || NETSTANDARD1_1 || NETSTANDARD1_2 || NETSTANDARD1_3 || NETSTANDARD1_4 || NETSTANDARD1_5 || NETSTANDARD1_6 + catch (MemberAccessException) +#else + catch (MethodAccessException) +#endif + { + // Ignore methods we can't access - added for Silverlight + } + } + + var abstractInterfaceTypes = from type in types + where ((type.IsInterface() || type.IsAbstract()) && (type.DeclaringType != GetType()) && (!type.IsGenericTypeDefinition())) + select type; + + foreach (var type in abstractInterfaceTypes) + { + var localType = type; + var implementations = from implementationType in concreteTypes + where localType.IsAssignableFrom(implementationType) + select implementationType; + + if (implementations.Skip(1).Any()) + { + if (duplicateAction == DuplicateImplementationActions.Fail) + throw new TinyIoCAutoRegistrationException(type, implementations); + + if (duplicateAction == DuplicateImplementationActions.RegisterMultiple) + { + RegisterMultiple(type, implementations); + } + } + + var firstImplementation = implementations.FirstOrDefault(); + if (firstImplementation != null) + { + try + { + RegisterInternal(type, string.Empty, GetDefaultObjectFactory(type, firstImplementation)); + } +#if PORTABLE || NETSTANDARD1_0 || NETSTANDARD1_1 || NETSTANDARD1_2 || NETSTANDARD1_3 || NETSTANDARD1_4 || NETSTANDARD1_5 || NETSTANDARD1_6 + catch (MemberAccessException) +#else + catch (MethodAccessException) +#endif + { + // Ignore methods we can't access - added for Silverlight + } + } + } + } + } + + private bool IsIgnoredAssembly(Assembly assembly) + { + // TODO - find a better way to remove "system" assemblies from the auto registration + var ignoreChecks = new List>() + { + asm => asm.FullName.StartsWith("Microsoft.", StringComparison.Ordinal), + asm => asm.FullName.StartsWith("System.", StringComparison.Ordinal), + asm => asm.FullName.StartsWith("System,", StringComparison.Ordinal), + asm => asm.FullName.StartsWith("CR_ExtUnitTest", StringComparison.Ordinal), + asm => asm.FullName.StartsWith("mscorlib,", StringComparison.Ordinal), + asm => asm.FullName.StartsWith("CR_VSTest", StringComparison.Ordinal), + asm => asm.FullName.StartsWith("DevExpress.CodeRush", StringComparison.Ordinal), + asm => asm.FullName.StartsWith("xunit.", StringComparison.Ordinal), + }; + + foreach (var check in ignoreChecks) + { + if (check(assembly)) + return true; + } + + return false; + } + + private bool IsIgnoredType(Type type, Func registrationPredicate) + { + // TODO - find a better way to remove "system" types from the auto registration + var ignoreChecks = new List>() + { + t => t.FullName.StartsWith("System.", StringComparison.Ordinal), + t => t.FullName.StartsWith("Microsoft.", StringComparison.Ordinal), + t => t.IsPrimitive(), +#if !UNBOUND_GENERICS_GETCONSTRUCTORS + t => t.IsGenericTypeDefinition(), +#endif + t => (t.GetConstructors(BindingFlags.Instance | BindingFlags.Public).Length == 0) && !(t.IsInterface() || t.IsAbstract()), + }; + + if (registrationPredicate != null) + { + ignoreChecks.Add(t => !registrationPredicate(t)); + } + + foreach (var check in ignoreChecks) + { + if (check(type)) + return true; + } + + return false; + } + + private void RegisterDefaultTypes() + { + Register(this); + +#if TINYMESSENGER + // Only register the TinyMessenger singleton if we are the root container + if (_Parent == null) + Register(); +#endif + } + + private ObjectFactoryBase GetCurrentFactory(TypeRegistration registration) + { +#pragma warning disable IDE0018 // Inline variable declaration - Build.Bat Error Happens if We Do + ObjectFactoryBase current; + _RegisteredTypes.TryGetValue(registration, out current); + + return current; +#pragma warning restore IDE0018 // Inline variable declaration - Build.Bat Error Happens if We Do + } + + private RegisterOptions RegisterInternal(Type registerType, string name, ObjectFactoryBase factory) + { + var typeRegistration = new TypeRegistration(registerType, name); + + return AddUpdateRegistration(typeRegistration, factory); + } + + private RegisterOptions AddUpdateRegistration(TypeRegistration typeRegistration, ObjectFactoryBase factory) + { + _RegisteredTypes[typeRegistration] = factory; + + return new RegisterOptions(this, typeRegistration); + } + + private bool RemoveRegistration(TypeRegistration typeRegistration) + { + return _RegisteredTypes.Remove(typeRegistration); + } + + private ObjectFactoryBase GetDefaultObjectFactory(Type registerType, Type registerImplementation) + { + //#if NETFX_CORE + // if (registerType.GetTypeInfo().IsInterface() || registerType.GetTypeInfo().IsAbstract()) + //#else + if (registerType.IsInterface() || registerType.IsAbstract()) + //#endif + return new SingletonFactory(registerType, registerImplementation); + + return new MultiInstanceFactory(registerType, registerImplementation); + } + + private bool CanResolveInternal(TypeRegistration registration, NamedParameterOverloads parameters, ResolveOptions options) + { +#pragma warning disable IDE0018 // Inline variable declaration - Build.Bat Error Happens if We Do + if (parameters == null) + throw new ArgumentNullException("parameters"); + + Type checkType = registration.Type; + string name = registration.Name; + ObjectFactoryBase factory; + + if (_RegisteredTypes.TryGetValue(new TypeRegistration(checkType, name), out factory)) + { + if (factory.AssumeConstruction) + return true; + + if (factory.Constructor == null) + return (GetBestConstructor(factory.CreatesType, parameters, options) != null) ? true : false; + else + return CanConstruct(factory.Constructor, parameters, options); + } + +#if RESOLVE_OPEN_GENERICS + if (checkType.IsInterface() && checkType.IsGenericType()) + { + // if the type is registered as an open generic, then see if the open generic is registered + if (_RegisteredTypes.TryGetValue(new TypeRegistration(checkType.GetGenericTypeDefinition(), name), out factory)) + { + if (factory.AssumeConstruction) + return true; + + if (factory.Constructor == null) + return (GetBestConstructor(factory.CreatesType, parameters, options) != null) ? true : false; + else + return CanConstruct(factory.Constructor, parameters, options); + } + } +#endif + + // Fail if requesting named resolution and settings set to fail if unresolved + // Or bubble up if we have a parent + if (!string.IsNullOrEmpty(name) && options.NamedResolutionFailureAction == NamedResolutionFailureActions.Fail) + return (_Parent != null) ? _Parent.CanResolveInternal(registration, parameters, options) : false; + + // Attemped unnamed fallback container resolution if relevant and requested + if (!string.IsNullOrEmpty(name) && options.NamedResolutionFailureAction == NamedResolutionFailureActions.AttemptUnnamedResolution) + { + if (_RegisteredTypes.TryGetValue(new TypeRegistration(checkType), out factory)) + { + if (factory.AssumeConstruction) + return true; + + return (GetBestConstructor(factory.CreatesType, parameters, options) != null) ? true : false; + } + } + + // Check if type is an automatic lazy factory request + if (IsAutomaticLazyFactoryRequest(checkType)) + return true; + + // Check if type is an IEnumerable + if (IsIEnumerableRequest(registration.Type)) + return true; + + // Attempt unregistered construction if possible and requested + // If we cant', bubble if we have a parent + if ((options.UnregisteredResolutionAction == UnregisteredResolutionActions.AttemptResolve) || (checkType.IsGenericType() && options.UnregisteredResolutionAction == UnregisteredResolutionActions.GenericsOnly)) + return (GetBestConstructor(checkType, parameters, options) != null) ? true : (_Parent != null) ? _Parent.CanResolveInternal(registration, parameters, options) : false; + + // Bubble resolution up the container tree if we have a parent + if (_Parent != null) + return _Parent.CanResolveInternal(registration, parameters, options); + + return false; +#pragma warning restore IDE0018 // Inline variable declaration - Build.Bat Error Happens if We Do + } + + private bool IsIEnumerableRequest(Type type) + { + if (!type.IsGenericType()) + return false; + + Type genericType = type.GetGenericTypeDefinition(); + + if (genericType == typeof(IEnumerable<>)) + return true; + + return false; + } + + private bool IsAutomaticLazyFactoryRequest(Type type) + { + if (!type.IsGenericType()) + return false; + + Type genericType = type.GetGenericTypeDefinition(); + + // Just a func + if (genericType == typeof(Func<>)) + return true; + + // 2 parameter func with string as first parameter (name) + //#if NETFX_CORE + // if ((genericType == typeof(Func<,>) && type.GetTypeInfo().GenericTypeArguments[0] == typeof(string))) + //#else + if ((genericType == typeof(Func<,>) && type.GetGenericArguments()[0] == typeof(string))) + //#endif + return true; + + // 3 parameter func with string as first parameter (name) and IDictionary as second (parameters) + //#if NETFX_CORE + // if ((genericType == typeof(Func<,,>) && type.GetTypeInfo().GenericTypeArguments[0] == typeof(string) && type.GetTypeInfo().GenericTypeArguments[1] == typeof(IDictionary))) + //#else + if ((genericType == typeof(Func<,,>) && type.GetGenericArguments()[0] == typeof(string) && type.GetGenericArguments()[1] == typeof(IDictionary))) + //#endif + return true; + + return false; + } + + private ObjectFactoryBase GetParentObjectFactory(TypeRegistration registration) + { + if (_Parent == null) + return null; + + ObjectFactoryBase factory; + + if (registration.Type.IsGenericType()) + { + var openTypeRegistration = new TypeRegistration(registration.Type.GetGenericTypeDefinition(), registration.Name); + + if (_Parent._RegisteredTypes.TryGetValue(openTypeRegistration, out factory)) + { + return factory.GetFactoryForChildContainer(openTypeRegistration.Type, _Parent, this); + } + + return _Parent.GetParentObjectFactory(registration); + } + + if (_Parent._RegisteredTypes.TryGetValue(registration, out factory)) + { + return factory.GetFactoryForChildContainer(registration.Type, _Parent, this); + } + + return _Parent.GetParentObjectFactory(registration); + } + + private object ResolveInternal(TypeRegistration registration, NamedParameterOverloads parameters, ResolveOptions options) + { +#pragma warning disable IDE0018 // Inline variable declaration - Build.Bat Error Happens if We Do + ObjectFactoryBase factory; + // Attempt container resolution + if (_RegisteredTypes.TryGetValue(registration, out factory)) + { + try + { + return factory.GetObject(registration.Type, this, parameters, options); + } + catch (TinyIoCResolutionException) + { + throw; + } + catch (Exception ex) + { + throw new TinyIoCResolutionException(registration.Type, ex); + } + } + +#if RESOLVE_OPEN_GENERICS + // Attempt container resolution of open generic + if (registration.Type.IsGenericType()) + { + var openTypeRegistration = new TypeRegistration(registration.Type.GetGenericTypeDefinition(), + registration.Name); + + if (_RegisteredTypes.TryGetValue(openTypeRegistration, out factory)) + { + try + { + return factory.GetObject(registration.Type, this, parameters, options); + } + catch (TinyIoCResolutionException) + { + throw; + } + catch (Exception ex) + { + throw new TinyIoCResolutionException(registration.Type, ex); + } + } + } +#endif + + // Attempt to get a factory from parent if we can + var bubbledObjectFactory = GetParentObjectFactory(registration); + if (bubbledObjectFactory != null) + { + try + { + return bubbledObjectFactory.GetObject(registration.Type, this, parameters, options); + } + catch (TinyIoCResolutionException) + { + throw; + } + catch (Exception ex) + { + throw new TinyIoCResolutionException(registration.Type, ex); + } + } + + // Fail if requesting named resolution and settings set to fail if unresolved + if (!string.IsNullOrEmpty(registration.Name) && options.NamedResolutionFailureAction == NamedResolutionFailureActions.Fail) + throw new TinyIoCResolutionException(registration.Type); + + // Attemped unnamed fallback container resolution if relevant and requested + if (!string.IsNullOrEmpty(registration.Name) && options.NamedResolutionFailureAction == NamedResolutionFailureActions.AttemptUnnamedResolution) + { + if (_RegisteredTypes.TryGetValue(new TypeRegistration(registration.Type, string.Empty), out factory)) + { + try + { + return factory.GetObject(registration.Type, this, parameters, options); + } + catch (TinyIoCResolutionException) + { + throw; + } + catch (Exception ex) + { + throw new TinyIoCResolutionException(registration.Type, ex); + } + } + } + +#if EXPRESSIONS + // Attempt to construct an automatic lazy factory if possible + if (IsAutomaticLazyFactoryRequest(registration.Type)) + return GetLazyAutomaticFactoryRequest(registration.Type); +#endif + if (IsIEnumerableRequest(registration.Type)) + return GetIEnumerableRequest(registration.Type); + + // Attempt unregistered construction if possible and requested + if ((options.UnregisteredResolutionAction == UnregisteredResolutionActions.AttemptResolve) || (registration.Type.IsGenericType() && options.UnregisteredResolutionAction == UnregisteredResolutionActions.GenericsOnly)) + { + if (!registration.Type.IsAbstract() && !registration.Type.IsInterface()) + return ConstructType(null, registration.Type, parameters, options); + } + + // Unable to resolve - throw + throw new TinyIoCResolutionException(registration.Type); +#pragma warning restore IDE0018 // Inline variable declaration - Build.Bat Error Happens if We Do + } + +#if EXPRESSIONS + private object GetLazyAutomaticFactoryRequest(Type type) + { + if (!type.IsGenericType()) + return null; + + Type genericType = type.GetGenericTypeDefinition(); + //#if NETFX_CORE + // Type[] genericArguments = type.GetTypeInfo().GenericTypeArguments.ToArray(); + //#else + Type[] genericArguments = type.GetGenericArguments(); + //#endif + + // Just a func + if (genericType == typeof(Func<>)) + { + Type returnType = genericArguments[0]; + + //#if NETFX_CORE + // MethodInfo resolveMethod = typeof(TinyIoCContainer).GetTypeInfo().GetDeclaredMethods("Resolve").First(mi => !mi.GetParameters().Any()); + //#else + MethodInfo resolveMethod = typeof(TinyIoCContainer).GetMethod("Resolve", new Type[] { }); + //#endif + resolveMethod = resolveMethod.MakeGenericMethod(returnType); + + var resolveCall = Expression.Call(Expression.Constant(this), resolveMethod); + + var resolveLambda = Expression.Lambda(resolveCall).Compile(); + + return resolveLambda; + } + + // 2 parameter func with string as first parameter (name) + if ((genericType == typeof(Func<,>)) && (genericArguments[0] == typeof(string))) + { + Type returnType = genericArguments[1]; + + //#if NETFX_CORE + // MethodInfo resolveMethod = typeof(TinyIoCContainer).GetTypeInfo().GetDeclaredMethods("Resolve").First(mi => mi.GetParameters().Length == 1 && mi.GetParameters()[0].GetType() == typeof(String)); + //#else + MethodInfo resolveMethod = typeof(TinyIoCContainer).GetMethod("Resolve", new Type[] { typeof(String) }); + //#endif + resolveMethod = resolveMethod.MakeGenericMethod(returnType); + + ParameterExpression[] resolveParameters = new ParameterExpression[] { Expression.Parameter(typeof(String), "name") }; + var resolveCall = Expression.Call(Expression.Constant(this), resolveMethod, resolveParameters); + + var resolveLambda = Expression.Lambda(resolveCall, resolveParameters).Compile(); + + return resolveLambda; + } + + // 3 parameter func with string as first parameter (name) and IDictionary as second (parameters) + //#if NETFX_CORE + // if ((genericType == typeof(Func<,,>) && type.GenericTypeArguments[0] == typeof(string) && type.GenericTypeArguments[1] == typeof(IDictionary))) + //#else + if ((genericType == typeof(Func<,,>) && type.GetGenericArguments()[0] == typeof(string) && type.GetGenericArguments()[1] == typeof(IDictionary))) + //#endif + { + Type returnType = genericArguments[2]; + + var name = Expression.Parameter(typeof(string), "name"); + var parameters = Expression.Parameter(typeof(IDictionary), "parameters"); + + //#if NETFX_CORE + // MethodInfo resolveMethod = typeof(TinyIoCContainer).GetTypeInfo().GetDeclaredMethods("Resolve").First(mi => mi.GetParameters().Length == 2 && mi.GetParameters()[0].GetType() == typeof(String) && mi.GetParameters()[1].GetType() == typeof(NamedParameterOverloads)); + //#else + MethodInfo resolveMethod = typeof(TinyIoCContainer).GetMethod("Resolve", new Type[] { typeof(String), typeof(NamedParameterOverloads) }); + //#endif + resolveMethod = resolveMethod.MakeGenericMethod(returnType); + + var resolveCall = Expression.Call(Expression.Constant(this), resolveMethod, name, Expression.Call(typeof(NamedParameterOverloads), "FromIDictionary", null, parameters)); + + var resolveLambda = Expression.Lambda(resolveCall, name, parameters).Compile(); + + return resolveLambda; + } + + throw new TinyIoCResolutionException(type); + } +#endif + private object GetIEnumerableRequest(Type type) + { + //#if NETFX_CORE + // var genericResolveAllMethod = this.GetType().GetGenericMethod("ResolveAll", type.GenericTypeArguments, new[] { typeof(bool) }); + //#else + var genericResolveAllMethod = GetType().GetGenericMethod(BindingFlags.Public | BindingFlags.Instance, "ResolveAll", type.GetGenericArguments(), new[] { typeof(bool) }); + //#endif + + return genericResolveAllMethod.Invoke(this, new object[] { false }); + } + + private bool CanConstruct(ConstructorInfo ctor, NamedParameterOverloads parameters, ResolveOptions options) + { + if (parameters == null) + throw new ArgumentNullException("parameters"); + + foreach (var parameter in ctor.GetParameters()) + { + if (parameter.IsOptional) + return true; + + if (string.IsNullOrEmpty(parameter.Name)) + return false; + + var isParameterOverload = parameters.ContainsKey(parameter.Name); + + //#if NETFX_CORE + // if (parameter.ParameterType.GetTypeInfo().IsPrimitive && !isParameterOverload) + //#else + if (parameter.ParameterType.IsPrimitive() && !isParameterOverload) + //#endif + return false; + + if (!isParameterOverload && !CanResolveInternal(new TypeRegistration(parameter.ParameterType), NamedParameterOverloads.Default, options)) + return false; + } + + return true; + } + + private ConstructorInfo GetBestConstructor(Type type, NamedParameterOverloads parameters, ResolveOptions options) + { + if (parameters == null) + throw new ArgumentNullException("parameters"); + + //#if NETFX_CORE + // if (type.GetTypeInfo().IsValueType) + //#else + if (type.IsValueType()) + //#endif + return null; + + // Get constructors in reverse order based on the number of parameters + // i.e. be as "greedy" as possible so we satify the most amount of dependencies possible + var ctors = GetTypeConstructors(type); + + foreach (var ctor in ctors) + { + if (CanConstruct(ctor, parameters, options)) + return ctor; + } + + return null; + } + + private IEnumerable GetTypeConstructors(Type type) + { + //#if NETFX_CORE + // return type.GetTypeInfo().DeclaredConstructors.OrderByDescending(ctor => ctor.GetParameters().Count()); + //#else + return type.GetConstructors().OrderByDescending(ctor => ctor.GetParameters().Count()); + //#endif + } + + private object ConstructType(Type requestedType, Type implementationType, ResolveOptions options) + { + return ConstructType(requestedType, implementationType, null, NamedParameterOverloads.Default, options); + } + + private object ConstructType(Type requestedType, Type implementationType, ConstructorInfo constructor, ResolveOptions options) + { + return ConstructType(requestedType, implementationType, constructor, NamedParameterOverloads.Default, options); + } + + private object ConstructType(Type requestedType, Type implementationType, NamedParameterOverloads parameters, ResolveOptions options) + { + return ConstructType(requestedType, implementationType, null, parameters, options); + } + + private static readonly SafeDictionary _DefaultValues = new SafeDictionary(); + + private object ConstructType(Type requestedType, Type implementationType, ConstructorInfo constructor, NamedParameterOverloads parameters, ResolveOptions options) + { +#pragma warning disable IDE0018 // Inline variable declaration - Build.Bat Error Happens if We Do + var typeToConstruct = implementationType; + +#if RESOLVE_OPEN_GENERICS + if (implementationType.IsGenericTypeDefinition()) + { + if (requestedType == null || !requestedType.IsGenericType() || !requestedType.GetGenericArguments().Any()) + throw new TinyIoCResolutionException(typeToConstruct); + + typeToConstruct = typeToConstruct.MakeGenericType(requestedType.GetGenericArguments()); + } +#endif + if (constructor == null) + { + // Try and get the best constructor that we can construct + // if we can't construct any then get the constructor + // with the least number of parameters so we can throw a meaningful + // resolve exception + constructor = GetBestConstructor(typeToConstruct, parameters, options) ?? GetTypeConstructors(typeToConstruct).LastOrDefault(); + } + + if (constructor == null) + throw new TinyIoCResolutionException(typeToConstruct); + + var ctorParams = constructor.GetParameters(); + object[] args = new object[ctorParams.Count()]; + object parameterValue; + + for (int parameterIndex = 0; parameterIndex < ctorParams.Count(); parameterIndex++) + { + var currentParam = ctorParams[parameterIndex]; + + try + { + if (parameters.TryGetValue(currentParam.Name, out parameterValue)) + args[parameterIndex] = parameterValue; + else + { + if (currentParam.IsOptional && (currentParam.ParameterType == typeof(string) || currentParam.ParameterType.IsValueType())) + { + try + { + args[parameterIndex] = currentParam.DefaultValue; + } + catch + { + object defaultValue; + // The currentParam.DefaultValue is not always valid, e.g. with default(DateTime), we get a 'SystemFormatException'- possibly due to culture differences. + // If so, Activator.CreateInstance(Type) *should* always work. + if (!_DefaultValues.TryGetValue(currentParam.ParameterType, out defaultValue)) + { + // Potentially creating 'defaultValue' twice in multi-threaded writes isn't a problem; all instances arae equivalent and it'll only happen once, + // so even if Activator.CreateInstance(Type) were unpleasantly slow, it wouldn't really matter. + defaultValue = Activator.CreateInstance(currentParam.ParameterType); + _DefaultValues[currentParam.ParameterType] = defaultValue; + } + args[parameterIndex] = defaultValue; + } + } + else + { + args[parameterIndex] = ResolveInternal(new TypeRegistration(currentParam.ParameterType), NamedParameterOverloads.Default, options); + } + } + } + catch (TinyIoCResolutionException ex) + { + // If a constructor parameter can't be resolved + // it will throw, so wrap it and throw that this can't + // be resolved. + throw new TinyIoCResolutionException(typeToConstruct, ex); + } + catch (Exception ex) + { + throw new TinyIoCResolutionException(typeToConstruct, ex); + } + } + + try + { +#if USE_OBJECT_CONSTRUCTOR + var constructionDelegate = CreateObjectConstructionDelegateWithCache(constructor); + return constructionDelegate.Invoke(args); +#else + return constructor.Invoke(args); +#endif + } + catch (Exception ex) + { + throw new TinyIoCResolutionException(typeToConstruct, ex); + } +#pragma warning restore IDE0018 // Inline variable declaration - Build.Bat Error Happens if We Do + } + +#if USE_OBJECT_CONSTRUCTOR + private static ObjectConstructor CreateObjectConstructionDelegateWithCache(ConstructorInfo constructor) + { +#pragma warning disable IDE0018 // Inline variable declaration - Build.Bat Error Happens if We Do + ObjectConstructor objectConstructor; + if (_ObjectConstructorCache.TryGetValue(constructor, out objectConstructor)) + return objectConstructor; + + // We could lock the cache here, but there's no real side + // effect to two threads creating the same ObjectConstructor + // at the same time, compared to the cost of a lock for + // every creation. + var constructorParams = constructor.GetParameters(); + var lambdaParams = Expression.Parameter(typeof(object[]), "parameters"); + var newParams = new Expression[constructorParams.Length]; + + for (int i = 0; i < constructorParams.Length; i++) + { + var paramsParameter = Expression.ArrayIndex(lambdaParams, Expression.Constant(i)); + + newParams[i] = Expression.Convert(paramsParameter, constructorParams[i].ParameterType); + } + + var newExpression = Expression.New(constructor, newParams); + + var constructionLambda = Expression.Lambda(typeof(ObjectConstructor), newExpression, lambdaParams); + + objectConstructor = (ObjectConstructor)constructionLambda.Compile(); + + _ObjectConstructorCache[constructor] = objectConstructor; + return objectConstructor; +#pragma warning restore IDE0018 // Inline variable declaration - Build.Bat Error Happens if We Do + } +#endif + + private void BuildUpInternal(object input, ResolveOptions resolveOptions) + { + //#if NETFX_CORE + // var properties = from property in input.GetType().GetTypeInfo().DeclaredProperties + // where (property.GetMethod != null) && (property.SetMethod != null) && !property.PropertyType.GetTypeInfo().IsValueType + // select property; + //#else + var properties = from property in input.GetType().GetProperties() + where (property.GetGetMethod() != null) && (property.GetSetMethod() != null) && !property.PropertyType.IsValueType() + select property; + //#endif + + foreach (var property in properties) + { + if (property.GetValue(input, null) == null) + { + try + { + property.SetValue(input, ResolveInternal(new TypeRegistration(property.PropertyType), NamedParameterOverloads.Default, resolveOptions), null); + } + catch (TinyIoCResolutionException) + { + // Catch any resolution errors and ignore them + } + } + } + } + + private IEnumerable GetParentRegistrationsForType(Type resolveType) + { + if (_Parent == null) + return new TypeRegistration[] { }; + + var registrations = _Parent._RegisteredTypes.Keys.Where(tr => tr.Type == resolveType); + + return registrations.Concat(_Parent.GetParentRegistrationsForType(resolveType)); + } + + private IEnumerable ResolveAllInternal(Type resolveType, bool includeUnnamed) + { + var registrations = _RegisteredTypes.Keys.Where(tr => tr.Type == resolveType).Concat(GetParentRegistrationsForType(resolveType)).Distinct(); + + if (!includeUnnamed) + registrations = registrations.Where(tr => tr.Name != string.Empty); + + return registrations.Select(registration => ResolveInternal(registration, NamedParameterOverloads.Default, ResolveOptions.Default)); + } + + private static bool IsValidAssignment(Type registerType, Type registerImplementation) + { + if (!registerType.IsGenericTypeDefinition()) + { + if (!registerType.IsAssignableFrom(registerImplementation)) + return false; + } + else + { + if (registerType.IsInterface()) + { +#if (PORTABLE || NETSTANDARD1_0 || NETSTANDARD1_1 || NETSTANDARD1_2 || NETSTANDARD1_3 || NETSTANDARD1_4 || NETSTANDARD1_5 || NETSTANDARD1_6) + if (!registerImplementation.GetInterfaces().Any(t => t.Name == registerType.Name)) + return false; +#else + if (!registerImplementation.FindInterfaces((t, o) => t.Name == registerType.Name, null).Any()) + return false; +#endif + } + else if (registerType.IsAbstract() && registerImplementation.BaseType() != registerType) + { + return false; + } + } + //#endif + return true; + } + + #endregion + + #region IDisposable Members + bool disposed = false; + public void Dispose() + { + if (!disposed) + { + disposed = true; + + _RegisteredTypes.Dispose(); + + GC.SuppressFinalize(this); + } + } + + #endregion + } + +#if PORTABLE || NETSTANDARD1_0 || NETSTANDARD1_1 || NETSTANDARD1_2 || NETSTANDARD1_3 || NETSTANDARD1_4 || NETSTANDARD1_5 || NETSTANDARD1_6 + static class ReverseTypeExtender + { + public static bool IsClass(this Type type) + { + return type.GetTypeInfo().IsClass; + } + + public static bool IsAbstract(this Type type) + { + return type.GetTypeInfo().IsAbstract; + } + + public static bool IsInterface(this Type type) + { + return type.GetTypeInfo().IsInterface; + } + + public static bool IsPrimitive(this Type type) + { + return type.GetTypeInfo().IsPrimitive; + } + + public static bool IsValueType(this Type type) + { + return type.GetTypeInfo().IsValueType; + } + + public static bool IsGenericType(this Type type) + { + return type.GetTypeInfo().IsGenericType; + } + + public static bool IsGenericParameter(this Type type) + { + return type.IsGenericParameter; + } + + public static bool IsGenericTypeDefinition(this Type type) + { + return type.GetTypeInfo().IsGenericTypeDefinition; + } + + public static Type BaseType(this Type type) + { + return type.GetTypeInfo().BaseType; + } + + public static Assembly Assembly(this Type type) + { + return type.GetTypeInfo().Assembly; + } + } +#endif + // reverse shim for WinRT SR changes... +#if (!NETFX_CORE && !PORTABLE && !NETSTANDARD1_0 && !NETSTANDARD1_1 && !NETSTANDARD1_2 && !NETSTANDARD1_3 && !NETSTANDARD1_4 && !NETSTANDARD1_5 && !NETSTANDARD1_6) + static class ReverseTypeExtender + { + public static bool IsClass(this Type type) + { + return type.IsClass; + } + + public static bool IsAbstract(this Type type) + { + return type.IsAbstract; + } + + public static bool IsInterface(this Type type) + { + return type.IsInterface; + } + + public static bool IsPrimitive(this Type type) + { + return type.IsPrimitive; + } + + public static bool IsValueType(this Type type) + { + return type.IsValueType; + } + + public static bool IsGenericType(this Type type) + { + return type.IsGenericType; + } + + public static bool IsGenericParameter(this Type type) + { + return type.IsGenericParameter; + } + + public static bool IsGenericTypeDefinition(this Type type) + { + return type.IsGenericTypeDefinition; + } + + public static Type BaseType(this Type type) + { + return type.BaseType; + } + + public static Assembly Assembly(this Type type) + { + return type.Assembly; + } + } +#endif +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Utils/AsyncLock.cs b/PoGo.NecroBot.Logic/Utils/AsyncLock.cs new file mode 100644 index 000000000..67d3e30d1 --- /dev/null +++ b/PoGo.NecroBot.Logic/Utils/AsyncLock.cs @@ -0,0 +1,37 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +/// +/// Implementation of AsyncLock from http://www.hanselman.com/blog/ComparingTwoTechniquesInNETAsynchronousCoordinationPrimitives.aspx +/// +namespace PoGo.NecroBot.Logic.Utils +{ + public sealed class AsyncLock + { + private readonly SemaphoreSlim m_semaphore = new SemaphoreSlim(1, 1); + private readonly Task m_releaser; + + public AsyncLock() + { + m_releaser = Task.FromResult((IDisposable)new Releaser(this)); + } + + public Task LockAsync() + { + var wait = m_semaphore.WaitAsync(); + return wait.IsCompleted ? + m_releaser : + wait.ContinueWith((_, state) => (IDisposable)state, + m_releaser.Result, CancellationToken.None, + TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); + } + + private sealed class Releaser : IDisposable + { + private readonly AsyncLock m_toRelease; + internal Releaser(AsyncLock toRelease) { m_toRelease = toRelease; } + public void Dispose() { m_toRelease.m_semaphore.Release(); } + } + } +} diff --git a/PoGo.NecroBot.Logic/Utils/DelayingUtils.cs b/PoGo.NecroBot.Logic/Utils/DelayingUtils.cs new file mode 100644 index 000000000..a91a78e69 --- /dev/null +++ b/PoGo.NecroBot.Logic/Utils/DelayingUtils.cs @@ -0,0 +1,49 @@ +#region using directives + +using System; +using System.Threading; +using System.Threading.Tasks; + +#endregion + +namespace PoGo.NecroBot.Logic.Utils +{ + public static class DelayingUtils + { + private static readonly Random RandomDevice = new Random(); + + public static void Delay(int delay, int defdelay) + { + if (delay > defdelay) + { + var randomFactor = 0.3f; + var randomMin = (int) (delay * (1 - randomFactor)); + var randomMax = (int) (delay * (1 + randomFactor)); + var randomizedDelay = RandomDevice.Next(randomMin, randomMax); + + Thread.Sleep(randomizedDelay); + } + else if (defdelay > 0) + { + Thread.Sleep(defdelay); + } + } + + public static async Task DelayAsync(int delay, int defdelay, CancellationToken token) + { + if (delay > defdelay) + { + var randomFactor = 0.3f; + var randomMin = (int) (delay * (1 - randomFactor)); + var randomMax = (int) (delay * (1 + randomFactor)); + var randomizedDelay = RandomDevice.Next(randomMin, randomMax); + + await Task.Delay(randomizedDelay, token).ConfigureAwait(false); + } + else if (defdelay > 0) + { + await Task.Delay(defdelay, token).ConfigureAwait(false); + } + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Utils/EggWalker.cs b/PoGo.NecroBot.Logic/Utils/EggWalker.cs new file mode 100644 index 000000000..2cb91448a --- /dev/null +++ b/PoGo.NecroBot.Logic/Utils/EggWalker.cs @@ -0,0 +1,41 @@ +#region using directives + +using System.Threading; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.State; +using PoGo.NecroBot.Logic.Tasks; + +#endregion + +namespace PoGo.NecroBot.Logic.Utils +{ + internal class EggWalker + { + private readonly double _checkInterval; + private readonly ISession _session; + + private double _distanceTraveled; + + public EggWalker(double checkIncubatorsIntervalMeters, ISession session) + { + _checkInterval = checkIncubatorsIntervalMeters; + _session = session; + } + + public async Task ApplyDistance(double distanceTraveled, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + TinyIoC.TinyIoCContainer.Current.Resolve().ThrowIfSwitchAccountRequested(); + + if (!_session.LogicSettings.UseEggIncubators) + return; + + _distanceTraveled += distanceTraveled; + if (_distanceTraveled > _checkInterval) + { + await UseIncubatorsTask.Execute(_session, cancellationToken).ConfigureAwait(false); + _distanceTraveled = 0; + } + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Utils/ErrorHandler.cs b/PoGo.NecroBot.Logic/Utils/ErrorHandler.cs new file mode 100644 index 000000000..9bc81d37a --- /dev/null +++ b/PoGo.NecroBot.Logic/Utils/ErrorHandler.cs @@ -0,0 +1,35 @@ +using System; +using System.Diagnostics; +using System.Reflection; +using System.Threading; +using PoGo.NecroBot.Logic.Logging; + +namespace PoGo.NecroBot.Logic.Utils +{ + class ErrorHandler + { + /// + /// Alerts that a fatal error has occurred, displaying a message and exiting the application + /// + /// Optional message to display - Leave NULL to exclude message + /// The total seconds the messag will display before shutting down + public static void ThrowFatalError(string strMessage, int timeout, LogLevel level, bool boolRestart = false) + { + if (strMessage != null) + Logger.Write(strMessage, level); + + Logger.Write("Ending Application... ", LogLevel.Error); + + for (int i = timeout; i > 0; i--) + { + Logger.Write("\b" + i, LogLevel.Error); + Thread.Sleep(1000); + } + + if (boolRestart) + Process.Start(Assembly.GetEntryAssembly().Location); + + Environment.Exit(-1); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Utils/ExcelConfigHelper.cs b/PoGo.NecroBot.Logic/Utils/ExcelConfigHelper.cs new file mode 100644 index 000000000..2ebe65102 --- /dev/null +++ b/PoGo.NecroBot.Logic/Utils/ExcelConfigHelper.cs @@ -0,0 +1,820 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; +using OfficeOpenXml; +using OfficeOpenXml.DataValidation; +using OfficeOpenXml.Style; +using PoGo.NecroBot.Logic.Logging; +using PoGo.NecroBot.Logic.Model.Settings; +using POGOProtos.Enums; +using POGOProtos.Inventory.Item; + +namespace PoGo.NecroBot.Logic.Utils +{ + public class ExcelConfigHelper + { + private static int OFFSET_START = 4; + private static int COL_OFFSET = 9; + + public static void MigrateFromObject(GlobalSettings setting, string destination) + { + var assembly = Assembly.GetExecutingAssembly(); + var resourceName = "PoGo.NecroBot.Logic.config.xlsm"; + + BackwardCompitableUpdate(setting); + + using (Stream stream = assembly.GetManifestResourceStream(resourceName)) + using (var package = new ExcelPackage(stream)) + { + var pokemonFilter = package.Workbook.Worksheets["Pokemons"]; + pokemonFilter.Protection.IsProtected = true; + pokemonFilter.Cells["J2:AZ155"].Style.Locked = false; + MigrateItemRecycleFilter(package, setting); + + foreach (var item in setting.GetType().GetFields()) + { + var att = item.GetCustomAttributes(true).FirstOrDefault(); + if (att != null) + { + ExcelWorksheet workSheet = BuildSheetHeader(package, item, att); + + var configProp = item.GetValue(setting); + if (item.FieldType.IsGenericType && + item.FieldType.GetGenericTypeDefinition() == typeof(Dictionary<,>)) + { + var type = configProp.GetType(); + Type keyType = type.GetGenericArguments()[0]; + Type valueType = type.GetGenericArguments()[1]; + + MethodInfo method = typeof(ExcelConfigHelper).GetMethod("BuildDictionaryDataSheet", + BindingFlags.Public | BindingFlags.Static | BindingFlags.NonPublic); + MethodInfo genericMethod = method.MakeGenericMethod(valueType); + genericMethod.Invoke(null, new object[] {workSheet, configProp}); + } + else + { + BuildCustomObjectData(workSheet, configProp); + } + + workSheet.Protection.IsProtected = true; + } + } + package.SaveAs(new FileInfo(destination)); + } + } + + private static void BackwardCompitableUpdate(GlobalSettings setting) + { + //foreach (var item in setting.PokemonsTransferFilter) + //{ + // setting.PokemonsTransferFilter[item].AllowTransfer = true; + // item.Value.AllowTransfer = true; + //} + foreach (var item in setting.PokemonsNotToTransfer) + { + if (setting.PokemonsTransferFilter.ContainsKey(item)) + { + setting.PokemonsTransferFilter[item].DoNotTransfer = true; + } + else + { + setting.PokemonsTransferFilter.Add(item, new TransferFilter() + { + AllowTransfer = true, + DoNotTransfer = true + }); + } + } + } + + private static void BuildDictionaryDataSheet(ExcelWorksheet sheet, Dictionary dictionary) + { + //Dictionary dictionary = input; + var type = dictionary.GetType(); + Type keyType = type.GetGenericArguments()[0]; + Type valueType = type.GetGenericArguments()[1]; + int col = 0; + for (int i = 1; i <= 151; i++) + { + int id = sheet.Cells[4 + i, 1].GetValue(); + var pokemonId = (PokemonId) id; + if (dictionary.ContainsKey(pokemonId)) + { + var obj = dictionary[pokemonId]; + + foreach (var prop in valueType.GetProperties()) + { + var att = prop.GetCustomAttribute(); + if (att != null) + { + col = Math.Max(col, att.Position); + if (att.IsPrimaryKey) + { + sheet.Cells[4 + i, COL_OFFSET + att.Position].Value = true; + continue; + } + var val = prop.GetValue(obj); + + if (prop.PropertyType == typeof(List>) && val != null) + { + sheet.Cells[4 + i, COL_OFFSET + att.Position].Value = "[]"; + } + else + { + sheet.Cells[4 + i, COL_OFFSET + att.Position].Value = val; + } + } + else + { + //maybe throw exception, + } + } + } + } + //sheet.Cells[$"A1:{GetCol(col)}:253"].AutoFilter = true; + sheet.Cells[sheet.Dimension.Address].AutoFitColumns(); + for (int i = 3; i <= COL_OFFSET; i++) + { + sheet.Column(i).Hidden = true; + } + sheet.Protection.AllowAutoFilter = true; + sheet.Protection.AllowDeleteRows = false; + sheet.Protection.AllowInsertColumns = false; + } + + private static void BuildCustomObjectData(ExcelWorksheet workSheet, object configProp) + { + foreach (var cfg in configProp.GetType().GetFields()) + { + WriteOnePropertyToSheet(workSheet, configProp, cfg); + } + workSheet.Cells[workSheet.Dimension.Address].AutoFitColumns(); + } + + private static void WriteOnePropertyToSheet(ExcelWorksheet workSheet, object configProp, FieldInfo cfg) + { + var att2 = cfg.GetCustomAttributes(typeof(NecroBotConfigAttribute), true).FirstOrDefault(); + if (att2 != null) + { + var exAtt = att2 as NecroBotConfigAttribute; + string configKey = string.IsNullOrEmpty(exAtt.Key) ? cfg.Name : exAtt.Key; + var propValue = cfg.GetValue(configProp); + workSheet.Cells[exAtt.Position + OFFSET_START, 1].Value = configKey; + workSheet.Cells[exAtt.Position + OFFSET_START, 2].Value = propValue; + workSheet.Cells[exAtt.Position + OFFSET_START, 2].Style.Locked = false; + workSheet.Cells[exAtt.Position + OFFSET_START, 2].Style.Font.Bold = true; + workSheet.Cells[exAtt.Position + OFFSET_START, 2].Style.HorizontalAlignment = ExcelHorizontalAlignment.Center; + workSheet.Cells[exAtt.Position + OFFSET_START, 3].Value = exAtt.Description; + workSheet.Cells[exAtt.Position + OFFSET_START, 3].Style.Locked = false; + workSheet.Cells[exAtt.Position + OFFSET_START, 1].AutoFitColumns(); + workSheet.Cells[exAtt.Position + OFFSET_START, 2].AutoFitColumns(); + workSheet.Cells[exAtt.Position + OFFSET_START, 3].AutoFitColumns(); + //AddValidationForType(workSheet, cfg, $"B{exAtt.Position + OFFSET_START}"); + if (propValue is Boolean) + { + var validation = workSheet.DataValidations.AddListValidation($"B{exAtt.Position + OFFSET_START}"); + validation.ShowErrorMessage = true; + validation.ErrorStyle = ExcelDataValidationWarningStyle.stop; + validation.Error = "Please select from list"; + validation.ErrorTitle = $"{configKey} Validation"; + validation.Formula.Values.Add("TRUE"); + validation.Formula.Values.Add("FALSE"); + validation.PromptTitle = "Boolean only"; + validation.Prompt = "Only TRUE or FALSE are accepted"; + validation.ShowInputMessage = true; + //data validation + } + + if (propValue is int || propValue is double) + { + var validation = workSheet.DataValidations.AddIntegerValidation($"B{exAtt.Position + OFFSET_START}"); + validation.ShowErrorMessage = true; + validation.Error = "Please enter a valid number"; + validation.ErrorTitle = $"{configKey} Validation"; + validation.ErrorStyle = ExcelDataValidationWarningStyle.stop; + validation.PromptTitle = "Enter a integer value here"; + validation.Prompt = "Please enter a negative number here"; + validation.ShowInputMessage = true; + validation.ShowErrorMessage = true; + validation.Operator = ExcelDataValidationOperator.between; + validation.Formula.Value = 0; + validation.Formula2.Value = int.MaxValue; + var range = cfg.GetCustomAttributes(typeof(RangeAttribute), true) + .Cast() + .FirstOrDefault(); + if (range != null) + { + validation.Formula.Value = (int) range.Minimum; + validation.Formula2.Value = (int) range.Maximum; + validation.Prompt = $"Please enter a valid number from {validation.Formula.Value} to {validation.Formula2.Value}"; + validation.Error = $"Please enter a valid number from {validation.Formula.Value} to {validation.Formula2.Value}"; + } + } + if (propValue is string) + { + var maxLength = cfg.GetCustomAttributes(typeof(MaxLengthAttribute), true) + .Cast() + .FirstOrDefault(); + var minLength = cfg.GetCustomAttributes(typeof(MinLengthAttribute), true) + .Cast() + .FirstOrDefault(); + if (maxLength != null && minLength != null) + { + var validation = workSheet.DataValidations.AddTextLengthValidation($"B{exAtt.Position + OFFSET_START}"); + validation.ErrorTitle = $"{configKey} Validation"; + validation.ErrorStyle = ExcelDataValidationWarningStyle.stop; + validation.PromptTitle = "String Validation"; + validation.ShowInputMessage = true; + validation.ShowErrorMessage = true; + + validation.Error = $"Please enter a string from {minLength.Length} to {maxLength.Length} characters"; + validation.Prompt = $"Please enter a string from {minLength.Length} to {maxLength.Length} characters"; + + validation.Operator = ExcelDataValidationOperator.between; + validation.Formula.Value = minLength.Length; + validation.Formula2.Value = maxLength.Length; + } + else + { + if (minLength != null) + { + var validation = workSheet.DataValidations.AddTextLengthValidation($"B{exAtt.Position + OFFSET_START}"); + validation.ErrorTitle = $"{configKey} Validation"; + validation.ErrorStyle = ExcelDataValidationWarningStyle.stop; + validation.PromptTitle = "String Validation"; + validation.ShowInputMessage = true; + validation.ShowErrorMessage = true; + + validation.Error = $"Please enter a string atleast {minLength.Length} characters"; + validation.Prompt = $"Please enter a string atleast {minLength.Length} characters"; + + validation.Operator = ExcelDataValidationOperator.greaterThan; + validation.Formula.Value = minLength.Length; + } + } + } + var enumDataType = cfg.GetCustomAttributes(typeof(EnumDataTypeAttribute), true) + .Cast() + .FirstOrDefault(); + if (enumDataType != null) + { + var validation = workSheet.DataValidations.AddListValidation($"B{exAtt.Position + OFFSET_START}"); + validation.ErrorTitle = $"{configKey} Validation"; + validation.ErrorStyle = ExcelDataValidationWarningStyle.stop; + validation.PromptTitle = $"{configKey} Validation"; + validation.ShowInputMessage = true; + validation.ShowErrorMessage = true; + + List values = new List(); + foreach (var v in Enum.GetValues(enumDataType.EnumType)) + { + validation.Formula.Values.Add(v.ToString()); + values.Add(v.ToString()); + } + string value = String.Join(",", values); + validation.Error = $"Please select data from a list: {value}"; + validation.Prompt = $"Please select data from a list: {value}"; + } + } + } + + public static string GetCol(int col) + { + return Convert.ToChar(col + 64).ToString(); + } + + private static ExcelWorksheet BuildSheetHeader(ExcelPackage package, FieldInfo item, object att) + { + NecroBotConfigAttribute excelAtt = att as NecroBotConfigAttribute; + ExcelWorksheet workSheet = package.Workbook.Worksheets[excelAtt.SheetName]; + if (workSheet == null) + { + var type = item.FieldType; + + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>)) + { + var pkmRef = package.Workbook.Worksheets["Pokemons"]; + + workSheet = package.Workbook.Worksheets.Add(excelAtt.SheetName, pkmRef); + Type keyType = type.GetGenericArguments()[0]; + Type valueType = type.GetGenericArguments()[1]; + int pos = 1; + + workSheet.Cells[1, 1].Value = excelAtt.SheetName; + workSheet.Cells[2, 1].Value = excelAtt.Description; + + foreach (var vtp in valueType.GetProperties()) + { + var att1 = vtp.GetCustomAttributes(true).FirstOrDefault(); + int colIndex = (att1 == null ? pos : att1.Position) + COL_OFFSET; + workSheet.Column(colIndex).AutoFit(); + workSheet.Cells[4, colIndex].Value = att1 == null ? vtp.Name : att1.Key; + if (att1 != null) + { + workSheet.Cells[4, colIndex].AddComment(att1.Description, "NecroBot2"); + AddValidationForType(workSheet, vtp, $"{GetCol(colIndex)}5:{GetCol(colIndex)}155"); + } + pos++; + } + workSheet.Cells[$"A1:{GetCol(COL_OFFSET + pos)}1"].Merge = true; + workSheet.Cells[$"A2:{GetCol(COL_OFFSET + pos)}2"].Merge = true; + workSheet.Cells[$"A1:{GetCol(COL_OFFSET + pos)}1"].Style.Font.Size = 16; + } + else + { + workSheet = package.Workbook.Worksheets.Add(excelAtt.SheetName); + workSheet.Cells[1, 1].Value = excelAtt.SheetName; + workSheet.Cells[2, 1].Value = excelAtt.Description; + + workSheet.Cells[$"A1:C1"].Merge = true; + workSheet.Cells[$"A2:C2"].Merge = true; + + + workSheet.Cells["A1:C1"].Style.Font.Size = 16; + workSheet.Row(1).CustomHeight = true; + workSheet.Row(1).Height = 30; + + workSheet.Cells["A1:C1"].Style.HorizontalAlignment = ExcelHorizontalAlignment.Center; + workSheet.Cells["A1:C1"].Style.HorizontalAlignment = ExcelHorizontalAlignment.Justify; + + workSheet.Cells[4, 1].Value = "Key"; + workSheet.Cells[4, 2].Value = "Value"; + workSheet.Cells[4, 3].Value = "Description"; + } + + workSheet.Row(4).Style.Font.Bold = true; + } + + return workSheet; + } + + private static void AddValidationForType(ExcelWorksheet sheet, PropertyInfo vtp, string address) + { + var type = vtp.PropertyType; + if (type == typeof(bool)) + { + AddListValidation(sheet, address, $"{type.Name}- Validation", "TRUE or FALSE only", "TRUE", "FALSE"); + } + if (type == typeof(int) || type == typeof(float) || type == typeof(double) || type == typeof(long)) + { + var range = vtp.GetCustomAttribute(); + if (range != null) + { + AddNumberValidation( + sheet, address, $"{type.Name} - Validation", + $"Any number from {range.Minimum} to {range.Maximum}", (int) range.Minimum, + (int) range.Maximum + ); + } + else + { + } + } + + var enumtype = vtp.GetCustomAttribute(); + if (enumtype != null) + { + AddEnumValidation(sheet, address, $"{type.Name}- Validation", "Select item from the list", enumtype); + } + } + + + public static void MigrateItemRecycleFilter(ExcelPackage package, GlobalSettings setting) + { + var workSheet = package.Workbook.Worksheets.Add("ItemRecycleFilter"); + + workSheet.Cells[1, 1].Value = "ItemRecycleFilter"; + workSheet.Cells[2, 1].Value = "Special number of each item to keep "; + + workSheet.Cells["A1:C1"].Merge = true; + + workSheet.Cells["A1:C1"].Style.Font.Size = 16; + workSheet.Row(1).CustomHeight = true; + workSheet.Row(1).Height = 30; + + workSheet.Cells["A1:C1"].Style.HorizontalAlignment = ExcelHorizontalAlignment.Center; + workSheet.Cells["A1:C1"].Style.HorizontalAlignment = ExcelHorizontalAlignment.Justify; + + workSheet.Cells[4, 1].Value = "Item Type"; + workSheet.Cells[4, 2].Value = "Value"; + workSheet.Row(4).Style.Font.Bold = true; + int index = 1; + foreach (var item in setting.ItemRecycleFilter) + { + workSheet.Cells[index + OFFSET_START, 1].Value = item.Key.ToString(); + + workSheet.Cells[index + OFFSET_START, 2].Value = item.Value; + workSheet.Cells[index + OFFSET_START, 2].Style.Font.Bold = true; + workSheet.Cells[index + OFFSET_START, 2].Style.Locked = false; + AddNumberValidation( + workSheet, $"B{index + OFFSET_START}", "Item filter validation", + "Number from 0 to 10000", 0, 10000 + ); + index++; + } + workSheet.Column(1).AutoFit(); + workSheet.Protection.IsProtected = true; + } + + public static void SetValue(object inputObject, string propertyName, object propertyVal) + { + //find out the type + Type type = inputObject.GetType(); + + //get the property information based on the type + PropertyInfo propertyInfo = type.GetProperty(propertyName); + + //find the property type + Type propertyType = propertyInfo.PropertyType; + + //Convert.ChangeType does not handle conversion to nullable types + //if the property type is nullable, we need to get the underlying type of the property + var targetType = IsNullableType(propertyInfo.PropertyType) + ? Nullable.GetUnderlyingType(propertyInfo.PropertyType) + : propertyInfo.PropertyType; + + //Returns an System.Object with the specified System.Type and whose value is + //equivalent to the specified object. + propertyVal = Convert.ChangeType(propertyVal, targetType); + + //Set the value of the property + propertyInfo.SetValue(inputObject, propertyVal, null); + } + + private static bool IsNullableType(Type type) + { + return type.IsGenericType && type.GetGenericTypeDefinition().Equals(typeof(Nullable<>)); + } + + public static GlobalSettings ReadExcel(GlobalSettings setting, string configFile) + { + if (File.Exists(configFile + ".tmp")) + { + //need rename the config.xlsm by the .tmp file + try + { + File.Delete(configFile); //remove existing config file + File.Move(configFile + ".tmp", configFile); + //File.Delete(configFile + ".tmp"); + } + catch (Exception) + { + Logger.Write( + "Seem that you are opening config.xlsm, You need to close it for migration new config." + ); + } + } + bool needSave = false; + using (FileStream stream = File.Open(configFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + using (var package = new ExcelPackage(stream)) + { + foreach (var item in setting.GetType().GetFields()) + { + var att = item.GetCustomAttributes(typeof(NecroBotConfigAttribute), true) + .Cast() + .FirstOrDefault(); + if (att != null) + { + var ws = package.Workbook.Worksheets[att.SheetName]; + var configProp = item.GetValue(setting); + + if (item.FieldType.IsGenericType && + item.FieldType.GetGenericTypeDefinition() == typeof(Dictionary<,>)) + { + var type = item.FieldType; + Type keyType = type.GetGenericArguments()[0]; + Type valueType = type.GetGenericArguments()[1]; + + MethodInfo method = typeof(ExcelConfigHelper).GetMethod("ReadSheetAsDictionary", + BindingFlags.Public | BindingFlags.Static | BindingFlags.NonPublic); + MethodInfo genericMethod = method.MakeGenericMethod(valueType); + configProp = genericMethod.Invoke(null, new object[] {ws}); + + //configProp = ReadSheetAsDictionary(ws); + } + else + { + foreach (var cfg in configProp.GetType().GetFields()) + { + var peAtt = cfg.GetCustomAttributes(typeof(NecroBotConfigAttribute), true) + .Cast() + .FirstOrDefault(); + if (peAtt != null) + { + string key = string.IsNullOrEmpty(peAtt.Key) ? cfg.Name : peAtt.Key; + string keyFromExcel = ws.Cells[$"A{peAtt.Position + OFFSET_START}"] + .GetValue(); + if (keyFromExcel == key) + { + var value = ws.Cells[$"B{peAtt.Position + OFFSET_START}"].Value; + var convertedValue = Convert.ChangeType(value, cfg.FieldType); + cfg.SetValue(configProp, convertedValue); + } + else + { + //migrate config + needSave = true; + WriteOnePropertyToSheet(ws, configProp, cfg); + } + } + } + } + item.SetValue(setting, configProp); + //set to original object + } + } + var pkmSheet = package.Workbook.Worksheets["Pokemons"]; + setting.ItemRecycleFilter = ReadItemRecycleFilter(package); + if (needSave || hasUpdate) + { + package.SaveAs(new FileInfo(configFile + ".tmp")); //use to migrate new config, hack hack hack + } + } + + return ConvertToBackwardCompitable(setting); + } + + private static GlobalSettings ConvertToBackwardCompitable(GlobalSettings setting) + { + if (setting.PokemonsTransferFilter != null) + { + setting.PokemonsNotToTransfer = setting.PokemonsTransferFilter.Where(p => p.Value.DoNotTransfer) + .Select(p => p.Key) + .ToList(); + } + + return setting; + } + + private static bool hasUpdate; + + private static Dictionary ReadSheetAsDictionary(ExcelWorksheet ws) + { + hasUpdate = hasUpdate || SyncHeader(ws); + Dictionary results = new Dictionary(); + for (int i = 5; i <= 155; i++) + { + T obj = Activator.CreateInstance(); + + var id = ws.Cells[i, 1].GetValue(); + var pokemonId = (PokemonId) id; + + foreach (var prop in typeof(T).GetProperties()) + { + var attr = prop.GetCustomAttribute(); + if (attr != null) + { + var celvalue = ws.Cells[i, COL_OFFSET + attr.Position].Value; + if (celvalue == null && attr.IsPrimaryKey) continue; + + if (celvalue != null) + { + if (prop.PropertyType == typeof(List>) && celvalue != null) + { + prop.SetValue(obj, ParseMoves(celvalue.ToString())); + } + else + { + var convertedVal = Convert.ChangeType(celvalue, prop.PropertyType); + prop.SetValue(obj, convertedVal); + if (attr.IsPrimaryKey && (bool) convertedVal) + { + results.Add(pokemonId, obj); + } + } + } + else + { + ws.Cells[i, COL_OFFSET + attr.Position].Value = prop.GetValue(obj); + } + } + } + } + return results; + } + + private static bool SyncHeader(ExcelWorksheet ws) + { + bool needUpdate = false; + var type = typeof(T); + foreach (var fi in type.GetProperties()) + { + var attr = fi.GetCustomAttributes(true).FirstOrDefault(); + if (attr != null) + { + var cell = ws.Cells[4, attr.Position + COL_OFFSET]; + + var cellHeader = ws.Cells[4, attr.Position + COL_OFFSET].Value; + if (cellHeader != null) + { + cell.Value = attr.Key; + } + else + { + needUpdate = true; + cell.Value = attr.Key; + AddValidationForType( + ws, fi, $"{GetCol(attr.Position + COL_OFFSET)}5:{GetCol(attr.Position + COL_OFFSET)}155" + ); + } + } + } + return needUpdate; + } + + private static List ReadItemRecycleFilter(ExcelPackage package) + { + List result = new List(); + var worksheet = package.Workbook.Worksheets["ItemRecycleFilter"]; + string key = ""; + int row = OFFSET_START + 1; + do + { + key = worksheet.Cells[row, 1].GetValue(); + + if (!string.IsNullOrEmpty(key)) + { + int val = worksheet.Cells[row, 2].GetValue(); + result.Add(new ItemRecycleFilter() + { + Key = (ItemId) Enum.Parse(typeof(ItemId), key), + Value = val + }); + } + row++; + } while (!string.IsNullOrEmpty(key)); + return result; + } + + public static Dictionary ReadListObjectAsDictionary(ExcelWorksheet sheet, + string column, bool compare) + { + Dictionary results = new Dictionary(); + for (int i = 4; i <= 155; i++) + { + string address = $"{column}{i}"; + var isAllow = Convert.ToBoolean(sheet.Cells[address].GetValue()); + if (isAllow == compare) + { + int id = sheet.Cells[$"A{i}"].GetValue(); + + var pokemonId = (PokemonId) id; + var obj = (T) Activator.CreateInstance(typeof(T)); + + foreach (var fi in typeof(T).GetProperties()) + { + var attr = fi.GetCustomAttributes(true).FirstOrDefault(); + if (attr != null) + { + string addr = $"{attr.Key}{i}"; + var v = sheet.Cells[addr].Value; + if (fi.PropertyType == typeof(List>) && v != null) + { + fi.SetValue(obj, ParseMoves(v.ToString())); + } + else + { + if (v == null) + { + //throw exception + } + var converted = Convert.ChangeType(v, fi.PropertyType); + fi.SetValue(obj, converted); + } + } + } + + foreach (var fi in typeof(T).GetFields()) + { + var attr = fi.GetCustomAttributes(true).FirstOrDefault(); + if (attr != null) + { + string addr = $"{attr.Key}{i}"; + var v = sheet.Cells[addr].Value; + if (fi.FieldType == typeof(List>)) + { + fi.SetValue(obj, ParseMoves(v.ToString())); + } + else + { + var converted = Convert.ChangeType(v, fi.FieldType); + fi.SetValue(obj, converted); + } + } + } + + results.Add(pokemonId, obj); + } + } + return results; + } + + public static List> ParseMoves(string moves) + { + List> results = new List>(); + + string regexPattern = @"\[([a-zA-Z\s]*),([a-zA-Z\s]*)\]"; + var matches = Regex.Matches(moves, regexPattern); + foreach (Match match in matches) + { + try + { + string move1 = match.Groups[1].Value; + string move2 = match.Groups[2].Value; + PokemonMove pmove1 = (PokemonMove) Enum.Parse(typeof(PokemonMove), move1.Replace(" ", "")); + PokemonMove pmove2 = (PokemonMove) Enum.Parse(typeof(PokemonMove), move2.Replace(" ", "")); + results.Add(new List() + { + pmove1, + pmove2 + }); + } + catch (Exception) + { + } + } + return results; + } + + public static void AddListValidation(ExcelWorksheet pokemonFilter, string address, string errorTitle, + string promptTitle, params string[] values) + { + var validation = pokemonFilter.DataValidations.AddListValidation(address); + validation.ShowErrorMessage = true; + validation.ErrorStyle = ExcelDataValidationWarningStyle.stop; + validation.Error = "Please select from list"; + validation.ErrorTitle = errorTitle; + validation.AllowBlank = false; + foreach (var item in values) + { + validation.Formula.Values.Add(item); + } + + validation.PromptTitle = promptTitle; + validation.Prompt = $"ONLY {string.Join(",", values)} are accepted"; + validation.ShowInputMessage = true; + } + + public static void AddEnumValidation(ExcelWorksheet workSheet, string address, string errorTitle, + string promptTitle, EnumDataTypeAttribute enumDataType) + { + //var enumDataType = cfg.GetCustomAttributes(typeof(EnumDataTypeAttribute), true).Cast().FirstOrDefault(); + if (enumDataType != null) + { + var validation = workSheet.DataValidations.AddListValidation(address); + validation.ErrorTitle = $"Enum Validation"; + validation.ErrorStyle = ExcelDataValidationWarningStyle.stop; + validation.PromptTitle = $"Select item from the list"; + validation.ShowInputMessage = true; + validation.ShowErrorMessage = true; + + List values = new List(); + foreach (var v in Enum.GetValues(enumDataType.EnumType)) + { + validation.Formula.Values.Add(v.ToString()); + values.Add(v.ToString()); + } + string value = String.Join(",", values); + validation.Error = $"Please select data from a list: {value}"; + validation.Prompt = $"Please select data from a list: {value}"; + } + } + + public static void AddNumberValidation(ExcelWorksheet workSheet, string address, string errorTitle, + string promptTitle, int? minValue, int? maxValue) + { + var validation = workSheet.DataValidations.AddIntegerValidation(address); + validation.ShowErrorMessage = true; + validation.Error = "Please enter a valid number"; + validation.ErrorTitle = errorTitle; + validation.ErrorStyle = ExcelDataValidationWarningStyle.stop; + validation.PromptTitle = promptTitle; + validation.Prompt = "Please enter a negative number here"; + validation.ShowInputMessage = true; + validation.ShowErrorMessage = true; + validation.Operator = ExcelDataValidationOperator.between; + validation.Formula.Value = 0; + validation.Formula2.Value = int.MaxValue; + validation.AllowBlank = false; + if (minValue.HasValue) + { + validation.Formula.Value = (int) minValue.Value; + } + if (maxValue.HasValue) + { + validation.Formula2.Value = (int) maxValue.Value; + } + + if (maxValue.HasValue || minValue.HasValue) + + { + validation.Prompt = $"Please enter a valid number from {validation.Formula.Value} to {validation.Formula2.Value}"; + validation.Error = $"Please enter a valid number from {validation.Formula.Value} to {validation.Formula2.Value}"; + } + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Utils/GPXReader.cs b/PoGo.NecroBot.Logic/Utils/GPXReader.cs new file mode 100644 index 000000000..116dad8c4 --- /dev/null +++ b/PoGo.NecroBot.Logic/Utils/GPXReader.cs @@ -0,0 +1,733 @@ +#region using directives + +#region using directives + +using System; +using System.Collections.Generic; +using System.Xml; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.Logging; +using PoGo.NecroBot.Logic.State; + +#endregion + +// ReSharper disable All + +#endregion + +namespace PoGo.NecroBot.Logic.Utils +{ + public class GpxReader + { + private readonly XmlDocument _gpx = new XmlDocument(); + + public string Author = ""; + public GpsBoundary Bounds = new GpsBoundary(); + + public string Description = ""; + public string EMail = ""; + public string KeyWords = ""; + public List Routes = new List(); + public string Time = ""; + public List Tracks = new List(); + public string Url = ""; + public string UrlName = ""; + public List WayPoints = new List(); + + public GpxReader(string xml, ISession session) : this(xml, session?.Translation) + { + } + + public GpxReader(string xml, ITranslation translation) + { + if (xml.Equals("")) return; + _gpx.LoadXml(xml); + if (_gpx.DocumentElement == null || !_gpx.DocumentElement.Name.Equals("gpx")) return; + var gpxNodes = _gpx.GetElementsByTagName("gpx")[0].ChildNodes; + foreach (XmlNode node in gpxNodes) + { + switch (node.Name) + { + case "name": + Name = node.InnerText; + break; + case "desc": + Description = node.InnerText; + break; + case "author": + Author = node.InnerText; + break; + case "email": + EMail = node.InnerText; + break; + case "time": + Time = node.InnerText; + break; + case "keywords": + KeyWords = node.InnerText; + break; + case "bounds": + Bounds = new GpsBoundary(); + if (node.Attributes != null) + foreach (XmlAttribute att in node.Attributes) + { + switch (att.Name) + { + case "minlat": + Bounds.Min.Lat = att.Value; + break; + case "minlon": + Bounds.Min.Lon = att.Value; + break; + case "maxlat": + Bounds.Max.Lat = att.Value; + break; + case "maxlon": + Bounds.Max.Lon = att.Value; + break; + } + } + break; + case "wpt": + var newWayPoint = new Wpt(node); + WayPoints.Add(newWayPoint); + break; + case "rte": + var newRoute = new Rte(node); + Routes.Add(newRoute); + break; + case "trk": + var track = new Trk(node); + Tracks.Add(track); + break; + case "url": + Url = node.InnerText; + break; + case "urlname": + UrlName = node.InnerText; + break; + case "topografix:active_point": + case "topografix:map": + break; + default: + if (translation != null) + { + Logger.Write(translation.GetTranslation(TranslationString.UnhandledGpxData), + LogLevel.Info); + } + break; + } + } + } + + public string Name { get; set; } = ""; + + public class Travelbug + { + public string GroundspeakName = ""; + public string Id; + public string Reference; + + public Travelbug(XmlNode travelBugNode) + { + Id = travelBugNode.Attributes?["id"].Value; + Reference = travelBugNode.Attributes?["ref"].Value; + foreach (XmlNode tbChildNode in travelBugNode.ChildNodes) + { + switch (tbChildNode.Name) + { + case "groundspeak:name": + GroundspeakName = tbChildNode.InnerText; + break; + default: + throw new Exception("Unhandled Child Node: " + tbChildNode.Name); + } + } + } + } + + public class Cachelog + { + public string GroundspeakDate = ""; + public string GroundspeakFinder = ""; + public string GroundspeakFinderId = ""; + public GpsCoordinates GroundspeakLogWayPoint = new GpsCoordinates(); + public string GroundspeakText = ""; + public string GroundspeakTextEncoded = ""; + public string GroundspeakType = ""; + public string Id; + + public Cachelog(XmlNode childNode) + { + Id = childNode.Attributes?["id"].Value; + foreach (XmlNode node in childNode.ChildNodes) + { + switch (node.Name) + { + case "groundspeak:date": + GroundspeakDate = node.InnerText; + break; + case "groundspeak:type": + GroundspeakType = node.InnerText; + break; + case "groundspeak:finder": + GroundspeakFinder = node.InnerText; + GroundspeakFinderId = node.Attributes?["id"].Value; + break; + case "groundspeak:text": + GroundspeakText = node.InnerText; + GroundspeakTextEncoded = node.Attributes?["encoded"].Value; + break; + case "groundspeak:log_wpt": + GroundspeakLogWayPoint.Lat = node.Attributes?["lat"].Value; + GroundspeakLogWayPoint.Lon = node.Attributes?["lon"].Value; + break; + default: + throw new Exception("Unhandled Child Node: " + node.Name); + } + } + } + } + + public class Cache + { + public string Archived = ""; + public string Available = ""; + public List GroundspeakAttributes = new List(); + public string GroundspeakContainer = ""; + public string GroundspeakCountry = ""; + public string GroundspeakDifficulty = ""; + public string GroundspeakEncodedHint = ""; + + public List GroundspeakLogs = new List(); + public string GroundspeakLongDescription = ""; + public bool GroundspeakLongDescriptionIsHtml; + + public string GroundspeakName = ""; + public string GroundspeakOwner = ""; + public string GroundspeakOwnerId = ""; + public string GroundspeakPlacedBy = ""; + public string GroundspeakShortDescription = ""; + public bool GroundspeakShortDescriptionIsHtml; + public string GroundspeakState = ""; + public string GroundspeakTerrain = ""; + public List GroundspeakTravelbugs = new List(); + public string GroundspeakType = ""; + public string Id = ""; + public string Xmlns = ""; + + public Cache(XmlNode node) + { + #region Attributes + + if (node.Attributes == null) return; + foreach (XmlAttribute attribute in node.Attributes) + { + switch (attribute.Name) + { + case "id": + Id = attribute.Value; + break; + case "available": + Available = attribute.Value; + break; + case "archived": + Archived = attribute.Value; + break; + case "xmlns:groundspeak": + Xmlns = attribute.Value; + break; + default: + throw new Exception("Unhandled Attribute: " + attribute.Name); + } + } + + #endregion Attributes + + foreach (XmlNode childNode in node.ChildNodes) + { + switch (childNode.Name) + { + case "groundspeak:name": + GroundspeakName = childNode.InnerText; + break; + case "groundspeak:placed_by": + GroundspeakPlacedBy = childNode.InnerText; + break; + case "groundspeak:owner": + GroundspeakOwner = childNode.InnerText; + GroundspeakOwnerId = childNode.Attributes?["id"].Value; + break; + case "groundspeak:type": + GroundspeakType = childNode.InnerText; + break; + case "groundspeak:container": + GroundspeakContainer = childNode.InnerText; + break; + case "groundspeak:difficulty": + GroundspeakDifficulty = childNode.InnerText; + break; + case "groundspeak:terrain": + GroundspeakTerrain = childNode.InnerText; + break; + case "groundspeak:country": + GroundspeakCountry = childNode.InnerText; + break; + case "groundspeak:state": + GroundspeakState = childNode.InnerText; + break; + case "groundspeak:short_description": + GroundspeakShortDescription = childNode.InnerText; + if (childNode.Attributes != null && childNode.Attributes["html"].Value.Equals("True")) + { + GroundspeakShortDescriptionIsHtml = true; + } + break; + case "groundspeak:long_description": + GroundspeakLongDescription = childNode.InnerText; + if (childNode.Attributes != null && childNode.Attributes["html"].Value.Equals("True")) + { + GroundspeakLongDescriptionIsHtml = true; + } + break; + case "groundspeak:encoded_hints": + GroundspeakEncodedHint = childNode.InnerText; + break; + case "groundspeak:logs": + foreach (XmlNode logNode in childNode.ChildNodes) + { + var groundspeakLogEntry = new Cachelog(logNode); + GroundspeakLogs.Add(groundspeakLogEntry); + } + break; + case "groundspeak:travelbugs": + foreach (XmlNode travelBugNode in childNode.ChildNodes) + { + var travelbug = new Travelbug(travelBugNode); + GroundspeakTravelbugs.Add(travelbug); + } + break; + case "groundspeak:attributes": + foreach (XmlNode attributeNode in childNode.ChildNodes) + { + var cacheAttribute = new Attribute(attributeNode); + GroundspeakAttributes.Add(cacheAttribute); + } + break; + default: + throw new Exception("Unhandled Child Node: " + childNode.Name); + } + } + } + + public Cache() + { + } + } + + + //WayPoint contains Caches and other Objects + + public class Wpt + { + public string Cmt = ""; + public GpsCoordinates Coordinates = new GpsCoordinates(); + public string Desc = ""; + public string Ele = ""; + + public Cache GroundspeakCache = new Cache(); + public string Name = ""; + public string OpencachingAwesomeness = ""; + public string OpencachingDifficulty = ""; + public string OpencachingSeriesId = ""; + public string OpencachingSeriesName = ""; + public string OpencachingSize = ""; + public List OpencachingTags = new List(); + public string OpencachingTerrain = ""; + public string OpencachingVerificationChirp = ""; + public string OpencachingVerificationNumber = ""; + public string OpencachingVerificationPhrase = ""; + public string OpencachingVerificationQr = ""; + public string Sym = ""; + public string Time = ""; + public string Type = ""; + public string Url = ""; + public string UrlName = ""; + + public Wpt(XmlNode node) + { + Coordinates.Lat = node.Attributes?["lat"].Value; + Coordinates.Lon = node.Attributes?["lon"].Value; + foreach (XmlNode childNode in node.ChildNodes) + { + switch (childNode.Name) + { + case "time": + Time = childNode.InnerText; + break; + case "name": + Name = childNode.InnerText; + break; + case "desc": + Desc = childNode.InnerText; + break; + case "url": + Url = childNode.InnerText; + break; + case "urlname": + UrlName = childNode.InnerText; + break; + case "sym": + Sym = childNode.InnerText; + break; + case "type": + Type = childNode.InnerText; + break; + case "ele": + Ele = childNode.InnerText; + break; + case "cmt": + Cmt = childNode.InnerText; + break; + case "groundspeak:cache": + GroundspeakCache = new Cache(childNode); + break; + case "ox:opencaching": + foreach (XmlNode openCachingChildNode in childNode.ChildNodes) + { + switch (openCachingChildNode.Name) + { + case "ox:ratings": + foreach (XmlNode openCachingRatingsChildNode in openCachingChildNode.ChildNodes) + { + switch (openCachingRatingsChildNode.Name) + { + case "ox:awesomeness": + OpencachingAwesomeness = openCachingRatingsChildNode.InnerText; + break; + case "ox:difficulty": + OpencachingDifficulty = openCachingRatingsChildNode.InnerText; + break; + case "ox:terrain": + OpencachingTerrain = openCachingRatingsChildNode.InnerText; + break; + case "ox:size": + OpencachingSize = openCachingRatingsChildNode.InnerText; + break; + default: + throw new Exception("Unhandled for Child Object: " + + openCachingRatingsChildNode.Name); + } + } + break; + case "ox:tags": + foreach (XmlNode openCachingTagNode in openCachingChildNode.ChildNodes) + { + switch (openCachingTagNode.Name) + { + case "ox:tag": + OpencachingTags.Add(openCachingTagNode.InnerXml); + break; + default: + throw new Exception("Unhandled for Child Object: " + + openCachingTagNode.Name); + } + } + break; + case "ox:verification": + foreach (XmlNode openCachingVerificationNode in openCachingChildNode.ChildNodes) + { + switch (openCachingVerificationNode.Name) + { + case "ox:phrase": + OpencachingVerificationPhrase = openCachingChildNode.InnerText; + break; + case "ox:number": + OpencachingVerificationNumber = openCachingChildNode.InnerText; + break; + case "ox:QR": + OpencachingVerificationQr = openCachingChildNode.InnerText; + break; + case "ox:chirp": + OpencachingVerificationChirp = openCachingChildNode.InnerText; + break; + default: + throw new Exception("Unhandled for Child Object: " + + openCachingVerificationNode.Name); + } + } + break; + case "ox:series": + OpencachingSeriesName = openCachingChildNode.InnerText; + OpencachingSeriesId = openCachingChildNode.Attributes?["id"].Value; + break; + default: + throw new Exception("Unhandled for Child Object: " + openCachingChildNode.Name); + } + } + break; + default: + throw new Exception("Unhandled for Child Object: " + childNode.Name); + } + } + } + } + + public class GpsBoundary + { + public GpsCoordinates Max = new GpsCoordinates(); + public GpsCoordinates Min = new GpsCoordinates(); + } + + public class GpsCoordinates + { + public string Lat = ""; + public string Lon = ""; + } + + public class Attribute : IComparable + { + public string Description = ""; + public string Id = ""; + public string Inc = ""; + + public Attribute(XmlNode attributeNode) + { + Id = attributeNode.Attributes?["id"].Value; + Inc = attributeNode.Attributes?["inc"].Value; + Description = attributeNode.InnerText; + } + + public Attribute() + { + } + + + public int CompareTo(Attribute other) + { + return string.Compare(Description, other.Description, StringComparison.Ordinal); + } + } + + //Route ans Route Points + + public class Rte + { + public string Desc = ""; + public string Name = ""; + + public string Number = ""; + + // private readonly List _routePoints = new List(); + public string Url = ""; + + public string UrlName = ""; + + public Rte(XmlNode node) + { + foreach (XmlNode childNode in node.ChildNodes) + { + switch (childNode.Name) + { + case "name": + Name = childNode.InnerText; + break; + case "desc": + Desc = childNode.InnerText; + break; + case "number": + Number = childNode.InnerText; + break; + case "rtept": + //var routePoint = new Rtept(childNode); + //_routePoints.Add(routePoint); + break; + case "url": + Url = childNode.InnerText; + break; + case "urlname": + UrlName = childNode.InnerText; + break; + case "topografix:color": + break; + default: + throw new Exception("Unhandled for Child Object: " + childNode.Name); + } + } + } + } + + public class Rtept + { + public string Cmt = ""; + public string Desc = ""; + public string Ele = ""; + public string Lat; + public string Lon; + public string Name = ""; + public string Sym = ""; + public string Time = ""; + public string Type = ""; + public string Url = ""; + public string UrlName = ""; + + public Rtept(XmlNode node) + { + Lat = node.Attributes?["lat"].Value; + Lon = node.Attributes?["lon"].Value; + foreach (XmlNode childNode in node.ChildNodes) + { + switch (childNode.Name) + { + case "ele": + Ele = childNode.InnerText; + break; + case "time": + Time = childNode.InnerText; + break; + case "name": + Name = childNode.InnerText; + break; + case "cmt": + Cmt = childNode.InnerText; + break; + case "desc": + Desc = childNode.InnerText; + break; + case "sym": + Sym = childNode.InnerText; + break; + case "type": + Type = childNode.InnerText; + break; + case "url": + Url = childNode.InnerText; + break; + case "urlname": + UrlName = childNode.InnerText; + break; + case "topografix:leg": + break; + default: + throw new Exception("Unhandled for Child Object: " + childNode.Name); + } + } + } + } + + //Tracks + + public class Trk + { + public string Desc = ""; + public string Name = ""; + public string Number = ""; + public List Segments = new List(); + public string Url = ""; + public string UrlName = ""; + + public Trk(XmlNode node) + { + foreach (XmlNode childNode in node) + { + switch (childNode.Name) + { + case "name": + Name = childNode.InnerText; + break; + case "desc": + Desc = childNode.InnerText; + break; + case "number": + Number = childNode.InnerText; + break; + case "trkseg": + var segment = new Trkseg(childNode); + Segments.Add(segment); + break; + case "url": + Url = childNode.InnerText; + break; + case "urlname": + UrlName = childNode.InnerText; + break; + case "topografix:color": + break; + default: + throw new Exception("Unhandled for Child Object: " + childNode.Name); + } + } + } + } + + public class Trkseg + { + public List TrackPoints = new List(); + + public Trkseg(XmlNode node) + { + foreach (XmlNode childNode in node) + { + switch (childNode.Name) + { + case "trkpt": + var trackPoint = new Trkpt(childNode); + TrackPoints.Add(trackPoint); + break; + default: + throw new Exception("Unhandled for Child Object: " + childNode.Name); + } + } + } + } + + public class Trkpt + { + public string Cmt; + public string Desc; + public string Ele; + public string Lat; + public string Lon; + public string Name; + public string Sym; + public string Time; + + public Trkpt(XmlNode node) + { + Lat = node.Attributes?["lat"].Value; + Lon = node.Attributes?["lon"].Value; + foreach (XmlNode childNode in node) + { + switch (childNode.Name) + { + case "sym": + Sym = childNode.InnerText; + break; + case "ele": + Ele = childNode.InnerText; + break; + case "time": + Time = childNode.InnerText; + break; + case "cmt": + Cmt = childNode.InnerText; + break; + case "name": + Name = childNode.InnerText; + break; + case "desc": + Desc = childNode.InnerText; + break; + default: + throw new Exception("Unhandled for Child Object: " + childNode.Name); + } + } + } + + public override string ToString() + { + return "Latitude: " + Lat + " Longitude: " + Lon + " Elevation: " + Ele; + } + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Utils/JitterUtils.cs b/PoGo.NecroBot.Logic/Utils/JitterUtils.cs new file mode 100644 index 000000000..48bb7488f --- /dev/null +++ b/PoGo.NecroBot.Logic/Utils/JitterUtils.cs @@ -0,0 +1,19 @@ +#region using directives + +using System; +using System.Threading.Tasks; + +#endregion + +namespace PoGo.NecroBot.Logic.Utils +{ + public static class JitterUtils + { + private static readonly Random RandomDevice = new Random(); + + public static Task RandomDelay(int min, int max) + { + return Task.Delay(RandomDevice.Next(min, max)); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Utils/LocationUtils.cs b/PoGo.NecroBot.Logic/Utils/LocationUtils.cs new file mode 100644 index 000000000..0a7556401 --- /dev/null +++ b/PoGo.NecroBot.Logic/Utils/LocationUtils.cs @@ -0,0 +1,151 @@ +#region using directives + +using System; +using PoGo.NecroBot.Logic.Service.Elevation; +using PoGo.NecroBot.Logic.State; +using GeoCoordinatePortable; +using System.Threading.Tasks; + +#endregion + +namespace PoGo.NecroBot.Logic.Utils +{ + public static class LocationUtils + { + public static async Task UpdatePlayerLocationWithAltitude(ISession session, + GeoCoordinate position, float speed) + { + double altitude = await session.ElevationService.GetElevation(position.Latitude, position.Longitude).ConfigureAwait(false); + + session.Client.Player.UpdatePlayerLocation(position.Latitude, position.Longitude, altitude, speed); + } + + public static bool IsValidLocation(double latitude, double longitude) + { + return latitude <= 90 && latitude >= -90 && longitude >= -180 && longitude <= 180; + } + + public static double CalculateDistanceInMeters(double sourceLat, double sourceLng, + double destLat, double destLng) + // from http://stackoverflow.com/questions/6366408/calculating-distance-between-two-latitude-and-longitude-geocoordinates + { + try + { + var sourceLocation = new GeoCoordinate(sourceLat, sourceLng); + var targetLocation = new GeoCoordinate(destLat, destLng); + return sourceLocation.GetDistanceTo(targetLocation); + } + catch (ArgumentOutOfRangeException) + { + return double.MaxValue; + } + } + + public static double CalculateDistanceInMeters(GeoCoordinate sourceLocation, GeoCoordinate destinationLocation) + { + return CalculateDistanceInMeters(sourceLocation.Latitude, sourceLocation.Longitude, + destinationLocation.Latitude, destinationLocation.Longitude); + } + + public static async Task GetElevation(IElevationService elevationService, double lat, double lon) + { + if (elevationService != null) + return await elevationService.GetElevation(lat, lon).ConfigureAwait(false); + + Random random = new Random(); + double maximum = 11.0f; + double minimum = 8.6f; + double return1 = random.NextDouble() * (maximum - minimum) + minimum; + + return return1; + } + + public static async Task CreateWaypoint(GeoCoordinate sourceLocation, + double distanceInMeters, double bearingDegrees) + //from http://stackoverflow.com/a/17545955 + { + var distanceKm = distanceInMeters / 1000.0; + var distanceRadians = distanceKm / 6371; //6371 = Earth's radius in km + + var bearingRadians = ToRad(bearingDegrees); + var sourceLatitudeRadians = ToRad(sourceLocation.Latitude); + var sourceLongitudeRadians = ToRad(sourceLocation.Longitude); + + var targetLatitudeRadians = Math.Asin(Math.Sin(sourceLatitudeRadians) * Math.Cos(distanceRadians) + + + Math.Cos(sourceLatitudeRadians) * Math.Sin(distanceRadians) * + Math.Cos(bearingRadians)); + + var targetLongitudeRadians = sourceLongitudeRadians + Math.Atan2(Math.Sin(bearingRadians) + * Math.Sin(distanceRadians) * + Math.Cos(sourceLatitudeRadians), + Math.Cos(distanceRadians) + - Math.Sin(sourceLatitudeRadians) * Math.Sin(targetLatitudeRadians)); + + // adjust toLonRadians to be in the range -180 to +180... + targetLongitudeRadians = (targetLongitudeRadians + 3 * Math.PI) % (2 * Math.PI) - Math.PI; + + return new GeoCoordinate( + ToDegrees(targetLatitudeRadians), + ToDegrees(targetLongitudeRadians), + await GetElevation(null, sourceLocation.Latitude, sourceLocation.Longitude).ConfigureAwait(false) + ); + } + + public static GeoCoordinate CreateWaypoint(GeoCoordinate sourceLocation, double distanceInMeters, + double bearingDegrees, double altitude) + //from http://stackoverflow.com/a/17545955 + { + var distanceKm = distanceInMeters / 1000.0; + var distanceRadians = distanceKm / 6371; //6371 = Earth's radius in km + + var bearingRadians = ToRad(bearingDegrees); + var sourceLatitudeRadians = ToRad(sourceLocation.Latitude); + var sourceLongitudeRadians = ToRad(sourceLocation.Longitude); + + var targetLatitudeRadians = Math.Asin(Math.Sin(sourceLatitudeRadians) * Math.Cos(distanceRadians) + + + Math.Cos(sourceLatitudeRadians) * Math.Sin(distanceRadians) * + Math.Cos(bearingRadians)); + + var targetLongitudeRadians = sourceLongitudeRadians + Math.Atan2(Math.Sin(bearingRadians) + * Math.Sin(distanceRadians) * + Math.Cos(sourceLatitudeRadians), + Math.Cos(distanceRadians) + - Math.Sin(sourceLatitudeRadians) * Math.Sin(targetLatitudeRadians)); + + // adjust toLonRadians to be in the range -180 to +180... + targetLongitudeRadians = (targetLongitudeRadians + 3 * Math.PI) % (2 * Math.PI) - Math.PI; + + return new GeoCoordinate(ToDegrees(targetLatitudeRadians), ToDegrees(targetLongitudeRadians), altitude); + } + + public static double DegreeBearing(GeoCoordinate sourceLocation, GeoCoordinate targetLocation) + // from http://stackoverflow.com/questions/2042599/direction-between-2-latitude-longitude-points-in-c-sharp + { + var dLon = ToRad(targetLocation.Longitude - sourceLocation.Longitude); + var dPhi = Math.Log( + Math.Tan(ToRad(targetLocation.Latitude) / 2 + Math.PI / 4) / + Math.Tan(ToRad(sourceLocation.Latitude) / 2 + Math.PI / 4)); + if (Math.Abs(dLon) > Math.PI) + dLon = dLon > 0 ? -(2 * Math.PI - dLon) : 2 * Math.PI + dLon; + return ToBearing(Math.Atan2(dLon, dPhi)); + } + + public static double ToBearing(double radians) + { + // convert radians to degrees (as bearing: 0...360) + return (ToDegrees(radians) + 360) % 360; + } + + public static double ToDegrees(double radians) + { + return radians * 180 / Math.PI; + } + + public static double ToRad(double degrees) + { + return degrees * (Math.PI / 180); + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Utils/NecroWebClient.cs b/PoGo.NecroBot.Logic/Utils/NecroWebClient.cs new file mode 100644 index 000000000..aaacf9183 --- /dev/null +++ b/PoGo.NecroBot.Logic/Utils/NecroWebClient.cs @@ -0,0 +1,15 @@ +using System; +using System.Net; + +namespace PoGo.NecroBot.Logic.Utils +{ + public class NecroWebClient : WebClient + { + protected override WebRequest GetWebRequest(Uri uri) + { + WebRequest w = base.GetWebRequest(uri); + w.Timeout = 10000; + return w; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Utils/PushNotificationClient.cs b/PoGo.NecroBot.Logic/Utils/PushNotificationClient.cs new file mode 100644 index 000000000..5433baaa8 --- /dev/null +++ b/PoGo.NecroBot.Logic/Utils/PushNotificationClient.cs @@ -0,0 +1,156 @@ +using System; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Net.Mail; +using System.Threading.Tasks; +using PoGo.NecroBot.Logic.Common; +using PoGo.NecroBot.Logic.Event; +using PoGo.NecroBot.Logic.Model.Settings; +using PoGo.NecroBot.Logic.State; + +namespace PoGo.NecroBot.Logic.Utils +{ + //somehow, we can add PushBulletSharp in project, that is a simple client to just for send note message, + public class PushNotificationClient + { + private static void HandleEvent(ErrorEvent errorEvent, ISession session) + { + //SendPushNotificationV2("Error occured", errorEvent.Message); + } + + private static StreamContent AddContent(Stream stream, string filename) + { + var fileContent = new StreamContent(stream); + fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") + { + Name = "\"file\"", + FileName = "\"" + filename + "\"" + }; + fileContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); + return fileContent; + } + + private static StringContent AddContent(string name, string content) + { + var fileContent = new StringContent(content); + fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") + { + Name = "\"" + name + "\"" + }; + return fileContent; + } + + public static async Task SendMailNotification(NotificationConfig cfg, string title, string body) + { + await Task.Run(() => + { + var fromAddress = new MailAddress(cfg.GmailUsername, "NecroBot Notifier"); + //var toAddress = new MailAddress(cfg.Recipients); + + string fromPassword = cfg.GmailPassword; + + var smtp = new SmtpClient + { + Host = "smtp.gmail.com", + Port = 587, + EnableSsl = true, + DeliveryMethod = SmtpDeliveryMethod.Network, + UseDefaultCredentials = false, + Credentials = new NetworkCredential(fromAddress.Address, fromPassword) + }; + + using (var message = new MailMessage() + { + Subject = title, + Body = body + }) + { + message.From = fromAddress; + foreach (var item in cfg.Recipients.Split(';')) + { + message.To.Add(item); + } + try + { + smtp.Send(message); + + } + catch(Exception) + { + //Logic.Logging.Logger.Debug("Fail to send notification", ex); + } + } + }).ConfigureAwait(false); + } + + public static async Task SendNotification(ISession session, string title, string body, bool push = false) + { + var cfg = session.LogicSettings.NotificationConfig; + try + { + if (cfg.EnableEmailNotification) + { + await SendMailNotification(cfg, title, body).ConfigureAwait(false); + } + + if (push) + { + if (cfg.EnablePushBulletNotification) + { + await SendPushNotificationV2(cfg.PushBulletApiKey, $"{DateTime.Now:MMM dd, yy @ HH:mm}\r\n" + title, body).ConfigureAwait(false); + } + // TODO function is deprecated / obsolete + // jjskuld - Ignore CS0618 warning for now. + #pragma warning disable 0618 + if (session.Telegram != null) + await session.Telegram.SendMessage($"{title}\r\n{body}").ConfigureAwait(false); + #pragma warning restore 0618 + } + } + catch (Exception) + { + session.EventDispatcher.Send(new WarnEvent() + { + Message = session.Translation.GetTranslation(TranslationString.FailedSendNotification) + }); + } + } + + public static async Task SendPushNotificationV2(string apiKey, string title, string body) + { + bool isSusccess = false; + + var handler = new HttpClientHandler + { + Credentials = new NetworkCredential(apiKey, "") + }; + + // string name = Path.GetFileName(pathFile); + using (var wc = new HttpClient(handler)) + { + using (var multiPartCont = new MultipartFormDataContent()) + { + multiPartCont.Add(AddContent("type", "note")); + multiPartCont.Add(AddContent("title", title)); + multiPartCont.Add(AddContent("body", body)); + //multiPartCont.Add(AddContent(new FileStream(pathFile, FileMode.Open), name)); + + try + { + var resp = await wc.PostAsync("https://api.pushbullet.com/v2/pushes", multiPartCont).ConfigureAwait(false); + var result = await resp.Content.ReadAsStringAsync().ConfigureAwait(false); + isSusccess = true; + } + catch (Exception ex) + { + isSusccess = false; + Console.WriteLine(ex.Message); + } + } + } + return isSusccess; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Utils/RouteOptimizeUtil.cs b/PoGo.NecroBot.Logic/Utils/RouteOptimizeUtil.cs new file mode 100644 index 000000000..b3348cca3 --- /dev/null +++ b/PoGo.NecroBot.Logic/Utils/RouteOptimizeUtil.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using POGOProtos.Map.Fort; + +namespace PoGo.NecroBot.Logic.Utils +{ + public static class RouteOptimizeUtil + { + public static List Optimize(FortData[] pokeStops, double lat, double lng) + { + var optimizedRoute = new List(pokeStops); + // NN + var nn = FindNn(optimizedRoute, lat, lng); + optimizedRoute.Remove(nn); + optimizedRoute.Insert(0, nn); + for (var i = 1; i < pokeStops.Length; i++) + { + nn = FindNn(optimizedRoute.Skip(i), nn.Latitude, nn.Longitude); + optimizedRoute.Remove(nn); + optimizedRoute.Insert(i, nn); + } + // 2-Opt + bool isOptimized; + do + { + optimizedRoute = Optimize2Opt(optimizedRoute, out isOptimized); + } while (isOptimized); + return optimizedRoute; + } + + private static List Optimize2Opt(List pokeStops, out bool isOptimized) + { + var n = pokeStops.Count; + float bestGain = 0; + var bestI = -1; + var bestJ = -1; + + for (var ai = 0; ai < n; ai++) + { + for (var ci = 0; ci < n; ci++) + { + var bi = (ai + 1)%n; + var di = (ci + 1)%n; + + var a = pokeStops[ai]; + var b = pokeStops[bi]; + var c = pokeStops[ci]; + var d = pokeStops[di]; + + var ab = GetDistance(a, b); + var cd = GetDistance(c, d); + var ac = GetDistance(a, c); + var bd = GetDistance(b, d); + + if (ci != ai && ci != bi) + { + var gain = ab + cd - (ac + bd); + if (gain > bestGain) + { + bestGain = gain; + bestI = bi; + bestJ = ci; + } + } + } + } + + if (bestI != -1) + { + List optimizedRoute; + if (bestI > bestJ) + { + optimizedRoute = new List {pokeStops[0]}; + optimizedRoute.AddRange(pokeStops.Skip(bestI)); + optimizedRoute.Reverse(1, n - bestI); + optimizedRoute.AddRange(pokeStops.GetRange(bestJ + 1, bestI - bestJ - 1)); + optimizedRoute.AddRange(pokeStops.GetRange(1, bestJ)); + optimizedRoute.Reverse(n - bestJ, bestJ); + } + else if (bestI == 0) + { + optimizedRoute = new List(pokeStops); + optimizedRoute.Reverse(bestJ + 1, n - bestJ - 1); + } + else + { + optimizedRoute = new List(pokeStops); + optimizedRoute.Reverse(bestI, bestJ - bestI + 1); + } + + isOptimized = true; + return optimizedRoute; + } + isOptimized = false; + return pokeStops; + } + + private static FortData FindNn(IEnumerable pokeStops, double cLatitude, double cLongitude) + { + return pokeStops.OrderBy(p => GetDistance(cLatitude, cLongitude, p.Latitude, p.Longitude)).First(); + } + + private static float GetDistance(FortData a, FortData b) + { + return GetDistance(a.Latitude, a.Longitude, b.Latitude, b.Longitude); + } + + private static float GetDistance(double lat1, double lng1, double lat2, double lng2) + { + const double R = 6371e3; + Func toRad = x => (float) (x*(Math.PI/180)); + lat1 = toRad(lat1); + lat2 = toRad(lat2); + var dLng = toRad(lng2 - lng1); + + return (float) (Math.Acos(Math.Sin(lat1)*Math.Sin(lat2) + Math.Cos(lat1)*Math.Cos(lat2)*Math.Cos(dLng))*R); + } + } +} diff --git a/PoGo.NecroBot.Logic/Utils/Statistics.cs b/PoGo.NecroBot.Logic/Utils/Statistics.cs new file mode 100644 index 000000000..60ae0931d --- /dev/null +++ b/PoGo.NecroBot.Logic/Utils/Statistics.cs @@ -0,0 +1,290 @@ +#region using directives + +#region using directives + +using System; +using System.Linq; +using System.Threading.Tasks; +using Google.Protobuf.Collections; +using PoGo.NecroBot.Logic.Exceptions; +using PoGo.NecroBot.Logic.Logging; +using PoGo.NecroBot.Logic.Model; +using PoGo.NecroBot.Logic.Model.Settings; +using PoGo.NecroBot.Logic.State; +using POGOProtos.Inventory.Item; +using POGOProtos.Networking.Responses; +using TinyIoC; + +#endregion + +// ReSharper disable CyclomaticComplexity + +#endregion + +namespace PoGo.NecroBot.Logic.Utils +{ + public delegate void StatisticsDirtyDelegate(); + + public class Statistics + { + private AccountConfigContext _context = new AccountConfigContext(); + private DateTime _initSessionDateTime = DateTime.Now; + + private StatsExport _exportStats; + private string _playerName; + public int TotalExperience; + public int TotalItemsRemoved; + public int TotalPokemons = 0; + public int TotalPokemonEvolved = 0; + public int TotalPokestops = 0; + public int TotalPokemonTransferred; + public int TotalStardust; + public int LevelForRewards = -1; + public bool isRandomTimeSet = false; + public int newRandomSwitchTime = 1; // Initializing random switch time + + public StatsExport StatsExport => _exportStats; + + + + public void Dirty(Inventory inventory, ISession session) + { + _exportStats = GetCurrentInfo(session, inventory).Result; + TotalStardust = inventory.GetStarDust(); + TinyIoCContainer.Current.Resolve().DirtyEventHandle(this); + DirtyEvent?.Invoke(); + OnStatisticChanged(session); + } + + public void OnStatisticChanged(ISession session) + { + var manager = TinyIoCContainer.Current.Resolve(); + if (MultipleBotConfig.IsMultiBotActive(session.LogicSettings, manager) && manager.AllowSwitch()) + { + var config = session.LogicSettings.MultipleBotConfig; + + if (config.PokestopSwitch > 0 && config.PokestopSwitch <= TotalPokestops) + { + session.CancellationTokenSource.Cancel(); + + //Activate switcher by pokestop + throw new ActiveSwitchByRuleException() + { + MatchedRule = SwitchRules.Pokestop, + ReachedValue = TotalPokestops + }; + } + + if (config.PokemonSwitch > 0 && config.PokemonSwitch <= TotalPokemons) + { + session.CancellationTokenSource.Cancel(); + //Activate switcher by Pokemon + throw new ActiveSwitchByRuleException() + { + MatchedRule = SwitchRules.Pokemon, + ReachedValue = TotalPokemons + }; + } + + if (config.EXPSwitch > 0 && config.EXPSwitch <= TotalExperience) + { + session.CancellationTokenSource.Cancel(); + //Activate switcher by EXP + throw new ActiveSwitchByRuleException() + { + MatchedRule = SwitchRules.EXP, + ReachedValue = TotalExperience + }; + } + + // When bot starts OR did the account switch by time, random time for Runtime has not been set. So we need to set it + if (!isRandomTimeSet) + { + Random random = new Random(); + newRandomSwitchTime = config.RuntimeSwitch + random.Next((config.RuntimeSwitchRandomTime * -1), config.RuntimeSwitchRandomTime); //config.RuntimeSwitchRandomTime * -1, 0); + isRandomTimeSet = true; + + Logger.Write($"Current Account will run for aprox: {newRandomSwitchTime} Min.",LogLevel.Info, ConsoleColor.Red); + } + + var totalMin = (DateTime.Now - _initSessionDateTime).TotalMinutes; + if (newRandomSwitchTime > 0 && newRandomSwitchTime <= totalMin) + { + + + // Setup random time to false, so that next account generates new random runtime + isRandomTimeSet = false; + + session.CancellationTokenSource.Cancel(); + //Activate switcher by pokestop + throw new ActiveSwitchByRuleException() + { + MatchedRule = SwitchRules.Runtime, + ReachedValue = Math.Round(totalMin, 1) + }; + } + + + + + + } + } + + public event StatisticsDirtyDelegate DirtyEvent; + + public string FormatRuntime() + { + return (DateTime.Now - _initSessionDateTime).ToString(@"dd\.hh\:mm\:ss"); + } + + public async Task GetCurrentInfo(ISession session, Inventory inventory) + { + var stats = await inventory.GetPlayerStats().ConfigureAwait(false); + StatsExport output = null; + var stat = stats.FirstOrDefault(); + if (stat != null) + { + var ep = stat.NextLevelXp - stat.Experience; + var time = Math.Round(ep / (TotalExperience / GetRuntime()), 2); + var hours = 0.00; + var minutes = 0.00; + + var TotXP = 0; + + for (int i = 0; i < stat.Level + 1; i++) + { + TotXP = TotXP + Statistics.GetXpDiff(i); + } + + if (double.IsInfinity(time) == false && time > 0) + { + hours = Math.Truncate(TimeSpan.FromHours(time).TotalHours); + minutes = TimeSpan.FromHours(time).Minutes; + } + + if (LevelForRewards == -1 || stat.Level >= LevelForRewards) + { + if (session.LogicSettings.SkipCollectingLevelUpRewards) + { + Logger.Write("Current Lvl: " + stat.Level + ". Skipped collecting level up rewards.", LogLevel.Info); + } + else + { + LevelUpRewardsResponse Result = await inventory.GetLevelUpRewards(stat.Level).ConfigureAwait(false); + + if (Result.ToString().ToLower().Contains("awarded_already")) + LevelForRewards = stat.Level + 1; + + if (Result.ToString().ToLower().Contains("success")) + { + Logger.Write($"{session.Profile.PlayerData.Username} has leveled up: " + stat.Level, LogLevel.Info); + LevelForRewards = stat.Level + 1; + + RepeatedField items = Result.ItemsAwarded; + string Rewards = ""; + + if (items.Any()) + { + Logger.Write("- Received Items -", LogLevel.Info); + Rewards = "\nItems Recieved:"; + foreach (ItemAward item in items) + { + Logger.Write($"{item.ItemCount,2:#0}x {item.ItemId}'s", LogLevel.Info); + Rewards += $"\n{item.ItemCount,2:#0}x {item.ItemId}'s"; + } + } + + if (session.LogicSettings.NotificationConfig.EnablePushBulletNotification) + await PushNotificationClient.SendNotification(session, $"{session.Profile.PlayerData.Username} has leveled up.", $"Trainer just reached level {stat.Level}{Rewards}", true).ConfigureAwait(false); + } + } + } + + output = new StatsExport + { + Level = stat.Level, + HoursUntilLvl = hours, + MinutesUntilLevel = minutes, + LevelXp = TotXP, + CurrentXp = stat.Experience, + PreviousXp = stat.PrevLevelXp, + LevelupXp = stat.NextLevelXp + }; + } + return output; + } + + private object GetCurrentAccount() + { + var session = TinyIoCContainer.Current.Resolve(); + return _context.Account.FirstOrDefault(a => session.Settings.Username == a.Username && session.Settings.AuthType == a.AuthType); + //throw new NotImplementedException(); + } + + internal void Reset() + { + TotalExperience = 0; + TotalItemsRemoved = 0; + TotalPokemons = 0; + TotalPokemonEvolved = 0; + TotalPokestops = 0; + TotalStardust = 0; + TotalPokemonTransferred = 0; + _initSessionDateTime = DateTime.Now; + _exportStats = new StatsExport(); + } + + public async Task GetLevelUpRewards(ISession ctx) + { + return await ctx.Inventory.GetLevelUpRewards(LevelForRewards).ConfigureAwait(false); + } + + public double GetRuntime() + { + return (DateTime.Now - _initSessionDateTime).TotalSeconds / 3600; + } + + public string GetTemplatedStats(string template, string xpTemplate) + { + var xpStats = string.Format(xpTemplate, _exportStats.Level, _exportStats.HoursUntilLvl, + _exportStats.MinutesUntilLevel, _exportStats.CurrentXp - _exportStats.LevelXp, _exportStats.LevelupXp - _exportStats.LevelXp); + + return string.Format(template, _playerName, FormatRuntime(), xpStats, TotalExperience / GetRuntime(), + TotalPokestops / GetRuntime(), + TotalStardust, TotalPokemonTransferred, TotalItemsRemoved); + } + + public static int GetXpDiff(int level) + { + if (level > 0 && level <= 40) + { + int[] xpTable = + { + 0, 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, + 10000, 10000, 10000, 10000, 15000, 20000, 20000, 20000, 25000, 25000, + 50000, 75000, 100000, 125000, 150000, 190000, 200000, 250000, 300000, 350000, + 500000, 500000, 750000, 1000000, 1250000, 1500000, 2000000, 2500000, 3000000, 5000000 + }; + return xpTable[level - 1]; + } + return 0; + } + + public void SetUsername(GetPlayerResponse profile) + { + _playerName = profile.PlayerData.Username ?? ""; + } + } + + public class StatsExport + { + public long CurrentXp; + public double HoursUntilLvl; + public int Level; + public int LevelXp; + public long LevelupXp; + public long PreviousXp; + public double MinutesUntilLevel; + } +} diff --git a/PoGo.NecroBot.Logic/Utils/StringUtils.cs b/PoGo.NecroBot.Logic/Utils/StringUtils.cs new file mode 100644 index 000000000..e94205ad1 --- /dev/null +++ b/PoGo.NecroBot.Logic/Utils/StringUtils.cs @@ -0,0 +1,69 @@ +#region using directives + +using System; +using System.Collections.Generic; +using System.Linq; +using POGOProtos.Inventory.Item; +using POGOProtos.Inventory; + +#endregion + +namespace PoGo.NecroBot.Logic.Utils +{ + public static class StringUtils + { + public static string GetSummedFriendlyNameOfGetLootList(IEnumerable items) + { + var enumerable = items as IList ?? items.ToList(); + + if (!enumerable.Any()) + return string.Empty; + + return + enumerable.GroupBy(i => i.Item) + .Select(kvp => new { ItemName = kvp.Key.ToString(), Amount = kvp.Sum(x => x.Count) }) + .Select(y => $"{y.Amount} x {y.ItemName}") + .Aggregate((a, b) => $"{a}, {b}"); + } + + public static string GetSummedFriendlyNameOfItemAwardList(IEnumerable items) + { + var enumerable = items as IList ?? items.ToList(); + + if (!enumerable.Any()) + return string.Empty; + + return + enumerable.GroupBy(i => i.ItemId) + .Select(kvp => new { ItemName = kvp.Key.ToString(), Amount = kvp.Sum(x => x.ItemCount) }) + .Select(y => $"{y.Amount} x {y.ItemName}") + .Aggregate((a, b) => $"{a}, {b}"); + } + + private static readonly Func AndFunc = (x, y) => x && y; + private static readonly Func OrFunc = (x, y) => x || y; + + private static readonly Func> GetBoolOperator = + myOperator => myOperator.ToLower().Equals("and") ? AndFunc : OrFunc; + + public static bool BoolFunc(this bool expr, bool expr2, string operatorStr) + { + return GetBoolOperator(operatorStr)(expr, expr2); + } + + public static bool BoolFunc(this string operatorStr, params bool[] expr) + { + return operatorStr.ToLower().Equals("and") ? expr.All(b => b) : expr.Any(b => b); + } + + public static bool ReverseBoolFunc(this string operatorStr, params bool[] expr) + { + return operatorStr.ToLower().Equals("and") ? expr.Any(b => b) : expr.All(b => b); + } + + public static bool InverseBool(this string operatorStr, bool expr) + { + return operatorStr.ToLower().Equals("and") ? !expr : expr; + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/Utils/WebUtils.cs b/PoGo.NecroBot.Logic/Utils/WebUtils.cs new file mode 100644 index 000000000..c3cbeb05b --- /dev/null +++ b/PoGo.NecroBot.Logic/Utils/WebUtils.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Specialized; +using System.Linq; +using System.Text; + +namespace PoGo.NecroBot.Logic.Utils +{ + public static class WebUtils + { + public static Encoding GetEncodingFrom( + NameValueCollection responseHeaders, + Encoding defaultEncoding = null) + { + if (responseHeaders == null) + throw new ArgumentNullException("responseHeaders"); + + //Note that key lookup is case-insensitive + var contentType = responseHeaders["Content-Type"]; + if (contentType == null) + return defaultEncoding; + + var contentTypeParts = contentType.Split(';'); + if (contentTypeParts.Length <= 1) + return defaultEncoding; + + var charsetPart = + contentTypeParts.Skip(1).FirstOrDefault( + p => p.TrimStart().StartsWith("charset", StringComparison.InvariantCultureIgnoreCase)); + if (charsetPart == null) + return defaultEncoding; + + var charsetPartParts = charsetPart.Split('='); + if (charsetPartParts.Length != 2) + return defaultEncoding; + + var charsetName = charsetPartParts[1].Trim(); + if (charsetName == "") + return defaultEncoding; + + try + { + return Encoding.GetEncoding(charsetName); + } + catch (ArgumentException) + { + return null; + } + } + } +} \ No newline at end of file diff --git a/PoGo.NecroBot.Logic/config.xlsm b/PoGo.NecroBot.Logic/config.xlsm new file mode 100644 index 0000000000000000000000000000000000000000..5b9ad86f1aa70a1028b01c9e1baba7de20cfa390 GIT binary patch literal 51509 zcmeFYWmH^Ew=LXw;}8h$79hC0yL)hVcXxLW?(Xhx!3pjbAV6>k?q4V8J?B36d(R!? z{y(?Jpqovts+u*|TC>)sYb!`YK%xVn0YCr%Kn$RsXdlW30|4fr001-q5L`>x&eqw) z)>%)*!`{S6htA!`ny3I0oH7pp4zmA$um8ae45>`X?J=PA)DCfr^c1Xw48m1sIIft) z!GE{}D0{7?akcDYOFiFlOBxs`T9gayCQ{~1-ZSr|Up-d9FMfc3M8|b05U&_8Pr7#4 zDqGLozD=(PC*}fD85S-{9D6(7*myU7m-uQdZmBYn<%cEQ@(oF%G+L3QQH4wYk;W;h zpH4saz?tRA);cme!RnlG!T1H<%$~5VLw)Q@Y-~YX^>yQAS(z2i5orrNN})0O{G(*H zDYM-yxz{#qQ;9YTUUT>cRCW~Ruw%@mVRZ89xCXylQGgj*=>64w8(JMV5*1$s!BFv?owy9M18AC(P%{gwyw-T|RCCmL)a5D^e359h{! zm~I@pJjPA=ZufX@dm{(^1HvWCHvuU{E5_8_w+kI&l3;J>#Q$c@mObw|O^fnZjmCk= z+Yz{~;l*ry)_|&{o4LltHl*Vw`+RNg{1e!QYqyCY(4Jyd5DK~Wa=!0=)dhoy=JaZN z^ujn9>TV~+u$LIWgLcDoWi+W56p-)l5CDb$EvuVV8AxwHEi#~_g$HG|o}-Di6FuFZ z_y3p8|ASNbA4{)Fl$9T3KnT5%cnKT5U)+pG5t4Qjl;|W@@%5M7KyHpHB*We8{e*|2 zf)fNG?$_=6GQPga6LUODe7DO|74-p~o3zEPIyCjk!3Bnz!ZAh6p=xIc)phZ9@h)9l z%7fCiJC3@ttt?-9Y@1YU{z{}CWt#2_4lG(RUML1{dVuDztj3n%Lk-xXkn&k|Xk9CN z-f_}&rr&Z($q^!dIH%0%T>8gRCquK<8t+kS;@d|YRb_Kdi#o#`M=lZ%J!9*>Ymv-e zv{!FhxvWto5|$4SOw-~cV4xikWcxq+)s@sQ_a8CSCH$3JR!d*V z{D=-{!4A0x*05kRn9&wFm}e)y5aIH*#j%{@Dem`klAl-m6Ps)kj5toUzC~6YuxdbP zU8?SW>Us(yYE)Wl6q**e1Xi!=>(lHVBKC9{VY#VAcdVQ+UmO>^KUm0aj5{`tV?KB#_$ z4!ACT9}kpy4u2-!9OBHR9r(>yO8~z;9{KL8G#gaX8}sB(V+>6FXf-5QX~NGeFDR4k z0(V-EyO$6PV=U0LTb5o_Nza@k0?p0}T^^WAIicEOzf~yQ8V4R~V86I-C>@_{-+4Q` zKO%#o;@`syvEzGhHb{SsL6y}X3s89dGq8$OCT!OkP`WUG@iPS1wh&x>4%{bd*&7l_ zz_Q8EMu1Ef(F%T4kgRZ3xY_PZE>NqcxA016E8<}8Y^)sHSG2$RYB`u3?J#rASiq!; zoz#it=d12If`u(@EY_CDGEBelW8&!9Eh%X3S)`m79AIIXrKKg3V>XusXXsgx_d%{j z3f_=-fqNP~BXRO)vSxqx%zao6k<2a*@tj@1!aA+=kx+4Q-rN#VhNGci&zOb*4ogB0 zlpIPoeUxp52p9_N-eiwT9##a4=2_7k*-rc&BNoeO9ZvuT&Ej}j^%+?5mnfbw_RDUf zGUXh(mgxDBj?-}TZ}_OOAx1+H^~>0eEKyfLA(y;fn_mrSU!w;t7`Tw>8NeIOeO}ki z+fQL2{8n@jrbjm4z8TKohb&LnV(CtczggoUrCwjGWWKT3RckF1I-2I_>U^Ut)KYSM z2Dq{b1vswU2lWvPc+|8NYkrVEl+v`{TO4_>_735l{`e5hH%CuCkG?j6&5+%fRD>!y znX!WRHDkmb)n@6Q{T^4cKTu@V%)I^e;JYZZ3A>d`p{MM^bDk^BW>Xefsm*Up-Nc2j z13{V#o+r1tF6nX`Sv*Xh487DBLeAcXOv+(cgA3hFOJSDJ5hiSL4lDOQ9JTkhRO|a} z!5iqfqV;+Ga!f@S_+503VhO$s=U&-3w&-^*ogsKHw|Zc+;#Wi?Md$( z?2BKoYW9aY6@gd*aalr1JmlI3EJECxdG~w0?>HYlM~==S zmCpPlyTWIrPHtzeBaKC&sBC{`99&9Md?msuu{`={B`T?jMcm)rckh)$L?d1tnlF)4 zD1vfp)aZ&vwItFAwI_2?MfWq@_`xGxS`-5>&(^sK2ibAe_YAg~ejbcN5dJkA} z&LmUHj8PSY-H(K#Sx8I064|{eJ8vh5on<+0w6#ihxo9WMUn9 zak(Pk8yN5eW|owX8>Mk9KG?QG7dlD6k@KJSd~&7#xVqDq@$0KrE(#@?S-D_+RtpvH z+0@?AM$WpBX-%-G0l)sFPz5@ZmQN=GC3WgBsT?yRfwFZwlOFBzr5kZ`C?o?xaWsvI zTwA+Ivj{7o8Fv3=$+*|A5#oyt^QY}_^JUs!2wzoO0fNdber$st!O zX@ADwW?b~x3d8z5l8x+TbO04zWEJ#{jlo95$^jzfUBF$1!kxiwx6#|=PYzMCC{+n zofYRrpM528!So;%*ZN>#`w6`@vF5uJ^&Q%Pfu%n9OEFwJooHiGvti94o0k)pHpFrI zgxYVoaJSHQyOR<4gf|@DEib5!Y?U4B^8b@#x(g{A+LDLKor z1sl&&Sgsz_{b9h``LnsP8N0%v5|+z6;I4R`Q9cFelgt!?nN;2FZ&h!g-MFE$n|Hc$ zqE@l?4x1tMP2;T)gQ;7dbfHz}4I1<=` z!%V_lv~^q0`a4#>>C4v=Yf^w_^9grn>RLs(0`r@pr?mA{k=ARme=Cl6c8nqFlO3N2 zd)m}q-`R6g&-&TS41RD@I<9MJ(kczXLjeElqGxl@?v$n8iUOH~o4URGYQ^{p+>1>k zM*dP!u$`B+|K>0s(DN+}<=J-Qs($?}p=bTVMTi~Af#JmE(=`K<*2*AePofX<-qY8v zZ#xRif&n-#RmZ^QFT@fIyhg!a#0RY2d>G$AYVhwHAKDNN+5ku3_(@pN-GS z+{DD$>0c$*-wUSyDzJj%`Yi_;P(&_5UV>J<$hgB4BFXinh?gl|0iwF=1$R>Bt>ll- zzgB{UVh4@37kJdpG0D&zucdkt>r{Ik`S`ZZukxnbU{$yK+4I87Y6u%8MeHmezuRjtIvXv0GS$0w%+Rt zk66Iv`o5DMd76Ja z>;2x3_RZPF_pW7|$8VMAJg1fY@O`5v`~C5{_kF!|+pV^@%V*v8-fV2(dVT7#_4RFk z@7uTa*VeQ7gR{YNljkh4o^#z?w&$DaPrg$c`Nd#1Td?{{ZA`)9^yM?G=vrBf&Ly*)2SzV|nYGA}om z4|VTmHa9P)M{j#ihliUd2c;v}sW_btyI@9~p5c$mk_-aZaK z2{b-$D%Y3y%Z(y;cXpYjEF;@)z1F#NCRuvkZ#VnII@>$aoL-y0-JI|FPY)YD{@BGipw)Z!CuLpGtmdQOsFBiS|J^my2^2mB?@|~OXVf=c2?VN-~E?w3+ zt!D2pSL8ZRQ;!cX*1cYjCwC{lJw7six5BJ7->2E`pTo9W{N(#GuXA0WU(b*H%kYhI z`JSFSo|o_1b=U74GV3fBVJdAdXGdpGyKqX=x87=dTAm1x2Izi&{r2wHF)n&05m8IO zUYn^$cz?aQ_s-|N>etEH`(&?ySRNmTmq*B67&sTXQ)lwkPeYx2%H7yFpT_zpzldDzqLT?sk2d%L(WO+^ z-<^dcgy9@I&tN)eN%TJMK2)hlX_Y5zrgE;(Hj}gl6>JBw41;W@f@zAljbxK;F&e01 zJ$I}IC985#56;U?;rv69J3j{dSL57#kciS>2Xo03&Kw7ZPFa>XiZ40cf>+vHg6%yH zfd-B{z#WGppn=1Fc6$SRh;(ow>k~AU8C#8Kyv(oy|h0hMg;*{mF z3QG9ItrG*NQWd1hD&nItouO71-eun`Bg>)Gki*d`cS#!O)dwxfi>AOxX8Td4DoK;o z#3`%mkkU=}QT9JZJ70QAxEL|iT!*E!5}hm2rMV0F$|8&o8To%;?R>4j)_nPWUQp&8y=qr!aHkW=iSv$u3SgY z;PVSYv$8a>viAG)P=;U+{jqT-!d&nuU{N>KT_+_N;5FkojuRQgm?urXBcz4qvQ0_) zq?q#w$g#llHXgHJnXBc4VU@0)wJ{HBi!tj*n~p^9m#ELWU{5i8!JMMLA~MBundoeY zzn!d@Hy^xK&-DpT;J(CQ_|808`~l*$ET?e4Ea#*?>-L_}#*RX8IR(e!GbNBi)!u_; zhK7#qQ}ir?&~yRxC`(~GUSWGz7V8RZ&TL70)vpT5BkD!xKbCGsSs;u+?g1vu6elR2jplPtM|y=>Y+iv{mns3L%ALh)Ud8gSBU zCmmT1jnF3(y+2PQD1kE(n+|6R=7hMIYQg)*FDwuxQ3qv!^n;}DoC|)`s{X=X1eK@u z0edW+_->|Y6Xwv|VvR-=|1vFvW{QT3w}=I=Q-V@|?{;r=f5Bsrb3%raaMG9rs8XzB z8;dD(4wzHJq)o)C*kVBuHR9dst?*Yz(KbF^tkdXOQo~@Gtu|*L@z)uxWBbqk>L!a; zXe&6G(;*#jp2dSAn#a4%wI!TJ`pUn0*4{LO@QoRMN@2Kh1_3mIKT{UR%ZH^?Mu>yC zWb9L!P9Z0Be;KFBqS7ZjUDi}m7}8eviDHM zcSP0q{aFgZwkGGAvNPNM=n(|Uev5MCQyGmP26h9lg$8!yE>1Nnz|1s!9ooxOrkLz4mfKY|nZY4bdLgLvZinQ!5=7fw5bV^5njGjMr^7!F@%Q1y?T+mJl zA+87=+k}uNPC7-c05E3hJe^x{gfY!BjnC?E3ASoE%){+qvI{lb$&30`fPyDV%%Mb_ zCT_H%(2qz&it|L#yai`UrwRlge#IUt2thpFYx+c7CVH++@G(R`X4F79M4uq$t;z-m zz?3oZwUwKJd6o`wpzH%=bQq%L2}?%rGXC0k_DCgr^x_{9U1llb5K~|GalbKb)2>|A zf$ekJ?CAItztW{N{R;Som?3+A)Tacbvnh#kv`631gC5c#h>_9Su-{bCXe7~S7!?ei zEQKwq7H7yT#WR+0ONjT6Jt%ZgL|+QW<5fXFn3ZS|Lxvnfbd)9{$3>)C1u8Yb7U7AP!k+DHOF%k90bKfy1aS8{rKaI&){#NcD!XR)^hl4~u$$ zI2&`kpZaZ1rm;^PkZhc4=(z8u|4lL!Jcqy+X4Z`C9yQci6+f<5QW0}0e6ryf#F++$ zUoOO`r{h@s&Cf*aspHXTj4$?6yL_Jk7JY!xqP;72D(ZnhsU-R*j61LqiAu52^XE=c ztQbmfe|?|-fN8@s7^qV2pD$Msm1zF~S8RkD_S&3G)5#3LIXiPyF2X$&0UiCwTrOsl z)(<~#qXg-q0{=-=Qk}Fxx{_?NFu>v~lM{e*UKefwAZtqF*cTySgu~D=BDic4!Ax5g zby$FtHB>WeANy;CHA9?qdTknv=(B$k@n#z-x6p3jDb_6SafrJCHI#**jtJ&gea|yB zcg6q=gg+S2kU$s|yqOE~E6hcLvg>&=Q=2T;pGKsaE8YdB?3I#(Sa;ERn%>wZ3KOh0 z<-y}^9O8-h)>t!+1RIRxHVe&xt~E`?5cEBg&}(+=)Z_h<$w8jxHXtw0-thzr`+PXJ z8;eIpEaq94A`u>D!Jf+|>tdO>>Qj=yS<}We*&$0oy8X3A6DL;V5(gpR8lQuJy*xu& zu|8{DItK^u-!#-a0Hu)vgX6SNFUmy~If#?67Zy3lgHzUGR1a{h5RAiKjK$Wfa-AGa zgO0X+8O(qPyP!V{ymjV5EB)*4X=>y@DdNV3LMC{DA(*H@Dz?rIEV_bVSk^=u8Cxht zU3d_G{%9c@!x1A67X;2l;pxHRSdD`!IP4PFK#apwGZvp9OZ1hVBYVT#3Ot6JAbP$) zp$c`iQI+ywcC_XE8BM|}%v=J0{bP;^k+C=_P`@fG?&?Skkip;Z?)DnIZv zmLQDuuWwZX6I)+O^>;`O{ZO3KqB~zOs4L8^f?2%FA|-ko`$vaPYN-5aUWtTc5o(!0?81O%w)La>FALmEwq`DxP7@)3*`X9FoVg?xBv7Ap~}?$S8i6 zlMno5f2y#J%=kB~mLcMuHibVY;x9(F5$$C}Mx?0veSsxdB$>j{?tSIkEzs`bA##LN zYzHOS;*&O6JoK&>q@-F5)>j6g5-bGKe3ZGwElD6(PIRhhHz>td8-9T(F+L%n4#+ws z@I*z#UiG;lQFmQ>P-tnvhdlWKMm(Km$yyGg%wwOC z0gj7T<3SSz>Q$(vzw)tXL0*hxT_ufI(&j01hf&KiWH02E(I2J^Z75XQcn0k5n3?zZ z!R3h=hc@Sc!ej7>Aeu8E*&x;UUa`CNX2~U9 zWI+0dPtgasM8{;wbbZnId_}A$uETHwq_kwrh7rs~B1K~?eNQn*ZW1xQEBmsB7G4UaGc4(5XuQ-{ZrEx|+F?(o=CfJJBQR*oc#J!E)x_~1$622$z?K@Sm1JxBp}yhMe_-VL9EHB zsvLT$uyg8j-^!zX!#syZ=3xOtSLEVgd?KN=<{zp#3H)iU1%xv^0lzeN7Xm37XLg>h zq`XQU1T$pk1GyG{HV6l|oJDJEp45zt9vu)R2 z9e2*f7VvdNJ}G!Ge-#H~q_N$n&*PMq}G%B7Z__f{EdRFR^H( zqW4lufdnU8liZNNrcAE4Z&NPh0Hl*U5BV~#Fs`Q|(*E_N6HNr9Ys*gMw1)0i_*z9@ zL?b{sGfRyM+7H&0crcaZnv6C|%eP9+wDm>rVvYJ%DvQQqdwdBPcGk^#QEH>W>p}ik zB?1*RO{VUUa|FnJE1573Oti*eqfE#Hzf7qNnlhm=7ZjPh=qf;hov!wGrJBPe@r<%| z9to@G_G})Bv?xXU9|1@CcOUhsy%-6;FNwM;lIV_PU3u z7$5GcqYTArXe{5w4^-@u&Q}b<8XMqPhH0+$k_tn>&l`^ousFwA0(JYH)g1cCT|$lu zB)VepOQ?PSRzZ$Y3QY+62VF`VyPP8!J<@scnzkqN`dBY9S4h;{-raeX@o># zE}t^#Bdr8{L{nakkRdB2+&5CPq-v>p>{x3nBq7R^1M>LyXvgl|LC1{8DmNV3)%7bB z6Rky`lpZl*;8;k?QW<1H|CP>8$$PYjs{O^>OZfF_}ji8ORc*EPrvGiA{3CL_Xi-e>_nt4qKi8f5T8tme3}*ky!;EH;BAq;kzU7{r4Ii*(sMgUQ4 z(&3I0bSv}W8v%0oQvN=es z637P(xoW6N--Rk2*I@UXxTU;w(N5~_X2MrXx~WfQaOQ?E(F#0xc!ZlpIBEf^s}T%h zV-)Z~G-Z1Y(Qeu)-t(U`9L%|!+iJce`d3tmT1g0ZMa=RLM6^1iDu{R!vPzXSSvV^0 zG-SEw&V1)9prXY=qwL`ocr)acRfVw49MBB`1t|=**;2ldbH^44 z3?tWw2`o7w>!J~@T!GCDO!44I{@A=pgo%?tNY;_ z<)+w(OGsFCa7qJ7wPvJBCvjqF5XoZemruL-#fuRz#O2&j5$sB*!NVufJR7g@W8X)GYVykwE}L@4ig0I19g?Po?OrmSubv&j zc6tDstJtl9#@D6zP*>$*51u(&Ci((L$n?$$}uR##WLmuWJ@4a5uQ)Ho{7X zu^{uygqy`=EnH!1YtiV8sv?N&?34v3g&fqyoLv$G{w$>mV%&G4oJ85PBwnC}HCYHD znWK3pOW=5TY*p}6*!_6#;j#a*wHI~@)Zcj=LGu)ORUkE};y&1Yu7g4im9fa=Pj5I2 zOdjyhSa8Gqv<0^B<02u_NavB0QhZdW%7CB2GPRfBF2Ag^?QsvE1|B2wvKaLdo z1+rlY0I=|Lhhxlh=xl)yGtto$#xY~p$ryn+ydwpC%yT9^#1L$G6-qHygl{oM4Nb9) zQwCp^BMso0W8Q4t9 z9ZwDHBKETQ-|@fv8K3m5BmuC`tp@;K69hj#r^EiuM3wLT1z)iRN|+}28tN>MkNWfc zpMV%=i4$LvB|{yba{+*zD5NhIK8&qSkhn<_Rl`(0sY~U&Z5{<*H!$Eg2bZ6p6ayd< zWwv|xKthjHLSqyBcsinE&$Wpsoi*>h)G3X&Hq7Ne5T_12GST6({3z3G#F&88vEYjg zp(w|d)-afFJ}wu=G@e_W_sOR-fo`^$Dvt|vx|BxNN&<3hbbh=<2qC4!>EdI0K68d%jAAhQMx@d^x%R%7UPSrZAOy z!xM`>kypU#PXgI!2&kRla0Okp;zG4_XX6T%xRJxM2vQ*rcd*;Th>jp<@z1jbS-(@ku#tHL4!@$Jbl|hUKa_XOc3lk4q!yThqF|S3{(OE|7Yoqxi=pmkGTN;WL2vBDT<((oXb4`pKP{<^JS5y8-IGlgbEZ~OR|v%SEC&t z83CeIL6go)f_Fz-I|YHT0j>;CP2A(tP<`IZI-MPR4OrF0=`J>bImK?0>PcglbCy{C zBuo7%jpsy{E@d`S#xjif0s>qr+P7b|jH$b@l(oBQ#71ZtD`I9MoE73V)pF4yC1AAV zxPTJdM!_bJ0Tuw&J}wz^{uq%jE4PUUmfA}NtVLHYpOX%*unbOt2zlYI#}G@6;`-xm zx&UMPQXbuIo=*WTddY-4Ak%KAh-E-m)nHj^wRt$CQ>QCMqR0QvRrQ5=h|_ZNXs`832n@o*F4~nr4#CuVyK7{G9@Gu_@>vO$F)L9=yN5i@3*< zL}j8v^FT4g37?+X6BE_M98RK-4n2K6l9E!hhvBB;)+~@HoL+y7&2234V0mnDGdVZo zy#6=3p1=>+J7&4@2GAOd*7N6h2q2=5NCm)-^4_hzFlhv#u$$?yA%i_ z8*BglhPXBJ_29a6s`hzvcgLaEhxb3eWi@zl;N|OmcsVk;zP{sUtL5kC>CNV~?LD{q z4nvq2=;8D5xPJ70;YNR3YVpW_cK5tFf26nFl1ab5JlH!IbMMkBy%hKE-uk@f`hGI8 zI&om~{IoaN+roZG<+I++XPtYW`8s(1c<^F!-{H5(b2{%$NZ7NH)mzi65+1os8Gqa;m7t%#_%Liv~{4d8%QT1wLuF-rWcyEJ#p%hU7P`D(5^%~L!LejqVLuSelGZ&vh58rgh`pA`os zi?_T)ax|(5tnDK_&5QJUuCF7i%@}ZG1>#Y}IzNR_l5?FJ;~OwEp{>u9BVd~%dPHJ? zG#6t84?k!t4dZjMQZ*K7;)8CWp@NqW6EW#2;#Vm@uktSW9mWJ9#AwRxFBSFV`Sp8) z4MO1|zm(4LiPwG`r!rHi4sGbb~E4HipTC{zsi-NN3m+n_duBoJ4-gQ3;46+O5c4vopQynN1E zWGAA};q`jEJh~3+<@LMy^*E^4(B4cfZ3{!LtbD#0r}zH)in*oN`*wMJ#eTH#>1tr8 z_vLXSwfFss>$Yxp1D(Fd=lAcUB+PA}+e7&{{IzvF{IpBri2BQ#9it11D1!i`w+H`R zIYTUBL&-d#Gq=jUi7{9$EsDzy*9zN?4U)z%R2NULmBVJ-gZ77!$uET~;OC5D+|k^K z_Nr4R>h2u!CFf(08$+8SzeZWzFv(Mbp0BZszU(Tk#?*;kLyJa;C}-&(m2a;Q563QU z5)U<;5MVn{tcZBJg)a*ZStJs3)`3%Dt`RdnnCy@MYg()u-PTVSjx}A730EanFv7V; zkr|sO-La?0BXy|5C`(HE-4dd;$?h?|jd!1f0!vHNm}>clh1!g3oYZS~ZYtvqHf*;G zqgKQmC6RQv+)#}u8b**hQ6tUdzLQ1BoSN;T2%&>cUpe>Rr*#W2Fh78i<8Jz7>Q8Li z$w9UfwPz&%1}OGUwhyMJGB)1t1AMc*s3TL28)5bb_D2H5jS#eo_}mFFMY+MQ@i5{@C#BKF)Tzs+A?d1D`w;Za3$R z*kv0+m*x0f#WOn=N%jXT_&(uK)I8-f)Gvm>&R)rr7@&q(swc*|yN>;YpIzgKzTOt^ zSq~((w&uYW>^CK>aZWq)BkRE>G>t+ix3 z+rQR4=t|+NHPN=yBP>c8o4_oy{!JHm{y2oI#fK(z)T6D?_1Sq@ybpKE9T3!kU+k4w ziSdIbuvQ~zY(SQCj)}nRw-ZU>D{v~0Rb>frvV2rUK7`!}oJ-78y;bQ7cC+JxCGLKO z>hd z*hv|v0P;H~a7TIekIOL=%9YZ>XS6&v2@C^M6b8Wtkn{r@&Kb1#jW;gLZsnr#>4xpN zjySi&VVBKPB%d51WG7_=(JtEdmn(=jDxznq_T}@85cKx2hhi1%bi(C!)=bDeI(D|n z8zRAGi1Y8C=&YbU3)Y+UUz6UC9w zkq=ei&ZK^wIYNr+c|xR@Ag++y8>^8Hu-ko?oaohqTNiBo;0j+Xwn7iNn5SYMd}f)C zAG+wsPm}7IJr2R4Zj7Efc+=qv_=4q}ptE*z)g1Ng?jh}DFtkfDk9jRBtF5<@`Z<&S zoEKd3(6^e&TrD$OGhf378BVZvo|2`rSuX#}$&y!)F1s}g46^ryL{N}xGw#Auj8OH) z8AhO{4tu?a#Ik1Sz2ZC_W$@*O{Dcm4NsfMhb_9QmO0acd0! z$5U{BH{s|^S>-*651|*h_3!;B6IsJ8FE-$5zo60P!#2}aa#nfawfw=?e@hiu94yIqrbV7+f93b}d+=Dj zV72jGIp{oA*tp@3EP0#4<;fNi8>wAFjYmmM!o?0Scf9{H7HK=qZ>NJUJp9bfZyb5tM5cNli!!t^;-#TOH=Txu6o8ug(qT&9wiPG4rVapwXBx! z=kv(T^Af+-g2h)t1hqjb}nU`3pJlw zTj>YSM7}V-6HJ$Hw9wyJ@?p`Nty=Tt+%pf6X1ex*Dy=lhoVZKaA?PX3Q|& z=_`2!S_uvEW5_-$K)>lBu_x^OszhMV?2CB$L8*)#(lbu!{OEhB&Slkuexn+TUrUGf z6!n=g72>E8k3$CuW7V8hqb=1QYuSJKiqGzP{iMV5LeXyaqh+keuZ7b{X!xj>G$gtc zdtJ#6Ew&wh`5jvu``NXVg2d3k!z3gi2S~X9C`z;PqoyJ(!D>-Ck9}awUE*SX? zxl*sWy(4`WT?VIW{xDM8Co-)xv63sd`p|REvUIl+R4a3zeAC%W_l)h@sjQHZ0*Cu3LKQ&9(FS;83ITrsR17-eqDT}7vLQ$y3Z;;{ zaMKs6e_Y2mkO!-v8u|yP0l>gT^S)i9;C&7y1110|)6An?ziVLEFYlxjTxqb@?S3Gp zz*v1|D|CZB40Nb6LxUSL505}KB${oUrRJKZkOG*Yp@ke(7~wZb9qvwwP3%Q;V(;#* zrcoj^u}!#V7iVN|)on(Zu4Th>V@|Pjp>sA~Y4I`h(nn;(9C=bXGNkqp>gjCZG0i_D zQbt?&{Q1kl7p(c$bQ`~jOF3y+qE0LUgJ|%!x<)oJa3y0i<{otbBVJ`s0wjHg2m`K+ z%62q8h60um7~Mi+0+5jk{?BFF%J-QZ?UjBkc!>n3S-L;%0rk!PA{?2?P{Ww;l+pD7 zOjlDQ8Y0o})_+^Ud{58yqoImM`?YEE7>2=(o|ouRb(gB=zG&S}Oftuh*VKoIv`t%EE7_Mmh>W5d%z^H^|#=+6H z!SSr3eK6ulhKh2aD0-kcmawe;&rKe8VK9`Sj>kuSyMO2S+wosxs>4g58Y*F=Jz)G$ zH>!dv`m<;mZ+QDE#-%?eIQ&VYXagGPIes<#U$_rQo3F~~dI8>~iOz0WMS z1B<=2{?pYgsiAM-pG!La=XAG!mhhj`J^gb!pa0)<|Fs(BzYA^lM<=E*2}lRc^HJ=B zwR{91DB>+|thH}aUEo6qBWN{Ne;fZ7h)EEL8|=^C_NM>h_TZmDuKx+-@gG1Y|G93{ zf6~hQPay688xV$-);=CKj++=VxIZDV1nvZ~Jz4#;(C;ABe~ej&H3cG3K;&%vlbrK^ zV!Hn)rsscR`X@OD|HK3jn&)#g*-Fk|$H3rjgTotvNum#{3q)2yVf?XdtOs$EB|8rO z$J8qNUp$8V1ChW3eB+A&6o1R!e>tD#pCtSp=09WW-$#MQ$?~)5NyRBDBCES0@2fA6 zaKygeBD0dY$yM=zp^1SY6O2>@ESX2v`kS1ZxNpTnQq5dk6~a`_qHhBxR>ma)4q{J8 zSnKH0%p>VRCdNeqcJRfSRr{2mr;4}V@lF=QmN2B)H6|31zS+H-oEl}pJGUrwjA_O? zQu^N08{v{d;b>8A8qY)7ZI*N*cynnm&1-TiX3H|0I{*w2=`3w=%(Yc>vS zX4k)lE!R1uws@aKJ*BhTus0dJob_?%5c6K3mu5E`FdrAc+&%5@sV^8%u4VZ6;v*AT z+kLTdzkiW^FWs(KHk5zKeY=Y2u+(B3(Rp*2M}8jlP{BJ&HcYg?fdgf?+4p=(36}0Ue=*;L~1PBf+{{H zFaYS8xId3}x*8fNINDj77&+4!TG*cYz`JXUe>J@oRyr|hwH`^a;p|D#Ybd0&c_im< znNNI)PvTHCBv%ZFanrKY8ffSsZMO0FkoPf4@bNP6gOHeUBT*Ot36f7x2nB)0F%ci# zHAS@n0ch%~U@A0x4S+OKKT9mTe?M9JTCsjV-oU(L)Du@(tFm~Q#pUw7Tscmw^>}ON zU4y~%?sJqbro}SS4l3H1bwYXnrbK7ewlDF+dJsc`3h98i}$* zYAvE3sSsO-kdI}+IRF>@6u*^);{&`63HJ z444M{0JTtBNH)Uj5DmBokb*N&0)bzFs=!4cH>xY~sn|-8LQoy%j%lzKimm8Mus!a; z8L%1XMzj%8N0^VaLl@kMdM;Qd-i8^_uo?CM7|12+!rUPe0uo2t1BnAK`GReO2Z7=! z87LX3u_VqUO+gX;MdMbg7FA zzZzYa^?7I}@Si_1FIgPx@0+S$5UA%Prtp!S2}VtyYLaaxfIV8^vef_`DVX6jnkl2jlzuu1JfZNzY*^kYmD{FZ#^ z3cj^G;`_E8R)3onTzYx`T`&_%l|{E{(D&>2AfCOWoA~rXOQ=qs(bJK(Q|9?}Q3kzk zsnn=v5uxJRZ&(~0l8qYC6*oMwN9Zl{hm|7_z$GT1%7Y5Gxsajx%s$mp!qU@3T= zuV^FObu>I1OIG*8(dZ9lgbbp2N-K7YsgAqHnOfIO7QUqkV5iYa7x0MS>vGKa@fVMD z)iq98(_^l@UtjX76mg$~W*)j~9_i4LZd&1j-68U9n0|00fP%?@z2{*YNFJ=^KKJnZ_CfUt4%0$gH{S}?6<5*2YyW0 z&u_JNr=1R4XUO>OSE=dik#FP3SSNu`^O)+;^r=x>eJU`9dY&ye1yfhe|3#Dz2Cdj&%>;4?DlBMI&}>Bz8fDiekN}*7>2eQFkb&Xv?Bdzy4F z#{{(Dxk<;3-5EMBj{oKoSS~a+Gj?8-bjQ1-aANqnLf#fum?dq0Q8GeP3gHOpk1I|o zxR#h&kG}y0$IWqpToW9J<~MK+XPASukS)HlUJ5O|`Yoc+5Rat@*@JgxCa$w?WW$=p z)$^_}*CCM}8t?JflU9@O3VChbwD9x)2XjD-zqlOasmE~BJrSL>U7gn@4E$xO`U-V@ z$|G%!fAlF<3i*b*dSrc_0GvK!!Rg~p@?--u9iI9AhJ*#Z)^i#29Dc*C;&m z*Q@DiOyOB~;nSOBevVMNDnI!qkY;?jevC9B)i&zESN=xatoTx{T-DbS_mJWn>Kc)H zvH!6Alz9EJ+6CJu`!LEI>N4%Y{)qmWP#KB(w9-zPogvdYoz(*gYD^!r1=pYil@rX*L(C=cBx=zUz) zS0jGY9>jCvr0aQ3)xY6>)N{u3Wlt4y&_1bGSb%yPfEQ};R@@L1UIDmIgKxz;C*k9O zk7JU-dZ(Q*layuDsIpkzg|zWJsNp#8p3YX?s0Y@WU)h!SXHN92yP4R%33Q`yJj(9} z{|lP@>~9D!0er*;UJv+?2B-f>e*}EM22Od-X>iMr2=CS4R=w22uO9=Pa?qYJ1mft= z*DCFt`nhq)(8r~7MaTBj4;cHaF~kXl(c9&KMn7HuN3IsWgiG_dWPgbKEDeMOJ@KTkw+Y9lFa{i!~*E5a&w5^j4ew0HTZGE+R zTKT%6OQ9-@dJx`=-?IG!w^P%{(z68c4Jq)CR$2bD!iK&c^z&@sF~GexaLRBotzRH) zt(u1^lgq^r!g;TeAxF!p8>)cPg?LLZra32KoDS#jArSUj%vq-b#JHNgbrjR)@?k9J zYTu1{zrHv7(skJiJXCVot?6z~TUb40mWs8b6-XH@7U{K!YE+ubQca`+ob6JsGAkx;l;t0~koq1Rx%NFv!IYHZF3}BUG z9kLj;HBNxix7J*zrG~P;Nizq}Ksbgo?Z>Ju=c!10GI1BkF`VgHzywt~2kBaK-Bmh* z^clI%>=q%#hijxwNLzBoO!}#EA&(`4xi0D#gXS6|p772S!$@0shtxdR_eycV?ZR{5S~tsICn_5ojc9AcICQ+ZC4PN#D-!W|w6$b^_(a6mZmeIU z7&cf8;kAd{uZkAevzYF@DjMC-qFui#4!aHTTqfS@t_p_S9&uAP`LCMf-*CJ=9zCYG zUm|7WU)+@Ia!qk|r*K>;LTbq_rJnWFnqPc^C@ilv-NMw-ny*&J_zCEFr2 z4K33CbCeX*G@sJgl&?(Gx$!_e!TKo|hPv({pE6Z`dl}4KQgy$c z2Y=VDvfbf^$lgX&!@58K{=#SdDfsGeEZ!V#1wrv$u~?+ds1CPBiba##_mR&Ok={Q^ zzpJ&;pe$P?pF)RnwyaxunR0H8o}4Tv+|U*+ko1?9L;vKRzP&zMHk$Yqj{4NLMJE-? z75rtdkI9GrKzyF$XX$E=B=o~;W&O5Cz%m?Q*SJ&0bs5?<`WU16L1z<~pA zvORM}fxSr$V)9!cs>5htX5-!67ClBUWBU`|3Q&$YQVv=7`%djYeuyZ-ubtGlOshNA znn99c?_WQ&j&xIYDTB+her-It`1@nO{&d-2kbYOH9dB=~Y_J?-b+NI^sB3C&zbD#u z^iVWjO@hQ2QVj$*-QxiQF$wYrldAQPa6to=h8S^4s-o1i$o;cZFMg`hS)|DFN zYs&5pw;Yd{D~04|$o}1fXP3`H5<{Hvl#0K@nBZ4@f|%10L)|FPN{L-2J_-#uGMG6% zGN*|v0q(ivbI&~|XNkJ?#A~;k9UsfvzeUPJ4^hrBXFie_2_hrwk;#u$( z7!~^wv;J6G0eCUZ*dvxzu0!fo@l{Y{io0Tsm@&L6z6sk^p?<#L;ke8yZ$#j%(yC9+ zqIt324V1@n`9zr-Rs~;>W35$R=P)^v^{Hakr`c+(h$%0hUQ?a{#4kXr zec|>vhNrg3;YeGgwIO1}%*rrejIdF1jP1A7h+_aQHCj<=rA8U9HO6#aay9n%io2U5 zCoi@B&A7p!yqvKz#v0QHUJ#Y>cv~}O86slbU3C!MGZN_X(P;~-*Y+PR&dS)GAD%llB-&gNrAT~~8F zai+4}*q`{{d96o0D_wc$`3y9oy_o7AiL^DG&h5;`yvUPz+Pzy~MBf!|XflE)+l{@g z#@+|YIy*PCH$@`x_Lp*kx3?O*+S)|koxVu>@!*GV!&LcSsxzyXZ5IV)o$a2CYL|aC zx}Wnq(D?|f_nCYCNadcrhr4drw!^r&xN>Ll;L=FzOZmn}jTdW9&vKPrXRLSmKU~>y ze%rm->(`W>=v?^HV9xHP<)y~Du8dF45*yZ($v*wGchk;gcScXPmw1ObP1?3xe&E36T{Eym%>M(LQcs{--W1cxiV`9({mIn*Vg}-+! z=qkfrPb2t4SKTe=h5Hkc&V6MygV}Atmgd%@?PZBRLsYglG)3FCZeRA&X3Q2#D?is3 zNwl|b-CkT=>UyGj(EEmqlZ2<&uj(?2pK=wWBN_BQRM)qoF1oY1z3=dgr;IGR^M%>} zZP|!n>^gMnXLF3DbC=Hj{9yjlxfyfsiuJ9E+%dPkX`X1=d-xw+H@MF3*nYxjxM{@^ z<9yrGnWyWbqUHFp*7lO(DzSBE@tU&A@6hd~rv^*29(v|fb#(A} zM`rx|2RlzzM~@u~2U;7e!f%{>{ZIFu$_jPP_Abl9BtAc9;qt=OI}`Qd8@SWSGa3>( zK@V@5gmu>>tlOpz+&7JW?Bxeef5MZsy8_d+aE&YfaL)e7wz#ooo{{}}?(1IT*h{(8HUf&hM;W4Sv7M8b6ex#B^mDb){P#NQ~SBgynXJf8fB5Cuu2sWDoFhK7?AXn-OYWK-nN{w6`E0}LAxt7VaW^>naP_f0-|JuYg~a`s ztcBZ(Lvp_0>U=2@EpID1P_}#Y!c(Igx14TRzS+MNLb=wcljkb8^ z-2C3Twb9t|Sm{3-yXLNc3WevtwqR4nzvkyuxxPKC`t^qkN{zfZ({W$7{Sg zYoRx5aq=&-m*#(LZF%|9{H1G;oL!P%k9pwI{K{P8|E$@PtlfOeTwlK1J3GJsJq39; zEJ}KBtGsD;{_Old-wm5@*nEHVSIakJzIM|hcmBfso~3J!eE!eQW)!&$++o`-9{)NWKoqk)!4 zxOKSrk>-YINKVkkKH~T6zvYc{dtBQuddq72zS;U(eCOE>r4d(kReaSGiPh$OZuscu z>oPj`1Rmep+LuvsIO9U8QT*JkYo7m1xVJ60xNO}6JB$sRKU*FC;6u3yU-64QbI*Eq z&5nIKp8U$~7k%Zo{mzxR=i|5k!-EBhhS%pE7r4QA>5l06Xvxuo`#YbnDn7C^y7u_! zUsVsDJo?!D{Y{U&{(;*My)W_kQsbu!zVq6GH*yEBIl17!EPgq+bmt;h$A{H0{Kx1D zEzovXLgip=rMb3dJ-;?%kGW3{aISfB_wJa=XX3r$e)SCEELM?t1yk^Du|~n~#kwi4 zQz|~I;`1tgUB%g0r{%TIZ^jR+-(eMhMa8eE_;nS#xHXyiRD7?BA5rm-bIP>tAB2B8uMoCZ3!*l>Kx*17KHnOY&m;O-RG_6{6kM?h*_?ClrDd+;sq-Hk@Z{Y zSPP8}Q|&pw;Rac(jjeEr{1VtfWcll`p$CUeUi$z^cpqS3m4Wkj>}+1d|H=s^L{D=S zxR80>BI}|nb|NX3dPB(!uUL^G^7Gu8;*L=Az5_{bV{)RcJ~_uDGH$`db`E|gD{GQj zBDAg}xv?fG@>@ERi+$pE+lzkS78{SANM7_jv$G<(vZN3X-d6&-dwgOgIh$e+qdZ?I zIq|fq(xOmO+*eXTQe<$6mE^yp2G71wyOjeIIbx+^2`(ig6CO}p!t1Ht36op0I!A!x zrc68+AD962#uRzHwwVW1=C?mR(aV+q1{(p}40#q#mJWrc%I$TTIy?pSapAdLb)L*- znd2JqyFgLEEw*nRpGe^Wp7t7;oS?I*0S)^J6lhj>zF09!oUfPL34e{2G&waa@Z7Gp>H?p~nBQTHpoXC^YttR=|mJ2MfkN#2iYc>CYxiG|%0 zzV3;MFU%KTQ+{J^YNgZBi^{!t#M?8m#)~JQ-4l}rz4p6YGI(c_&3#uSgIKw&Y}Ye% zWw%(jwIaDdukQ=W@t!j4+a*}vTo6(9ol8$|ua~l1Er zF@bu!y$H~=1ZTJ8iMPf(k|J1AmxOEkI+Ec=xvBE4;i{RJ`2m3EL}jqRs0*O{5KmLlSQ9F zzjHC!<;Oafw3!2Ysh+tM z5QhyrU@P6ewCOwqFTzFGjB6Yv%;Q(OxAj>srg30*W6{1oRnVT!`(jJCd0)VJf5#baDINfVUv_YI# zMBFy&6QMMsvyz=ld_%>Iu3gfpel4#M=>Kq`UXVQxd;W|RX6k17lFtx%)nWzgC=Li6L)kS3kW#q9y<56>U> z24yvKkvF6$(SgbwHHgz6`jQjjkMo&`!kY`o=3=t`(7`Y9*5Y!xn-vlgeL zNb@)0J(K*cha-m!K3o~cFp&Dtr1c2tV=5m9Zpjy#kv_3-CX457M8;uKT=^ZS0#o|Mlc@ z^ZXYd2L2T3pCfz?JrxHVI`RJbhnr~m%b)*hzV*?8TYfZl>(@t?ZaI*5;sb`V%9jXwDVu-+0u)i`T4J@ z8Hl-)&MNTt7uP~fx7a_;`7f-&_^q>Enn99d|wck5Vo(&2_GhyI|$ zOSJM+_CwFW-gb83ndo$VQuZlhvP67H(>FyPg%3L5x!08W4F`Ne;b~8uFZXFOpWh*0 zpUS7>?erV6;iK2Dk>&^7$I5b9SGqjI3LZ<7vjk%p>FXWjtWkI;zvVt|=GXl;-H#|s z1->Z_?z~@fQ@n#sQC)7EJ30JRd|ooci5D#UCUo z`eUU=<_kFZ1?~tXp1ZQ_?AxdCV-9{ZsPMy1{;%-;PX3Q&2kO=1fajiX;#tpj@^SYJ z@wER?B{-GJo}I^eDIT73?9p08wnZtb@#zenNeH@5mKJpImAe}$*t+Tq8n^GFVO zKh_^9->5@91{9v{&W^uzUW@J4w!c*Q+3s!iS9tatw)!i4&;dWJ@P-3^%*yZ3&Uijb z)~^FTpzwVT_*#W04_o~eKIDM!QTU((zF*-D2mH9g1D(>Yc*c%0@v`J%`{O%_?Bp!O zb9mB^AfFw+LgAg{8A9RqjL*e@z@VyFO;(#AecvQ2kd?N}^9qr^FS9rgc-=a&8 z_5LY<{Rh;GmmOceQjK`(rRQr>a3_7b72Zi7zrxG0Lp#4csNewyc}5l9PS2Eiij7{n zpYUvB%6vl6b6&%X6ZjN<_-)xKW!|IU{xte-1s?}rJG-7!c-?+Z^`*;w4*c};`xT#U ze?O~2hYu)tdc7G1rXE*PZpyq&@lCI{62+JGrqc%%9N-jqg@O+v*69Z)?L0MJ>iUnQ z=|86U6oP-c{rFxM%4t4@OI_Q9^f8Qj+gvCz(Zx}0|T=s&uC z9V#D+xuhO+x;`8Jc6Jz2@~7Wt^uqp>k84{_?IqM@zK?|OsmTSMWdmJ~Q6)z@|N43M z?YvKw>(u{tsC<{x|H2IIG^F^wiPJiE_PL<&SKE#gDnIqHt6#WEO8t&$`ssG$t94l) z)LVxql%BM`4j-Fo{VTrAZ>zt;SD;`!yl=iKSD^#GPt%_^7(>6})IR>b;JO{oDfuqf z&g}eYy7By+X5W-{r|gyP@8il2>2RN-x9hL$?A@{8(tg8s+|&FzWn5AANS9+ovr~%w zH2G56wW1HE`CW~YkM^{yPdhtP_d>{P-#*gT*&O6gsB$?z&7_^xYj)K23V=ZZJe=h0 zLq45;MA5TT~K31i_@eLIa0cJF7hlV6v+MC0q!zq4L?AZJRwD}B?)#|j5{ zuPP_KpX*a_%Bl0O$3D(<`|J2Yl~14VQkDw9Q~X@ffxR4Ko4|J__8w8?U+K8)U+nBR zx?B~C&aS=dbhXMqDZdWyQ1a2wb$GwxYw0_XW{+Xb9(Mi3@HFyM)*9g7jsCd?PZ3$) zS8DHs%4g^2)AidyP0!epshx)g(kbm)t0x_fBOy}W8pv#C$3pO-KKgt?&tItcru%O< z>XkMKBEPgZVkdk1u|dqT8ELJn39{+G->o#u=hI(ZuO6))Yk{YK>hG&z`zPG;&yKYG z70PbJC%C&3aSRGr?*$6rw_aYiR{wp9p4#)7Fn?R+46}}xn$KVC-k-yA^!Mj5pL`Du zV!QXykY0Ze4fUd(D^O+y${}8VA5D6{*Q?}aIrWM@{XI40H$WW>^W8ANk2<~DGWsDW z^A15={k=MbS$5Rlt7DZzJLzRw>j8;0_&d?P5B6i7kO$i%FP7Ukf3-Y-KsbNL5nFyn z8cC0Q=3YzIpn0)9TJl(Q{H;H`)#1dA0Wdhp0lp=?@>k5=DBq=bseV4}bf|~dUg`UN zc3j1a)AO&{9+C&b9^K(u(IBh?`X8#?wLkb&>k*u1&6u;jtGcW<_&{sCOum4ny<|hF z>q8mUQ67g}<1afAZaL2*VIg_OKhxj2d2j7uoM27NFWH?@>NScx540DrG3r81k=Fd2 zgwJ!dEWvZII34zK#)F2DQ6`G3J{k1xJzCa_7r~rg@$&NgobPAdk~mqFVN|#6J^bHY z`^E7?`B-=VjT=GmZ3VV)Vv z6y}+s%uCJ?ur>0+!n_O|QLT$G>k25mb@qV2d6AxUGtFmi%DjAec%X@ULu)U+8DsMW zH?n?E6H6v9w|{=h+9VGma2LM0e;(B%xS_q#ow|SitmN#tp}kb?tmkXrJs~ePR~adt znw6bgruM;aP+RT!*#s8aqIF)RyiB2{-mRBdV5a8}bItc}fsMHlnCnuk{(SKY){A)k zO2rFddtTOgJ^J=ph2tBzwgK0yXzrCBxU=PTN|pAo z`mMJ*k8lTE2_8^5hfgx`q`_wqXCZz?=E_m00CU7EbR|55ymZ{S8puBEyFY*XK1y7DK#KQzrl?8RNqDWH;UuZ_Uo=ve?RhJQDnyY-v`?N2T$jv<`}cow_P^K zZ@b{TE?4N6NTUGiEq-#~0`%h>w zUOz;-UZsD8^Z}LrN2Eh4{S%}cRr+s`Zc^!gK)MBKEaPAG#X}IU!AvZC>lzx+ZA-qcux_ww`*i!}2FRGN8X zrtW%P$Z)5KnKU@Hw@mb#a>|*G#h-k*iz-boDUW)Kq|Os1xkXUf8M(~%XW49G3)#t!p~aTZK^#N2d?|}4_>W3w)bc6 z`Or^peQx+8cb6s$BMsflfA`}4A69p%2h&8mO?f*~DmR|JC_y6nPf0>#N*^Q|FcWM6H2Q{((zt$W@QojGffO!%C=WhMhi2?UE&Js}wa;%t4l}x5eCsTpR)P~8FDS>qW z17B!ze-4h~7EfSS{s7+Gw-K|_E%=hlPNaCQpaStmyanufz^egestVr*+o`Zsz;41D z`l@jrWH;X6S%F_0@g0~4@T&^&^?(qz9{D!ne>Gs_wGJFMA+-g6mH0XtOIi;Isdpo6 zkfrRN{H6?d^SE5StIRVB0cg+q<66MwzA5AGG~bm?%{k_fp80uMe!=wATB9Mqp+cOW z^xC&#dbM1a<}Xy=idj4Pt(YN2PYImpSq{6<+=3_ETq}0^W(DaUOq%mWj0XM@oEPM} z#-H%rWoUcT?dfOJ%Plx3<>Fj4T^8#%=c9aI21lE}b%7Fpe>ZtntP}lo^7xTe=Qjqu zBtP_7DQm?F&U&)S$t`%^J%Zm(->@<3Yw8<6KXBn?6kHMFH)Fu>P~yw(L;>$Fr@jpX zZb7P}`=u_24{}jH;w?FJyme;4316e~bCzS5ze(ZQuI%uffv|kt4tn_sRUdl!c6^2} zqlX`(sH}&y`Z;Hl&*C5I`iYx+uemmCecOb#)aARN>XGuLJabWeNuTaV)ACbW&`%2`~5uLwZ@kzIjbw@Hqd+vK3@ch<`)ZYPbecOR=6t@@?;r$N& z#c$4#zZ1U02A^9n;qJ#g&3xxc>R3%EeZpGTD-;t;cz!bze;zeY_5b-M8=fjzpUuYmug%wLp0je(yI=QnYvKfiBdTmK3_qTw$$ z9_sw9J(ZLxW!{G6dX~$xAfc`ma?dHhfuw(vEYwx(>46>cE`hHyzwMR=x0Am@m1CeR z+x|!G0cX9Xw0m?Ml#gdXba=PoH{{@t=M5kHT}ju;jqr=E-jcT7Eq4pW6GZ^B=Y;-*)^{ zbRDpZZU2Won3S{L0bil;6%P1%g)el#cPRWg{L;4nSNIVJ{D8s_IN-TAo$~XWLALrU ze1`*`dsU_W4tVZ$C7%6&9shcT=QoUO`#*&rM?Yw*zrq_1eo}}&o#pc?EhzP{pT|fL z;%}>8S7`Zr1>Oz7b1&S3{jw`l-Y@=Ge2bqZPwDsdZ#%N=dX!_^|EcmTH2Lw40uRTQ z>Gt?Pk3Igf-H5L(8OCd~Zoyx^8o!DYH%mX~N?Xr-O3zn0On={MsQW^WUv_ey!(1$( z43RQ!Dfu}L+xq_;RsRn73Wcw5z}G8$q0{(h;USwLKTo+8LdM z;=}$TWjs;v3u*8%1s~PQO}T%YYwA7dfDb5qp98*D;VG+~U1JI#a=`Z}e9!^kukeNg z-rB1VG{*l;o?g)F_9I@`7jLJM{;>tChj#KB3eTs3cKD#e2ORJrh4(w)t-b$axF50O zZ|#jAcEArR{(Rb!?mxPo))@t-db9SJ#~kch^8eZU7WgQObMM(pLb4=b6A=QUyb#22 zNxHkakhDc}7ZgY&QD}?Jbs-DMZrI&mtheQ&f>K0O#8azatJT&E^rG}sD;%|luhp;g zXcg@_6>s06=k$V}(^~7PIsgB>GrMm#8}2>r?{~fp!^}IEXP$ZHnP+a#%&2xWQoYSn z;Um>sw+eTU0Dq^YowD9o&&hm$tn$SmFJi28RqupnP0xG}aLOJ_XxRnkgwSVnTdN7@}JjvtU zjS4R1fTUki{8afd-ht!59Z+yVqM_?Z@Q6ujoB0_SPh0UTL|9ht-`gpd(}bgqQB(Cx zpP)bBSe#+|Y-xD;>uS*^?u6LKZU2_Hj`L-IV8&aAzK-#;a$OG2d5(2Q(6yGg&|id7 z4DZCjnKC(vXZe%yCfsAm;|!ELxgH27*`KlTYKhOal_C9+ zq6=vz+d&c>o;^$DPyS2gPyCibznblYKV47MeNR=dER)qg8NVZnuEg)O>+Lk>?|<$5 zN$*0?HkppQC!tP3>uHvHW<4C0tH-(?QrDNRpW*e8y57zBiyUo`+!_z&FcP1f8hps( zVSK^pD16Y;#cw;{dtxlS-#E#{Yb~LL)x237t%aczRCU`1wYxJ z@?Q2MNpQ?IGt&C!JX!9oz>7b&iH+#TX*a(mb zlQAIa`M+JR>_t8C$9midcQRievV=dJ6fXN1FbL~=BB-zDx0+uLB*kYw17q;RKf(GXUotN1mi}~?!;k%<><^RD)4y*Db2szf3cQRB%Ot<_ z0p4K(ivMErMqjIsPPY_riT^%jAAo%(`g_tMneL|x_hD7uRP^BZJe5D?wYxym)1(07 zZ3QeVehm5F-fm92en95|#gF8vGyQuQ@Y&B;;gZkj&vrXfJ2`@I59?R;0~|k+FX&Hr zUHI1HPule7kNF%xKR|mY=5K$AGMy=y)I0f0;`i(jK8AFZ|21M;4wi#uJU8s`A|B^sgr)h@jFgt$aB`#p8aRC1D6yVIe%tP*2tU%+4XU4%TuE%jgx!P^Dl4}Am^d-Ij` zT(On@$jdEqMO)mRC1T3We5s`|B;QyjXcLD6oimr{GvUj!OC027?^*E|;H~keQh(Tl z51PBp7fKO79c$FPSr&QOyGu}8Xt@4!wxE`j)jxjeeUg7%_nVnRN7|sJ+!`O9wkpYA zrINb`)dC;w7 zYfKdS=?Kdn{DUd2=L`y?210ruF>#~c-k~UtsVLk ze$3)#ubSL0q&lB9KrrS=<$GHDw(H8fI zoM-dxPL)@GW6uh7TxF77KIUS`{HLk-`6|BtE>z#s zRrtjC_cSpZFS6e&n*02n+w-|gL%ejnnKE7%{8-<#`3u01FYuzGL#J==iv|o)j&Bd( zog-gjb3w{n0QgTKCTpKqRXF^e`kPo(I6^`FO)M)MB|?8QcgEM{Bp>Pgg3-d&U4ZHV zopgM17xifMH>p(NSha8{d}Gm2_^emXkj)p?*=w_&4#6hYHC1&SBgvm^2UyqE_-r3b zEjm1aB5SZ%T!NTxNtZ?P)%q(jV!c2taxgyqX_JmR{b9IsbvsC~KcFpgRN^i9)d**c ze#%)Xf`I)b+RYav#4WNNXgI{T4fyJO_)UFfO?AFYpKw<=o^s0fF=RQLWcy_8WH}P? z=uX5l^EDwbq{p5sahVP56L7UJOKpf{j@yCxEZ8;`XQBl@-3j=QN}8D|9z_{Pi?^$o zjwjS?c%g2j@G9;n%+++!5+)UXh6U@9@XN8_M|bM-Gh}%A3r_$(5q>e`S)1--Uc~q@^dC+>mXV{o8uF4 zKxCd*i1M?2GY_^O$_IWk#x&VlMoT}^p1<8_pTw8uZ5b}d$4HI(QE-{pEiw+HoLqCJz>@tq+9Kl_ zvcDuByy%|_>@!`3f;pNKf7hg@_W7dO-&jG}@_S?jh<=QRbaQw6d<4&AU+9hJL|5N;m z9;E1dBR&4MFoX*&)N5$&FkXOjZN>v|Hv4AJHT7ksl{lkX<|(QuE5ni0(%QP}qM8zW zotRWpR_?3CSyXRDdD(T>;1H^>q@tp{1gB3+YJEkeUK~3us;sP@Q-t%TXhdrdwZx36A49B0eP z^@tX~`E;C7=f~4=+w*Gst0Gap9CvG7oe1=rqi7+A(egpK`N61vg+J`fcG)pKu5 zo1N|2;ELVaNs;#HrZ&b%F z-jaSrejpO4y`_4s(bC<QQOC^J^Q&&Rqc%U-QhKU zBg1!jO~~CJ>?*8r&M;>Em*I7l@6XvUHk==`ug|&hlI$6_Uwr}7l-sH{)V4pje!4Mr z_Na`{{J3ai#+6l{-<7?;YX0&$ojG3TSyN?tO)mi3dW2W{D|ho)S#ytlD> zo?~3~*Rod6^0p2>an+Ht_dDLaw6%9p@8HaTo%!s_i|<^uFF$L6cUIW~-w%wR?sq(E z@GmmsQw=Rw47R$zbH$;Ft6i<`sxcp*-|GH-=aQ0E_o_o_Z`oSiuPod1M7cjw_(5lA ziE()4;Cn0fWISuMy1%!))%`{HYS-jt?#t3Y<9=1Np*=Kg426x4qH~P}FLhkCtM8jx z|8c$%t!apEz%zVzu2`C8Y*?MqRPjjqXN|$pd85KbY1{nGqkfQeSz3wMm+e~QABaAZ zwda&m;nzx8ra7Msx9+E(2e4XlaX zQ@z93|K3(PnOwW(srx$H)25_@op*_6o#7w#uDGjvR1@A)y!ZC6x((bMcKaB-op^My zf4TA52dZ*5K3J9g-qf!bH|65eF~_~RtDpF(-M%Nodsp4Ii1EEoIcy)UalS2Hu&@5n zU9bC=+Lk+>bDi^H=4_*EM!|&Qv^Bm@rSp5lSn2sN_x8x4%&ma{uH3&+D$rZZs;#yyXL7@AT{E z8Xr!q&fj{`Ja>DfHgxy^<~bidY4go!*|TTvi_U*tbinzEZ}+U4*Q+neaa{2EJAb~! zK6qf+t=HXeUvchFLigM6wvX<(;ZD1)GWYTRyvmFnc^l95zVw9M|CX!l%DKi*>>b+% zH=cdS_R!hQd3#4~Iy=+x?5I!tpX>koQET$wOq=&e)IApp344U!*Xk|4A`lUQjvl`; z*Ju!Y;*vr@bQ$xPQyvJk!Fj|H!Uog!!bqQxjZ4bZ5?tbjJKRu(3;o4tr}pLyUvVwx zh}VXK;=atb+!pc2vuD@eVBh4vGUu@|1-UP0{@niBd=YGph6`~`oao0ZmaF>%o%U60 zS9xdP1~|`S8+y@@*B4)Q)0pCQ`M1Y zJ2$qcg=V+rjlQk=9)JGgypM0V?7q&D`|}>%m65(RZ_4CBAF+j^=0>ZZ1^KN2Y{aPPBKZ;f;Y0{e3oHcxSiNbzESv_Bl| zm@j^Tp28T->u*#1%<(AK*Xs~EA9}U?-NS(>Mx(3MH}CAx#vC!~k!&iP(B`Fpwn;(b ztv}kF2sF367byA8u3ZWm?+w!C_|T4pH^aCu)CAm_ixcjc}cr@2^ zP0c>~bA?X-_^p4*>CCm?lTT-^HJ*GrbM5`)(;ZR;;Ks+vl+Rq-H0g0N>C83clfP@z z+Kepv*g8-&Eg3^{@NmUJXzoW#kS$VgZ>$Omq)NwVv^*8M{(2wZ|k?(2=u~}YS9#}sRhns|K{KlHDs>_4QrWOms4=bCREc`yIY-*uM z%oRUSLxtaNWpn48-_%f{&9iy5J<8@@2EVzJPMaDuw40R8-FAL+FHQ4@{+5r)o1~Ec zTf-Sg|3>oH;cf+kWX*sCra!%`FsyFqt^gZ#_iC!ERxcWIm3s#+`+C!Y=1DtwZ)uLG zxiJg>AZP=c{TKF^Q#=UCujy>*S9K|>741_YxIauuE z%B$Z9`&ab$w1*A1W5g9T&7I*uUldd26>V{B zE;+0ViXUUC#XrNg%;wC?cGY2(IW(oHR=879<2CWAc<-d z&nTh_P1UvH+r`-~tiFwEN}ubT>A1XbD(G5e5_a93zNV>IXimGZIBoe{$HtzFG4odh zLt+2<_Q2dUEaI+hs0#^A#KV$`vAad>O;=8wds9QP>p?L$+qJl@zG42NIJA7%PB5u~T}30(-*N@y3tr z$;=-?GFK<~Z>@wcacIjDnAXRC3^03oo0uY{v`q~pdAik<@(z#ZYbJsxK292`K-T-O z%^vsUD1iI7vxTUGw##C0(1VK%_r zsAzLDY|8XaFx-W{6=pNcmtnTR+(sBMyrFa(>^orYgt-f5JIviMl>PU>?Y(7GCe4yA zic8}Tjk~)xPUG(G-ni=vyf`%4xVuB+?(Xhxjk~)Y_BZ#QJ^PzI>)!MGO#P^;T&v>A zh*go15mA*HulQr?2IoOfG-KXO551>>%aAu9@HnYe1c7hJW1I#A!5$F%X-u2`+qO@} zOwql(ptZPWNMntL9pE>*$rs(cus7)NFKHLOuYB-^0thcS$%H+;Wv}(icD-%jHwUs3~);83=z!fE6onbaMm z0tojMKGzBp*^Iqp89&Og14+=p=6_@D`+WXZ>fe4xKf8%DbU&W{as_;|FmHUpZPK+? z8@61S`bC)2d9#sm;eFfBoWy%)C}a>J5NG$C+2ut;+!tq9dV!WD?=Wz^_=W*yzIJ)a z@iX)#%fk<62zD`}(CX1^gaUK;B?));(>%S6@OQT+mThkm>Ec~o`j|YA$2`zrbr*e) z3h=sa*hPH=Z2x&1O=o819GeW4A8P|&MiBU!l?wi=P3@;~M)bI{A$OFFc{5zI{6b`k zCs+D!VmGR^d8$?+>C$bBa(45elDD66@2bN-Dxs2((z-i1+23BiJ9+u6fZNF`IMTA> zg`y2L03Ia3J6AIAOC?sR5B-U3r&x%FKX*=ZVEhEAv}1OQ^0@ z3^Y#4sHszS${iAQlRh`gLgiku^0b~*rtWSi?;bDj_{|Uzflv@XgMJtPlB#tRKbaVp(-EYs_WJ5TCw?yRj?%pUpxgjXx%0M1sx z9{rgU8J$$Rl2_6Q&srIZ8q^2L?pW(8U)5;AY5*6%VEsrYzY4i&H1z$?k<7UEO5IVF z6%)c6%A%wh58jj>`#(vg6YOmG0~aywy%a0R&q+z_7ZFreRXKD$2uljVO)1BY{n_-U z(H<2jru7K*PBcVTVPB7F%EG0(vF=0@_U7*lLc?lCMm%9M!W`|6To&hiA$eU0Dl1`J z^feV#j%oY*-{v=JT5`4K8j2d)8fJDzFIAI|mGE)18C=TZWr&5#q=oP-h9SnO$y1KA z8ScVVl1lBu%cSdJ3K&!m7LJurgiECRX|EyhuO1i^4F!J>3N&`LgYPd|{<4k&n#mfI zV{RFfJ(=?NUG8HM>NXZ#*9@FBLks-SR!%py7lrHerjSAyPDb%meug|)dz~M5;q>;h z_xs?K-_qEH9j@Iq8P+5OzOo|SMCpwQ%H19Gyc*M?Iu({{&s`aKhWEzCZAic)JG(lvp=HX0qx!FxfYW8x%TlF%(C7I{)2O<=t5{hvbjA4ri?wh&R`vmW;H$R3h-io7D7xcD4NUyXgtL z(>VxhhxqbMyl#PYX19@hTup1jdf_Rk=>s(&uf}*}Bj>by#7AS9$1tt4$yh^Lvj0xT z`0iqW(;WP{=m1G~(7kh>qLDbdx>xi>s8&g&w>G3SJ!zQgSSw)-S27W0Fw& z*sj0@(B{{T^V7p`fEpFAOmjJwmOfJY345SxWU6^Y2AgxHVV2owtD-C$-UN5Zq*h_f zTFIim!!D`CI;U(!gGNp=K!C#`S_yi&@>HKXjlEi{nbkfvxIT8&RPJ>n_G49~;JM*? zbcnQFWzDtJ2kauhAtQGpAmPiPcBkAaE2}C-#8)&!&YfNL(U8U5v!55a#Ap<9!Q*wS z4q~l#*4z|vC#Sh5x2GV>FRy(QiSd(<&5u^S3 zrKP*JaSpgM>U&HltM~kRMW0+CJB;N0#L?$`JeOkyk&i0F=&U-pYx;iZDCxC~Z9iay z|Bx3K+VS@kR(UXnSRp`=@u#s)t17Rw)9Gkqm}<`HP^63g*+rY{r2DpL+ziC3EicFF zXbBGEmbWOUqA80(drEFBEH13*pi}#ha&h_Hz?QhbszJC)tQNQmhztg)ioy;8$Jk>v z8nw(xCj7$9(q9wgH(~#|OZ{k`n3Ee|iLoV-or7(xvK(vU+7vDgH~i!pi;k*kxxHif zG7-0*Ag|i^>}J5<+b_x<@MuYgzsxd_qqA}Sw%96~iZ+3|S&SQB+vW@+7vf|HIB@GY z!<3_>$$6~?+e^LS(!r6_q|H#Qv})7NH&5B^$Q!5E(QFyv&J*xTG|U#uo&~ygYMYqW zJ42I5f8SQc&zUN<+H?pX!(M^EAJ#g_Azh2kXnkFb>04eqdk}9Iry%*BIOnkkZ#qzB z%S>Zs$XY}jv%(Aky>8;YM*0e+7d7gP>K<6~0PLB$+K4I_Fx4Yf99@Kb9?KZ?Gr1sNUic754*O+{kYwGh=yZMhnd@t*ncx$+)cHG*vD z0z#5ncSE(wlb7dxer4$Z$!CwgF`0|5wcih8T`^@cxkk9_HwJ0-twP;a6X|cvl6Z$6<==v=)xH)?SSfhtK#t1Nc`sCEObJK zkjKHAB26u2W#l5`??LYdK;1EVF&-;n1r);28Dm7QesWMAh~>Bgv6WsAoZsO=>cWOu zG4CK0tctZ_B=PpeMMsdFu3N{ieDB4b#CXT^n%?#ZHA+40cl)Kg`@tT8*b633p&JqsraGk2jSxbxts`bW>%!`>4EQw*oNr12bw3A) zqR9jOaa~DtOlPMyjPB-jY4K{E$VYoUC_Z$Nw|0ySg!ZgT4~2JQn>7(0@S<{j9su5>`%@D<>MH z|J`H!OL&nmJzp(?Dt)jBZ)n=(AjMT2h5~XqO>c?z0m+#5YzEv4&nw>s<78B;xAxYA zb1A6=_=a{?t1qb+B%0=IjPS;2{R~WO=B+e1nVzK|VbefFG94%*Srp3{WQUn>d`n z`(^ZlE}dSu_KDrz&+>rgm$M3#+|isHWG<(l@l1NV0rf>ZiLP=$fRwZ3N6-ajKBmV_ zPniqq=G>g5SNJb7?NI?E44tcM`X6r-hk|v@_~K`;^uKhF3!p!!r)?7!qZy4MFMo~D z?C*c3p!?PE%0;#iMD!a0nSj(sQ??q*%z-O7eDxuWN^?|823Efa4KZKlh4B^DJt@4rrK4vnI z8<8X5u;sY=QU%H2X1H{xSXZ2|M0Av-ShspMmG19ct9funLfe;-s%l$kkk5{N+>IF zV%9%?Rws)6>3991wDsICGv>_7i~vw)jQHYNB%D6hFy6;=f@NGpSv@kqmm55*VM^OY zSAk9I0=WcB)8%;jk^w$UoojLQcjQ)e7YFY4TpFh z@JsGKsYlllElpoM9uwJ+E0Vyw2eu7j!U-F4Uk7fs8%eq^AfmI@uuNUm>hbq9bGIy) z!}C>1Ad}=>&FK7|!@j&jw~Fz_;77PZexAdiZp*$ATcNW5E!X_znV~o9pCcny^X8e zZEkv|H}~FkYto5;?t9^vMU>jhv~TjG3;i_=C*1)zLI)5@^iZ-%0cpoVi@x#c*8M_F zOX~L;eUS)S{;4DhVHdRB(EO4yp7x=`((x3h0aSLy-P-|WN>YVimhQHE9bXfM?!O}% z*|Elxn8yZ;T_4Sma~+zD(Dyw)?;_kQVmEFf8RN8~0kH>7h^3;|Ddur+wg})JxRNE0 zZ9HYAoF;=GIF#p^y+z6+b6P9*Y4{fhW>rRrGg4oF5O98Jg`xl9gr`jxvlFU%sJOIK z(Iae1Fo*79o!DCu2!w^VwXcm%H?yzOv(;Sf@0B_l$+Je+3k@z=3)^yVHR~FXUidlm zn*p!kgN@6*)!2QlyL)f#qbGXMz7MGo&4gdeM`9}$zYchRV^%rgfFiBP4&jV7!pg-3 zSAb1DzGWbpmI7Ig1t?1vjf#k>MM7;;2P5#IFtfiz1vrj%3uWHyRQ}w`930@i2yZ<0 zh=z2VRM&_-#^16RFru$Risa;yNKk)ofj#D?N=883FHl&f=#o|fY3s_Qx!>B-q#^}g zUtW-+(%H!{u`^5bZm$Le1n3V~nQ)zU`&E^>&?Lo&~ zt(-mm7O+))t)I2ncsdiJ@3VDg0o3@`{k7t0Ceq6`yzOb(YVcfl%$|kM=Gsh#!*6q$ z9|ci+z{|(P2*rl(-ZZdyKqn&HXKL*uJpSL0s8QOYIHBwbW>7 z(!m~ZF6?xtb&jH`hH=i%WZjJ_s@*JT$JUo5Qe`y#*39>l-PofH4Ku2u7c-rwV=vnc zZ=zQ#dS&M3?TfbOi0-xBOuI(r<%;@H0qKp1cIStZj^ODZtcjW{s#VA}V?QO(sA|#I zNh4;mUYuhM>gaPD{b5Fk9J!Y_EMt6Ff1ONFw#EGSP*t(^F9q3ptRyb)r)nbEsS zSW%Y=Mn{Y{)8BFDf8GhT9%5)vI++Ox^Z+l^L&cPTBoMY%1z5#6BtD1A^igCtml>g5 z(GLyHE~!drd51x7gfg`@(bV9dv|AW`TdZ-chTVZMb`q`wO`8y+BjV`?QL85G)eGOi zvw#%=yb>CS)6Z(o5*{A?fm)3v?Zu3&4ATkiCuqHdrZkivU8DS|o-OeB`l8d@gE9bD zHB!i&<>rzlu!aUx5$5&Gz`&#m&C@Ep4Myj7##C~h zQI9;TU!%aaxE?B6<*fSM8qWCJ>ky|OnT8qakU@o!VHU{mIn+`(k!R19$_;;0fsihi zrKWb+V}m<}nv-h?t;fuRgNmck9lDcd$0Vv5b&GCvo;w7;!&{kA+aE7qKRE0INipMk zdU;k~CeecH!5=3EQdg6GE%2#tZKTqZXUJmt&#~Q*cgPnNHke{H|`dvaK`*-AmSqDMEu>DLG7kD^RX{ z3*!*qIv_?AZ^cg8IfHaUA8*3y%3$~slb9#L8o;Ao? z-Gc2Wft%B*tH;BSkPmk1E|;K>!t<59ywZucl$17M0<_P9+guR1xh6f|%qdt?j7&Bp z$v^{ri!s^>cM3Yw3M2_pa@v8+ zQ>!>8<*u?GF8j~-+fHDx*M!>D*x1tvR$`|q<_4*)P)*T+Fe}__Kfq`t*;<`fRcYU7 zl=1{PLB{C)D0F)$PPbuYWdSwt(RA(=J;?NT~Q97LxiB~k1aXdSaEj$saVX2J)5 zWL1lTcoKD~S596d{;-3&2G*a0V_Qyss3fdKwAIqyYOLz=<4*O&_9V)gIM+!sTkTF1 zsM9I)tyQOFHZIJ?&*m}#1?cy&ha$ULuffA#(?k!u7Xdz=dM%NA%b~4wHMwBu^2cv? zc8>`x6GkMHKz?fbPH^GqlDnON{?JL>UoOeK64$?YY3Rq~2F)iZH){VN)&dJAGEe8Q1?Qye9)&)OP z^|pes_`?yVe&pA3#Q@2Q)!SU2BR%(Gvo$o;OvlDK%aM0Y%QFO|MNg!P>y`-1XwuU= z4;^BOJv`8LQ!b*YS*-GpP7H1dso_8{#0U2=x0fGjN^ieR zbB~7>Lzs?0=i$u?9A~|Bzk=j5)?;J1S zB4$i^-{-`F<$X^$aDE zs!3i}E|EhYZ&#Ly(wChmC_c@wOXusKc7-0;S(t~6<5avd{4u(|@Nx1%OPo^zu4quG z6qjV6!VhMBYw*?w5Glc4TjTD~t;g%}l;aKsByJXB&GKW02$g{x{4T4UJcOsu#~%~L zzz^%5PPOV2kp#0L1;(DyCKr>rI zF1$4dWSZ=LRk@*@QBLD#j2&gJe3(*kvk>Z@+Dbo?t^yy(MiQ*W1u(tKB0aZ6NvcTjkI>q7X%x<kjaW5i zmBq^9I;7kit5=5wby#`DYD`9waR@ZF)EcdR(7Z=xD(5gklW|Es4N?H6I5b*TkJ6{cYUKp)@VZx*0|rgC)NL^nidCCR=oHjF zL$m;Kg|1fWED$MC=w-^+ZM@^U-!vjlPUKm1ABt+@$A5`@1} z?@|Glb@#@pc|>$bC|@QP-%q&!XKlv^o4xbIIUkzZ0jIrj>WJ#aS-&Bh#oAShFUW3O z7dqYdP+|y8kcR<{y6|DWJ)N{6L#L#&rKBrkO8DCiq?d|gbH4K^kJOPZa;0TXcWxtI zQP`$4N%gx}+l_D^*+ifFbk{ak-CcOOESwkU+qOQ8y#vhq^eqD*i{|@bv!{EoX@}J% z8>Adn=TZy}yW%g?j5zPm$bl(=v7&5T+hpY8vhh_HndgOw8Q#Gmu+zs`ZQ~QOIXJWn z(>4{X-K*7lLFPetvZ0vJ>`{F0Uz?P~ za$W$#EcS>73&3tjUP%PSd^btQ;1ZlS%QN*}@h>{Dy2tdGqlaNHvVH{PQ(e~M;wzPtj zfs}#P1?$6Vz-d4qLj8ueMn3u=*d-KA8vkIvi%V75FQru1vuONI%ZSrDygBA5T2Vob zQYVvO$ly^z@eb^3nHrzpbC19jRJ_$3iH(p*p~+!+YL(5n*=nDQFzbR6aE!i{P>2E2VF5iO`Mc@^s?A z@Y9k0u;~R`n_F^lUb@ls!K5H9Du!Uv8OsP=h#7HewqefI4E$mY7m#kHNFZq&VQPu9=F`b3yp2D60gj+Htw=_lRI?f;y>g!)LJ_ z8cq?90sCBm-^DVYAM@vC=gnylx7@Za>=QHnrA%v)b9~xFOI`C%zlo(~lV~EQ`VT4Y zSRcJ68Pk58^iEuK)=#an8o!LAH#qm3SK>cU^PO!zZ}eWY0`JE9wN>uMDW?6wTJhl^>s>L(gAQwDhINcBsxnPC5dOL%8KsoTJOhk zJ%D>?0!He8^Uv=|^N{T>wgV>yT_+2?QGbkkMrKmA?+ZzD9J3e8ws~^3O;1cF( z5DjU#Ra^*RlrooBVv?ESvhG*84!9kUyT`((>ie>36)SJDpfC}kFZRCT zo9Ocnb9jWdy_2wz5g?ylCjfJm?l_N8h5~hlHAarJfQBxG<`SGB(jh2}$y?wnUm`)5 zIxq1Q5c%3IheKz>AD99|0ZluvOHr*esNWGw5(qsyB9WpX2~MFxaTW3VvP8m+h8oAj zFj0g&|2?b0Zm663tf<&O1dFhJui0F${-Q()vBTdG4%G!F1x zjYcfy)k{qJtLD*#h>ptRSs~X`=Bv#30Ix&hQCd!ox zwnfH4hW}4McmEVfai&nRL`z*n&3e!aBPo|L@-@kqnsV<(ugwg~Go-fg?$&GU>c|n@ zL>uZsL)g#wDv&<}{8o^+El3m1p!!+c~;VW*! zy2l^1(%HhX1{Qwew!@Iw6Yv{l8__q9H+un@5(j*|&rr}Eqip1SF~Nz-jxg;SZ$PwV z()`bs)iImH6c=~2ac@%$KIJTG7p4=3&ViYuZ?V*|7G*LoOGYNgpH##j7DcL`nXt5SV~5w|pII|#okB-&jx)F;)8s7Pp5?vHy$daJk3n3{f7VzD zF{4vPq?#<;1e?Mg?RgTC#s4f|pSk8!2K&!o9TaxT4PJq^$C->NKWAwE$%xf?zLJYb zAb!C&5~RwSS>x{EagM7QEApETrjbJ~LI#W9-kGN6aId26fIyid2-vX^RK}%ui*LZQ zU|qH-Qqp3ao#j!s$CT)_rgHA6k89|*5TZr9FKt2*FQ9bY#UWThKe>p$+cCmnc{5#p zn|_(QQyxo;wSlSBU99#@SXs-rz%V_1hI28WvUyzJ2;HAr&&d{E3}|v?iLfGPAHP_v zGm9R*2s<^3h>RyMc_jE<8dk=6*{4g}#_gH~$&bVh^JVAun}HNp6XgDlj`puurSC;$ zkm&g&JjPt~hEXIgy?BfGgdBTYj+|*5t(MXsOM=gQ2$tYUG8Rkn91TTNtm8#{abJ@P zkfxjhYqJL4!PE3y3q|@z+wN|~VEBp-4qvdsMK`mKAFYJnw*pgItF+Z;+0+7DNIFcW z*#Eh7qJyqW3{@IXKeXM;U$V^22bO__MxDdSZdksQdHY- z5xB-Mj*=e+5r^ea7pN;&DolSk@51Eyt~;9ZqiStUuxm`t%zpa^Jj^)4^09@53aqn^ zeAq?PNv7CcY8cCMY`Iom>N8=!n1nuVGR|W0SNAMIHx?QC39*Fs?iE^G6uZXLDV0^z z9a|1TTtPR_%f)gJ99zo;YjkCtudkT1EqHK;C5UAo(EqLm9&uz~k2nkn2s8l*2>O52 zz;pUx=xA!9?CfY^YwpD0Ze#7GxoS(=jN*;-3rG~71d)t0Ibjqq-m?>Q-VtHlU%u4c zrnnxTs!a`ITDd5!<#@f3^K5J9wBgx6lOL>As(s$3M7)akRSe%${!(DHn<6@o+v zJu=r!Yu*0F`#5NM|9;?S>0be-+q~jMP*;fa>UlRPpu-`nkgUy_1#JowqR+sP+QtT0 z_p*mpHa6LL)q{S$TMggeieyNgNv^77;QtKa7sJ~d?gaN37C>Koo^NLb`&WFz&Ac$* z9tDbc4$je=oF5PwVRimW_s!@gj}2+opEdA6f4lcQ_Z^=$byyP-^4e>8yY= zIS;;7iF-Thc{6(oTQy4o*jYT~zov0ZS<%z3mjcpe zQ3eYJMCXEtPsV>=0KKnxZsZ=UG86XnLfTGG*voDSLZUlhuN7vDrB_^LzaQa`t!FJ- z*wXYSrLd`wYSezUh~l%*uR^8wYGL#amO&8qb-E3q(6iGmA4uSz(Xx0^EO;tjZv5eG zAOKkKo8$~TVFz-*V%E2?oAk?d%rxY(=GPlWmJ*-vTM?$`PYywC@6=83Djn)H2DvR6 z$SXR=^&LI-bH zbMF<}IhK`4UgC6ZbmGl`VNB&FQJ2(@GNqxUdTsTxN0D2 zbk{vnLsLMAY*eru(zu#@(6mh(P^4m@&n`c+^Ufm5C_D!ka0HfKY65t49|z`|~w!m-0A<)T?EdeQ(;(mfFY{ktZR+dfmJd z7C+eM3|p;!bJ*Z6RUqpQPIr3d+*xm+L$)ws7@Q%&t0aPYlUx)sPZJq~8l!xoc0>U^h+$hMVQ<|_-;7Kx^; zh|Ga^;#fL@8m#SyT+nUAo~_1KE?Tg+#-P}?^CRv^{u`U8)N0T#88aXfY&t#2{$mm4 zy|}@H9{#aLZwn*=jUi3MO1#rW$_X&5ZKzy>PMnM|Hys1@6s_a=jNI=|Lrmhb>j!+xo_GsE?DJlHn>``5F zv+wd5J;&;to4W&$7lOjKQp4wBy3?TV+|mV-9}4S>LVXiAzZ8&f`sEGupqBI2f6e}phK+_C zS85^_vLD_3{5U7V#VqbD27_INXb8uGI|08is>m;&EFMI0Jqqv@Al_}}BFYoxS`A^68C5~^^DjE7RE#Dk6VPHncC&~jguli;nW@8vm6 z2APf0Rx7*k_wcv#HoeLtFZmznG;1KsPl!0OFzu8o0u#!V+d97A5Fd}+YS;H;PBv_b zjOa&391xxv{6&H*Sjc}WlDzED#L9)M@Hr*8*z|79V`w9og7;oOSrKtmM>5)j;gYm| zWUN}}Bjzzv3cYGHD3yaa5QBi;{z&%wJ!Qv)vDeNM>Qam5ERH{i{rq14=|dv3x)Yi& zEaKSpu}j3OWaM09oc@A5)22C%bDqrT3i`;#6pZXhGVy?85MgjxT~I0et%U!+@$^sz zA@vr-4P3Cw>4c8haGGPVS=U%`3rhMTPDe;1KplnuIl81K_(U-*c4>+}$~zk8e=&u8?^ z^uZCJE3~}JJ}=rC44LkgnodQV-IXv9Dur^CY=Nj3{z61~tR>izxqTJ;a#a}s2mkh> z-+70TVbRI#4!^Uqzir#%-lQIG*pSL!I&9B=IxZVs)X4^&v7?AlnHPD*Xl7q8sF0m} zXJ*X1VHhdpg)UVFhHrUyz*kY`Q3S^Dlu_#Nb}C)5amLEQe}gDKJkC| zksd2=wZnwme*yP`A8{VJcB+&}R2JVLjo8ZetB$YinhP_AJTE2HM8JEHLpJ>^XKMA; zgZJ&0%%$DVYeDEcmM}4cdGip_l157vec#UhLqB5-af-rMLU8myAXF814|bLbMsPpz z3Rz@^bCwg@2DiI)JZ+JUnt0-Z}W(ccGL29#sd)I#6}xq+R#v=g zHPsan;tJPn3ers8KWKMQE$63m!KIl^58oZEonqRt0||GPCD(b;1vg1*;arU4G;4eqU^{b#->o*^fCA zaT2ku6B9LDhv!Uf{T#Bsq0pj6@f(@(->u=*BM*9$;L!H;U?YAf)>}P-iY>gqAX6KR z&=HQ9Rmr3#EHA2U?!ha)3JGNOc&Ou^zgT_P)zhPGvskw=^A_EfL-LK0kI=58GOP;C z;Dp^^@U2ulS@o(&P-Wru1{;b+}F9AW$? zV_DCNLsK}M&}dp1nXvl?!{436hOzKWIaHsRlfIQ}R+;m8jEgy}0c z*z>E)6s_MjIT65A?%=-NO{{F5g?wXPUYvt4U#}WY)2W@Xf8%0eqrBl)HzLQceh%fz zByOz)-%ln4NVl@0MF@@K+r3db&o@sVM&7bs05K7usH*Y4sE-7GmFeMB#^gj0CiFBr z1Iz~lrcRkUY|rK2q#MS6)>@5BHZLj$j5{6B^kT>-P8M%E%Oxiied2yd3f{|`j_;{e z9w$6YIabe6Ndk8t-WIQRo^7W*CwI_=`iiN4Z-;)U4U>eh@hy7qrnrssDj&2d4}SSb zs~P%M(p}6#2ch?=FaK4elvn%20zY-F|5K|_{MP zj7Otw6}u>m<9Ny@_U;=4tei0*BMUMa&`#hOHp zyRsG4P>lmizg}sw74<2v9F@n6AiImh6QQA z@M+j5Q&Vd1qDDB5ET6wG>!pbUQE^EwjwPck+@YPvv5=V8o_hX!kCdzSQ+1l=4~{v z&{#$2H75zt33;MB{Uq(ymS=JFjrmWzjC<1N&9Lu#3l=9XV`PFM*vGVEYDumz*_0?j zDj^Lwbtl$lM`h*DAC?VtIJBDWW)b>K8#=sElAGy-(p=U{#=Z|+42!?_!zbHHCh_E< zj7Hw>cYb#MTh;wPH-^mrV+{Yt82-%|f`ZY1CZM1|K66nZ#2_@I&3zf4=5PiQ1O)A$ zga7}z(-yY-oNH~J^;JCVO`UZAVICEv{}rHKm5KEB^KqF^dkXh201KZEr@o`9wG$)5 zKhA$A^uvzWBK+*R`}47{|3dfz_18rIAGB_Ej#fr?c2@uB``_Xqv`lnne0FZ~DFM>I z;Ap}Ah4Y^g{>}t+H%T!B1PBNO3J3_ozYvqb|3&QN>|t&CS90#}q-cF~Xg!}K-#=v_ z{TEVYH`{eG^^ZAD${}qt=zXfEZT&+_0 z3<_R1KtQnng+mS0zXSf0x4+9-as16x{xhXHK=gMF*Ls@yZ=dV*Zx=k|9{-tzk~j( zFY`~x&q4J$gZyun{$J1L@8thIPhOvj{r~N1|24ZPNJD-O*MEeT$RLWJBWa@VAFcld D5Q{*D literal 0 HcmV?d00001 diff --git a/PoGo.NecroBot.Logic/packages.config b/PoGo.NecroBot.Logic/packages.config new file mode 100644 index 000000000..3d13856b2 --- /dev/null +++ b/PoGo.NecroBot.Logic/packages.config @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/PoGo.NecroBot.Window/PoGo.Necrobot.Window.csproj b/PoGo.NecroBot.Window/PoGo.Necrobot.Window.csproj index b4da8b2ea..5934dc43b 100644 --- a/PoGo.NecroBot.Window/PoGo.Necrobot.Window.csproj +++ b/PoGo.NecroBot.Window/PoGo.Necrobot.Window.csproj @@ -106,8 +106,8 @@ $(SolutionDir)\packages\GMap.NET.Presentation.1.7.5\lib\net40\GMap.NET.WindowsPresentation.dll True - - $(SolutionDir)\packages\Google.Protobuf.3.4.1\lib\net45\Google.Protobuf.dll + + $(SolutionDir)\packages\Google.Protobuf.3.5.0\lib\net45\Google.Protobuf.dll False @@ -157,8 +157,8 @@ $(SolutionDir)\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll - - $(SolutionDir)\packages\POGOProtos.Core.2.19.1\lib\net45\POGOProtos.Core.dll + + $(SolutionDir)\packages\POGOProtos.Core.2.20.0\lib\net45\POGOProtos.Core.dll $(SolutionDir)\packages\Remotion.Linq.2.1.2\lib\net45\Remotion.Linq.dll diff --git a/PoGo.NecroBot.Window/packages.config b/PoGo.NecroBot.Window/packages.config index 24e4c6730..25a3bd8d9 100644 --- a/PoGo.NecroBot.Window/packages.config +++ b/PoGo.NecroBot.Window/packages.config @@ -6,7 +6,7 @@ - + @@ -28,7 +28,7 @@ - + diff --git a/README.md b/README.md index ca57b09d5..2174585f5 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Github All Releases](https://img.shields.io/github/downloads/Necrobot-Private/NecroBot/total.svg)](https://github.com/Necrobot-Private/NecroBot/releases) [![ license](https://img.shields.io/badge/license-AGPL-blue.svg)](https://raw.githubusercontent.com/Necrobot-Private/NecroBot/master/LICENSE.md) -

Necrobot2 is now compatible with 0.83.2 API.

+

Necrobot2 is now compatible with 0.85.1 API.

Necrobot2 itself is free but now you will need to purchase an API key from Bossland in order to run the bot. diff --git a/RocketBot2/Properties/AssemblyInfo.cs b/RocketBot2/Properties/AssemblyInfo.cs index 28eee13fb..89f97f484 100644 --- a/RocketBot2/Properties/AssemblyInfo.cs +++ b/RocketBot2/Properties/AssemblyInfo.cs @@ -37,8 +37,8 @@ // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: -// [assembly: AssemblyVersion("2.19.0.1")] +// [assembly: AssemblyVersion("2.19.2.0")] -[assembly: AssemblyVersion("2.19.0.1")] +[assembly: AssemblyVersion("2.19.2.0")] [assembly: AssemblyFileVersion("1.0.0.0")] -[assembly: AssemblyInformationalVersion("v2.19.0.1")] +[assembly: AssemblyInformationalVersion("v2.19.2.0")] diff --git a/RocketBot2/RocketBot2.csproj b/RocketBot2/RocketBot2.csproj index d54f0e550..95c86e40c 100644 --- a/RocketBot2/RocketBot2.csproj +++ b/RocketBot2/RocketBot2.csproj @@ -125,8 +125,8 @@ $(SolutionDir)\packages\GeoCoordinate.NetStandard1.1.0.1\lib\netstandard1.1\GeoCoordinate.NetStandard1.dll - - $(SolutionDir)\packages\Google.Protobuf.3.4.1\lib\net45\Google.Protobuf.dll + + $(SolutionDir)\packages\Google.Protobuf.3.5.0\lib\net45\Google.Protobuf.dll $(SolutionDir)\packages\log4net.2.0.8\lib\net45-full\log4net.dll @@ -163,8 +163,8 @@ $(SolutionDir)\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll - - $(SolutionDir)\packages\POGOProtos.Core.2.19.1\lib\net45\POGOProtos.Core.dll + + $(SolutionDir)\packages\POGOProtos.Core.2.20.0\lib\net45\POGOProtos.Core.dll $(SolutionDir)\packages\Remotion.Linq.2.1.2\lib\net45\Remotion.Linq.dll diff --git a/RocketBot2/packages.config b/RocketBot2/packages.config index c9795abc3..9e64b4cef 100644 --- a/RocketBot2/packages.config +++ b/RocketBot2/packages.config @@ -6,7 +6,7 @@ - + @@ -22,7 +22,7 @@ - +