summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorchai <chaifix@163.com>2020-12-30 20:59:04 +0800
committerchai <chaifix@163.com>2020-12-30 20:59:04 +0800
commite9ea621b93fbb58d9edfca8375918791637bbd52 (patch)
tree19ce3b1c1f2d51eda6878c9d0f2c9edc27f13650
+init
-rw-r--r--Client/AmongUs.sln22
-rw-r--r--Client/Assembly-CSharp/AcceptDivertPowerGame.cs84
-rw-r--r--Client/Assembly-CSharp/AdDataCollectScreen.cs65
-rw-r--r--Client/Assembly-CSharp/AdPlayer.cs118
-rw-r--r--Client/Assembly-CSharp/AlignGame.cs146
-rw-r--r--Client/Assembly-CSharp/AlphaBlink.cs44
-rw-r--r--Client/Assembly-CSharp/AlphaPulse.cs43
-rw-r--r--Client/Assembly-CSharp/AmongUsClient.cs497
-rw-r--r--Client/Assembly-CSharp/AmongUsProduct.cs9
-rw-r--r--Client/Assembly-CSharp/Announcement.cs10
-rw-r--r--Client/Assembly-CSharp/AnnouncementPopUp.cs154
-rw-r--r--Client/Assembly-CSharp/ArrowBehaviour.cs47
-rw-r--r--Client/Assembly-CSharp/AspectPosition.cs86
-rw-r--r--Client/Assembly-CSharp/AspectSize.cs32
-rw-r--r--Client/Assembly-CSharp/Assembly-CSharp.csproj464
-rw-r--r--Client/Assembly-CSharp/Assets/CoreScripts/Telemetry.cs300
-rw-r--r--Client/Assembly-CSharp/Asteroid.cs71
-rw-r--r--Client/Assembly-CSharp/AutoOpenDoor.cs37
-rw-r--r--Client/Assembly-CSharp/BanButton.cs52
-rw-r--r--Client/Assembly-CSharp/BanMenu.cs153
-rw-r--r--Client/Assembly-CSharp/BlockedWords.cs877
-rw-r--r--Client/Assembly-CSharp/BoolRange.cs10
-rw-r--r--Client/Assembly-CSharp/ButtonBehavior.cs64
-rw-r--r--Client/Assembly-CSharp/ButtonRolloverHandler.cs35
-rw-r--r--Client/Assembly-CSharp/CardSlideGame.cs190
-rw-r--r--Client/Assembly-CSharp/ChainBehaviour.cs27
-rw-r--r--Client/Assembly-CSharp/ChatBubble.cs83
-rw-r--r--Client/Assembly-CSharp/ChatController.cs326
-rw-r--r--Client/Assembly-CSharp/ChatNoteTypes.cs6
-rw-r--r--Client/Assembly-CSharp/CloudGenerator.cs159
-rw-r--r--Client/Assembly-CSharp/ColorChip.cs11
-rw-r--r--Client/Assembly-CSharp/ConditionalHide.cs21
-rw-r--r--Client/Assembly-CSharp/ConditionalStore.cs9
-rw-r--r--Client/Assembly-CSharp/Console.cs97
-rw-r--r--Client/Assembly-CSharp/Constants.cs90
-rw-r--r--Client/Assembly-CSharp/Controller.cs157
-rw-r--r--Client/Assembly-CSharp/CooldownHelpers.cs33
-rw-r--r--Client/Assembly-CSharp/CounterArea.cs44
-rw-r--r--Client/Assembly-CSharp/CourseMinigame.cs206
-rw-r--r--Client/Assembly-CSharp/CourseStarBehaviour.cs17
-rw-r--r--Client/Assembly-CSharp/CreateGameOptions.cs104
-rw-r--r--Client/Assembly-CSharp/CreateOptionsPicker.cs165
-rw-r--r--Client/Assembly-CSharp/CreateStoreButton.cs16
-rw-r--r--Client/Assembly-CSharp/CrewVisualizer.cs63
-rw-r--r--Client/Assembly-CSharp/CrossFadeImages.cs20
-rw-r--r--Client/Assembly-CSharp/CrossFader.cs74
-rw-r--r--Client/Assembly-CSharp/CrystalBehaviour.cs110
-rw-r--r--Client/Assembly-CSharp/CrystalMinigame.cs120
-rw-r--r--Client/Assembly-CSharp/CustomNetworkTransform.cs265
-rw-r--r--Client/Assembly-CSharp/CustomPlayerMenu.cs41
-rw-r--r--Client/Assembly-CSharp/DataCollectScreen.cs56
-rw-r--r--Client/Assembly-CSharp/DeadBody.cs35
-rw-r--r--Client/Assembly-CSharp/DeathReason.cs8
-rw-r--r--Client/Assembly-CSharp/DeconSystem.cs163
-rw-r--r--Client/Assembly-CSharp/DefaultPool.cs82
-rw-r--r--Client/Assembly-CSharp/DemoKeyboardStick.cs25
-rw-r--r--Client/Assembly-CSharp/DestroyableSingleton.cs58
-rw-r--r--Client/Assembly-CSharp/DialBehaviour.cs46
-rw-r--r--Client/Assembly-CSharp/DialogueBox.cs27
-rw-r--r--Client/Assembly-CSharp/DisconnectPopup.cs112
-rw-r--r--Client/Assembly-CSharp/DiscordManager.cs207
-rw-r--r--Client/Assembly-CSharp/DiscoveryState.cs7
-rw-r--r--Client/Assembly-CSharp/DiscussBehaviour.cs65
-rw-r--r--Client/Assembly-CSharp/DivertPowerMetagame.cs25
-rw-r--r--Client/Assembly-CSharp/DivertPowerMinigame.cs91
-rw-r--r--Client/Assembly-CSharp/DivertPowerTask.cs51
-rw-r--r--Client/Assembly-CSharp/DoorsSystemType.cs119
-rw-r--r--Client/Assembly-CSharp/DotAligner.cs43
-rw-r--r--Client/Assembly-CSharp/DragState.cs9
-rw-r--r--Client/Assembly-CSharp/DummyBehaviour.cs63
-rw-r--r--Client/Assembly-CSharp/DummyConsole.cs57
-rw-r--r--Client/Assembly-CSharp/DynamicSound.cs26
-rw-r--r--Client/Assembly-CSharp/Effects.cs248
-rw-r--r--Client/Assembly-CSharp/ElectricTask.cs82
-rw-r--r--Client/Assembly-CSharp/EmergencyMinigame.cs120
-rw-r--r--Client/Assembly-CSharp/EmptyGarbageMinigame.cs195
-rw-r--r--Client/Assembly-CSharp/EndGameManager.cs236
-rw-r--r--Client/Assembly-CSharp/EngineBehaviour.cs48
-rw-r--r--Client/Assembly-CSharp/EnterCodeMinigame.cs121
-rw-r--r--Client/Assembly-CSharp/ExileController.cs160
-rw-r--r--Client/Assembly-CSharp/ExitGameButton.cs24
-rw-r--r--Client/Assembly-CSharp/Extensions.cs409
-rw-r--r--Client/Assembly-CSharp/FindAGameManager.cs124
-rw-r--r--Client/Assembly-CSharp/FindGameButton.cs63
-rw-r--r--Client/Assembly-CSharp/FingerBehaviour.cs101
-rw-r--r--Client/Assembly-CSharp/FixedActionConsole.cs64
-rw-r--r--Client/Assembly-CSharp/FlatWaveBehaviour.cs75
-rw-r--r--Client/Assembly-CSharp/FloatRange.cs159
-rw-r--r--Client/Assembly-CSharp/FollowerCamera.cs54
-rw-r--r--Client/Assembly-CSharp/FontCache.cs78
-rw-r--r--Client/Assembly-CSharp/FontData.cs32
-rw-r--r--Client/Assembly-CSharp/FontExtensionData.cs56
-rw-r--r--Client/Assembly-CSharp/FontLoader.cs106
-rw-r--r--Client/Assembly-CSharp/GameData.cs556
-rw-r--r--Client/Assembly-CSharp/GameDiscovery.cs83
-rw-r--r--Client/Assembly-CSharp/GameModes.cs8
-rw-r--r--Client/Assembly-CSharp/GameObjectExtensions.cs77
-rw-r--r--Client/Assembly-CSharp/GameOptionsData.cs332
-rw-r--r--Client/Assembly-CSharp/GameOptionsMenu.cs133
-rw-r--r--Client/Assembly-CSharp/GameOverReason.cs12
-rw-r--r--Client/Assembly-CSharp/GameSettingMenu.cs37
-rw-r--r--Client/Assembly-CSharp/GameStartManager.cs215
-rw-r--r--Client/Assembly-CSharp/GarbageBehaviour.cs13
-rw-r--r--Client/Assembly-CSharp/GoogleMobileAds/Api/AdErrorEventArgs.cs9
-rw-r--r--Client/Assembly-CSharp/GoogleMobileAds/Api/AdFailedToLoadEventArgs.cs9
-rw-r--r--Client/Assembly-CSharp/GoogleMobileAds/Api/AdLoader.cs93
-rw-r--r--Client/Assembly-CSharp/GoogleMobileAds/Api/AdPosition.cs15
-rw-r--r--Client/Assembly-CSharp/GoogleMobileAds/Api/AdRequest.cs113
-rw-r--r--Client/Assembly-CSharp/GoogleMobileAds/Api/AdSize.cs88
-rw-r--r--Client/Assembly-CSharp/GoogleMobileAds/Api/AdapterState.cs10
-rw-r--r--Client/Assembly-CSharp/GoogleMobileAds/Api/AdapterStatus.cs20
-rw-r--r--Client/Assembly-CSharp/GoogleMobileAds/Api/BannerView.cs121
-rw-r--r--Client/Assembly-CSharp/GoogleMobileAds/Api/CustomNativeEventArgs.cs9
-rw-r--r--Client/Assembly-CSharp/GoogleMobileAds/Api/CustomNativeTemplateAd.cs52
-rw-r--r--Client/Assembly-CSharp/GoogleMobileAds/Api/Gender.cs11
-rw-r--r--Client/Assembly-CSharp/GoogleMobileAds/Api/InterstitialAd.cs88
-rw-r--r--Client/Assembly-CSharp/GoogleMobileAds/Api/Mediation/MediationExtras.cs19
-rw-r--r--Client/Assembly-CSharp/GoogleMobileAds/Api/MobileAds.cs37
-rw-r--r--Client/Assembly-CSharp/GoogleMobileAds/Api/NativeAdType.cs9
-rw-r--r--Client/Assembly-CSharp/GoogleMobileAds/Api/Reward.cs11
-rw-r--r--Client/Assembly-CSharp/GoogleMobileAds/Api/RewardBasedVideoAd.cs125
-rw-r--r--Client/Assembly-CSharp/GoogleMobileAds/Api/RewardedAd.cs97
-rw-r--r--Client/Assembly-CSharp/GoogleMobileAds/Api/ServerSideVerificationOptions.cs41
-rw-r--r--Client/Assembly-CSharp/GoogleMobileAds/Common/DummyClient.cs185
-rw-r--r--Client/Assembly-CSharp/GoogleMobileAds/Common/IAdLoaderClient.cs14
-rw-r--r--Client/Assembly-CSharp/GoogleMobileAds/Common/IBannerClient.cs40
-rw-r--r--Client/Assembly-CSharp/GoogleMobileAds/Common/ICustomNativeTemplateClient.cs20
-rw-r--r--Client/Assembly-CSharp/GoogleMobileAds/Common/IInterstitialClient.cs30
-rw-r--r--Client/Assembly-CSharp/GoogleMobileAds/Common/IMobileAdsClient.cs15
-rw-r--r--Client/Assembly-CSharp/GoogleMobileAds/Common/IRewardBasedVideoAdClient.cs36
-rw-r--r--Client/Assembly-CSharp/GoogleMobileAds/Common/IRewardedAdClient.cs32
-rw-r--r--Client/Assembly-CSharp/GoogleMobileAds/Common/MobileAdsEventExecutor.cs72
-rw-r--r--Client/Assembly-CSharp/GoogleMobileAds/Common/RewardedAdDummyClient.cs59
-rw-r--r--Client/Assembly-CSharp/GoogleMobileAds/Common/Utils.cs27
-rw-r--r--Client/Assembly-CSharp/GoogleMobileAds/GoogleMobileAdsClientFactory.cs39
-rw-r--r--Client/Assembly-CSharp/HashRandom.cs36
-rw-r--r--Client/Assembly-CSharp/HatBehaviour.cs34
-rw-r--r--Client/Assembly-CSharp/HatManager.cs89
-rw-r--r--Client/Assembly-CSharp/HatsTab.cs83
-rw-r--r--Client/Assembly-CSharp/HorizontalGauge.cs26
-rw-r--r--Client/Assembly-CSharp/HostGameButton.cs135
-rw-r--r--Client/Assembly-CSharp/HowToPlayController.cs67
-rw-r--r--Client/Assembly-CSharp/HudManager.cs312
-rw-r--r--Client/Assembly-CSharp/HudOverrideSystemType.cs40
-rw-r--r--Client/Assembly-CSharp/HudOverrideTask.cs82
-rw-r--r--Client/Assembly-CSharp/IActivatable.cs6
-rw-r--r--Client/Assembly-CSharp/IBuyable.cs6
-rw-r--r--Client/Assembly-CSharp/IBytesSerializable.cs6
-rw-r--r--Client/Assembly-CSharp/IConnectButton.cs8
-rw-r--r--Client/Assembly-CSharp/IDisconnectHandler.cs9
-rw-r--r--Client/Assembly-CSharp/IFocusHolder.cs11
-rw-r--r--Client/Assembly-CSharp/IGameListHandler.cs8
-rw-r--r--Client/Assembly-CSharp/ILocationActivate.cs6
-rw-r--r--Client/Assembly-CSharp/IObjectPool.cs13
-rw-r--r--Client/Assembly-CSharp/ISoundPlayer.cs11
-rw-r--r--Client/Assembly-CSharp/ISystemType.cs15
-rw-r--r--Client/Assembly-CSharp/ITranslatedText.cs6
-rw-r--r--Client/Assembly-CSharp/IUsable.cs14
-rw-r--r--Client/Assembly-CSharp/IVirtualJoystick.cs7
-rw-r--r--Client/Assembly-CSharp/ImageData.cs10
-rw-r--r--Client/Assembly-CSharp/ImageNames.cs12
-rw-r--r--Client/Assembly-CSharp/ImageTranslator.cs24
-rw-r--r--Client/Assembly-CSharp/ImportantTextTask.cs41
-rw-r--r--Client/Assembly-CSharp/InfectedOverlay.cs51
-rw-r--r--Client/Assembly-CSharp/InnerNet/AlterGameTags.cs9
-rw-r--r--Client/Assembly-CSharp/InnerNet/ClientData.cs21
-rw-r--r--Client/Assembly-CSharp/InnerNet/DisconnectReasons.cs25
-rw-r--r--Client/Assembly-CSharp/InnerNet/GameKeywords.cs16
-rw-r--r--Client/Assembly-CSharp/InnerNet/GameListing.cs30
-rw-r--r--Client/Assembly-CSharp/InnerNet/GameStates.cs12
-rw-r--r--Client/Assembly-CSharp/InnerNet/InnerDiscover.cs114
-rw-r--r--Client/Assembly-CSharp/InnerNet/InnerNetClient.cs1726
-rw-r--r--Client/Assembly-CSharp/InnerNet/InnerNetObject.cs74
-rw-r--r--Client/Assembly-CSharp/InnerNet/InnerNetServer.cs592
-rw-r--r--Client/Assembly-CSharp/InnerNet/JoinFailureReasons.cs15
-rw-r--r--Client/Assembly-CSharp/InnerNet/LimboStates.cs11
-rw-r--r--Client/Assembly-CSharp/InnerNet/MatchMakerModes.cs11
-rw-r--r--Client/Assembly-CSharp/InnerNet/MessageExtensions.cs24
-rw-r--r--Client/Assembly-CSharp/InnerNet/SpawnFlags.cs11
-rw-r--r--Client/Assembly-CSharp/InnerNet/Tags.cs35
-rw-r--r--Client/Assembly-CSharp/IntRange.cs71
-rw-r--r--Client/Assembly-CSharp/IntroCutscene.cs155
-rw-r--r--Client/Assembly-CSharp/JoinGameButton.cs119
-rw-r--r--Client/Assembly-CSharp/KerningPair.cs11
-rw-r--r--Client/Assembly-CSharp/KeyboardJoystick.cs79
-rw-r--r--Client/Assembly-CSharp/KeypadGame.cs135
-rw-r--r--Client/Assembly-CSharp/KillAnimType.cs10
-rw-r--r--Client/Assembly-CSharp/KillAnimation.cs52
-rw-r--r--Client/Assembly-CSharp/KillButtonManager.cs69
-rw-r--r--Client/Assembly-CSharp/KillOverlay.cs114
-rw-r--r--Client/Assembly-CSharp/LanguageButton.cs12
-rw-r--r--Client/Assembly-CSharp/LanguageSetter.cs50
-rw-r--r--Client/Assembly-CSharp/LanguageUnit.cs79
-rw-r--r--Client/Assembly-CSharp/LeafBehaviour.cs40
-rw-r--r--Client/Assembly-CSharp/LeafMinigame.cs104
-rw-r--r--Client/Assembly-CSharp/LetterTree.cs299
-rw-r--r--Client/Assembly-CSharp/LifeSuppSystemType.cs121
-rw-r--r--Client/Assembly-CSharp/LightSource.cs335
-rw-r--r--Client/Assembly-CSharp/LobbyBehaviour.cs82
-rw-r--r--Client/Assembly-CSharp/MMOnlineManager.cs28
-rw-r--r--Client/Assembly-CSharp/MainMenuManager.cs66
-rw-r--r--Client/Assembly-CSharp/ManualDoor.cs90
-rw-r--r--Client/Assembly-CSharp/MapBehaviour.cs113
-rw-r--r--Client/Assembly-CSharp/MapConsole.cs65
-rw-r--r--Client/Assembly-CSharp/MapCountOverlay.cs90
-rw-r--r--Client/Assembly-CSharp/MapRoom.cs92
-rw-r--r--Client/Assembly-CSharp/MapTaskOverlay.cs101
-rw-r--r--Client/Assembly-CSharp/MatchMaker.cs41
-rw-r--r--Client/Assembly-CSharp/MatchMakerGameButton.cs59
-rw-r--r--Client/Assembly-CSharp/MedScanMinigame.cs197
-rw-r--r--Client/Assembly-CSharp/MedScanSystem.cs81
-rw-r--r--Client/Assembly-CSharp/MeetingHud.cs734
-rw-r--r--Client/Assembly-CSharp/MeetingRoomManager.cs36
-rw-r--r--Client/Assembly-CSharp/MemSafeStringExtensions.cs37
-rw-r--r--Client/Assembly-CSharp/MeshRendererExtensions.cs23
-rw-r--r--Client/Assembly-CSharp/Minigame.cs166
-rw-r--r--Client/Assembly-CSharp/MonoPInvokeCallbackAttribute.cs8
-rw-r--r--Client/Assembly-CSharp/NameTextBehaviour.cs61
-rw-r--r--Client/Assembly-CSharp/NavigationMinigame.cs88
-rw-r--r--Client/Assembly-CSharp/NoOxyTask.cs106
-rw-r--r--Client/Assembly-CSharp/NoShadowBehaviour.cs55
-rw-r--r--Client/Assembly-CSharp/NormalPlayerTask.cs284
-rw-r--r--Client/Assembly-CSharp/NotificationPopper.cs58
-rw-r--r--Client/Assembly-CSharp/NumberOption.cs111
-rw-r--r--Client/Assembly-CSharp/ObjectPoolBehavior.cs138
-rw-r--r--Client/Assembly-CSharp/OffsetAdjustment.cs11
-rw-r--r--Client/Assembly-CSharp/OneWayShadows.cs22
-rw-r--r--Client/Assembly-CSharp/OptionBehaviour.cs33
-rw-r--r--Client/Assembly-CSharp/OptionsConsole.cs64
-rw-r--r--Client/Assembly-CSharp/OptionsMenuBehaviour.cs200
-rw-r--r--Client/Assembly-CSharp/OverlayKillAnimation.cs150
-rw-r--r--Client/Assembly-CSharp/Palette.cs67
-rw-r--r--Client/Assembly-CSharp/ParallaxController.cs22
-rw-r--r--Client/Assembly-CSharp/ParticleInfo.cs12
-rw-r--r--Client/Assembly-CSharp/PassiveButton.cs47
-rw-r--r--Client/Assembly-CSharp/PassiveButtonManager.cs256
-rw-r--r--Client/Assembly-CSharp/PetBehaviour.cs140
-rw-r--r--Client/Assembly-CSharp/PetsTab.cs78
-rw-r--r--Client/Assembly-CSharp/PhysicsHelpers.cs38
-rw-r--r--Client/Assembly-CSharp/PingTracker.cs19
-rw-r--r--Client/Assembly-CSharp/PlayerAnimator.cs90
-rw-r--r--Client/Assembly-CSharp/PlayerControl.cs1470
-rw-r--r--Client/Assembly-CSharp/PlayerParticle.cs39
-rw-r--r--Client/Assembly-CSharp/PlayerParticleInfo.cs12
-rw-r--r--Client/Assembly-CSharp/PlayerParticles.cs73
-rw-r--r--Client/Assembly-CSharp/PlayerPhysics.cs330
-rw-r--r--Client/Assembly-CSharp/PlayerTab.cs100
-rw-r--r--Client/Assembly-CSharp/PlayerTask.cs137
-rw-r--r--Client/Assembly-CSharp/PlayerVoteArea.cs211
-rw-r--r--Client/Assembly-CSharp/PoolableBehavior.cs17
-rw-r--r--Client/Assembly-CSharp/PoolablePlayer.cs45
-rw-r--r--Client/Assembly-CSharp/PooledMapIcon.cs35
-rw-r--r--Client/Assembly-CSharp/PowerTools/SpriteAnim.cs255
-rw-r--r--Client/Assembly-CSharp/PowerTools/SpriteAnimEventHandler.cs111
-rw-r--r--Client/Assembly-CSharp/PowerTools/SpriteAnimNodeSync.cs36
-rw-r--r--Client/Assembly-CSharp/PowerTools/SpriteAnimNodes.cs232
-rw-r--r--Client/Assembly-CSharp/PowerTools/WaitForAnimationFinish.cs55
-rw-r--r--Client/Assembly-CSharp/ProgressTracker.cs39
-rw-r--r--Client/Assembly-CSharp/Properties/AssemblyInfo.cs6
-rw-r--r--Client/Assembly-CSharp/PurchaseButton.cs90
-rw-r--r--Client/Assembly-CSharp/PurchaseStates.cs9
-rw-r--r--Client/Assembly-CSharp/RadioWaveBehaviour.cs81
-rw-r--r--Client/Assembly-CSharp/RandomFill.cs33
-rw-r--r--Client/Assembly-CSharp/ReactorMinigame.cs110
-rw-r--r--Client/Assembly-CSharp/ReactorRoomWire.cs51
-rw-r--r--Client/Assembly-CSharp/ReactorShipRoom.cs74
-rw-r--r--Client/Assembly-CSharp/ReactorSystemType.cs137
-rw-r--r--Client/Assembly-CSharp/ReactorTask.cs85
-rw-r--r--Client/Assembly-CSharp/RefuelMinigame.cs23
-rw-r--r--Client/Assembly-CSharp/RefuelStage.cs98
-rw-r--r--Client/Assembly-CSharp/ReportButtonManager.cs28
-rw-r--r--Client/Assembly-CSharp/ResSetter.cs29
-rw-r--r--Client/Assembly-CSharp/ResolutionManager.cs44
-rw-r--r--Client/Assembly-CSharp/ResolutionSlider.cs73
-rw-r--r--Client/Assembly-CSharp/RingBuffer.cs75
-rw-r--r--Client/Assembly-CSharp/RoomTracker.cs159
-rw-r--r--Client/Assembly-CSharp/SabotageSystemType.cs135
-rw-r--r--Client/Assembly-CSharp/SabotageTask.cs33
-rw-r--r--Client/Assembly-CSharp/SampleMinigame.cs382
-rw-r--r--Client/Assembly-CSharp/SaveManager.cs750
-rw-r--r--Client/Assembly-CSharp/Scene0Controller.cs79
-rw-r--r--Client/Assembly-CSharp/Scene1Controller.cs94
-rw-r--r--Client/Assembly-CSharp/SceneChanger.cs27
-rw-r--r--Client/Assembly-CSharp/SceneController.cs6
-rw-r--r--Client/Assembly-CSharp/ScreenJoystick.cs54
-rw-r--r--Client/Assembly-CSharp/Scroller.cs85
-rw-r--r--Client/Assembly-CSharp/SecurityCameraSystemType.cs78
-rw-r--r--Client/Assembly-CSharp/ServerInfo.cs43
-rw-r--r--Client/Assembly-CSharp/ServerManager.cs59
-rw-r--r--Client/Assembly-CSharp/ServerSelectUi.cs59
-rw-r--r--Client/Assembly-CSharp/ServerSelector.cs53
-rw-r--r--Client/Assembly-CSharp/SettingsMode.cs7
-rw-r--r--Client/Assembly-CSharp/ShadowCamera.cs17
-rw-r--r--Client/Assembly-CSharp/ShhhBehaviour.cs135
-rw-r--r--Client/Assembly-CSharp/ShieldMinigame.cs84
-rw-r--r--Client/Assembly-CSharp/ShipRoom.cs15
-rw-r--r--Client/Assembly-CSharp/ShipStatus.cs738
-rw-r--r--Client/Assembly-CSharp/ShowAdsState.cs9
-rw-r--r--Client/Assembly-CSharp/SimonSaysGame.cs243
-rw-r--r--Client/Assembly-CSharp/SinglePopHelp.cs6
-rw-r--r--Client/Assembly-CSharp/SkinData.cs48
-rw-r--r--Client/Assembly-CSharp/SkinLayer.cs101
-rw-r--r--Client/Assembly-CSharp/SkinsTab.cs83
-rw-r--r--Client/Assembly-CSharp/SlideBar.cs78
-rw-r--r--Client/Assembly-CSharp/SortGameObject.cs16
-rw-r--r--Client/Assembly-CSharp/SortMinigame.cs72
-rw-r--r--Client/Assembly-CSharp/SoundGroup.cs13
-rw-r--r--Client/Assembly-CSharp/SoundManager.cs239
-rw-r--r--Client/Assembly-CSharp/SoundStarter.cs23
-rw-r--r--Client/Assembly-CSharp/SpinAnimator.cs72
-rw-r--r--Client/Assembly-CSharp/SpriteParticle.cs128
-rw-r--r--Client/Assembly-CSharp/StarGen.cs170
-rw-r--r--Client/Assembly-CSharp/StatsManager.cs439
-rw-r--r--Client/Assembly-CSharp/StatsPopup.cs32
-rw-r--r--Client/Assembly-CSharp/SteamBehaviour.cs35
-rw-r--r--Client/Assembly-CSharp/SteamManager.cs131
-rw-r--r--Client/Assembly-CSharp/SteamPurchasingModule.cs118
-rw-r--r--Client/Assembly-CSharp/StoreMenu.cs409
-rw-r--r--Client/Assembly-CSharp/StringExtensions.cs32
-rw-r--r--Client/Assembly-CSharp/StringNames.cs168
-rw-r--r--Client/Assembly-CSharp/StringOption.cs60
-rw-r--r--Client/Assembly-CSharp/SubString.cs77
-rw-r--r--Client/Assembly-CSharp/SubStringReader.cs51
-rw-r--r--Client/Assembly-CSharp/SurvCamera.cs28
-rw-r--r--Client/Assembly-CSharp/SurveillanceMinigame.cs140
-rw-r--r--Client/Assembly-CSharp/SweepMinigame.cs123
-rw-r--r--Client/Assembly-CSharp/SwitchMinigame.cs97
-rw-r--r--Client/Assembly-CSharp/SwitchSystem.cs107
-rw-r--r--Client/Assembly-CSharp/SystemConsole.cs77
-rw-r--r--Client/Assembly-CSharp/SystemTypeHelpers.cs7
-rw-r--r--Client/Assembly-CSharp/SystemTypes.cs30
-rw-r--r--Client/Assembly-CSharp/TabButton.cs10
-rw-r--r--Client/Assembly-CSharp/TabGroup.cs25
-rw-r--r--Client/Assembly-CSharp/TaskAddButton.cs109
-rw-r--r--Client/Assembly-CSharp/TaskAdderGame.cs168
-rw-r--r--Client/Assembly-CSharp/TaskFolder.cs31
-rw-r--r--Client/Assembly-CSharp/TaskPanelBehaviour.cs52
-rw-r--r--Client/Assembly-CSharp/TaskSet.cs9
-rw-r--r--Client/Assembly-CSharp/TaskTypes.cs31
-rw-r--r--Client/Assembly-CSharp/TaskTypesHelpers.cs7
-rw-r--r--Client/Assembly-CSharp/TempData.cs74
-rw-r--r--Client/Assembly-CSharp/TextBox.cs257
-rw-r--r--Client/Assembly-CSharp/TextController.cs130
-rw-r--r--Client/Assembly-CSharp/TextLink.cs26
-rw-r--r--Client/Assembly-CSharp/TextRenderer.cs482
-rw-r--r--Client/Assembly-CSharp/TextTranslator.cs24
-rw-r--r--Client/Assembly-CSharp/ToggleButtonBehaviour.cs42
-rw-r--r--Client/Assembly-CSharp/ToggleOption.cs45
-rw-r--r--Client/Assembly-CSharp/TowerBehaviour.cs43
-rw-r--r--Client/Assembly-CSharp/TransitionOpen.cs54
-rw-r--r--Client/Assembly-CSharp/TransitionType.cs7
-rw-r--r--Client/Assembly-CSharp/TranslatedImageSet.cs8
-rw-r--r--Client/Assembly-CSharp/TranslationController.cs70
-rw-r--r--Client/Assembly-CSharp/TumbleBoxBehaviour.cs24
-rw-r--r--Client/Assembly-CSharp/TuneRadioMinigame.cs104
-rw-r--r--Client/Assembly-CSharp/TutorialManager.cs63
-rw-r--r--Client/Assembly-CSharp/TutorialStatsManager.cs12
-rw-r--r--Client/Assembly-CSharp/TwitterLink.cs12
-rw-r--r--Client/Assembly-CSharp/UnlockManifoldsMinigame.cs100
-rw-r--r--Client/Assembly-CSharp/UnlockPopUp.cs22
-rw-r--r--Client/Assembly-CSharp/UploadDataGame.cs203
-rw-r--r--Client/Assembly-CSharp/UploadDataTask.cs34
-rw-r--r--Client/Assembly-CSharp/UseButtonManager.cs108
-rw-r--r--Client/Assembly-CSharp/Vector2Range.cs55
-rw-r--r--Client/Assembly-CSharp/VendingMinigame.cs188
-rw-r--r--Client/Assembly-CSharp/VendingSlot.cs37
-rw-r--r--Client/Assembly-CSharp/Vent.cs163
-rw-r--r--Client/Assembly-CSharp/VersionShower.cs19
-rw-r--r--Client/Assembly-CSharp/VerticalGauge.cs28
-rw-r--r--Client/Assembly-CSharp/VirtualJoystick.cs59
-rw-r--r--Client/Assembly-CSharp/VoteBanSystem.cs111
-rw-r--r--Client/Assembly-CSharp/WaitForHostPopup.cs27
-rw-r--r--Client/Assembly-CSharp/WaitForLerp.cs38
-rw-r--r--Client/Assembly-CSharp/WeaponsMinigame.cs149
-rw-r--r--Client/Assembly-CSharp/WeatherMinigame.cs34
-rw-r--r--Client/Assembly-CSharp/WinningPlayerData.cs36
-rw-r--r--Client/Assembly-CSharp/Wire.cs61
-rw-r--r--Client/Assembly-CSharp/WireMinigame.cs157
-rw-r--r--Client/Assembly-CSharp/WireNode.cs19
-rw-r--r--Client/Assembly-CSharp/XXHash.cs189
-rw-r--r--Client/Assembly-CSharp/obj/Debug/Assembly-CSharp.csproj.CoreCompileInputs.cache1
-rw-r--r--Client/Assembly-CSharp/obj/Debug/Assembly-CSharp.csprojAssemblyReference.cachebin0 -> 44249 bytes
-rw-r--r--Impostor-dev/.config/dotnet-tools.json12
-rw-r--r--Impostor-dev/.dockerignore9
-rw-r--r--Impostor-dev/.github/ISSUE_TEMPLATE/1--bug-reporting.md35
-rw-r--r--Impostor-dev/.github/ISSUE_TEMPLATE/2--feature-request.md19
-rw-r--r--Impostor-dev/.github/ISSUE_TEMPLATE/3--api-suggestion.md20
-rw-r--r--Impostor-dev/.github/ISSUE_TEMPLATE/4--api-invalid-data.md25
-rw-r--r--Impostor-dev/.github/ISSUE_TEMPLATE/5--api-unavailable-data.md20
-rw-r--r--Impostor-dev/.github/ISSUE_TEMPLATE/6--api-other.md10
-rw-r--r--Impostor-dev/.github/ISSUE_TEMPLATE/config.yml1
-rw-r--r--Impostor-dev/.github/PULL_REQUEST_TEMPLATE.md16
-rw-r--r--Impostor-dev/.github/stale.yml17
-rw-r--r--Impostor-dev/.github/workflows/docker.yml70
-rw-r--r--Impostor-dev/.gitignore7
-rw-r--r--Impostor-dev/CONTRIBUTING.md22
-rw-r--r--Impostor-dev/Dockerfile39
-rw-r--r--Impostor-dev/LICENSE674
-rw-r--r--Impostor-dev/README.md72
-rw-r--r--Impostor-dev/appveyor.yml51
-rw-r--r--Impostor-dev/build.cake178
-rw-r--r--Impostor-dev/docs/Building-from-source.md45
-rw-r--r--Impostor-dev/docs/FAQ.md11
-rw-r--r--Impostor-dev/docs/README.md7
-rw-r--r--Impostor-dev/docs/Running-the-server.md101
-rw-r--r--Impostor-dev/docs/Server-configuration.md77
-rw-r--r--Impostor-dev/docs/TROUBLESHOOTING.md28
-rw-r--r--Impostor-dev/docs/Writing-a-plugin.md343
-rw-r--r--Impostor-dev/docs/images/client.jpgbin0 -> 51524 bytes
-rw-r--r--Impostor-dev/docs/images/logo_458.pngbin0 -> 64934 bytes
-rw-r--r--Impostor-dev/docs/images/logo_64.pngbin0 -> 7075 bytes
-rw-r--r--Impostor-dev/src/.editorconfig230
-rw-r--r--Impostor-dev/src/.gitattributes63
-rw-r--r--Impostor-dev/src/.gitignore263
-rw-r--r--Impostor-dev/src/Impostor.Api/Events/Attributes/EventListenerAttribute.cs34
-rw-r--r--Impostor-dev/src/Impostor.Api/Events/EventPriority.cs12
-rw-r--r--Impostor-dev/src/Impostor.Api/Events/Game/IGameAlterEvent.cs7
-rw-r--r--Impostor-dev/src/Impostor.Api/Events/Game/IGameCreatedEvent.cs11
-rw-r--r--Impostor-dev/src/Impostor.Api/Events/Game/IGameDestroyedEvent.cs11
-rw-r--r--Impostor-dev/src/Impostor.Api/Events/Game/IGameEndedEvent.cs9
-rw-r--r--Impostor-dev/src/Impostor.Api/Events/Game/IGameEvent.cs12
-rw-r--r--Impostor-dev/src/Impostor.Api/Events/Game/IGamePlayerJoinedEvent.cs6
-rw-r--r--Impostor-dev/src/Impostor.Api/Events/Game/IGamePlayerLeftEvent.cs6
-rw-r--r--Impostor-dev/src/Impostor.Api/Events/Game/IGameStartedEvent.cs9
-rw-r--r--Impostor-dev/src/Impostor.Api/Events/Game/IGameStartingEvent.cs11
-rw-r--r--Impostor-dev/src/Impostor.Api/Events/Game/Meeting/IMeetingEndedEvent.cs6
-rw-r--r--Impostor-dev/src/Impostor.Api/Events/Game/Meeting/IMeetingEvent.cs9
-rw-r--r--Impostor-dev/src/Impostor.Api/Events/Game/Meeting/IMeetingStartedEvent.cs6
-rw-r--r--Impostor-dev/src/Impostor.Api/Events/Game/Player/IPlayerChatEvent.cs10
-rw-r--r--Impostor-dev/src/Impostor.Api/Events/Game/Player/IPlayerCompletedTaskEvent.cs10
-rw-r--r--Impostor-dev/src/Impostor.Api/Events/Game/Player/IPlayerDestroyedEvent.cs6
-rw-r--r--Impostor-dev/src/Impostor.Api/Events/Game/Player/IPlayerEvent.cs19
-rw-r--r--Impostor-dev/src/Impostor.Api/Events/Game/Player/IPlayerExileEvent.cs9
-rw-r--r--Impostor-dev/src/Impostor.Api/Events/Game/Player/IPlayerMurderEvent.cs12
-rw-r--r--Impostor-dev/src/Impostor.Api/Events/Game/Player/IPlayerSetStartCounterEvent.cs10
-rw-r--r--Impostor-dev/src/Impostor.Api/Events/Game/Player/IPlayerSpawnedEvent.cs6
-rw-r--r--Impostor-dev/src/Impostor.Api/Events/Game/Player/IPlayerStartMeetingEvent.cs12
-rw-r--r--Impostor-dev/src/Impostor.Api/Events/Game/Player/IPlayerVentEvent.cs17
-rw-r--r--Impostor-dev/src/Impostor.Api/Events/IEvent.cs6
-rw-r--r--Impostor-dev/src/Impostor.Api/Events/IEventCancelable.cs10
-rw-r--r--Impostor-dev/src/Impostor.Api/Events/IEventListener.cs6
-rw-r--r--Impostor-dev/src/Impostor.Api/Events/IManualEventListener.cs13
-rw-r--r--Impostor-dev/src/Impostor.Api/Events/Managers/IEventManager.cs35
-rw-r--r--Impostor-dev/src/Impostor.Api/Exceptions/ImpostorCheatException.cs24
-rw-r--r--Impostor-dev/src/Impostor.Api/Exceptions/ImpostorConfigException.cs24
-rw-r--r--Impostor-dev/src/Impostor.Api/Exceptions/ImpostorException.cs24
-rw-r--r--Impostor-dev/src/Impostor.Api/Exceptions/ImpostorProtocolException.cs24
-rw-r--r--Impostor-dev/src/Impostor.Api/Extensions/SpanReaderExtensions.cs70
-rw-r--r--Impostor-dev/src/Impostor.Api/Extensions/SystemTypesExtensions.cs12
-rw-r--r--Impostor-dev/src/Impostor.Api/Games/Extensions/GameExtensions.cs42
-rw-r--r--Impostor-dev/src/Impostor.Api/Games/Extensions/GameManagerExtensions.cs14
-rw-r--r--Impostor-dev/src/Impostor.Api/Games/GameCode.cs74
-rw-r--r--Impostor-dev/src/Impostor.Api/Games/GameJoinError.cs48
-rw-r--r--Impostor-dev/src/Impostor.Api/Games/GameJoinResult.cs48
-rw-r--r--Impostor-dev/src/Impostor.Api/Games/IGame.cs88
-rw-r--r--Impostor-dev/src/Impostor.Api/Games/IGameCodeFactory.cs7
-rw-r--r--Impostor-dev/src/Impostor.Api/Games/Managers/IGameManager.cs11
-rw-r--r--Impostor-dev/src/Impostor.Api/Impostor.Api.csproj38
-rw-r--r--Impostor-dev/src/Impostor.Api/Impostor.Api.csproj.DotSettings8
-rw-r--r--Impostor-dev/src/Impostor.Api/Innersloth/AlterGameTags.cs7
-rw-r--r--Impostor-dev/src/Impostor.Api/Innersloth/ChatNoteType.cs7
-rw-r--r--Impostor-dev/src/Impostor.Api/Innersloth/Customization/ColorType.cs18
-rw-r--r--Impostor-dev/src/Impostor.Api/Innersloth/Customization/HatType.cs100
-rw-r--r--Impostor-dev/src/Impostor.Api/Innersloth/Customization/PetType.cs18
-rw-r--r--Impostor-dev/src/Impostor.Api/Innersloth/Customization/SkinType.cs22
-rw-r--r--Impostor-dev/src/Impostor.Api/Innersloth/DeathReason.cs9
-rw-r--r--Impostor-dev/src/Impostor.Api/Innersloth/DisconnectReason.cs54
-rw-r--r--Impostor-dev/src/Impostor.Api/Innersloth/FloatRange.cs22
-rw-r--r--Impostor-dev/src/Impostor.Api/Innersloth/GameCodeParser.cs142
-rw-r--r--Impostor-dev/src/Impostor.Api/Innersloth/GameKeywords.cs19
-rw-r--r--Impostor-dev/src/Impostor.Api/Innersloth/GameOptionsData.cs261
-rw-r--r--Impostor-dev/src/Impostor.Api/Innersloth/GameOverReason.cs15
-rw-r--r--Impostor-dev/src/Impostor.Api/Innersloth/GameStates.cs11
-rw-r--r--Impostor-dev/src/Impostor.Api/Innersloth/GameVersion.cs10
-rw-r--r--Impostor-dev/src/Impostor.Api/Innersloth/KillDistances.cs12
-rw-r--r--Impostor-dev/src/Impostor.Api/Innersloth/MapFlags.cs12
-rw-r--r--Impostor-dev/src/Impostor.Api/Innersloth/MapTypes.cs9
-rw-r--r--Impostor-dev/src/Impostor.Api/Innersloth/RegionInfo.cs48
-rw-r--r--Impostor-dev/src/Impostor.Api/Innersloth/ServerInfo.cs37
-rw-r--r--Impostor-dev/src/Impostor.Api/Innersloth/SystemTypeHelpers.cs28
-rw-r--r--Impostor-dev/src/Impostor.Api/Innersloth/SystemTypes.cs42
-rw-r--r--Impostor-dev/src/Impostor.Api/Innersloth/TaskBarUpdate.cs9
-rw-r--r--Impostor-dev/src/Impostor.Api/Innersloth/TaskTypes.cs49
-rw-r--r--Impostor-dev/src/Impostor.Api/Innersloth/TextBox.cs10
-rw-r--r--Impostor-dev/src/Impostor.Api/Innersloth/VentLocation.cs48
-rw-r--r--Impostor-dev/src/Impostor.Api/Net/IClient.cs76
-rw-r--r--Impostor-dev/src/Impostor.Api/Net/IClientPlayer.cs43
-rw-r--r--Impostor-dev/src/Impostor.Api/Net/IConnection.cs7
-rw-r--r--Impostor-dev/src/Impostor.Api/Net/IHazelConnection.cs41
-rw-r--r--Impostor-dev/src/Impostor.Api/Net/Inner/IGameNet.cs18
-rw-r--r--Impostor-dev/src/Impostor.Api/Net/Inner/IInnerNetObject.cs9
-rw-r--r--Impostor-dev/src/Impostor.Api/Net/Inner/Objects/Components/IInnerCustomNetworkTransform.cs15
-rw-r--r--Impostor-dev/src/Impostor.Api/Net/Inner/Objects/Components/IInnerPlayerPhysics.cs6
-rw-r--r--Impostor-dev/src/Impostor.Api/Net/Inner/Objects/IInnerGameData.cs6
-rw-r--r--Impostor-dev/src/Impostor.Api/Net/Inner/Objects/IInnerLobbyBehaviour.cs6
-rw-r--r--Impostor-dev/src/Impostor.Api/Net/Inner/Objects/IInnerMeetingHud.cs6
-rw-r--r--Impostor-dev/src/Impostor.Api/Net/Inner/Objects/IInnerPlayerControl.cs116
-rw-r--r--Impostor-dev/src/Impostor.Api/Net/Inner/Objects/IInnerPlayerInfo.cs53
-rw-r--r--Impostor-dev/src/Impostor.Api/Net/Inner/Objects/IInnerShipStatus.cs7
-rw-r--r--Impostor-dev/src/Impostor.Api/Net/Inner/Objects/IInnerVoteBanSystem.cs7
-rw-r--r--Impostor-dev/src/Impostor.Api/Net/Inner/Objects/ITaskInfo.cs14
-rw-r--r--Impostor-dev/src/Impostor.Api/Net/LimboStates.cs13
-rw-r--r--Impostor-dev/src/Impostor.Api/Net/Manager/IClientManager.cs9
-rw-r--r--Impostor-dev/src/Impostor.Api/Net/Messages/C2S/Message00HostGameC2S.cs27
-rw-r--r--Impostor-dev/src/Impostor.Api/Net/Messages/C2S/Message01JoinGameC2S.cs20
-rw-r--r--Impostor-dev/src/Impostor.Api/Net/Messages/C2S/Message04RemovePlayerC2S.cs16
-rw-r--r--Impostor-dev/src/Impostor.Api/Net/Messages/C2S/Message08EndGameC2S.cs18
-rw-r--r--Impostor-dev/src/Impostor.Api/Net/Messages/C2S/Message10AlterGameC2S.cs20
-rw-r--r--Impostor-dev/src/Impostor.Api/Net/Messages/C2S/Message11KickPlayerC2S.cs16
-rw-r--r--Impostor-dev/src/Impostor.Api/Net/Messages/C2S/Message16GetGameListC2S.cs18
-rw-r--r--Impostor-dev/src/Impostor.Api/Net/Messages/IMessageReader.cs68
-rw-r--r--Impostor-dev/src/Impostor.Api/Net/Messages/IMessageWriter.cs127
-rw-r--r--Impostor-dev/src/Impostor.Api/Net/Messages/IMessageWriterProvider.cs16
-rw-r--r--Impostor-dev/src/Impostor.Api/Net/Messages/MessageFlags.cs22
-rw-r--r--Impostor-dev/src/Impostor.Api/Net/Messages/MessageType.cs32
-rw-r--r--Impostor-dev/src/Impostor.Api/Net/Messages/S2C/Message00HostGameS2C.cs20
-rw-r--r--Impostor-dev/src/Impostor.Api/Net/Messages/S2C/Message01JoinGameS2C.cs50
-rw-r--r--Impostor-dev/src/Impostor.Api/Net/Messages/S2C/Message04RemovePlayerS2C.cs29
-rw-r--r--Impostor-dev/src/Impostor.Api/Net/Messages/S2C/Message07JoinedGameS2C.cs31
-rw-r--r--Impostor-dev/src/Impostor.Api/Net/Messages/S2C/Message10AlterGameS2C.cs26
-rw-r--r--Impostor-dev/src/Impostor.Api/Net/Messages/S2C/Message11KickPlayerS2C.cs24
-rw-r--r--Impostor-dev/src/Impostor.Api/Net/Messages/S2C/Message12WaitForHostS2C.cs23
-rw-r--r--Impostor-dev/src/Impostor.Api/Net/Messages/S2C/Message13RedirectS2C.cs25
-rw-r--r--Impostor-dev/src/Impostor.Api/Net/Messages/S2C/Message16GetGameListS2C.cs46
-rw-r--r--Impostor-dev/src/Impostor.Api/Plugins/IPlugin.cs14
-rw-r--r--Impostor-dev/src/Impostor.Api/Plugins/IPluginStartup.cs12
-rw-r--r--Impostor-dev/src/Impostor.Api/Plugins/ImpostorPluginAttribute.cs24
-rw-r--r--Impostor-dev/src/Impostor.Api/Plugins/PluginBase.cs22
-rw-r--r--Impostor-dev/src/Impostor.Api/ProjectRules.ruleset17
-rw-r--r--Impostor-dev/src/Impostor.Api/Properties/AssemblyInfo.cs3
-rw-r--r--Impostor-dev/src/Impostor.Api/Unity/Mathf.cs54
-rw-r--r--Impostor-dev/src/Impostor.Benchmarks/.gitignore1
-rw-r--r--Impostor-dev/src/Impostor.Benchmarks/Data/MessageReader_Bytes.cs172
-rw-r--r--Impostor-dev/src/Impostor.Benchmarks/Data/MessageReader_Bytes_Pooled.cs220
-rw-r--r--Impostor-dev/src/Impostor.Benchmarks/Data/MessageReader_Bytes_Pooled_Improved.cs90
-rw-r--r--Impostor-dev/src/Impostor.Benchmarks/Data/MessageWriter.cs311
-rw-r--r--Impostor-dev/src/Impostor.Benchmarks/Data/Pool/MessageReader_Bytes_Pooled_ImprovedPolicy.cs26
-rw-r--r--Impostor-dev/src/Impostor.Benchmarks/Data/Span/MessageReaderOwner.cs22
-rw-r--r--Impostor-dev/src/Impostor.Benchmarks/Data/Span/MessageReader_Span.cs50
-rw-r--r--Impostor-dev/src/Impostor.Benchmarks/Extensions/SpanExtensions.cs24
-rw-r--r--Impostor-dev/src/Impostor.Benchmarks/Impostor.Benchmarks.csproj17
-rw-r--r--Impostor-dev/src/Impostor.Benchmarks/Program.cs23
-rw-r--r--Impostor-dev/src/Impostor.Benchmarks/Tests/EventManagerBenchmark.cs77
-rw-r--r--Impostor-dev/src/Impostor.Benchmarks/Tests/MessageReaderBenchmark.cs132
-rw-r--r--Impostor-dev/src/Impostor.Client.App/Impostor.Client.App.csproj16
-rw-r--r--Impostor-dev/src/Impostor.Client.App/Program.cs75
-rw-r--r--Impostor-dev/src/Impostor.Client/Impostor.Client.csproj12
-rw-r--r--Impostor-dev/src/Impostor.Hazel/Connection.cs249
-rw-r--r--Impostor-dev/src/Impostor.Hazel/ConnectionListener.cs100
-rw-r--r--Impostor-dev/src/Impostor.Hazel/ConnectionState.cs23
-rw-r--r--Impostor-dev/src/Impostor.Hazel/ConnectionStatistics.cs566
-rw-r--r--Impostor-dev/src/Impostor.Hazel/DataReceivedEventArgs.cs26
-rw-r--r--Impostor-dev/src/Impostor.Hazel/DisconnectedEventArgs.cs25
-rw-r--r--Impostor-dev/src/Impostor.Hazel/Extensions/ServiceProviderExtensions.cs21
-rw-r--r--Impostor-dev/src/Impostor.Hazel/HazelException.cs21
-rw-r--r--Impostor-dev/src/Impostor.Hazel/IPMode.cs24
-rw-r--r--Impostor-dev/src/Impostor.Hazel/IRecyclable.cs24
-rw-r--r--Impostor-dev/src/Impostor.Hazel/Impostor.Hazel.csproj19
-rw-r--r--Impostor-dev/src/Impostor.Hazel/MessageReader.cs256
-rw-r--r--Impostor-dev/src/Impostor.Hazel/MessageReaderPolicy.cs27
-rw-r--r--Impostor-dev/src/Impostor.Hazel/MessageWriter.cs335
-rw-r--r--Impostor-dev/src/Impostor.Hazel/NetworkConnection.cs121
-rw-r--r--Impostor-dev/src/Impostor.Hazel/NetworkConnectionListener.cs21
-rw-r--r--Impostor-dev/src/Impostor.Hazel/NewConnectionEventArgs.cs24
-rw-r--r--Impostor-dev/src/Impostor.Hazel/ObjectPoolCustom.cs107
-rw-r--r--Impostor-dev/src/Impostor.Hazel/Udp/SendOptionInternal.cs33
-rw-r--r--Impostor-dev/src/Impostor.Hazel/Udp/UdpBroadcastListener.cs156
-rw-r--r--Impostor-dev/src/Impostor.Hazel/Udp/UdpBroadcaster.cs79
-rw-r--r--Impostor-dev/src/Impostor.Hazel/Udp/UdpClientConnection.cs225
-rw-r--r--Impostor-dev/src/Impostor.Hazel/Udp/UdpConnection.KeepAlive.cs167
-rw-r--r--Impostor-dev/src/Impostor.Hazel/Udp/UdpConnection.Reliable.cs491
-rw-r--r--Impostor-dev/src/Impostor.Hazel/Udp/UdpConnection.cs312
-rw-r--r--Impostor-dev/src/Impostor.Hazel/Udp/UdpConnectionListener.cs281
-rw-r--r--Impostor-dev/src/Impostor.Hazel/Udp/UdpConnectionRateLimit.cs75
-rw-r--r--Impostor-dev/src/Impostor.Hazel/Udp/UdpServerConnection.cs97
-rw-r--r--Impostor-dev/src/Impostor.Patcher/Directory.Build.props8
-rw-r--r--Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.Cli/Impostor.Patcher.Cli.csproj19
-rw-r--r--Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.Cli/Program.cs88
-rw-r--r--Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.Shared/AmongUsModifier.cs247
-rw-r--r--Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.Shared/Configuration.cs65
-rw-r--r--Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.Shared/Events/ErrorEventArgs.cs14
-rw-r--r--Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.Shared/Events/SavedEventArgs.cs16
-rw-r--r--Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.Shared/Impostor.Patcher.Shared.csproj12
-rw-r--r--Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.Shared/Innersloth/RegionInfo.cs48
-rw-r--r--Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.Shared/Innersloth/ServerInfo.cs37
-rw-r--r--Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.WinForms/App.config7
-rw-r--r--Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.WinForms/Forms/FrmMain.Designer.cs141
-rw-r--r--Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.WinForms/Forms/FrmMain.cs114
-rw-r--r--Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.WinForms/Forms/FrmMain.resx2338
-rw-r--r--Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.WinForms/Impostor.Patcher.WinForms.csproj22
-rw-r--r--Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.WinForms/Program.cs17
-rw-r--r--Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.WinForms/Properties/Resources.Designer.cs69
-rw-r--r--Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.WinForms/Properties/Resources.resx117
-rw-r--r--Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.WinForms/Properties/Settings.Designer.cs26
-rw-r--r--Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.WinForms/Properties/Settings.settings7
-rw-r--r--Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.WinForms/icon.icobin0 -> 132738 bytes
-rw-r--r--Impostor-dev/src/Impostor.Plugins.Debugger/App.razor10
-rw-r--r--Impostor-dev/src/Impostor.Plugins.Debugger/DebugPlugin.cs13
-rw-r--r--Impostor-dev/src/Impostor.Plugins.Debugger/DebugPluginStartup.cs35
-rw-r--r--Impostor-dev/src/Impostor.Plugins.Debugger/Impostor.Plugins.Debugger.csproj16
-rw-r--r--Impostor-dev/src/Impostor.Plugins.Debugger/Pages/Index.razor69
-rw-r--r--Impostor-dev/src/Impostor.Plugins.Debugger/Pages/_Host.cshtml19
-rw-r--r--Impostor-dev/src/Impostor.Plugins.Debugger/Shared/MainLayout.razor5
-rw-r--r--Impostor-dev/src/Impostor.Plugins.Debugger/_Imports.razor8
-rw-r--r--Impostor-dev/src/Impostor.Plugins.Example/ExamplePlugin.cs33
-rw-r--r--Impostor-dev/src/Impostor.Plugins.Example/ExamplePluginStartup.cs22
-rw-r--r--Impostor-dev/src/Impostor.Plugins.Example/Handlers/GameEventListener.cs64
-rw-r--r--Impostor-dev/src/Impostor.Plugins.Example/Handlers/MeetingEventListener.cs21
-rw-r--r--Impostor-dev/src/Impostor.Plugins.Example/Handlers/PlayerEventListener.cs100
-rw-r--r--Impostor-dev/src/Impostor.Plugins.Example/Impostor.Plugins.Example.csproj11
-rw-r--r--Impostor-dev/src/Impostor.Server/Config/AntiCheatConfig.cs9
-rw-r--r--Impostor-dev/src/Impostor.Server/Config/DebugConfig.cs11
-rw-r--r--Impostor-dev/src/Impostor.Server/Config/DisconnectMessages.cs19
-rw-r--r--Impostor-dev/src/Impostor.Server/Config/ServerConfig.cs30
-rw-r--r--Impostor-dev/src/Impostor.Server/Config/ServerRedirectorConfig.cs24
-rw-r--r--Impostor-dev/src/Impostor.Server/Config/ServerRedirectorNode.cs9
-rw-r--r--Impostor-dev/src/Impostor.Server/Constants.cs8
-rw-r--r--Impostor-dev/src/Impostor.Server/Events/EventHandler.cs24
-rw-r--r--Impostor-dev/src/Impostor.Server/Events/EventManager.cs167
-rw-r--r--Impostor-dev/src/Impostor.Server/Events/Game/GameAlterEvent.cs18
-rw-r--r--Impostor-dev/src/Impostor.Server/Events/Game/GameCreatedEvent.cs15
-rw-r--r--Impostor-dev/src/Impostor.Server/Events/Game/GameDestroyedEvent.cs15
-rw-r--r--Impostor-dev/src/Impostor.Server/Events/Game/GameEndedEvent.cs19
-rw-r--r--Impostor-dev/src/Impostor.Server/Events/Game/GamePlayerJoinedEvent.cs19
-rw-r--r--Impostor-dev/src/Impostor.Server/Events/Game/GamePlayerLeftEvent.cs22
-rw-r--r--Impostor-dev/src/Impostor.Server/Events/Game/GameStartedEvent.cs15
-rw-r--r--Impostor-dev/src/Impostor.Server/Events/Game/GameStartingEvent.cs15
-rw-r--r--Impostor-dev/src/Impostor.Server/Events/Game/Meeting/MeetingEndedEvent.cs19
-rw-r--r--Impostor-dev/src/Impostor.Server/Events/Game/Meeting/MeetingStartedEvent.cs19
-rw-r--r--Impostor-dev/src/Impostor.Server/Events/Game/Player/PlayerChatEvent.cs26
-rw-r--r--Impostor-dev/src/Impostor.Server/Events/Game/Player/PlayerCompletedTaskEvent.cs27
-rw-r--r--Impostor-dev/src/Impostor.Server/Events/Game/Player/PlayerDestroyedEvent.cs23
-rw-r--r--Impostor-dev/src/Impostor.Server/Events/Game/Player/PlayerExileEvent.cs23
-rw-r--r--Impostor-dev/src/Impostor.Server/Events/Game/Player/PlayerMovementEvent.cs24
-rw-r--r--Impostor-dev/src/Impostor.Server/Events/Game/Player/PlayerMurderEvent.cs26
-rw-r--r--Impostor-dev/src/Impostor.Server/Events/Game/Player/PlayerSetStartCounterEvent.cs26
-rw-r--r--Impostor-dev/src/Impostor.Server/Events/Game/Player/PlayerSpawnedEvent.cs23
-rw-r--r--Impostor-dev/src/Impostor.Server/Events/Game/Player/PlayerStartMeetingEvent.cs26
-rw-r--r--Impostor-dev/src/Impostor.Server/Events/Game/Player/PlayerVentEvent.cs30
-rw-r--r--Impostor-dev/src/Impostor.Server/Events/MultiDisposable.cs26
-rw-r--r--Impostor-dev/src/Impostor.Server/Events/Register/IRegisteredEventListener.cs15
-rw-r--r--Impostor-dev/src/Impostor.Server/Events/Register/InvokedRegisteredEventListener.cs27
-rw-r--r--Impostor-dev/src/Impostor.Server/Events/Register/ManualRegisteredEventListener.cs30
-rw-r--r--Impostor-dev/src/Impostor.Server/Events/Register/RegisteredEventListener.cs166
-rw-r--r--Impostor-dev/src/Impostor.Server/Events/Register/TemporaryEventRegister.cs59
-rw-r--r--Impostor-dev/src/Impostor.Server/Events/Register/WrappedRegisteredEventListener.cs27
-rw-r--r--Impostor-dev/src/Impostor.Server/Extensions/MessageReaderExtensions.cs15
-rw-r--r--Impostor-dev/src/Impostor.Server/Extensions/NodeLocatorExtensions.cs13
-rw-r--r--Impostor-dev/src/Impostor.Server/Extensions/TypeExtensions.cs67
-rw-r--r--Impostor-dev/src/Impostor.Server/Impostor.Server.csproj58
-rw-r--r--Impostor-dev/src/Impostor.Server/Impostor.Server.csproj.DotSettings3
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Client.cs338
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/ClientBase.cs58
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Factories/ClientFactory.cs24
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Factories/IClientFactory.cs9
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/GameCodeFactory.cs12
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Hazel/HazelConnection.cs74
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Inner/GameDataTag.cs13
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Inner/GameObject.cs29
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Inner/InnerNetObject.cs31
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Components/InnerCustomNetworkTransform.Api.cs25
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Components/InnerCustomNetworkTransform.cs148
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Components/InnerPlayerPhysics.Api.cs8
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Components/InnerPlayerPhysics.cs70
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Components/InnerVoteBanSystem.cs88
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerGameData.TaskInfo.cs30
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerGameData.cs187
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerLobbyBehaviour.cs36
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerMeetingHud.Api.cs9
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerMeetingHud.PlayerVoteArea.cs49
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerMeetingHud.cs176
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.Api.cs138
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs455
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.Api.cs10
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs76
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerShipStatus.cs147
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/IActivatable.cs7
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ISystemType.cs11
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/DoorsSystemType.cs60
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/HudOverrideSystemType.cs19
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/LifeSuppSystemType.cs44
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/MedScanSystem.cs32
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/ReactorSystemType.cs39
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/SabotageSystemType.cs26
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/SecurityCameraSystemType.cs19
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/SwitchSystem.cs27
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Inner/RpcCalls.cs37
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Inner/SpawnFlags.cs11
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Manager/ClientManager.Api.cs11
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Manager/ClientManager.cs103
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Manager/GameManager.cs150
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Matchmaker.cs66
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/MatchmakerService.cs57
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Messages/MessageWriterProvider.cs13
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Redirector/ClientRedirector.cs97
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Redirector/INodeLocator.cs14
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Redirector/INodeProvider.cs9
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Redirector/NodeLocatorNoOp.cs14
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Redirector/NodeLocatorRedis.cs43
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Redirector/NodeLocatorUDP.cs134
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Redirector/NodeLocatorUDPService.cs101
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/Redirector/NodeProviderConfig.cs43
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/State/ClientPlayer.Api.cs18
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/State/ClientPlayer.cs88
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/State/Game.Api.cs70
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/State/Game.Data.cs449
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/State/Game.Incoming.cs240
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/State/Game.Outgoing.cs103
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/State/Game.State.cs136
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/State/Game.cs126
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/State/GameNet.Api.cs17
-rw-r--r--Impostor-dev/src/Impostor.Server/Net/State/GameNet.cs16
-rw-r--r--Impostor-dev/src/Impostor.Server/Plugins/AssemblyInformation.cs38
-rw-r--r--Impostor-dev/src/Impostor.Server/Plugins/IAssemblyInformation.cs14
-rw-r--r--Impostor-dev/src/Impostor.Server/Plugins/LoadedAssemblyInformation.cs25
-rw-r--r--Impostor-dev/src/Impostor.Server/Plugins/PluginConfig.cs11
-rw-r--r--Impostor-dev/src/Impostor.Server/Plugins/PluginInformation.cs38
-rw-r--r--Impostor-dev/src/Impostor.Server/Plugins/PluginLoader.cs147
-rw-r--r--Impostor-dev/src/Impostor.Server/Plugins/PluginLoaderException.cs25
-rw-r--r--Impostor-dev/src/Impostor.Server/Plugins/PluginLoaderService.cs60
-rw-r--r--Impostor-dev/src/Impostor.Server/Program.cs207
-rw-r--r--Impostor-dev/src/Impostor.Server/ProjectRules.ruleset21
-rw-r--r--Impostor-dev/src/Impostor.Server/Properties/AssemblyInfo.cs5
-rw-r--r--Impostor-dev/src/Impostor.Server/Recorder/ClientRecorder.cs86
-rw-r--r--Impostor-dev/src/Impostor.Server/Recorder/PacketRecorder.cs201
-rw-r--r--Impostor-dev/src/Impostor.Server/Recorder/PacketSerializationContext.cs56
-rw-r--r--Impostor-dev/src/Impostor.Server/Recorder/PacketSerializationContextPooledObjectPolicy.cs18
-rw-r--r--Impostor-dev/src/Impostor.Server/Recorder/RecordedPacketType.cs10
-rw-r--r--Impostor-dev/src/Impostor.Server/Utils/DotnetUtils.cs20
-rw-r--r--Impostor-dev/src/Impostor.Server/Utils/IpUtils.cs42
-rw-r--r--Impostor-dev/src/Impostor.Server/config-full.json29
-rw-r--r--Impostor-dev/src/Impostor.Server/config.json11
-rw-r--r--Impostor-dev/src/Impostor.Server/icon.icobin0 -> 132738 bytes
-rw-r--r--Impostor-dev/src/Impostor.Tests/Events/EventManagerTests.cs175
-rw-r--r--Impostor-dev/src/Impostor.Tests/GameCodeTests.cs29
-rw-r--r--Impostor-dev/src/Impostor.Tests/Hazel/MessageReaderTests.cs372
-rw-r--r--Impostor-dev/src/Impostor.Tests/Hazel/MessageWriterTests.cs38
-rw-r--r--Impostor-dev/src/Impostor.Tests/Impostor.Tests.csproj20
-rw-r--r--Impostor-dev/src/Impostor.Tools.Proxy/HexUtils.cs70
-rw-r--r--Impostor-dev/src/Impostor.Tools.Proxy/Impostor.Tools.Proxy.csproj17
-rw-r--r--Impostor-dev/src/Impostor.Tools.Proxy/Program.cs198
-rw-r--r--Impostor-dev/src/Impostor.Tools.ServerReplay/Impostor.Tools.ServerReplay.csproj16
-rw-r--r--Impostor-dev/src/Impostor.Tools.ServerReplay/Mocks/MockGameCodeFactory.cs14
-rw-r--r--Impostor-dev/src/Impostor.Tools.ServerReplay/Mocks/MockHazelConnection.cs31
-rw-r--r--Impostor-dev/src/Impostor.Tools.ServerReplay/Program.cs195
-rw-r--r--Impostor-dev/src/Impostor.Tools.ServerReplay/sessions/session_1604255331821_dead_player_exception.datbin0 -> 47057 bytes
-rw-r--r--Impostor-dev/src/Impostor.sln177
747 files changed, 60374 insertions, 0 deletions
diff --git a/Client/AmongUs.sln b/Client/AmongUs.sln
new file mode 100644
index 0000000..089d240
--- /dev/null
+++ b/Client/AmongUs.sln
@@ -0,0 +1,22 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.26228.4
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Assembly-CSharp", "Assembly-CSharp\Assembly-CSharp.csproj", "{4D37C17B-28D8-4D17-A439-F712A18E4C22}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {4D37C17B-28D8-4D17-A439-F712A18E4C22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4D37C17B-28D8-4D17-A439-F712A18E4C22}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4D37C17B-28D8-4D17-A439-F712A18E4C22}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4D37C17B-28D8-4D17-A439-F712A18E4C22}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/Client/Assembly-CSharp/AcceptDivertPowerGame.cs b/Client/Assembly-CSharp/AcceptDivertPowerGame.cs
new file mode 100644
index 0000000..14867f1
--- /dev/null
+++ b/Client/Assembly-CSharp/AcceptDivertPowerGame.cs
@@ -0,0 +1,84 @@
+using System;
+using System.Collections;
+using UnityEngine;
+
+public class AcceptDivertPowerGame : Minigame
+{
+ private LineRenderer[] LeftWires;
+
+ private LineRenderer[] RightWires;
+
+ public GameObject RightWireParent;
+
+ public GameObject LeftWireParent;
+
+ public SpriteRenderer Switch;
+
+ public AudioClip SwitchSound;
+
+ private bool done;
+
+ public void Start()
+ {
+ this.LeftWires = this.LeftWireParent.GetComponentsInChildren<LineRenderer>();
+ this.RightWires = this.RightWireParent.GetComponentsInChildren<LineRenderer>();
+ for (int i = 0; i < this.LeftWires.Length; i++)
+ {
+ this.LeftWires[i].material.SetColor("_Color", Color.yellow);
+ }
+ }
+
+ public void DoSwitch()
+ {
+ if (this.done)
+ {
+ return;
+ }
+ this.done = true;
+ if (Constants.ShouldPlaySfx())
+ {
+ SoundManager.Instance.PlaySound(this.SwitchSound, false, 1f);
+ }
+ base.StartCoroutine(this.CoDoSwitch());
+ }
+
+ private IEnumerator CoDoSwitch()
+ {
+ yield return new WaitForLerp(0.25f, delegate(float t)
+ {
+ this.Switch.transform.localEulerAngles = new Vector3(0f, 0f, Mathf.Lerp(0f, 90f, t));
+ });
+ this.LeftWires[0].SetPosition(1, new Vector3(1.265f, 0f, 0f));
+ for (int i = 0; i < this.RightWires.Length; i++)
+ {
+ this.RightWires[i].enabled = true;
+ this.RightWires[i].material.SetColor("_Color", Color.yellow);
+ }
+ for (int j = 0; j < this.LeftWires.Length; j++)
+ {
+ this.LeftWires[j].material.SetColor("_Color", Color.yellow);
+ }
+ if (this.MyNormTask)
+ {
+ this.MyNormTask.NextStep();
+ }
+ base.StartCoroutine(base.CoStartClose(0.75f));
+ yield break;
+ }
+
+ public void Update()
+ {
+ for (int i = 0; i < this.LeftWires.Length; i++)
+ {
+ Vector2 textureOffset = this.LeftWires[i].material.GetTextureOffset("_MainTex");
+ textureOffset.x -= Time.fixedDeltaTime * 3f;
+ this.LeftWires[i].material.SetTextureOffset("_MainTex", textureOffset);
+ }
+ for (int j = 0; j < this.RightWires.Length; j++)
+ {
+ Vector2 textureOffset2 = this.RightWires[j].material.GetTextureOffset("_MainTex");
+ textureOffset2.x += Time.fixedDeltaTime * 3f;
+ this.RightWires[j].material.SetTextureOffset("_MainTex", textureOffset2);
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/AdDataCollectScreen.cs b/Client/Assembly-CSharp/AdDataCollectScreen.cs
new file mode 100644
index 0000000..35212f6
--- /dev/null
+++ b/Client/Assembly-CSharp/AdDataCollectScreen.cs
@@ -0,0 +1,65 @@
+using System;
+using System.Collections;
+using UnityEngine;
+
+public class AdDataCollectScreen : MonoBehaviour
+{
+ public ToggleButtonBehaviour PersonalizedAdsButton;
+
+ private void Start()
+ {
+ this.UpdateButtons();
+ }
+
+ public IEnumerator Show()
+ {
+ if (!SaveManager.ShowAdsScreen.HasFlag(ShowAdsState.Accepted) && !SaveManager.BoughtNoAds)
+ {
+ base.gameObject.SetActive(true);
+ while (base.gameObject.activeSelf)
+ {
+ yield return null;
+ }
+ }
+ yield break;
+ }
+
+ public void Close()
+ {
+ SaveManager.ShowAdsScreen |= ShowAdsState.Accepted;
+ }
+
+ public void Update()
+ {
+ if (SaveManager.BoughtNoAds)
+ {
+ base.GetComponent<TransitionOpen>().Close();
+ }
+ }
+
+ public void TogglePersonalizedAd()
+ {
+ ShowAdsState showAdsState = SaveManager.ShowAdsScreen & (ShowAdsState)127;
+ if (showAdsState != ShowAdsState.Personalized)
+ {
+ if (showAdsState == ShowAdsState.NonPersonalized)
+ {
+ SaveManager.ShowAdsScreen = ShowAdsState.Personalized;
+ goto IL_34;
+ }
+ if (showAdsState == ShowAdsState.Purchased)
+ {
+ SaveManager.ShowAdsScreen = ShowAdsState.Purchased;
+ goto IL_34;
+ }
+ }
+ SaveManager.ShowAdsScreen = ShowAdsState.NonPersonalized;
+ IL_34:
+ this.UpdateButtons();
+ }
+
+ public void UpdateButtons()
+ {
+ this.PersonalizedAdsButton.UpdateText(!SaveManager.ShowAdsScreen.HasFlag(ShowAdsState.NonPersonalized));
+ }
+}
diff --git a/Client/Assembly-CSharp/AdPlayer.cs b/Client/Assembly-CSharp/AdPlayer.cs
new file mode 100644
index 0000000..92ab040
--- /dev/null
+++ b/Client/Assembly-CSharp/AdPlayer.cs
@@ -0,0 +1,118 @@
+using System;
+using System.Collections;
+using GoogleMobileAds.Api;
+using InnerNet;
+using UnityEngine;
+
+public static class AdPlayer
+{
+ private static InterstitialAd interstitial;
+
+ private const string appId = "unexpected_platform";
+
+ private const string adUnitId = "unexpected_platform";
+
+ public static void ShowInterstitial(MonoBehaviour parent, bool playAgain)
+ {
+ parent.StartCoroutine(AdPlayer.CoShowAd(playAgain));
+ }
+
+ private static IEnumerator CoShowAd(bool playAgain)
+ {
+ if (playAgain)
+ {
+ yield return DestroyableSingleton<EndGameManager>.Instance.CoJoinGame();
+ }
+ else
+ {
+ AmongUsClient.Instance.ExitGame(DisconnectReasons.ExitGame);
+ }
+ yield break;
+ }
+
+ public static void RequestInterstitial()
+ {
+ try
+ {
+ MobileAds.Initialize("unexpected_platform");
+ if (AdPlayer.interstitial == null)
+ {
+ AdPlayer.interstitial = new InterstitialAd("unexpected_platform");
+ AdRequest adRequest = new AdRequest.Builder().Build();
+ if (SaveManager.ShowAdsScreen.HasFlag(ShowAdsState.NonPersonalized))
+ {
+ adRequest.Extras.Add("npa", "1");
+ }
+ AdPlayer.interstitial.OnAdLoaded += AdPlayer.Interstitial_OnAdLoaded;
+ AdPlayer.interstitial.OnAdFailedToLoad += AdPlayer.Interstitial_OnAdFailedToLoad;
+ AdPlayer.interstitial.OnAdClosed += AdPlayer.Interstitial_OnAdClosed;
+ AdPlayer.interstitial.OnAdLeavingApplication += AdPlayer.Interstitial_OnAdLeavingApplication;
+ AdPlayer.interstitial.LoadAd(adRequest);
+ }
+ }
+ catch
+ {
+ try
+ {
+ if (AdPlayer.interstitial != null)
+ {
+ AdPlayer.interstitial.Destroy();
+ }
+ }
+ catch
+ {
+ }
+ AdPlayer.interstitial = null;
+ }
+ }
+
+ private static void Interstitial_OnAdLoaded(object sender, EventArgs e)
+ {
+ }
+
+ private static void Interstitial_OnAdLeavingApplication(object sender, EventArgs e)
+ {
+ try
+ {
+ if (AdPlayer.interstitial != null)
+ {
+ AdPlayer.interstitial.Destroy();
+ }
+ }
+ finally
+ {
+ AdPlayer.interstitial = null;
+ }
+ }
+
+ private static void Interstitial_OnAdFailedToLoad(object sender, AdFailedToLoadEventArgs e)
+ {
+ try
+ {
+ if (AdPlayer.interstitial != null)
+ {
+ AdPlayer.interstitial.Destroy();
+ Debug.LogError("Couldn't load ad: " + (e.Message ?? "No Message"));
+ }
+ }
+ finally
+ {
+ AdPlayer.interstitial = null;
+ }
+ }
+
+ private static void Interstitial_OnAdClosed(object sender, EventArgs e)
+ {
+ try
+ {
+ if (AdPlayer.interstitial != null)
+ {
+ AdPlayer.interstitial.Destroy();
+ }
+ }
+ finally
+ {
+ AdPlayer.interstitial = null;
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/AlignGame.cs b/Client/Assembly-CSharp/AlignGame.cs
new file mode 100644
index 0000000..1ee70f0
--- /dev/null
+++ b/Client/Assembly-CSharp/AlignGame.cs
@@ -0,0 +1,146 @@
+using System;
+using System.Collections;
+using UnityEngine;
+
+public class AlignGame : Minigame
+{
+ private Controller myController = new Controller();
+
+ public FloatRange YRange = new FloatRange(-0.425f, 0.425f);
+
+ public AnimationCurve curve;
+
+ public LineRenderer centerline;
+
+ public LineRenderer[] guidelines;
+
+ public SpriteRenderer engine;
+
+ public Collider2D col;
+
+ public TextController StatusText;
+
+ private float pulseTimer;
+
+ public override void Begin(PlayerTask task)
+ {
+ base.Begin(task);
+ float value = AlignGame.FromByte(this.MyNormTask.Data[base.ConsoleId]);
+ bool flag = AlignGame.IsSuccess(this.MyNormTask.Data[base.ConsoleId]);
+ Vector3 localPosition = this.col.transform.localPosition;
+ localPosition.y = this.YRange.Clamp(value);
+ float num = this.YRange.ReverseLerp(localPosition.y);
+ localPosition.x = this.curve.Evaluate(num);
+ this.col.transform.eulerAngles = new Vector3(0f, 0f, Mathf.Lerp(20f, -20f, num));
+ this.engine.transform.eulerAngles = new Vector3(0f, 0f, Mathf.Lerp(45f, -45f, num));
+ this.centerline.material.SetColor("_Color", flag ? Color.green : Color.red);
+ this.engine.color = (flag ? Color.green : Color.red);
+ this.col.transform.localPosition = localPosition;
+ this.guidelines[0].enabled = flag;
+ this.guidelines[1].enabled = flag;
+ this.StatusText.gameObject.SetActive(flag);
+ }
+
+ public void Update()
+ {
+ this.centerline.material.SetTextureOffset("_MainTex", new Vector2(Time.time, 0f));
+ this.guidelines[0].material.SetTextureOffset("_MainTex", new Vector2(Time.time, 0f));
+ this.guidelines[1].material.SetTextureOffset("_MainTex", new Vector2(Time.time, 0f));
+ if (this.MyTask && this.MyNormTask.IsComplete)
+ {
+ return;
+ }
+ Vector3 localPosition = this.col.transform.localPosition;
+ bool flag = AlignGame.IsSuccess(this.MyNormTask.Data[base.ConsoleId]);
+ bool flag2 = AlignGame.IsSuccess(AlignGame.ToByte(localPosition.y));
+ this.myController.Update();
+ switch (this.myController.CheckDrag(this.col, false))
+ {
+ case DragState.TouchStart:
+ this.pulseTimer = 0f;
+ break;
+ case DragState.Dragging:
+ if (!flag)
+ {
+ Vector2 vector = this.myController.DragPosition - base.transform.position;
+ float num = this.YRange.ReverseLerp(localPosition.y);
+ localPosition.y = this.YRange.Clamp(vector.y);
+ float num2 = this.YRange.ReverseLerp(localPosition.y);
+ localPosition.x = this.curve.Evaluate(num2);
+ this.col.transform.eulerAngles = new Vector3(0f, 0f, Mathf.Lerp(20f, -20f, num2));
+ this.engine.transform.eulerAngles = new Vector3(0f, 0f, Mathf.Lerp(45f, -45f, num2));
+ this.centerline.material.SetColor("_Color", flag2 ? Color.green : Color.red);
+ if (Mathf.Abs(num2 - num) > 0.001f)
+ {
+ this.pulseTimer += Time.deltaTime * 25f;
+ int num3 = (int)this.pulseTimer % 3;
+ if (num3 > 1)
+ {
+ if (num3 == 2)
+ {
+ this.engine.color = Color.clear;
+ }
+ }
+ else
+ {
+ this.engine.color = Color.red;
+ }
+ }
+ else
+ {
+ this.engine.color = Color.red;
+ }
+ }
+ break;
+ case DragState.Released:
+ if (!flag && flag2)
+ {
+ base.StartCoroutine(this.LockEngine());
+ this.MyNormTask.Data[base.ConsoleId] = AlignGame.ToByte(localPosition.y);
+ this.MyNormTask.NextStep();
+ base.StartCoroutine(base.CoStartClose(0.75f));
+ }
+ break;
+ }
+ this.col.transform.localPosition = localPosition;
+ }
+
+ private IEnumerator LockEngine()
+ {
+ int num;
+ for (int i = 0; i < 3; i = num)
+ {
+ this.guidelines[0].enabled = true;
+ this.guidelines[1].enabled = true;
+ yield return new WaitForSeconds(0.1f);
+ this.guidelines[0].enabled = false;
+ this.guidelines[1].enabled = false;
+ yield return new WaitForSeconds(0.1f);
+ num = i + 1;
+ }
+ this.StatusText.gameObject.SetActive(true);
+ Color green = new Color(0f, 0.7f, 0f);
+ yield return new WaitForLerp(1f, delegate(float t)
+ {
+ this.engine.color = Color.Lerp(Color.white, green, t);
+ });
+ this.guidelines[0].enabled = true;
+ this.guidelines[1].enabled = true;
+ yield break;
+ }
+
+ public static float FromByte(byte b)
+ {
+ return (float)b * 0.025f - 3f;
+ }
+
+ public static byte ToByte(float y)
+ {
+ return (byte)((y + 3f) / 0.025f);
+ }
+
+ public static bool IsSuccess(byte b)
+ {
+ return Mathf.Abs(AlignGame.FromByte(b)) <= 0.05f;
+ }
+}
diff --git a/Client/Assembly-CSharp/AlphaBlink.cs b/Client/Assembly-CSharp/AlphaBlink.cs
new file mode 100644
index 0000000..4813ba6
--- /dev/null
+++ b/Client/Assembly-CSharp/AlphaBlink.cs
@@ -0,0 +1,44 @@
+using System;
+using UnityEngine;
+
+public class AlphaBlink : MonoBehaviour
+{
+ public float Period = 1f;
+
+ public float Ratio = 0.5f;
+
+ private SpriteRenderer rend;
+
+ private MeshRenderer mesh;
+
+ public FloatRange AlphaRange = new FloatRange(0.2f, 0.5f);
+
+ public Color baseColor = Color.white;
+
+ public void SetColor(Color c)
+ {
+ this.Start();
+ this.baseColor = c;
+ this.Update();
+ }
+
+ private void Start()
+ {
+ this.mesh = base.GetComponent<MeshRenderer>();
+ this.rend = base.GetComponent<SpriteRenderer>();
+ }
+
+ public void Update()
+ {
+ float num = Time.time % this.Period / this.Period;
+ num = (float)((num < this.Ratio) ? 1 : 0);
+ if (this.rend)
+ {
+ this.rend.color = new Color(this.baseColor.r, this.baseColor.g, this.baseColor.b, this.AlphaRange.Lerp(num));
+ }
+ if (this.mesh)
+ {
+ this.mesh.material.SetColor("_Color", new Color(this.baseColor.r, this.baseColor.g, this.baseColor.b, this.AlphaRange.Lerp(num)));
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/AlphaPulse.cs b/Client/Assembly-CSharp/AlphaPulse.cs
new file mode 100644
index 0000000..e28579e
--- /dev/null
+++ b/Client/Assembly-CSharp/AlphaPulse.cs
@@ -0,0 +1,43 @@
+using System;
+using UnityEngine;
+
+public class AlphaPulse : MonoBehaviour
+{
+ public float Offset = 1f;
+
+ public float Duration = 2.5f;
+
+ private SpriteRenderer rend;
+
+ private MeshRenderer mesh;
+
+ public FloatRange AlphaRange = new FloatRange(0.2f, 0.5f);
+
+ public Color baseColor = Color.white;
+
+ public void SetColor(Color c)
+ {
+ this.Start();
+ this.baseColor = c;
+ this.Update();
+ }
+
+ private void Start()
+ {
+ this.mesh = base.GetComponent<MeshRenderer>();
+ this.rend = base.GetComponent<SpriteRenderer>();
+ }
+
+ public void Update()
+ {
+ float v = Mathf.Abs(Mathf.Cos((this.Offset + Time.time) * 3.1415927f / this.Duration));
+ if (this.rend)
+ {
+ this.rend.color = new Color(this.baseColor.r, this.baseColor.g, this.baseColor.b, this.AlphaRange.Lerp(v));
+ }
+ if (this.mesh)
+ {
+ this.mesh.material.SetColor("_Color", new Color(this.baseColor.r, this.baseColor.g, this.baseColor.b, this.AlphaRange.Lerp(v)));
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/AmongUsClient.cs b/Client/Assembly-CSharp/AmongUsClient.cs
new file mode 100644
index 0000000..5726992
--- /dev/null
+++ b/Client/Assembly-CSharp/AmongUsClient.cs
@@ -0,0 +1,497 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Assets.CoreScripts;
+using InnerNet;
+using UnityEngine;
+using UnityEngine.SceneManagement;
+
+public class AmongUsClient : InnerNetClient
+{
+ public static AmongUsClient Instance;
+
+ public GameModes GameMode;
+
+ public string OnlineScene;
+
+ public string MainMenuScene;
+
+ public GameData GameDataPrefab;
+
+ public PlayerControl PlayerPrefab;
+
+ public List<ShipStatus> ShipPrefabs;
+
+ public float SpawnRadius = 1.75f;
+
+ public DiscoveryState discoverState;
+
+ public List<IDisconnectHandler> DisconnectHandlers = new List<IDisconnectHandler>();
+
+ public List<IGameListHandler> GameListHandlers = new List<IGameListHandler>();
+
+ public void Awake()
+ {
+ if (AmongUsClient.Instance)
+ {
+ if (AmongUsClient.Instance != this)
+ {
+ UnityEngine.Object.Destroy(base.gameObject);
+ }
+ return;
+ }
+ AmongUsClient.Instance = this;
+ UnityEngine.Object.DontDestroyOnLoad(base.gameObject);
+ Application.targetFrameRate = 30;
+ }
+
+ protected override byte[] GetConnectionData()
+ {
+ byte[] result;
+ using (MemoryStream memoryStream = new MemoryStream())
+ {
+ using (BinaryWriter binaryWriter = new BinaryWriter(memoryStream))
+ {
+ binaryWriter.Write(Constants.GetBroadcastVersion());
+ binaryWriter.Write(SaveManager.PlayerName);
+ binaryWriter.Flush();
+ result = memoryStream.ToArray();
+ }
+ }
+ return result;
+ }
+
+ public void StartGame()
+ {
+ base.SendStartGame();
+ this.discoverState = DiscoveryState.Off;
+ }
+
+ public void ExitGame(DisconnectReasons reason = DisconnectReasons.ExitGame)
+ {
+ if (DestroyableSingleton<WaitForHostPopup>.InstanceExists)
+ {
+ DestroyableSingleton<WaitForHostPopup>.Instance.Hide();
+ }
+ SoundManager.Instance.StopAllSound();
+ this.discoverState = DiscoveryState.Off;
+ this.DisconnectHandlers.Clear();
+ base.DisconnectInternal(reason, null);
+ SceneManager.LoadScene(this.MainMenuScene);
+ }
+
+ protected override void OnGetGameList(int totalGames, List<GameListing> availableGames)
+ {
+ for (int i = 0; i < this.GameListHandlers.Count; i++)
+ {
+ try
+ {
+ this.GameListHandlers[i].HandleList(totalGames, availableGames);
+ }
+ catch
+ {
+ }
+ }
+ }
+
+ protected override void OnGameCreated(string gameIdString)
+ {
+ }
+
+ protected override void OnWaitForHost(string gameIdString)
+ {
+ if (this.GameState != InnerNetClient.GameStates.Joined)
+ {
+ Debug.Log("Waiting for host: " + gameIdString);
+ if (DestroyableSingleton<WaitForHostPopup>.InstanceExists)
+ {
+ DestroyableSingleton<WaitForHostPopup>.Instance.Show();
+ }
+ }
+ }
+
+ protected override void OnStartGame()
+ {
+ Debug.Log("Received game start: " + base.AmHost.ToString());
+ base.StartCoroutine(this.CoStartGame());
+ }
+
+ private IEnumerator CoStartGame()
+ {
+ yield return null;
+ while (!DestroyableSingleton<HudManager>.InstanceExists)
+ {
+ yield return null;
+ }
+ while (!PlayerControl.LocalPlayer)
+ {
+ yield return null;
+ }
+ PlayerControl.LocalPlayer.moveable = false;
+ CustomPlayerMenu customPlayerMenu = UnityEngine.Object.FindObjectOfType<CustomPlayerMenu>();
+ if (customPlayerMenu)
+ {
+ customPlayerMenu.Close(false);
+ }
+ if (DestroyableSingleton<GameStartManager>.InstanceExists)
+ {
+ this.DisconnectHandlers.Remove(DestroyableSingleton<GameStartManager>.Instance);
+ UnityEngine.Object.Destroy(DestroyableSingleton<GameStartManager>.Instance.gameObject);
+ }
+ if (DestroyableSingleton<DiscordManager>.InstanceExists)
+ {
+ DestroyableSingleton<DiscordManager>.Instance.SetPlayingGame();
+ }
+ yield return DestroyableSingleton<HudManager>.Instance.CoFadeFullScreen(Color.clear, Color.black, 0.2f);
+ while (!GameData.Instance)
+ {
+ yield return null;
+ }
+ while (!base.AmHost)
+ {
+ while (PlayerControl.LocalPlayer.Data == null && !base.AmHost)
+ {
+ yield return null;
+ }
+ if (!base.AmHost)
+ {
+ base.SendClientReady();
+ while (!ShipStatus.Instance && !base.AmHost)
+ {
+ yield return null;
+ }
+ if (!base.AmHost)
+ {
+ IL_324:
+ for (int i = 0; i < GameData.Instance.PlayerCount; i++)
+ {
+ PlayerControl @object = GameData.Instance.AllPlayers[i].Object;
+ if (@object)
+ {
+ @object.moveable = true;
+ @object.NetTransform.enabled = true;
+ @object.MyPhysics.enabled = true;
+ @object.MyPhysics.Awake();
+ @object.MyPhysics.ResetAnim(true);
+ @object.Collider.enabled = true;
+ Vector2 spawnLocation = ShipStatus.Instance.GetSpawnLocation(i, GameData.Instance.PlayerCount);
+ @object.NetTransform.SnapTo(spawnLocation);
+ }
+ }
+ yield break;
+ }
+ }
+ }
+ GameData.Instance.SetDirty();
+ base.SendClientReady();
+ float timer = 0f;
+ for (;;)
+ {
+ bool stopWaiting = true;
+ List<ClientData> allClients = this.allClients;
+ lock (allClients)
+ {
+ for (int j = 0; j < this.allClients.Count; j++)
+ {
+ ClientData clientData = this.allClients[j];
+ if (!clientData.IsReady)
+ {
+ if (timer < 5f)
+ {
+ stopWaiting = false;
+ }
+ else
+ {
+ base.SendLateRejection(clientData.Id, DisconnectReasons.Error);
+ clientData.IsReady = true;
+ this.OnPlayerLeft(clientData, DisconnectReasons.Error);
+ }
+ }
+ }
+ }
+ yield return null;
+ if (stopWaiting)
+ {
+ break;
+ }
+ timer += Time.deltaTime;
+ }
+ if (LobbyBehaviour.Instance)
+ {
+ LobbyBehaviour.Instance.Despawn();
+ }
+ if (!ShipStatus.Instance)
+ {
+ int index = Mathf.Clamp((int)PlayerControl.GameOptions.MapId, 0, this.ShipPrefabs.Count - 1);
+ ShipStatus.Instance = UnityEngine.Object.Instantiate<ShipStatus>(this.ShipPrefabs[index]);
+ }
+ base.Spawn(ShipStatus.Instance, -2, SpawnFlags.None);
+ ShipStatus.Instance.SelectInfected();
+ ShipStatus.Instance.Begin();
+ goto IL_324;
+ }
+
+ protected override void OnBecomeHost()
+ {
+ ClientData clientData = base.FindClientById(this.ClientId);
+ if (!clientData.Character)
+ {
+ this.OnGameJoined(null, clientData);
+ }
+ Debug.Log("Became Host");
+ base.RemoveUnownedObjects();
+ }
+
+ protected override void OnGameEnd(GameOverReason gameOverReason, bool showAd)
+ {
+ StatsManager.Instance.BanPoints -= 1.5f;
+ StatsManager.Instance.LastGameStarted = DateTime.MinValue;
+ this.DisconnectHandlers.Clear();
+ if (Minigame.Instance)
+ {
+ Minigame.Instance.Close();
+ Minigame.Instance.Close();
+ }
+ try
+ {
+ if (SaveManager.SendTelemetry)
+ {
+ DestroyableSingleton<Telemetry>.Instance.EndGame(gameOverReason);
+ }
+ }
+ catch
+ {
+ }
+ TempData.EndReason = gameOverReason;
+ TempData.showAd = showAd;
+ bool flag = TempData.DidHumansWin(gameOverReason);
+ TempData.winners = new List<WinningPlayerData>();
+ for (int i = 0; i < GameData.Instance.PlayerCount; i++)
+ {
+ GameData.PlayerInfo playerInfo = GameData.Instance.AllPlayers[i];
+ if (gameOverReason == GameOverReason.HumansDisconnect || gameOverReason == GameOverReason.ImpostorDisconnect || flag != playerInfo.IsImpostor)
+ {
+ TempData.winners.Add(new WinningPlayerData(playerInfo));
+ }
+ }
+ base.StartCoroutine(this.CoEndGame());
+ }
+
+ public IEnumerator CoEndGame()
+ {
+ yield return DestroyableSingleton<HudManager>.Instance.CoFadeFullScreen(Color.clear, Color.black, 0.5f);
+ SceneManager.LoadScene("EndGame");
+ yield break;
+ }
+
+ protected override void OnPlayerJoined(ClientData data)
+ {
+ if (DestroyableSingleton<GameStartManager>.InstanceExists)
+ {
+ DestroyableSingleton<GameStartManager>.Instance.ResetStartState();
+ }
+ if (base.AmHost && data.InScene)
+ {
+ this.CreatePlayer(data);
+ }
+ }
+
+ protected override void OnGameJoined(string gameIdString, ClientData data)
+ {
+ if (DestroyableSingleton<WaitForHostPopup>.InstanceExists)
+ {
+ DestroyableSingleton<WaitForHostPopup>.Instance.Hide();
+ }
+ if (!string.IsNullOrWhiteSpace(this.OnlineScene))
+ {
+ SceneManager.LoadScene(this.OnlineScene);
+ }
+ }
+
+ protected override void OnPlayerLeft(ClientData data, DisconnectReasons reason)
+ {
+ if (DestroyableSingleton<GameStartManager>.InstanceExists)
+ {
+ DestroyableSingleton<GameStartManager>.Instance.ResetStartState();
+ }
+ PlayerControl character = data.Character;
+ if (character)
+ {
+ for (int i = this.DisconnectHandlers.Count - 1; i > -1; i--)
+ {
+ try
+ {
+ this.DisconnectHandlers[i].HandleDisconnect(character, reason);
+ }
+ catch (Exception exception)
+ {
+ Debug.LogException(exception);
+ this.DisconnectHandlers.RemoveAt(i);
+ }
+ }
+ UnityEngine.Object.Destroy(character.gameObject);
+ }
+ else
+ {
+ Debug.LogWarning(string.Format("A player without a character disconnected: {0}: {1}", data.Id, data.InScene));
+ for (int j = this.DisconnectHandlers.Count - 1; j > -1; j--)
+ {
+ try
+ {
+ this.DisconnectHandlers[j].HandleDisconnect();
+ }
+ catch (Exception exception2)
+ {
+ Debug.LogException(exception2);
+ this.DisconnectHandlers.RemoveAt(j);
+ }
+ }
+ }
+ if (base.AmHost)
+ {
+ GameOptionsData gameOptions = PlayerControl.GameOptions;
+ if (gameOptions != null && gameOptions.isDefaults)
+ {
+ PlayerControl.GameOptions.SetRecommendations(GameData.Instance.PlayerCount, AmongUsClient.Instance.GameMode);
+ PlayerControl localPlayer = PlayerControl.LocalPlayer;
+ if (localPlayer != null)
+ {
+ localPlayer.RpcSyncSettings(PlayerControl.GameOptions);
+ }
+ }
+ }
+ base.RemoveUnownedObjects();
+ }
+
+ protected override void OnDisconnected()
+ {
+ SceneManager.LoadScene(this.MainMenuScene);
+ }
+
+ //c 切换场景
+ protected override void OnPlayerChangedScene(ClientData client, string currentScene)
+ {
+ client.InScene = true;
+ if (!base.AmHost)
+ {
+ return;
+ }
+ if (currentScene.Equals("Tutorial"))
+ {
+ GameData.Instance = UnityEngine.Object.Instantiate<GameData>(this.GameDataPrefab);
+ base.Spawn(GameData.Instance, -2, SpawnFlags.None);
+ base.Spawn(UnityEngine.Object.Instantiate<ShipStatus>(this.ShipPrefabs.Last<ShipStatus>()), -2, SpawnFlags.None);
+ this.CreatePlayer(client);
+ return;
+ }
+ if (currentScene.Equals("OnlineGame"))
+ {
+ if (client.Id != this.ClientId)
+ {
+ base.SendInitialData(client.Id);
+ }
+ else
+ {
+ if (this.GameMode == GameModes.LocalGame)
+ {
+ base.StartCoroutine(this.CoBroadcastManager());
+ }
+ if (!GameData.Instance)
+ {
+ GameData.Instance = UnityEngine.Object.Instantiate<GameData>(this.GameDataPrefab);
+ base.Spawn(GameData.Instance, -2, SpawnFlags.None);
+ }
+ }
+ this.CreatePlayer(client);
+ }
+ }
+
+ [ContextMenu("Spawn Tester")]
+ private void SpawnTester()
+ {
+ sbyte availableId = GameData.Instance.GetAvailableId();
+ Vector2 v = Vector2.up.Rotate((float)availableId * (360f / (float)Palette.PlayerColors.Length)) * this.SpawnRadius;
+ PlayerControl playerControl = UnityEngine.Object.Instantiate<PlayerControl>(this.PlayerPrefab, v, Quaternion.identity);
+ playerControl.PlayerId = (byte)availableId;
+ GameData.Instance.AddPlayer(playerControl);
+ base.Spawn(playerControl, -2, SpawnFlags.None);
+ playerControl.CmdCheckName("Test");
+ playerControl.CmdCheckColor(0);
+ if (DestroyableSingleton<HatManager>.InstanceExists)
+ {
+ playerControl.RpcSetHat((uint)((int)availableId % DestroyableSingleton<HatManager>.Instance.AllHats.Count));
+ playerControl.RpcSetSkin((uint)((int)availableId % DestroyableSingleton<HatManager>.Instance.AllSkins.Count));
+ playerControl.RpcSetPet((uint)availableId);
+ }
+ }
+
+ private void CreatePlayer(ClientData clientData)
+ {
+ if (clientData.Character)
+ {
+ return;
+ }
+ if (!base.AmHost)
+ {
+ Debug.Log("Waiting for host to make my player");
+ return;
+ }
+ if (!GameData.Instance)
+ {
+ GameData.Instance = UnityEngine.Object.Instantiate<GameData>(this.GameDataPrefab);
+ base.Spawn(GameData.Instance, -2, SpawnFlags.None);
+ }
+ sbyte availableId = GameData.Instance.GetAvailableId();
+ if (availableId == -1)
+ {
+ base.SendLateRejection(clientData.Id, DisconnectReasons.GameFull);
+ Debug.Log("Overfilled room.");
+ return;
+ }
+ Vector2 v = Vector2.zero;
+ if (ShipStatus.Instance)
+ {
+ v = ShipStatus.Instance.GetSpawnLocation((int)availableId, Palette.PlayerColors.Length);
+ }
+ else if (DestroyableSingleton<TutorialManager>.InstanceExists)
+ {
+ v = new Vector2(-1.9f, 3.25f);
+ }
+ Debug.Log(string.Format("Spawned player {0} for client {1}", availableId, clientData.Id));
+ PlayerControl playerControl = UnityEngine.Object.Instantiate<PlayerControl>(this.PlayerPrefab, v, Quaternion.identity);
+ playerControl.PlayerId = (byte)availableId;
+ clientData.Character = playerControl;
+ base.Spawn(playerControl, clientData.Id, SpawnFlags.IsClientCharacter);
+ GameData.Instance.AddPlayer(playerControl);
+ if (PlayerControl.GameOptions.isDefaults)
+ {
+ PlayerControl.GameOptions.SetRecommendations(GameData.Instance.PlayerCount, AmongUsClient.Instance.GameMode);
+ }
+ playerControl.RpcSyncSettings(PlayerControl.GameOptions);
+ }
+
+ private IEnumerator CoBroadcastManager()
+ {
+ while (!GameData.Instance)
+ {
+ yield return null;
+ }
+ int lastPlayerCount = 0;
+ this.discoverState = DiscoveryState.Broadcast;
+ while (this.discoverState == DiscoveryState.Broadcast)
+ {
+ if (lastPlayerCount != GameData.Instance.PlayerCount)
+ {
+ lastPlayerCount = GameData.Instance.PlayerCount;
+ string data = string.Format("{0}~Open~{1}~", SaveManager.PlayerName, GameData.Instance.PlayerCount);
+ DestroyableSingleton<InnerDiscover>.Instance.Interval = 1f;
+ DestroyableSingleton<InnerDiscover>.Instance.StartAsServer(data);
+ }
+ yield return null;
+ }
+ DestroyableSingleton<InnerDiscover>.Instance.StopServer();
+ yield break;
+ }
+}
diff --git a/Client/Assembly-CSharp/AmongUsProduct.cs b/Client/Assembly-CSharp/AmongUsProduct.cs
new file mode 100644
index 0000000..29e6d2d
--- /dev/null
+++ b/Client/Assembly-CSharp/AmongUsProduct.cs
@@ -0,0 +1,9 @@
+using System;
+
+[Serializable]
+public struct AmongUsProduct
+{
+ public string ProductId;
+
+ public HatBehaviour HatData;
+}
diff --git a/Client/Assembly-CSharp/Announcement.cs b/Client/Assembly-CSharp/Announcement.cs
new file mode 100644
index 0000000..25d3a48
--- /dev/null
+++ b/Client/Assembly-CSharp/Announcement.cs
@@ -0,0 +1,10 @@
+using System;
+
+public struct Announcement
+{
+ public int DateFetched;
+
+ public uint Id;
+
+ public string AnnounceText;
+}
diff --git a/Client/Assembly-CSharp/AnnouncementPopUp.cs b/Client/Assembly-CSharp/AnnouncementPopUp.cs
new file mode 100644
index 0000000..42b96b0
--- /dev/null
+++ b/Client/Assembly-CSharp/AnnouncementPopUp.cs
@@ -0,0 +1,154 @@
+using System;
+using System.Collections;
+using System.Net;
+using Hazel;
+using Hazel.Udp;
+using UnityEngine;
+
+public class AnnouncementPopUp : MonoBehaviour
+{
+ private const uint AnnouncementVersion = 1U;
+
+ private UdpClientConnection connection;
+
+ private static AnnouncementPopUp.AnnounceState AskedForUpdate;
+
+ public TextRenderer AnnounceText;
+
+ private Announcement announcementUpdate;
+
+ private enum AnnounceState
+ {
+ Fetching,
+ Failed,
+ Success,
+ Cached
+ }
+
+ private static bool IsSuccess(AnnouncementPopUp.AnnounceState state)
+ {
+ return state == AnnouncementPopUp.AnnounceState.Success || state == AnnouncementPopUp.AnnounceState.Cached;
+ }
+
+ public IEnumerator Init()
+ {
+ if (AnnouncementPopUp.AskedForUpdate != AnnouncementPopUp.AnnounceState.Fetching)
+ {
+ yield break;
+ }
+ yield return DestroyableSingleton<ServerManager>.Instance.WaitForServers();
+ this.connection = new UdpClientConnection(new IPEndPoint(IPAddress.Parse(DestroyableSingleton<ServerManager>.Instance.OnlineNetAddress), 22024), IPMode.IPv4);
+ this.connection.DataReceived += this.Connection_DataReceived;
+ this.connection.Disconnected += this.Connection_Disconnected;
+ Announcement lastAnnouncement = SaveManager.LastAnnouncement;
+ try
+ {
+ MessageWriter messageWriter = MessageWriter.Get(SendOption.None);
+ messageWriter.WritePacked(1U);
+ messageWriter.WritePacked(lastAnnouncement.Id);
+ this.connection.ConnectAsync(messageWriter.ToByteArray(true), 5000);
+ messageWriter.Recycle();
+ yield break;
+ }
+ catch
+ {
+ AnnouncementPopUp.AskedForUpdate = AnnouncementPopUp.AnnounceState.Failed;
+ yield break;
+ }
+ yield break;
+ }
+
+ private void Connection_Disconnected(object sender, DisconnectedEventArgs e)
+ {
+ AnnouncementPopUp.AskedForUpdate = AnnouncementPopUp.AnnounceState.Failed;
+ this.connection.Dispose();
+ this.connection = null;
+ }
+
+ private void Connection_DataReceived(DataReceivedEventArgs e)
+ {
+ MessageReader message = e.Message;
+ try
+ {
+ if (message.Length > 4)
+ {
+ this.announcementUpdate = default(Announcement);
+ this.announcementUpdate.DateFetched = DateTime.UtcNow.DayOfYear;
+ this.announcementUpdate.Id = message.ReadPackedUInt32();
+ this.announcementUpdate.AnnounceText = message.ReadString();
+ AnnouncementPopUp.AskedForUpdate = AnnouncementPopUp.AnnounceState.Success;
+ }
+ else
+ {
+ AnnouncementPopUp.AskedForUpdate = AnnouncementPopUp.AnnounceState.Cached;
+ }
+ }
+ finally
+ {
+ message.Recycle();
+ }
+ try
+ {
+ this.connection.Dispose();
+ this.connection = null;
+ }
+ catch
+ {
+ }
+ }
+
+ public IEnumerator Show()
+ {
+ float timer = 0f;
+ while (AnnouncementPopUp.AskedForUpdate == AnnouncementPopUp.AnnounceState.Fetching && this.connection != null && timer < 5f)
+ {
+ timer += Time.deltaTime;
+ yield return null;
+ }
+ if (!AnnouncementPopUp.IsSuccess(AnnouncementPopUp.AskedForUpdate))
+ {
+ Announcement lastAnnouncement = SaveManager.LastAnnouncement;
+ if (lastAnnouncement.Id == 0U)
+ {
+ this.AnnounceText.Text = "Couldn't get announcement.";
+ }
+ else
+ {
+ this.AnnounceText.Text = "Couldn't get announcement. Last Known:\r\n" + lastAnnouncement.AnnounceText;
+ }
+ }
+ else if (this.announcementUpdate.Id != SaveManager.LastAnnouncement.Id)
+ {
+ if (AnnouncementPopUp.AskedForUpdate != AnnouncementPopUp.AnnounceState.Cached)
+ {
+ base.gameObject.SetActive(true);
+ }
+ if (this.announcementUpdate.Id == 0U)
+ {
+ this.announcementUpdate = SaveManager.LastAnnouncement;
+ this.announcementUpdate.DateFetched = DateTime.UtcNow.DayOfYear;
+ }
+ SaveManager.LastAnnouncement = this.announcementUpdate;
+ this.AnnounceText.Text = this.announcementUpdate.AnnounceText;
+ }
+ while (base.gameObject.activeSelf)
+ {
+ yield return null;
+ }
+ yield break;
+ }
+
+ public void Close()
+ {
+ base.gameObject.SetActive(false);
+ }
+
+ private void OnDestroy()
+ {
+ if (this.connection != null)
+ {
+ this.connection.Dispose();
+ this.connection = null;
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/ArrowBehaviour.cs b/Client/Assembly-CSharp/ArrowBehaviour.cs
new file mode 100644
index 0000000..8e9365c
--- /dev/null
+++ b/Client/Assembly-CSharp/ArrowBehaviour.cs
@@ -0,0 +1,47 @@
+using System;
+using UnityEngine;
+
+public class ArrowBehaviour : MonoBehaviour
+{
+ public Vector3 target;
+
+ public float perc = 0.925f;
+
+ [HideInInspector]
+ public SpriteRenderer image;
+
+ public void Awake()
+ {
+ this.image = base.GetComponent<SpriteRenderer>();
+ }
+
+ public void Update()
+ {
+ Camera main = Camera.main;
+ Vector2 vector = this.target - main.transform.position;
+ float num = vector.magnitude / (main.orthographicSize * this.perc);
+ this.image.enabled = ((double)num > 0.3);
+ Vector2 vector2 = main.WorldToViewportPoint(this.target);
+ if (this.Between(vector2.x, 0f, 1f) && this.Between(vector2.y, 0f, 1f))
+ {
+ base.transform.position = this.target - vector.normalized * 0.6f;
+ float num2 = Mathf.Clamp(num, 0f, 1f);
+ base.transform.localScale = new Vector3(num2, num2, num2);
+ }
+ else
+ {
+ Vector2 vector3 = new Vector2(Mathf.Clamp(vector2.x * 2f - 1f, -1f, 1f), Mathf.Clamp(vector2.y * 2f - 1f, -1f, 1f));
+ float orthographicSize = main.orthographicSize;
+ float num3 = main.orthographicSize * main.aspect;
+ Vector3 b = new Vector3(Mathf.LerpUnclamped(0f, num3 * 0.88f, vector3.x), Mathf.LerpUnclamped(0f, orthographicSize * 0.79f, vector3.y), 0f);
+ base.transform.position = main.transform.position + b;
+ base.transform.localScale = Vector3.one;
+ }
+ base.transform.LookAt2d(this.target);
+ }
+
+ private bool Between(float value, float min, float max)
+ {
+ return value > min && value < max;
+ }
+}
diff --git a/Client/Assembly-CSharp/AspectPosition.cs b/Client/Assembly-CSharp/AspectPosition.cs
new file mode 100644
index 0000000..60654df
--- /dev/null
+++ b/Client/Assembly-CSharp/AspectPosition.cs
@@ -0,0 +1,86 @@
+using System;
+using UnityEngine;
+
+public class AspectPosition : MonoBehaviour
+{
+ public Camera parentCam;
+
+ private const int LeftFlag = 1;
+
+ private const int RightFlag = 2;
+
+ private const int BottomFlag = 4;
+
+ private const int TopFlag = 8;
+
+ public Vector3 DistanceFromEdge;
+
+ public AspectPosition.EdgeAlignments Alignment;
+
+ public enum EdgeAlignments
+ {
+ RightBottom = 6,
+ LeftBottom = 5,
+ RightTop = 10,
+ Left = 1,
+ Right,
+ Top = 8,
+ Bottom = 4,
+ LeftTop = 9
+ }
+
+ private void OnEnable()
+ {
+ this.AdjustPosition();
+ ResolutionManager.ResolutionChanged += this.AdjustPosition;
+ }
+
+ private void OnDisable()
+ {
+ ResolutionManager.ResolutionChanged -= this.AdjustPosition;
+ }
+
+ public void AdjustPosition()
+ {
+ Camera camera = this.parentCam ? this.parentCam : Camera.main;
+ float aspect = camera.aspect;
+ this.AdjustPosition(camera.aspect);
+ }
+
+ public void AdjustPosition(float aspect)
+ {
+ float orthographicSize = (this.parentCam ? this.parentCam : Camera.main).orthographicSize;
+ base.transform.localPosition = AspectPosition.ComputePosition(this.Alignment, this.DistanceFromEdge, orthographicSize, aspect);
+ }
+
+ public static Vector3 ComputePosition(AspectPosition.EdgeAlignments alignment, Vector3 relativePos)
+ {
+ Camera main = Camera.main;
+ float aspect = main.aspect;
+ float orthographicSize = main.orthographicSize;
+ return AspectPosition.ComputePosition(alignment, relativePos, orthographicSize, aspect);
+ }
+
+ public static Vector3 ComputePosition(AspectPosition.EdgeAlignments alignment, Vector3 relativePos, float cHeight, float aspect)
+ {
+ float num = cHeight * aspect;
+ Vector3 vector = relativePos;
+ if ((alignment & AspectPosition.EdgeAlignments.Left) != (AspectPosition.EdgeAlignments)0)
+ {
+ vector.x -= num;
+ }
+ else if ((alignment & AspectPosition.EdgeAlignments.Right) != (AspectPosition.EdgeAlignments)0)
+ {
+ vector.x = num - vector.x;
+ }
+ if ((alignment & AspectPosition.EdgeAlignments.Bottom) != (AspectPosition.EdgeAlignments)0)
+ {
+ vector.y -= cHeight;
+ }
+ else if ((alignment & AspectPosition.EdgeAlignments.Top) != (AspectPosition.EdgeAlignments)0)
+ {
+ vector.y = cHeight - vector.y;
+ }
+ return vector;
+ }
+}
diff --git a/Client/Assembly-CSharp/AspectSize.cs b/Client/Assembly-CSharp/AspectSize.cs
new file mode 100644
index 0000000..c0c8fe8
--- /dev/null
+++ b/Client/Assembly-CSharp/AspectSize.cs
@@ -0,0 +1,32 @@
+using System;
+using UnityEngine;
+
+public class AspectSize : MonoBehaviour
+{
+ public Sprite Background;
+
+ public SpriteRenderer Renderer;
+
+ public float PercentWidth = 0.95f;
+
+ public void OnEnable()
+ {
+ Camera main = Camera.main;
+ float num = main.orthographicSize * main.aspect;
+ float num2 = (this.Background ? this.Background : this.Renderer.sprite).bounds.size.x / 2f;
+ float num3 = num / num2 * this.PercentWidth;
+ if (num3 < 1f)
+ {
+ base.transform.localScale = new Vector3(num3, num3, num3);
+ }
+ }
+
+ public static float CalculateSize(Vector3 parentPos, Sprite sprite)
+ {
+ Camera main = Camera.main;
+ float num = main.orthographicSize * main.aspect + parentPos.x;
+ float x = sprite.bounds.size.x;
+ float b = num / x * 0.98f;
+ return Mathf.Min(1f, b);
+ }
+}
diff --git a/Client/Assembly-CSharp/Assembly-CSharp.csproj b/Client/Assembly-CSharp/Assembly-CSharp.csproj
new file mode 100644
index 0000000..a97f617
--- /dev/null
+++ b/Client/Assembly-CSharp/Assembly-CSharp.csproj
@@ -0,0 +1,464 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProjectGuid>{4D37C17B-28D8-4D17-A439-F712A18E4C22}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <RootNamespace>Assembly-CSharp</RootNamespace>
+ <AssemblyName>Assembly-CSharp</AssemblyName>
+ <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <PlatformTarget>AnyCPU</PlatformTarget>
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <PlatformTarget>AnyCPU</PlatformTarget>
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="Assembly-CSharp-firstpass">
+ <HintPath>..\..\..\Program Files (x86)\Steam\steamapps\common\Among Us\Among Us_Data\Managed\Assembly-CSharp-firstpass.dll</HintPath>
+ </Reference>
+ <Reference Include="Hazel">
+ <HintPath>..\..\..\Program Files (x86)\Steam\steamapps\common\Among Us\Among Us_Data\Managed\Hazel.dll</HintPath>
+ </Reference>
+ <Reference Include="System">
+ <HintPath>..\..\..\Program Files (x86)\Steam\steamapps\common\Among Us\Among Us_Data\Managed\System.dll</HintPath>
+ </Reference>
+ <Reference Include="System.Core">
+ <HintPath>..\..\..\Program Files (x86)\Steam\steamapps\common\Among Us\Among Us_Data\Managed\System.Core.dll</HintPath>
+ </Reference>
+ <Reference Include="UnityEngine.AnimationModule">
+ <HintPath>..\..\..\Program Files (x86)\Steam\steamapps\common\Among Us\Among Us_Data\Managed\UnityEngine.AnimationModule.dll</HintPath>
+ </Reference>
+ <Reference Include="UnityEngine.AudioModule">
+ <HintPath>..\..\..\Program Files (x86)\Steam\steamapps\common\Among Us\Among Us_Data\Managed\UnityEngine.AudioModule.dll</HintPath>
+ </Reference>
+ <Reference Include="UnityEngine.CoreModule">
+ <HintPath>..\..\..\Program Files (x86)\Steam\steamapps\common\Among Us\Among Us_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
+ </Reference>
+ <Reference Include="UnityEngine.ImageConversionModule">
+ <HintPath>..\..\..\Program Files (x86)\Steam\steamapps\common\Among Us\Among Us_Data\Managed\UnityEngine.ImageConversionModule.dll</HintPath>
+ </Reference>
+ <Reference Include="UnityEngine.ParticleSystemModule">
+ <HintPath>..\..\..\Program Files (x86)\Steam\steamapps\common\Among Us\Among Us_Data\Managed\UnityEngine.ParticleSystemModule.dll</HintPath>
+ </Reference>
+ <Reference Include="UnityEngine.Physics2DModule">
+ <HintPath>..\..\..\Program Files (x86)\Steam\steamapps\common\Among Us\Among Us_Data\Managed\UnityEngine.Physics2DModule.dll</HintPath>
+ </Reference>
+ <Reference Include="UnityEngine.Purchasing">
+ <HintPath>..\..\..\Program Files (x86)\Steam\steamapps\common\Among Us\Among Us_Data\Managed\UnityEngine.Purchasing.dll</HintPath>
+ </Reference>
+ <Reference Include="UnityEngine.ScreenCaptureModule">
+ <HintPath>..\..\..\Program Files (x86)\Steam\steamapps\common\Among Us\Among Us_Data\Managed\UnityEngine.ScreenCaptureModule.dll</HintPath>
+ </Reference>
+ <Reference Include="UnityEngine.SpriteMaskModule">
+ <HintPath>..\..\..\Program Files (x86)\Steam\steamapps\common\Among Us\Among Us_Data\Managed\UnityEngine.SpriteMaskModule.dll</HintPath>
+ </Reference>
+ <Reference Include="UnityEngine.UI">
+ <HintPath>..\..\..\Program Files (x86)\Steam\steamapps\common\Among Us\Among Us_Data\Managed\UnityEngine.UI.dll</HintPath>
+ </Reference>
+ <Reference Include="UnityEngine.UnityAnalyticsModule">
+ <HintPath>..\..\..\Program Files (x86)\Steam\steamapps\common\Among Us\Among Us_Data\Managed\UnityEngine.UnityAnalyticsModule.dll</HintPath>
+ </Reference>
+ </ItemGroup>
+ <ItemGroup>
+ <AppDesigner Include="Properties\" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="AcceptDivertPowerGame.cs" />
+ <Compile Include="AdDataCollectScreen.cs" />
+ <Compile Include="AdPlayer.cs" />
+ <Compile Include="AlignGame.cs" />
+ <Compile Include="AlphaBlink.cs" />
+ <Compile Include="AlphaPulse.cs" />
+ <Compile Include="AmongUsClient.cs" />
+ <Compile Include="AmongUsProduct.cs" />
+ <Compile Include="Announcement.cs" />
+ <Compile Include="AnnouncementPopUp.cs" />
+ <Compile Include="ArrowBehaviour.cs" />
+ <Compile Include="AspectPosition.cs" />
+ <Compile Include="AspectSize.cs" />
+ <Compile Include="Assets\CoreScripts\Telemetry.cs" />
+ <Compile Include="Asteroid.cs" />
+ <Compile Include="AutoOpenDoor.cs" />
+ <Compile Include="BanButton.cs" />
+ <Compile Include="BanMenu.cs" />
+ <Compile Include="BlockedWords.cs" />
+ <Compile Include="BoolRange.cs" />
+ <Compile Include="ButtonBehavior.cs" />
+ <Compile Include="ButtonRolloverHandler.cs" />
+ <Compile Include="CardSlideGame.cs" />
+ <Compile Include="ChainBehaviour.cs" />
+ <Compile Include="ChatBubble.cs" />
+ <Compile Include="ChatController.cs" />
+ <Compile Include="ChatNoteTypes.cs" />
+ <Compile Include="CloudGenerator.cs" />
+ <Compile Include="ColorChip.cs" />
+ <Compile Include="ConditionalHide.cs" />
+ <Compile Include="ConditionalStore.cs" />
+ <Compile Include="Console.cs" />
+ <Compile Include="Constants.cs" />
+ <Compile Include="Controller.cs" />
+ <Compile Include="CooldownHelpers.cs" />
+ <Compile Include="CounterArea.cs" />
+ <Compile Include="CourseMinigame.cs" />
+ <Compile Include="CourseStarBehaviour.cs" />
+ <Compile Include="CreateGameOptions.cs" />
+ <Compile Include="CreateOptionsPicker.cs" />
+ <Compile Include="CreateStoreButton.cs" />
+ <Compile Include="CrewVisualizer.cs" />
+ <Compile Include="CrossFadeImages.cs" />
+ <Compile Include="CrossFader.cs" />
+ <Compile Include="CrystalBehaviour.cs" />
+ <Compile Include="CrystalMinigame.cs" />
+ <Compile Include="CustomNetworkTransform.cs" />
+ <Compile Include="CustomPlayerMenu.cs" />
+ <Compile Include="DataCollectScreen.cs" />
+ <Compile Include="DeadBody.cs" />
+ <Compile Include="DeathReason.cs" />
+ <Compile Include="DeconSystem.cs" />
+ <Compile Include="DefaultPool.cs" />
+ <Compile Include="DemoKeyboardStick.cs" />
+ <Compile Include="DestroyableSingleton.cs" />
+ <Compile Include="DialBehaviour.cs" />
+ <Compile Include="DialogueBox.cs" />
+ <Compile Include="DisconnectPopup.cs" />
+ <Compile Include="DiscordManager.cs" />
+ <Compile Include="DiscoveryState.cs" />
+ <Compile Include="DiscussBehaviour.cs" />
+ <Compile Include="DivertPowerMetagame.cs" />
+ <Compile Include="DivertPowerMinigame.cs" />
+ <Compile Include="DivertPowerTask.cs" />
+ <Compile Include="DoorsSystemType.cs" />
+ <Compile Include="DotAligner.cs" />
+ <Compile Include="DragState.cs" />
+ <Compile Include="DummyBehaviour.cs" />
+ <Compile Include="DummyConsole.cs" />
+ <Compile Include="DynamicSound.cs" />
+ <Compile Include="Effects.cs" />
+ <Compile Include="ElectricTask.cs" />
+ <Compile Include="EmergencyMinigame.cs" />
+ <Compile Include="EmptyGarbageMinigame.cs" />
+ <Compile Include="EndGameManager.cs" />
+ <Compile Include="EngineBehaviour.cs" />
+ <Compile Include="EnterCodeMinigame.cs" />
+ <Compile Include="ExileController.cs" />
+ <Compile Include="ExitGameButton.cs" />
+ <Compile Include="Extensions.cs" />
+ <Compile Include="FindAGameManager.cs" />
+ <Compile Include="FindGameButton.cs" />
+ <Compile Include="FingerBehaviour.cs" />
+ <Compile Include="FixedActionConsole.cs" />
+ <Compile Include="FlatWaveBehaviour.cs" />
+ <Compile Include="FloatRange.cs" />
+ <Compile Include="FollowerCamera.cs" />
+ <Compile Include="FontCache.cs" />
+ <Compile Include="FontData.cs" />
+ <Compile Include="FontExtensionData.cs" />
+ <Compile Include="FontLoader.cs" />
+ <Compile Include="GameData.cs" />
+ <Compile Include="GameDiscovery.cs" />
+ <Compile Include="GameModes.cs" />
+ <Compile Include="GameObjectExtensions.cs" />
+ <Compile Include="GameOptionsData.cs" />
+ <Compile Include="GameOptionsMenu.cs" />
+ <Compile Include="GameOverReason.cs" />
+ <Compile Include="GameSettingMenu.cs" />
+ <Compile Include="GameStartManager.cs" />
+ <Compile Include="GarbageBehaviour.cs" />
+ <Compile Include="GoogleMobileAds\Api\AdapterState.cs" />
+ <Compile Include="GoogleMobileAds\Api\AdapterStatus.cs" />
+ <Compile Include="GoogleMobileAds\Api\AdErrorEventArgs.cs" />
+ <Compile Include="GoogleMobileAds\Api\AdFailedToLoadEventArgs.cs" />
+ <Compile Include="GoogleMobileAds\Api\AdLoader.cs" />
+ <Compile Include="GoogleMobileAds\Api\AdPosition.cs" />
+ <Compile Include="GoogleMobileAds\Api\AdRequest.cs" />
+ <Compile Include="GoogleMobileAds\Api\AdSize.cs" />
+ <Compile Include="GoogleMobileAds\Api\BannerView.cs" />
+ <Compile Include="GoogleMobileAds\Api\CustomNativeEventArgs.cs" />
+ <Compile Include="GoogleMobileAds\Api\CustomNativeTemplateAd.cs" />
+ <Compile Include="GoogleMobileAds\Api\Gender.cs" />
+ <Compile Include="GoogleMobileAds\Api\InterstitialAd.cs" />
+ <Compile Include="GoogleMobileAds\Api\Mediation\MediationExtras.cs" />
+ <Compile Include="GoogleMobileAds\Api\MobileAds.cs" />
+ <Compile Include="GoogleMobileAds\Api\NativeAdType.cs" />
+ <Compile Include="GoogleMobileAds\Api\Reward.cs" />
+ <Compile Include="GoogleMobileAds\Api\RewardBasedVideoAd.cs" />
+ <Compile Include="GoogleMobileAds\Api\RewardedAd.cs" />
+ <Compile Include="GoogleMobileAds\Api\ServerSideVerificationOptions.cs" />
+ <Compile Include="GoogleMobileAds\Common\DummyClient.cs" />
+ <Compile Include="GoogleMobileAds\Common\IAdLoaderClient.cs" />
+ <Compile Include="GoogleMobileAds\Common\IBannerClient.cs" />
+ <Compile Include="GoogleMobileAds\Common\ICustomNativeTemplateClient.cs" />
+ <Compile Include="GoogleMobileAds\Common\IInterstitialClient.cs" />
+ <Compile Include="GoogleMobileAds\Common\IMobileAdsClient.cs" />
+ <Compile Include="GoogleMobileAds\Common\IRewardBasedVideoAdClient.cs" />
+ <Compile Include="GoogleMobileAds\Common\IRewardedAdClient.cs" />
+ <Compile Include="GoogleMobileAds\Common\MobileAdsEventExecutor.cs" />
+ <Compile Include="GoogleMobileAds\Common\RewardedAdDummyClient.cs" />
+ <Compile Include="GoogleMobileAds\Common\Utils.cs" />
+ <Compile Include="GoogleMobileAds\GoogleMobileAdsClientFactory.cs" />
+ <Compile Include="HashRandom.cs" />
+ <Compile Include="HatBehaviour.cs" />
+ <Compile Include="HatManager.cs" />
+ <Compile Include="HatsTab.cs" />
+ <Compile Include="HorizontalGauge.cs" />
+ <Compile Include="HostGameButton.cs" />
+ <Compile Include="HowToPlayController.cs" />
+ <Compile Include="HudManager.cs" />
+ <Compile Include="HudOverrideSystemType.cs" />
+ <Compile Include="HudOverrideTask.cs" />
+ <Compile Include="IActivatable.cs" />
+ <Compile Include="IBuyable.cs" />
+ <Compile Include="IBytesSerializable.cs" />
+ <Compile Include="IConnectButton.cs" />
+ <Compile Include="IDisconnectHandler.cs" />
+ <Compile Include="IFocusHolder.cs" />
+ <Compile Include="IGameListHandler.cs" />
+ <Compile Include="ILocationActivate.cs" />
+ <Compile Include="ImageData.cs" />
+ <Compile Include="ImageNames.cs" />
+ <Compile Include="ImageTranslator.cs" />
+ <Compile Include="ImportantTextTask.cs" />
+ <Compile Include="InfectedOverlay.cs" />
+ <Compile Include="InnerNet\AlterGameTags.cs" />
+ <Compile Include="InnerNet\ClientData.cs" />
+ <Compile Include="InnerNet\DisconnectReasons.cs" />
+ <Compile Include="InnerNet\GameKeywords.cs" />
+ <Compile Include="InnerNet\GameListing.cs" />
+ <Compile Include="InnerNet\GameStates.cs" />
+ <Compile Include="InnerNet\InnerDiscover.cs" />
+ <Compile Include="InnerNet\InnerNetClient.cs" />
+ <Compile Include="InnerNet\InnerNetObject.cs" />
+ <Compile Include="InnerNet\InnerNetServer.cs" />
+ <Compile Include="InnerNet\JoinFailureReasons.cs" />
+ <Compile Include="InnerNet\LimboStates.cs" />
+ <Compile Include="InnerNet\MatchMakerModes.cs" />
+ <Compile Include="InnerNet\MessageExtensions.cs" />
+ <Compile Include="InnerNet\SpawnFlags.cs" />
+ <Compile Include="InnerNet\Tags.cs" />
+ <Compile Include="IntRange.cs" />
+ <Compile Include="IntroCutscene.cs" />
+ <Compile Include="IObjectPool.cs" />
+ <Compile Include="ISoundPlayer.cs" />
+ <Compile Include="ISystemType.cs" />
+ <Compile Include="ITranslatedText.cs" />
+ <Compile Include="IUsable.cs" />
+ <Compile Include="IVirtualJoystick.cs" />
+ <Compile Include="JoinGameButton.cs" />
+ <Compile Include="KerningPair.cs" />
+ <Compile Include="KeyboardJoystick.cs" />
+ <Compile Include="KeypadGame.cs" />
+ <Compile Include="KillAnimation.cs" />
+ <Compile Include="KillAnimType.cs" />
+ <Compile Include="KillButtonManager.cs" />
+ <Compile Include="KillOverlay.cs" />
+ <Compile Include="LanguageButton.cs" />
+ <Compile Include="LanguageSetter.cs" />
+ <Compile Include="LanguageUnit.cs" />
+ <Compile Include="LeafBehaviour.cs" />
+ <Compile Include="LeafMinigame.cs" />
+ <Compile Include="LetterTree.cs" />
+ <Compile Include="LifeSuppSystemType.cs" />
+ <Compile Include="LightSource.cs" />
+ <Compile Include="LobbyBehaviour.cs" />
+ <Compile Include="MainMenuManager.cs" />
+ <Compile Include="ManualDoor.cs" />
+ <Compile Include="MapBehaviour.cs" />
+ <Compile Include="MapConsole.cs" />
+ <Compile Include="MapCountOverlay.cs" />
+ <Compile Include="MapRoom.cs" />
+ <Compile Include="MapTaskOverlay.cs" />
+ <Compile Include="MatchMaker.cs" />
+ <Compile Include="MatchMakerGameButton.cs" />
+ <Compile Include="MedScanMinigame.cs" />
+ <Compile Include="MedScanSystem.cs" />
+ <Compile Include="MeetingHud.cs" />
+ <Compile Include="MeetingRoomManager.cs" />
+ <Compile Include="MemSafeStringExtensions.cs" />
+ <Compile Include="MeshRendererExtensions.cs" />
+ <Compile Include="Minigame.cs" />
+ <Compile Include="MMOnlineManager.cs" />
+ <Compile Include="MonoPInvokeCallbackAttribute.cs" />
+ <Compile Include="NameTextBehaviour.cs" />
+ <Compile Include="NavigationMinigame.cs" />
+ <Compile Include="NoOxyTask.cs" />
+ <Compile Include="NormalPlayerTask.cs" />
+ <Compile Include="NoShadowBehaviour.cs" />
+ <Compile Include="NotificationPopper.cs" />
+ <Compile Include="NumberOption.cs" />
+ <Compile Include="ObjectPoolBehavior.cs" />
+ <Compile Include="OffsetAdjustment.cs" />
+ <Compile Include="OneWayShadows.cs" />
+ <Compile Include="OptionBehaviour.cs" />
+ <Compile Include="OptionsConsole.cs" />
+ <Compile Include="OptionsMenuBehaviour.cs" />
+ <Compile Include="OverlayKillAnimation.cs" />
+ <Compile Include="Palette.cs" />
+ <Compile Include="ParallaxController.cs" />
+ <Compile Include="ParticleInfo.cs" />
+ <Compile Include="PassiveButton.cs" />
+ <Compile Include="PassiveButtonManager.cs" />
+ <Compile Include="PetBehaviour.cs" />
+ <Compile Include="PetsTab.cs" />
+ <Compile Include="PhysicsHelpers.cs" />
+ <Compile Include="PingTracker.cs" />
+ <Compile Include="PlayerAnimator.cs" />
+ <Compile Include="PlayerControl.cs" />
+ <Compile Include="PlayerParticle.cs" />
+ <Compile Include="PlayerParticleInfo.cs" />
+ <Compile Include="PlayerParticles.cs" />
+ <Compile Include="PlayerPhysics.cs" />
+ <Compile Include="PlayerTab.cs" />
+ <Compile Include="PlayerTask.cs" />
+ <Compile Include="PlayerVoteArea.cs" />
+ <Compile Include="PoolableBehavior.cs" />
+ <Compile Include="PoolablePlayer.cs" />
+ <Compile Include="PooledMapIcon.cs" />
+ <Compile Include="PowerTools\SpriteAnim.cs" />
+ <Compile Include="PowerTools\SpriteAnimEventHandler.cs" />
+ <Compile Include="PowerTools\SpriteAnimNodes.cs" />
+ <Compile Include="PowerTools\SpriteAnimNodeSync.cs" />
+ <Compile Include="PowerTools\WaitForAnimationFinish.cs" />
+ <Compile Include="ProgressTracker.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="PurchaseButton.cs" />
+ <Compile Include="PurchaseStates.cs" />
+ <Compile Include="RadioWaveBehaviour.cs" />
+ <Compile Include="RandomFill.cs" />
+ <Compile Include="ReactorMinigame.cs" />
+ <Compile Include="ReactorRoomWire.cs" />
+ <Compile Include="ReactorShipRoom.cs" />
+ <Compile Include="ReactorSystemType.cs" />
+ <Compile Include="ReactorTask.cs" />
+ <Compile Include="RefuelMinigame.cs" />
+ <Compile Include="RefuelStage.cs" />
+ <Compile Include="ReportButtonManager.cs" />
+ <Compile Include="ResolutionManager.cs" />
+ <Compile Include="ResolutionSlider.cs" />
+ <Compile Include="ResSetter.cs" />
+ <Compile Include="RingBuffer.cs" />
+ <Compile Include="RoomTracker.cs" />
+ <Compile Include="SabotageSystemType.cs" />
+ <Compile Include="SabotageTask.cs" />
+ <Compile Include="SampleMinigame.cs" />
+ <Compile Include="SaveManager.cs" />
+ <Compile Include="Scene0Controller.cs" />
+ <Compile Include="Scene1Controller.cs" />
+ <Compile Include="SceneChanger.cs" />
+ <Compile Include="SceneController.cs" />
+ <Compile Include="ScreenJoystick.cs" />
+ <Compile Include="Scroller.cs" />
+ <Compile Include="SecurityCameraSystemType.cs" />
+ <Compile Include="ServerInfo.cs" />
+ <Compile Include="ServerManager.cs" />
+ <Compile Include="ServerSelector.cs" />
+ <Compile Include="ServerSelectUi.cs" />
+ <Compile Include="SettingsMode.cs" />
+ <Compile Include="ShadowCamera.cs" />
+ <Compile Include="ShhhBehaviour.cs" />
+ <Compile Include="ShieldMinigame.cs" />
+ <Compile Include="ShipRoom.cs" />
+ <Compile Include="ShipStatus.cs" />
+ <Compile Include="ShowAdsState.cs" />
+ <Compile Include="SimonSaysGame.cs" />
+ <Compile Include="SinglePopHelp.cs" />
+ <Compile Include="SkinData.cs" />
+ <Compile Include="SkinLayer.cs" />
+ <Compile Include="SkinsTab.cs" />
+ <Compile Include="SlideBar.cs" />
+ <Compile Include="SortGameObject.cs" />
+ <Compile Include="SortMinigame.cs" />
+ <Compile Include="SoundGroup.cs" />
+ <Compile Include="SoundManager.cs" />
+ <Compile Include="SoundStarter.cs" />
+ <Compile Include="SpinAnimator.cs" />
+ <Compile Include="SpriteParticle.cs" />
+ <Compile Include="StarGen.cs" />
+ <Compile Include="StatsManager.cs" />
+ <Compile Include="StatsPopup.cs" />
+ <Compile Include="SteamBehaviour.cs" />
+ <Compile Include="SteamManager.cs" />
+ <Compile Include="SteamPurchasingModule.cs" />
+ <Compile Include="StoreMenu.cs" />
+ <Compile Include="StringExtensions.cs" />
+ <Compile Include="StringNames.cs" />
+ <Compile Include="StringOption.cs" />
+ <Compile Include="SubString.cs" />
+ <Compile Include="SubStringReader.cs" />
+ <Compile Include="SurvCamera.cs" />
+ <Compile Include="SurveillanceMinigame.cs" />
+ <Compile Include="SweepMinigame.cs" />
+ <Compile Include="SwitchMinigame.cs" />
+ <Compile Include="SwitchSystem.cs" />
+ <Compile Include="SystemConsole.cs" />
+ <Compile Include="SystemTypeHelpers.cs" />
+ <Compile Include="SystemTypes.cs" />
+ <Compile Include="TabButton.cs" />
+ <Compile Include="TabGroup.cs" />
+ <Compile Include="TaskAddButton.cs" />
+ <Compile Include="TaskAdderGame.cs" />
+ <Compile Include="TaskFolder.cs" />
+ <Compile Include="TaskPanelBehaviour.cs" />
+ <Compile Include="TaskSet.cs" />
+ <Compile Include="TaskTypes.cs" />
+ <Compile Include="TaskTypesHelpers.cs" />
+ <Compile Include="TempData.cs" />
+ <Compile Include="TextBox.cs" />
+ <Compile Include="TextController.cs" />
+ <Compile Include="TextLink.cs" />
+ <Compile Include="TextRenderer.cs" />
+ <Compile Include="TextTranslator.cs" />
+ <Compile Include="ToggleButtonBehaviour.cs" />
+ <Compile Include="ToggleOption.cs" />
+ <Compile Include="TowerBehaviour.cs" />
+ <Compile Include="TransitionOpen.cs" />
+ <Compile Include="TransitionType.cs" />
+ <Compile Include="TranslatedImageSet.cs" />
+ <Compile Include="TranslationController.cs" />
+ <Compile Include="TumbleBoxBehaviour.cs" />
+ <Compile Include="TuneRadioMinigame.cs" />
+ <Compile Include="TutorialManager.cs" />
+ <Compile Include="TutorialStatsManager.cs" />
+ <Compile Include="TwitterLink.cs" />
+ <Compile Include="UnlockManifoldsMinigame.cs" />
+ <Compile Include="UnlockPopUp.cs" />
+ <Compile Include="UploadDataGame.cs" />
+ <Compile Include="UploadDataTask.cs" />
+ <Compile Include="UseButtonManager.cs" />
+ <Compile Include="Vector2Range.cs" />
+ <Compile Include="VendingMinigame.cs" />
+ <Compile Include="VendingSlot.cs" />
+ <Compile Include="Vent.cs" />
+ <Compile Include="VersionShower.cs" />
+ <Compile Include="VerticalGauge.cs" />
+ <Compile Include="VirtualJoystick.cs" />
+ <Compile Include="VoteBanSystem.cs" />
+ <Compile Include="WaitForHostPopup.cs" />
+ <Compile Include="WaitForLerp.cs" />
+ <Compile Include="WeaponsMinigame.cs" />
+ <Compile Include="WeatherMinigame.cs" />
+ <Compile Include="WinningPlayerData.cs" />
+ <Compile Include="Wire.cs" />
+ <Compile Include="WireMinigame.cs" />
+ <Compile Include="WireNode.cs" />
+ <Compile Include="XXHash.cs" />
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project> \ No newline at end of file
diff --git a/Client/Assembly-CSharp/Assets/CoreScripts/Telemetry.cs b/Client/Assembly-CSharp/Assets/CoreScripts/Telemetry.cs
new file mode 100644
index 0000000..6d81ae0
--- /dev/null
+++ b/Client/Assembly-CSharp/Assets/CoreScripts/Telemetry.cs
@@ -0,0 +1,300 @@
+using System;
+using System.Collections.Generic;
+using InnerNet;
+using UnityEngine;
+using UnityEngine.Analytics;
+
+namespace Assets.CoreScripts
+{
+ public class Telemetry : DestroyableSingleton<Telemetry>
+ {
+ private static readonly string[] ColorNames = new string[]
+ {
+ "Red",
+ "Blue",
+ "Green",
+ "Pink",
+ "Orange",
+ "Yellow",
+ "Black",
+ "White",
+ "Purple",
+ "Brown"
+ };
+
+ private bool amHost;
+
+ private bool gameStarted;
+
+ private DateTime timeStarted;
+
+ public bool IsInitialized;
+
+ public Guid CurrentGuid;
+
+ public void Initialize()
+ {
+ this.Initialize(Guid.NewGuid());
+ }
+
+ public void Initialize(Guid gameGuid)
+ {
+ this.IsInitialized = true;
+ this.CurrentGuid = gameGuid;
+ }
+
+ public void StartGame(bool sendName, bool isHost, int playerCount, int impostorCount, GameModes gameMode, uint timesImpostor, uint gamesPlayed, uint crewStreak)
+ {
+ if (!SaveManager.SendTelemetry)
+ {
+ return;
+ }
+ this.gameStarted = true;
+ this.amHost = isHost;
+ this.timeStarted = DateTime.UtcNow;
+ Dictionary<string, object> dictionary = new Dictionary<string, object>
+ {
+ {
+ "Platform",
+ (byte)Application.platform
+ },
+ {
+ "TimesImpostor",
+ timesImpostor
+ },
+ {
+ "CrewStreak",
+ crewStreak
+ },
+ {
+ "GamesPlayed",
+ gamesPlayed
+ },
+ {
+ "GameMode",
+ gameMode
+ }
+ };
+ if (this.amHost)
+ {
+ dictionary.Add("PlayerCount", playerCount);
+ dictionary.Add("InfectedCount", impostorCount);
+ }
+ Analytics.CustomEvent("StartGame", dictionary);
+ }
+
+ public void WriteMeetingStarted(bool isEmergency)
+ {
+ if (!SaveManager.SendTelemetry)
+ {
+ return;
+ }
+ if (!this.amHost)
+ {
+ return;
+ }
+ Analytics.CustomEvent("MeetingStarted", new Dictionary<string, object>
+ {
+ {
+ "IsEmergency",
+ isEmergency
+ }
+ });
+ }
+
+ public void WriteMeetingEnded(byte[] results, float duration)
+ {
+ if (!SaveManager.SendTelemetry)
+ {
+ return;
+ }
+ if (!this.amHost)
+ {
+ return;
+ }
+ Analytics.CustomEvent("MeetingEnded", new Dictionary<string, object>
+ {
+ {
+ "IsEmergency",
+ duration
+ }
+ });
+ }
+
+ public void WritePosition(byte playerNum, Vector2 worldPos)
+ {
+ }
+
+ public void WriteMurder(byte sourcePlayerNum, byte targetPlayerNum, Vector3 worldPos)
+ {
+ if (!SaveManager.SendTelemetry)
+ {
+ return;
+ }
+ if (!this.gameStarted)
+ {
+ return;
+ }
+ Analytics.CustomEvent("Murder");
+ }
+
+ public void WriteSabotageUsed(SystemTypes systemType)
+ {
+ if (!SaveManager.SendTelemetry)
+ {
+ return;
+ }
+ if (!this.gameStarted)
+ {
+ return;
+ }
+ Analytics.CustomEvent("SabotageUsed", new Dictionary<string, object>
+ {
+ {
+ "SystemType",
+ systemType
+ }
+ });
+ }
+
+ public void WriteUse(byte playerNum, TaskTypes taskType, Vector3 worldPos)
+ {
+ if (!SaveManager.SendTelemetry)
+ {
+ return;
+ }
+ if (!this.gameStarted)
+ {
+ return;
+ }
+ Analytics.CustomEvent("ConsoleUsed", new Dictionary<string, object>
+ {
+ {
+ "TaskType",
+ taskType
+ }
+ });
+ }
+
+ public void WriteCompleteTask(byte playerNum, TaskTypes taskType)
+ {
+ if (!SaveManager.SendTelemetry)
+ {
+ return;
+ }
+ if (!this.gameStarted)
+ {
+ return;
+ }
+ Analytics.CustomEvent("TaskComplete", new Dictionary<string, object>
+ {
+ {
+ "TaskType",
+ taskType
+ }
+ });
+ }
+
+ internal void WriteDisconnect(DisconnectReasons reason)
+ {
+ if (!SaveManager.SendTelemetry)
+ {
+ return;
+ }
+ if (!this.gameStarted)
+ {
+ return;
+ }
+ Analytics.CustomEvent("Disconnect", new Dictionary<string, object>
+ {
+ {
+ "Reason",
+ reason
+ }
+ });
+ }
+
+ public void EndGame(GameOverReason endReason)
+ {
+ if (!SaveManager.SendTelemetry)
+ {
+ return;
+ }
+ if (!this.gameStarted)
+ {
+ return;
+ }
+ Dictionary<string, object> dictionary = new Dictionary<string, object>
+ {
+ {
+ "Reason",
+ endReason
+ }
+ };
+ if (this.amHost)
+ {
+ dictionary.Add("DurationSec", (DateTime.UtcNow - this.timeStarted).TotalSeconds);
+ }
+ Analytics.CustomEvent("EndGame", dictionary);
+ }
+
+ public void SendWho()
+ {
+ if (!SaveManager.SendTelemetry)
+ {
+ return;
+ }
+ if (!this.gameStarted)
+ {
+ return;
+ }
+ Analytics.CustomEvent("SentWho");
+ }
+
+ public void SelectInfected(int colorId, uint hatId)
+ {
+ if (!SaveManager.SendTelemetry)
+ {
+ return;
+ }
+ if (!this.gameStarted)
+ {
+ return;
+ }
+ Analytics.CustomEvent("SelectInfected", new Dictionary<string, object>
+ {
+ {
+ "Color",
+ Telemetry.ColorNames[colorId]
+ },
+ {
+ "Hat",
+ DestroyableSingleton<HatManager>.Instance.GetHatById(hatId).name
+ }
+ });
+ }
+
+ public void WonGame(int colorId, uint hatId)
+ {
+ if (!SaveManager.SendTelemetry)
+ {
+ return;
+ }
+ if (!this.gameStarted)
+ {
+ return;
+ }
+ Analytics.CustomEvent("WonGame", new Dictionary<string, object>
+ {
+ {
+ "Color",
+ Telemetry.ColorNames[colorId]
+ },
+ {
+ "Hat",
+ DestroyableSingleton<HatManager>.Instance.GetHatById(hatId).name
+ }
+ });
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/Asteroid.cs b/Client/Assembly-CSharp/Asteroid.cs
new file mode 100644
index 0000000..a633bef
--- /dev/null
+++ b/Client/Assembly-CSharp/Asteroid.cs
@@ -0,0 +1,71 @@
+using System;
+using System.Collections;
+using UnityEngine;
+
+public class Asteroid : PoolableBehavior
+{
+ public Vector3 TargetPosition { get; internal set; }
+
+ public Sprite[] AsteroidImages;
+
+ public Sprite[] BrokenImages;
+
+ private int imgIdx;
+
+ public FloatRange MoveSpeed = new FloatRange(2f, 5f);
+
+ public FloatRange RotateSpeed = new FloatRange(-10f, 10f);
+
+ public SpriteRenderer Explosion;
+
+ public void FixedUpdate()
+ {
+ base.transform.localRotation = Quaternion.Euler(0f, 0f, base.transform.localRotation.eulerAngles.z + this.RotateSpeed.Last * Time.fixedDeltaTime);
+ Vector3 a = this.TargetPosition - base.transform.localPosition;
+ if (a.sqrMagnitude > 0.05f)
+ {
+ a.Normalize();
+ base.transform.localPosition += a * this.MoveSpeed.Last * Time.fixedDeltaTime;
+ return;
+ }
+ this.OwnerPool.Reclaim(this);
+ }
+
+ public override void Reset()
+ {
+ base.enabled = true;
+ this.Explosion.enabled = false;
+ SpriteRenderer component = base.GetComponent<SpriteRenderer>();
+ this.imgIdx = this.AsteroidImages.RandomIdx<Sprite>();
+ component.sprite = this.AsteroidImages[this.imgIdx];
+ component.enabled = true;
+ ButtonBehavior component2 = base.GetComponent<ButtonBehavior>();
+ component2.enabled = true;
+ component2.OnClick.RemoveAllListeners();
+ base.transform.Rotate(0f, 0f, this.RotateSpeed.Next());
+ this.MoveSpeed.Next();
+ base.Reset();
+ }
+
+ public IEnumerator CoBreakApart()
+ {
+ base.enabled = false;
+ base.GetComponent<ButtonBehavior>().enabled = false;
+ this.Explosion.enabled = true;
+ yield return new WaitForLerp(0.1f, delegate(float t)
+ {
+ this.Explosion.transform.localScale = new Vector3(t, t, t);
+ });
+ yield return new WaitForSeconds(0.05f);
+ yield return new WaitForLerp(0.05f, delegate(float t)
+ {
+ this.Explosion.transform.localScale = new Vector3(1f - t, 1f - t, 1f - t);
+ });
+ SpriteRenderer rend = base.GetComponent<SpriteRenderer>();
+ yield return null;
+ rend.sprite = this.BrokenImages[this.imgIdx];
+ yield return new WaitForSeconds(0.2f);
+ this.OwnerPool.Reclaim(this);
+ yield break;
+ }
+}
diff --git a/Client/Assembly-CSharp/AutoOpenDoor.cs b/Client/Assembly-CSharp/AutoOpenDoor.cs
new file mode 100644
index 0000000..da09c70
--- /dev/null
+++ b/Client/Assembly-CSharp/AutoOpenDoor.cs
@@ -0,0 +1,37 @@
+using System;
+
+public class AutoOpenDoor : ManualDoor
+{
+ private const float ClosedDuration = 10f;
+
+ public SystemTypes Room;
+
+ public float ClosedTimer;
+
+ public float CooldownTimer;
+
+ public bool DoUpdate(float dt)
+ {
+ this.CooldownTimer = Math.Max(this.CooldownTimer - dt, 0f);
+ if (this.ClosedTimer > 0f)
+ {
+ this.ClosedTimer = Math.Max(this.ClosedTimer - dt, 0f);
+ if (this.ClosedTimer == 0f)
+ {
+ this.SetDoorway(true);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public override void SetDoorway(bool open)
+ {
+ if (!open)
+ {
+ this.ClosedTimer = 10f;
+ this.CooldownTimer = 30f;
+ }
+ base.SetDoorway(open);
+ }
+}
diff --git a/Client/Assembly-CSharp/BanButton.cs b/Client/Assembly-CSharp/BanButton.cs
new file mode 100644
index 0000000..0ed57a6
--- /dev/null
+++ b/Client/Assembly-CSharp/BanButton.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Collections;
+using UnityEngine;
+
+public class BanButton : MonoBehaviour
+{
+ public BanMenu Parent { get; set; }
+
+ public TextRenderer NameText;
+
+ public SpriteRenderer Background;
+
+ public int TargetClientId;
+
+ public int numVotes;
+
+ public void Start()
+ {
+ this.Background.SetCooldownNormalizedUvs();
+ }
+
+ public void Select()
+ {
+ this.Background.color = new Color(1f, 1f, 1f, 1f);
+ this.Parent.Select(this.TargetClientId);
+ }
+
+ public void Unselect()
+ {
+ this.Background.color = new Color(0.3f, 0.3f, 0.3f, 0.5f);
+ }
+
+ public void SetVotes(int newVotes)
+ {
+ base.StopAllCoroutines();
+ base.StartCoroutine(this.CoSetVotes(this.numVotes, newVotes));
+ this.numVotes = newVotes;
+ }
+
+ private IEnumerator CoSetVotes(int oldNum, int newNum)
+ {
+ float num = (float)oldNum / 3f;
+ float end = (float)newNum / 3f;
+ for (float timer = 0f; timer < 0.2f; timer += Time.deltaTime)
+ {
+ this.Background.material.SetFloat("_Percent", Mathf.SmoothStep(end, end, timer / 0.2f));
+ yield return null;
+ }
+ this.Background.material.SetFloat("_Percent", end);
+ yield break;
+ }
+}
diff --git a/Client/Assembly-CSharp/BanMenu.cs b/Client/Assembly-CSharp/BanMenu.cs
new file mode 100644
index 0000000..eaa7440
--- /dev/null
+++ b/Client/Assembly-CSharp/BanMenu.cs
@@ -0,0 +1,153 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using InnerNet;
+using UnityEngine;
+
+public class BanMenu : MonoBehaviour
+{
+ public BanButton BanButtonPrefab;
+
+ public SpriteRenderer Background;
+
+ public SpriteRenderer BanButton;
+
+ public SpriteRenderer KickButton;
+
+ public GameObject ContentParent;
+
+ public int selected = -1;
+
+ [HideInInspector]
+ public List<BanButton> allButtons = new List<BanButton>();
+
+ public void SetVisible(bool show)
+ {
+ show &= (PlayerControl.LocalPlayer && PlayerControl.LocalPlayer.Data != null && !PlayerControl.LocalPlayer.Data.IsDead);
+ show &= AmongUsClient.Instance.CanKick();
+ show &= (MeetingHud.Instance || !ShipStatus.Instance);
+ this.BanButton.gameObject.SetActive(AmongUsClient.Instance.CanBan());
+ this.KickButton.gameObject.SetActive(AmongUsClient.Instance.CanKick());
+ base.GetComponent<SpriteRenderer>().enabled = show;
+ base.GetComponent<PassiveButton>().enabled = show;
+ }
+
+ private void Update()
+ {
+ for (int i = 0; i < AmongUsClient.Instance.allClients.Count; i++)
+ {
+ try
+ {
+ ClientData client = AmongUsClient.Instance.allClients[i];
+ if (client == null)
+ {
+ break;
+ }
+ int[] source;
+ if (VoteBanSystem.Instance.HasMyVote(client.Id) && VoteBanSystem.Instance.Votes.TryGetValue(client.Id, out source))
+ {
+ int num = source.Count((int c) => c != 0);
+ BanButton banButton = this.allButtons.FirstOrDefault((BanButton b) => b.TargetClientId == client.Id);
+ if (banButton && banButton.numVotes != num)
+ {
+ banButton.SetVotes(num);
+ }
+ }
+ }
+ catch
+ {
+ break;
+ }
+ }
+ }
+
+ public void Show()
+ {
+ if (this.ContentParent.activeSelf)
+ {
+ this.Hide();
+ return;
+ }
+ this.selected = -1;
+ this.KickButton.color = Color.gray;
+ this.BanButton.color = Color.gray;
+ this.ContentParent.SetActive(true);
+ int num = 0;
+ if (AmongUsClient.Instance)
+ {
+ List<ClientData> allClients = AmongUsClient.Instance.allClients;
+ for (int i = 0; i < allClients.Count; i++)
+ {
+ ClientData clientData = allClients[i];
+ if (clientData.Id != AmongUsClient.Instance.ClientId && clientData.Character)
+ {
+ GameData.PlayerInfo data = clientData.Character.Data;
+ if (!string.IsNullOrWhiteSpace(data.PlayerName))
+ {
+ BanButton banButton = UnityEngine.Object.Instantiate<BanButton>(this.BanButtonPrefab, this.ContentParent.transform);
+ banButton.transform.localPosition = new Vector3(-0.2f, -0.15f - 0.4f * (float)num, -1f);
+ banButton.Parent = this;
+ banButton.NameText.Text = data.PlayerName;
+ banButton.TargetClientId = clientData.Id;
+ banButton.Unselect();
+ this.allButtons.Add(banButton);
+ num++;
+ }
+ }
+ }
+ }
+ this.KickButton.transform.localPosition = new Vector3(-0.8f, -0.15f - 0.4f * (float)num - 0.1f, -1f);
+ this.BanButton.transform.localPosition = new Vector3(0.3f, -0.15f - 0.4f * (float)num - 0.1f, -1f);
+ float num2 = 0.3f + (float)(num + 1) * 0.4f;
+ this.Background.size = new Vector2(3f, num2);
+ this.Background.GetComponent<BoxCollider2D>().size = new Vector2(3f, num2);
+ this.Background.transform.localPosition = new Vector3(0f, -num2 / 2f + 0.15f, 0.1f);
+ }
+
+ public void Hide()
+ {
+ this.selected = -1;
+ this.ContentParent.SetActive(false);
+ for (int i = 0; i < this.allButtons.Count; i++)
+ {
+ UnityEngine.Object.Destroy(this.allButtons[i].gameObject);
+ }
+ this.allButtons.Clear();
+ }
+
+ public void Select(int client)
+ {
+ if (VoteBanSystem.Instance.HasMyVote(client))
+ {
+ return;
+ }
+ this.selected = client;
+ for (int i = 0; i < this.allButtons.Count; i++)
+ {
+ BanButton banButton = this.allButtons[i];
+ if (banButton.TargetClientId != client)
+ {
+ banButton.Unselect();
+ }
+ }
+ this.KickButton.color = Color.white;
+ this.BanButton.color = Color.white;
+ }
+
+ public void Kick(bool ban)
+ {
+ if (this.selected >= 0)
+ {
+ if (AmongUsClient.Instance.CanBan())
+ {
+ AmongUsClient.Instance.KickPlayer(this.selected, ban);
+ this.Hide();
+ }
+ else
+ {
+ VoteBanSystem.Instance.CmdAddVote(this.selected);
+ }
+ }
+ this.Select(-1);
+ }
+}
diff --git a/Client/Assembly-CSharp/BlockedWords.cs b/Client/Assembly-CSharp/BlockedWords.cs
new file mode 100644
index 0000000..af08a2f
--- /dev/null
+++ b/Client/Assembly-CSharp/BlockedWords.cs
@@ -0,0 +1,877 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+public static class BlockedWords
+{
+ public static readonly HashSet<char> SymbolChars = new HashSet<char>
+ {
+ '?',
+ '!',
+ ',',
+ '.',
+ '\'',
+ ':',
+ ';',
+ '(',
+ ')',
+ '/',
+ '\\',
+ '%',
+ '^',
+ '&',
+ '-',
+ '='
+ };
+
+ private static readonly LetterTree SkipList;
+
+ public static readonly string[] AllWords = new string[]
+ {
+ "anal~",
+ "analingus",
+ "anilingus",
+ "anus^",
+ "apeshit",
+ "areola",
+ "areole",
+ "arian~",
+ "arse~",
+ "arsehole",
+ "aryan~",
+ "ass^",
+ "assbandit",
+ "assbang",
+ "assbite",
+ "assclown",
+ "asscock",
+ "asscracker",
+ "asses~",
+ "assface",
+ "assfuck",
+ "assfukka",
+ "assgoblin",
+ "asshat",
+ "asshead",
+ "assho1e",
+ "asshole",
+ "asshopper",
+ "assjabber",
+ "assjacker",
+ "asslick",
+ "assmaster",
+ "assmonkey",
+ "assmucus",
+ "assmunch",
+ "assnigger",
+ "asspirate",
+ "asswad",
+ "asswhole",
+ "asswipe",
+ "autoerotic",
+ "axwound",
+ "azazel",
+ "azz",
+ "babybatter",
+ "babyjuice",
+ "ballbag",
+ "ballgag",
+ "ballgravy",
+ "ballkicking",
+ "balllicking",
+ "balls~",
+ "ballsac",
+ "ballsak",
+ "bampot",
+ "bangbros",
+ "bareback",
+ "barelylegal",
+ "barenaked",
+ "bastard",
+ "bastinado",
+ "battyboy",
+ "bawdy",
+ "bbw",
+ "bdsm",
+ "beaner",
+ "beardedclam",
+ "beastial",
+ "beatch",
+ "beaver",
+ "beefcurtain",
+ "beeyotch",
+ "bellend",
+ "beotch",
+ "bescumber",
+ "bestial",
+ "b!atch",
+ "b!gblack",
+ "b!gbreasts",
+ "b!gknockers",
+ "b!gtits",
+ "b!mbo",
+ "b!nt",
+ "b!rdlock",
+ "b!tch",
+ "bisexual",
+ "blackcock",
+ "blondeaction",
+ "bloodclaat",
+ "bloodyhell",
+ "blowjob",
+ "blowme",
+ "blowmud",
+ "blowyourload",
+ "bluewaffle",
+ "blumpkin",
+ "b0iolas",
+ "b0llock",
+ "b0llok",
+ "b0llox",
+ "b0ndage",
+ "b0ned",
+ "b0ner",
+ "b0ng",
+ "b00b",
+ "b00ger",
+ "b00kie",
+ "b00tee",
+ "b00tie",
+ "b00ty",
+ "b00ze",
+ "b00zy",
+ "b0som",
+ "breasts",
+ "breeder",
+ "brotherfucker",
+ "brownshowers",
+ "brunetteaction",
+ "buceta",
+ "bukkake",
+ "bulldyke",
+ "bulletvibe",
+ "bullshit",
+ "bullturds",
+ "buncombe",
+ "bung",
+ "bunnyfucker",
+ "bustaload",
+ "busty",
+ "buttcheeks",
+ "buttfuck",
+ "butthole",
+ "buttmuch",
+ "buttmunch",
+ "buttpirate",
+ "buttplug",
+ "caca",
+ "cahone",
+ "cameltoe",
+ "camgirl",
+ "camslut",
+ "camwhore",
+ "carpetmuncher",
+ "cawk",
+ "cervix",
+ "chesticle",
+ "chichiman",
+ "chickwithadick",
+ "childfucker",
+ "chinc",
+ "chink",
+ "choad",
+ "chocice",
+ "chocolaterosebuds",
+ "chode",
+ "chotabags",
+ "cipa~",
+ "circlejerk",
+ "clevelandsteamer",
+ "cl1max",
+ "cl1t",
+ "cloverclamps",
+ "clunge",
+ "clusterfuck",
+ "cnut",
+ "c0cain",
+ "c0ccydynia",
+ "c0ck",
+ "c0ffindodger",
+ "c0ital",
+ "c0k",
+ "c0mmie",
+ "c0ndom",
+ "c0ochie",
+ "c0ochy",
+ "c0on",
+ "c0oter",
+ "c0prolagnia",
+ "c0prophilia",
+ "c0psomewood",
+ "c0rksucker",
+ "c0rnhole",
+ "c0rpulent",
+ "c0rpwhore",
+ "c0x",
+ "crack",
+ "creampie",
+ "cretin",
+ "crikey",
+ "cripple",
+ "crotte",
+ "cum~",
+ "cumbubble",
+ "cumchugger",
+ "cumdump",
+ "cumfreak",
+ "cumguzzler",
+ "cumjockey",
+ "cums~",
+ "cumtart",
+ "cunilingus",
+ "cunnie",
+ "cunny",
+ "cunt",
+ "cutrope",
+ "cyalis",
+ "cyberfuc",
+ "dago",
+ "darkie",
+ "daterape",
+ "dawgiestyle",
+ "deepthroat",
+ "deggo",
+ "dendrophilia",
+ "dick",
+ "diddle",
+ "dike",
+ "dild0",
+ "diligaf",
+ "dillweed",
+ "dimwit",
+ "dingle",
+ "dink",
+ "dipship",
+ "dipshit",
+ "dirsa",
+ "dirtypillows",
+ "dirtysanchez",
+ "dlck",
+ "d0gfucker",
+ "d0ggiestyle",
+ "d0ggin",
+ "d0ggystyle",
+ "d0gstyle",
+ "d0lcett",
+ "d0mination",
+ "d0minatrix",
+ "d0mmes",
+ "d0ng",
+ "d0nkeypunch",
+ "d0nkeyribber",
+ "d0ochbag",
+ "d0ofus",
+ "d0okie",
+ "d0osh",
+ "d0pey",
+ "d0ubledong",
+ "d0ublelift",
+ "d0ublepenetration",
+ "d0uch3",
+ "dpaction",
+ "drilldo",
+ "dryhump",
+ "duche",
+ "dumass",
+ "dumbass",
+ "dumbcunt",
+ "dumbfuck",
+ "dumbshit",
+ "dummy",
+ "dumshit",
+ "dvda",
+ "dyke",
+ "eatadick",
+ "eathairpie",
+ "eatmyass",
+ "ecchi",
+ "ejaculate",
+ "ejaculating",
+ "ejaculation",
+ "ejakulate",
+ "erect",
+ "erotic",
+ "erotism",
+ "essohbee",
+ "eunuch",
+ "extacy",
+ "extasy",
+ "f4cial",
+ "f4ck",
+ "f4g",
+ "f4ig",
+ "f4nny",
+ "f4nyy",
+ "f4tass",
+ "fcuk",
+ "fecal",
+ "feck",
+ "feist",
+ "felch",
+ "fellate",
+ "fellatio",
+ "feltch",
+ "femalesquirting",
+ "femdom",
+ "fenian",
+ "figging",
+ "fingerbang",
+ "fingerfuck",
+ "fingering",
+ "fisted",
+ "fistfuck",
+ "fisting",
+ "fisty",
+ "flange",
+ "flaps",
+ "fleshflute",
+ "flogthelog",
+ "floozy",
+ "foad",
+ "foah",
+ "fondle",
+ "foobar",
+ "fook",
+ "footfetish",
+ "footjob",
+ "foreskin",
+ "freex",
+ "frenchify",
+ "frigg",
+ "frotting",
+ "fubar",
+ "fuc",
+ "fudgepacker",
+ "fuk",
+ "fuq",
+ "furfag",
+ "furryfag",
+ "futanari",
+ "fuck",
+ "fux",
+ "fvck",
+ "fxck",
+ "gangbang",
+ "ganja",
+ "gash",
+ "gassyass",
+ "gay",
+ "genderbender",
+ "genitals",
+ "gey~",
+ "gfy",
+ "ghay",
+ "ghey",
+ "giantcock",
+ "gigolo",
+ "gippo",
+ "girlon",
+ "girlsgonewild",
+ "glans",
+ "goatcx",
+ "goatse",
+ "gokkun",
+ "goldenshower",
+ "golliwog",
+ "gonad",
+ "gooch",
+ "googirl",
+ "gook",
+ "goregasm",
+ "gringo",
+ "grope",
+ "groupsex",
+ "gspot",
+ "guido",
+ "guro",
+ "hamflap",
+ "handjob",
+ "hardon",
+ "hell~",
+ "hells",
+ "hemp",
+ "homo^",
+ "hentai",
+ "heroin",
+ "heshe~",
+ "hircismus",
+ "hitler",
+ "hugefat",
+ "hump~",
+ "hussy",
+ "hymen",
+ "inbred",
+ "incest",
+ "injun",
+ "intercourse",
+ "jackass",
+ "jackhole",
+ "jackoff",
+ "jaggi",
+ "jagoff",
+ "jailbait",
+ "jellydonut",
+ "jigaboo",
+ "jiggerboo",
+ "jism",
+ "jiz",
+ "jock",
+ "juggs",
+ "junglebunny",
+ "junkie",
+ "junky",
+ "kafir",
+ "kawk",
+ "kike",
+ "kinbaku",
+ "kinkster",
+ "kinky",
+ "klan",
+ "kock",
+ "kondum",
+ "kooch",
+ "kootch",
+ "kraut",
+ "kum",
+ "kunilingus",
+ "kunja",
+ "kunt~",
+ "kwif",
+ "kyke",
+ "labia",
+ "lameass",
+ "lardass",
+ "l3i+ch",
+ "l3monparty",
+ "l3per",
+ "l3sbian",
+ "l3sbo~",
+ "l3z~",
+ "lolita",
+ "looney",
+ "lovemaking",
+ "lube",
+ "lust",
+ "m4fugly",
+ "m4kemecome",
+ "m4lesquirting",
+ "m4ms",
+ "m45ochist",
+ "m45sa",
+ "m45terbate",
+ "m45terbating",
+ "m45terbation",
+ "m45terb8",
+ "m45turbate",
+ "m45turbating",
+ "m45turbation",
+ "mcfagget",
+ "menageatrois",
+ "menses",
+ "menstruate",
+ "menstruation",
+ "meth~",
+ "mfucking",
+ "mick",
+ "microphallus",
+ "middlefinger",
+ "midget",
+ "milf",
+ "minge",
+ "missionaryposition",
+ "m0f0",
+ "m0lest",
+ "m0olie",
+ "m0omoofoofoo",
+ "m0ron",
+ "m0thafuck",
+ "m0therfuck",
+ "m0undofvenus",
+ "mrhands",
+ "muff",
+ "munging",
+ "munter",
+ "mutha",
+ "muther",
+ "naked",
+ "nambla",
+ "napalm",
+ "nappy",
+ "nawashi",
+ "nazi",
+ "ngga^",
+ "nggr",
+ "nlgger",
+ "nlgga",
+ "nlggr",
+ "n1gaboo",
+ "n1qqa",
+ "n1qqer",
+ "n1gga",
+ "n1bba",
+ "n1ggr",
+ "n1gger",
+ "n1ggle",
+ "n1glet",
+ "n1gnog",
+ "n1mphomania",
+ "n1mrod",
+ "n1nny",
+ "n1pple",
+ "nonce",
+ "nsfwimages",
+ "nude",
+ "nudity",
+ "numbnuts",
+ "nutbutter",
+ "nutsack",
+ "nutter",
+ "nympho",
+ "octopussy",
+ "oldbag",
+ "omorashi",
+ "onecuptwogirls",
+ "oneguyonejar",
+ "opiate",
+ "opium",
+ "orally",
+ "orgasim",
+ "orgasm",
+ "orgies",
+ "orgy",
+ "ovary",
+ "ovum",
+ "paedophile",
+ "paki~",
+ "panooch",
+ "pansy",
+ "pantie",
+ "panty",
+ "pecker",
+ "pedo~",
+ "pedophile",
+ "pegging",
+ "penetrate",
+ "penetration",
+ "penial",
+ "penile",
+ "penis",
+ "perversion",
+ "phallic",
+ "phonesex",
+ "phuck",
+ "phuk",
+ "phuq",
+ "pigfucker",
+ "pikey",
+ "pillowbiter",
+ "pimp",
+ "pinko~",
+ "playboy",
+ "pleasurechest",
+ "p0lack",
+ "p0lesmoker",
+ "p0llock",
+ "p0nyplay",
+ "p0on",
+ "p0rchmonkey",
+ "p0rn",
+ "prick",
+ "prig",
+ "princealbertpiercing",
+ "pron",
+ "prostitute",
+ "prude",
+ "psycho",
+ "pthc",
+ "pube",
+ "pubic",
+ "pubis",
+ "punani",
+ "punkass",
+ "punky",
+ "punta",
+ "puss",
+ "puta~",
+ "puto~",
+ "queaf",
+ "queef",
+ "queer",
+ "quicky",
+ "quim",
+ "racy",
+ "raghead",
+ "ragingboner",
+ "rape~",
+ "raper~",
+ "raping",
+ "rapist",
+ "ratard",
+ "raunch",
+ "rectal",
+ "rectum",
+ "rectus",
+ "reefer",
+ "reich",
+ "renob",
+ "retard",
+ "reversecowgirl",
+ "revue",
+ "rimjaw",
+ "rimjob",
+ "rimming",
+ "ritard",
+ "rosypalm",
+ "rtard",
+ "rubbish",
+ "rump",
+ "ruski",
+ "rustytrombone",
+ "sadism",
+ "sadist",
+ "sambo",
+ "sandbar",
+ "sandler",
+ "sandnigger",
+ "sanger",
+ "santorum",
+ "sausagequeen",
+ "scag",
+ "scantily",
+ "scat",
+ "schizo",
+ "schlong",
+ "scissoring",
+ "scroat",
+ "scrog",
+ "scrot",
+ "scrud",
+ "seaman",
+ "seamen",
+ "seks",
+ "semen",
+ "sex",
+ "shag",
+ "shamedame",
+ "shavedbeaver",
+ "shavedpussy",
+ "shemale",
+ "shibari",
+ "shirtlifter",
+ "shit^",
+ "shiz",
+ "shota",
+ "shrimping",
+ "sissy",
+ "skag",
+ "skank",
+ "skeet",
+ "skullfuck",
+ "slag",
+ "slanteye",
+ "slave",
+ "sleaze",
+ "sleazy",
+ "slope",
+ "slut",
+ "smartass",
+ "smut",
+ "snatch",
+ "snowballing",
+ "snuff",
+ "s0doff",
+ "s0dom",
+ "s0nofabitch",
+ "s0nofawhore",
+ "spade",
+ "sperm",
+ "spic~",
+ "spick",
+ "spik~",
+ "splooge",
+ "spooge",
+ "spreadlegs",
+ "spunk",
+ "stfu",
+ "stiffy",
+ "strapon",
+ "strappado",
+ "styledoggy",
+ "suckass",
+ "suckingass",
+ "suicidegirls",
+ "sultrywomen",
+ "sumofabiatch",
+ "swastika",
+ "swinger",
+ "taintedlove",
+ "tampon",
+ "tard",
+ "tastemy",
+ "tawdry",
+ "teabagging",
+ "teat",
+ "teets",
+ "teez",
+ "teste~",
+ "testes",
+ "testical",
+ "testicle",
+ "testis",
+ "threesome",
+ "throating",
+ "thundercunt",
+ "thot~",
+ "t1edup",
+ "t1ghtwhite",
+ "t1nkle",
+ "t1t`",
+ "t1ts`",
+ "t1tty",
+ "t1ttie",
+ "tongueina",
+ "toots",
+ "topless",
+ "tosser",
+ "towelhead",
+ "tramp",
+ "tranny",
+ "transsexual",
+ "trashy",
+ "tribadism",
+ "tubgirl",
+ "turd",
+ "tush",
+ "tw4t^",
+ "twink",
+ "twofingers",
+ "twogirlsonecup",
+ "twunt",
+ "unclefucker",
+ "undies",
+ "undressing",
+ "upskirt",
+ "urethraplay",
+ "urinal",
+ "urine",
+ "urophilia",
+ "uterus",
+ "vajayjay",
+ "vajj",
+ "valium",
+ "venusmound",
+ "veqtable",
+ "v14gra",
+ "v1brator",
+ "v1gra",
+ "v1oletwand",
+ "v1rgin",
+ "v1xen",
+ "vjayjay",
+ "vodka",
+ "vomit",
+ "vorarephilia",
+ "voyeur",
+ "vulgar",
+ "vulva",
+ "wang",
+ "wank",
+ "wazoo",
+ "wedgie",
+ "weed",
+ "weenie",
+ "weewee",
+ "weiner",
+ "wetback",
+ "wetdream",
+ "whitepower",
+ "whiz",
+ "wh0ar~",
+ "wh0ars",
+ "wh0ralicious",
+ "wh0re^",
+ "wh0ring^",
+ "wigger",
+ "windowlicker",
+ "wiseass",
+ "w0g~",
+ "w00se",
+ "w0p~",
+ "wrappingmen",
+ "wrinkledstarfish",
+ "xrated",
+ "xxx",
+ "yaoi",
+ "yeasty",
+ "yellowshowers",
+ "yiffy",
+ "yobbo",
+ "zibbi",
+ "zoophilia",
+ "zubb"
+ };
+
+ private class LengthCompare : IComparer<string>
+ {
+ public static readonly BlockedWords.LengthCompare Instance = new BlockedWords.LengthCompare();
+
+ public int Compare(string x, string y)
+ {
+ return -x.Length.CompareTo(y.Length);
+ }
+ }
+
+ static BlockedWords()
+ {
+ BlockedWords.SkipList = new LetterTree();
+ for (int i = 0; i < BlockedWords.AllWords.Length; i++)
+ {
+ BlockedWords.SkipList.AddWord(BlockedWords.AllWords[i]);
+ }
+ }
+
+ public static bool ContainsWord(string chatText)
+ {
+ for (int i = 0; i < chatText.Length; i++)
+ {
+ if (BlockedWords.SkipList.Search(chatText, i) > 0)
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static string CensorWords(string chatText)
+ {
+ StringBuilder stringBuilder = new StringBuilder(chatText);
+ for (int i = 0; i < chatText.Length; i++)
+ {
+ int num = BlockedWords.SkipList.Search(stringBuilder, i);
+ if (num > 0)
+ {
+ for (int j = 0; j < num; j++)
+ {
+ stringBuilder[i + j] = '*';
+ }
+ i = i + num - 1;
+ }
+ }
+ return stringBuilder.ToString();
+ }
+
+ private static bool IsLetter(char letter)
+ {
+ return letter != ' ' && letter != '\r' && letter != '\n' && !BlockedWords.SymbolChars.Contains(letter);
+ }
+}
diff --git a/Client/Assembly-CSharp/BoolRange.cs b/Client/Assembly-CSharp/BoolRange.cs
new file mode 100644
index 0000000..866c81b
--- /dev/null
+++ b/Client/Assembly-CSharp/BoolRange.cs
@@ -0,0 +1,10 @@
+using System;
+using UnityEngine;
+
+public class BoolRange
+{
+ public static bool Next(float p = 0.5f)
+ {
+ return UnityEngine.Random.value <= p;
+ }
+}
diff --git a/Client/Assembly-CSharp/ButtonBehavior.cs b/Client/Assembly-CSharp/ButtonBehavior.cs
new file mode 100644
index 0000000..aa941c8
--- /dev/null
+++ b/Client/Assembly-CSharp/ButtonBehavior.cs
@@ -0,0 +1,64 @@
+using System;
+using UnityEngine;
+using UnityEngine.UI;
+
+public class ButtonBehavior : MonoBehaviour
+{
+ public bool OnUp = true;
+
+ public bool OnDown;
+
+ public bool Repeat;
+
+ public Button.ButtonClickedEvent OnClick = new Button.ButtonClickedEvent();
+
+ private Controller myController = new Controller();
+
+ private Collider2D[] colliders;
+
+ private float downTime;
+
+ public void OnEnable()
+ {
+ this.colliders = base.GetComponents<Collider2D>();
+ this.myController.Reset();
+ }
+
+ public void Update()
+ {
+ this.myController.Update();
+ foreach (Collider2D coll in this.colliders)
+ {
+ switch (this.myController.CheckDrag(coll, false))
+ {
+ case DragState.TouchStart:
+ if (this.OnDown)
+ {
+ this.OnClick.Invoke();
+ }
+ break;
+ case DragState.Dragging:
+ if (this.Repeat)
+ {
+ this.downTime += Time.fixedDeltaTime;
+ if (this.downTime >= 0.3f)
+ {
+ this.downTime = 0f;
+ this.OnClick.Invoke();
+ }
+ }
+ else
+ {
+ this.downTime = 0f;
+ }
+ break;
+ case DragState.Released:
+ if (this.OnUp)
+ {
+ this.OnClick.Invoke();
+ }
+ break;
+ }
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/ButtonRolloverHandler.cs b/Client/Assembly-CSharp/ButtonRolloverHandler.cs
new file mode 100644
index 0000000..963058d
--- /dev/null
+++ b/Client/Assembly-CSharp/ButtonRolloverHandler.cs
@@ -0,0 +1,35 @@
+using System;
+using UnityEngine;
+using UnityEngine.Events;
+
+public class ButtonRolloverHandler : MonoBehaviour
+{
+ public SpriteRenderer Target;
+
+ public Color OverColor = Color.green;
+
+ public Color OutColor = Color.white;
+
+ public AudioClip HoverSound;
+
+ public void Start()
+ {
+ PassiveButton component = base.GetComponent<PassiveButton>();
+ component.OnMouseOver.AddListener(new UnityAction(this.DoMouseOver));
+ component.OnMouseOut.AddListener(new UnityAction(this.DoMouseOut));
+ }
+
+ public void DoMouseOver()
+ {
+ this.Target.color = this.OverColor;
+ if (this.HoverSound)
+ {
+ SoundManager.Instance.PlaySound(this.HoverSound, false, 1f);
+ }
+ }
+
+ public void DoMouseOut()
+ {
+ this.Target.color = this.OutColor;
+ }
+}
diff --git a/Client/Assembly-CSharp/CardSlideGame.cs b/Client/Assembly-CSharp/CardSlideGame.cs
new file mode 100644
index 0000000..3c423a5
--- /dev/null
+++ b/Client/Assembly-CSharp/CardSlideGame.cs
@@ -0,0 +1,190 @@
+using System;
+using System.Collections;
+using UnityEngine;
+
+public class CardSlideGame : Minigame
+{
+ private Color gray = new Color(0.45f, 0.45f, 0.45f);
+
+ private Color green = new Color(0f, 0.8f, 0f);
+
+ private CardSlideGame.TaskStages State;
+
+ private Controller myController = new Controller();
+
+ private FloatRange XRange = new FloatRange(-2.38f, 2.38f);
+
+ public FloatRange AcceptedTime = new FloatRange(0.4f, 0.6f);
+
+ public Collider2D col;
+
+ public SpriteRenderer redLight;
+
+ public SpriteRenderer greenLight;
+
+ public TextRenderer StatusText;
+
+ public AudioClip AcceptSound;
+
+ public AudioClip[] CardMove;
+
+ public AudioClip WalletOut;
+
+ public float dragTime;
+
+ private bool moving;
+
+ private enum TaskStages
+ {
+ Before,
+ Animating,
+ Inserted,
+ After
+ }
+
+ public void Update()
+ {
+ if (this.MyNormTask.IsComplete)
+ {
+ return;
+ }
+ this.myController.Update();
+ Vector3 localPosition = this.col.transform.localPosition;
+ switch (this.myController.CheckDrag(this.col, false))
+ {
+ case DragState.NoTouch:
+ if (this.State == CardSlideGame.TaskStages.Inserted)
+ {
+ localPosition.x = Mathf.Lerp(localPosition.x, this.XRange.min, Time.deltaTime * 4f);
+ }
+ break;
+ case DragState.TouchStart:
+ this.dragTime = 0f;
+ break;
+ case DragState.Dragging:
+ if (this.State == CardSlideGame.TaskStages.Inserted)
+ {
+ Vector2 vector = this.myController.DragPosition - base.transform.position;
+ vector.x = this.XRange.Clamp(vector.x);
+ if (vector.x - localPosition.x > 0.01f)
+ {
+ this.dragTime += Time.deltaTime;
+ this.redLight.color = this.gray;
+ this.greenLight.color = this.gray;
+ if (!this.moving)
+ {
+ this.moving = true;
+ if (Constants.ShouldPlaySfx())
+ {
+ SoundManager.Instance.PlaySound(this.CardMove.Random<AudioClip>(), false, 1f);
+ }
+ }
+ }
+ localPosition.x = vector.x;
+ }
+ break;
+ case DragState.Released:
+ this.moving = false;
+ if (this.State == CardSlideGame.TaskStages.Before)
+ {
+ this.State = CardSlideGame.TaskStages.Animating;
+ base.StartCoroutine(this.InsertCard());
+ }
+ else if (this.State == CardSlideGame.TaskStages.Inserted)
+ {
+ if (this.XRange.max - localPosition.x < 0.05f)
+ {
+ if (this.AcceptedTime.Contains(this.dragTime))
+ {
+ if (Constants.ShouldPlaySfx())
+ {
+ SoundManager.Instance.PlaySound(this.AcceptSound, false, 1f);
+ }
+ this.State = CardSlideGame.TaskStages.After;
+ this.StatusText.Text = "Accepted. Thank you.";
+ base.StartCoroutine(this.PutCardBack());
+ if (this.MyNormTask)
+ {
+ this.MyNormTask.NextStep();
+ }
+ this.redLight.color = this.gray;
+ this.greenLight.color = this.green;
+ }
+ else
+ {
+ if (this.AcceptedTime.max < this.dragTime)
+ {
+ this.StatusText.Text = "Too slow. Try again";
+ }
+ else
+ {
+ this.StatusText.Text = "Too fast. Try again.";
+ }
+ this.redLight.color = Color.red;
+ this.greenLight.color = this.gray;
+ }
+ }
+ else
+ {
+ this.StatusText.Text = "Bad read. Try again.";
+ this.redLight.color = Color.red;
+ this.greenLight.color = this.gray;
+ }
+ }
+ break;
+ }
+ this.col.transform.localPosition = localPosition;
+ }
+
+ private IEnumerator PutCardBack()
+ {
+ if (Constants.ShouldPlaySfx())
+ {
+ SoundManager.Instance.PlaySound(this.WalletOut, false, 1f);
+ }
+ Vector3 pos = this.col.transform.localPosition;
+ Vector3 targ = new Vector3(-1.11f, -1.9f, pos.z);
+ float time = 0f;
+ for (;;)
+ {
+ float t = Mathf.Min(1f, time / 0.6f);
+ this.col.transform.localPosition = Vector3.Lerp(pos, targ, t);
+ this.col.transform.localScale = Vector3.Lerp(Vector3.one, Vector3.one * 0.75f, t);
+ if (time > 0.6f)
+ {
+ break;
+ }
+ yield return null;
+ time += Time.deltaTime;
+ }
+ base.StartCoroutine(base.CoStartClose(0.75f));
+ yield break;
+ }
+
+ private IEnumerator InsertCard()
+ {
+ if (Constants.ShouldPlaySfx())
+ {
+ SoundManager.Instance.PlaySound(this.WalletOut, false, 1f);
+ }
+ Vector3 pos = this.col.transform.localPosition;
+ Vector3 targ = new Vector3(this.XRange.min, 0.75f, pos.z);
+ float time = 0f;
+ for (;;)
+ {
+ float t = Mathf.Min(1f, time / 0.6f);
+ this.col.transform.localPosition = Vector3.Lerp(pos, targ, t);
+ this.col.transform.localScale = Vector3.Lerp(Vector3.one * 0.75f, Vector3.one, t);
+ if (time > 0.6f)
+ {
+ break;
+ }
+ yield return null;
+ time += Time.deltaTime;
+ }
+ this.StatusText.Text = "Please swipe card";
+ this.greenLight.color = this.green;
+ this.State = CardSlideGame.TaskStages.Inserted;
+ yield break;
+ }
+}
diff --git a/Client/Assembly-CSharp/ChainBehaviour.cs b/Client/Assembly-CSharp/ChainBehaviour.cs
new file mode 100644
index 0000000..38a5da5
--- /dev/null
+++ b/Client/Assembly-CSharp/ChainBehaviour.cs
@@ -0,0 +1,27 @@
+using System;
+using UnityEngine;
+
+public class ChainBehaviour : MonoBehaviour
+{
+ public FloatRange SwingRange = new FloatRange(0f, 30f);
+
+ public float SwingPeriod = 2f;
+
+ public float swingTime;
+
+ private Vector3 vec;
+
+ public void Awake()
+ {
+ this.swingTime = FloatRange.Next(0f, this.SwingPeriod);
+ this.vec.z = this.SwingRange.Lerp(Mathf.Sin(this.swingTime));
+ base.transform.eulerAngles = this.vec;
+ }
+
+ public void Update()
+ {
+ this.swingTime += Time.deltaTime / this.SwingPeriod;
+ this.vec.z = this.SwingRange.Lerp(Mathf.Sin(this.swingTime * 3.1415927f) / 2f + 0.5f);
+ base.transform.eulerAngles = this.vec;
+ }
+}
diff --git a/Client/Assembly-CSharp/ChatBubble.cs b/Client/Assembly-CSharp/ChatBubble.cs
new file mode 100644
index 0000000..3c26a40
--- /dev/null
+++ b/Client/Assembly-CSharp/ChatBubble.cs
@@ -0,0 +1,83 @@
+using System;
+using UnityEngine;
+
+internal class ChatBubble : PoolableBehavior
+{
+ public SpriteRenderer ChatFace;
+
+ public SpriteRenderer Xmark;
+
+ public SpriteRenderer votedMark;
+
+ public TextRenderer NameText;
+
+ public TextRenderer TextArea;
+
+ public SpriteRenderer Background;
+
+ public void SetLeft()
+ {
+ base.transform.localPosition = new Vector3(-3f, 0f, 0f);
+ this.ChatFace.flipX = false;
+ this.ChatFace.transform.localScale = new Vector3(1f, 1f, 1f);
+ this.ChatFace.transform.localPosition = new Vector3(0f, 0.07f, 0f);
+ this.Xmark.transform.localPosition = new Vector3(-0.15f, -0.13f, -0.0001f);
+ this.votedMark.transform.localPosition = new Vector3(-0.15f, -0.13f, -0.0001f);
+ this.NameText.transform.localPosition = new Vector3(0.5f, 0.34f, 0f);
+ this.NameText.RightAligned = false;
+ this.TextArea.transform.localPosition = new Vector3(0.5f, 0.09f, 0f);
+ this.TextArea.RightAligned = false;
+ }
+
+ public void SetNotification()
+ {
+ base.transform.localPosition = new Vector3(-2.75f, 0f, 0f);
+ this.ChatFace.flipX = false;
+ this.ChatFace.transform.localScale = new Vector3(0.75f, 0.75f, 1f);
+ this.ChatFace.transform.localPosition = new Vector3(0f, 0.18f, 0f);
+ this.Xmark.transform.localPosition = new Vector3(-0.15f, -0.13f, -0.0001f);
+ this.votedMark.transform.localPosition = new Vector3(-0.15f, -0.13f, -0.0001f);
+ this.NameText.transform.localPosition = new Vector3(0.5f, 0.34f, 0f);
+ this.NameText.RightAligned = false;
+ this.TextArea.transform.localPosition = new Vector3(0.5f, 0.09f, 0f);
+ this.TextArea.RightAligned = false;
+ this.TextArea.Text = string.Empty;
+ }
+
+ public void SetRight()
+ {
+ base.transform.localPosition = new Vector3(-2.35f, 0f, 0f);
+ this.ChatFace.flipX = true;
+ this.ChatFace.transform.localScale = new Vector3(1f, 1f, 1f);
+ this.ChatFace.transform.localPosition = new Vector3(4.75f, 0.07f, 0f);
+ this.Xmark.transform.localPosition = new Vector3(0.15f, -0.13f, -0.0001f);
+ this.votedMark.transform.localPosition = new Vector3(0.15f, -0.13f, -0.0001f);
+ this.NameText.transform.localPosition = new Vector3(4.35f, 0.34f, 0f);
+ this.NameText.RightAligned = true;
+ this.TextArea.transform.localPosition = new Vector3(4.35f, 0.09f, 0f);
+ this.TextArea.RightAligned = true;
+ }
+
+ public void SetName(string playerName, bool isDead, bool voted, Color color)
+ {
+ this.NameText.Text = (playerName ?? "...");
+ this.NameText.Color = color;
+ this.NameText.RefreshMesh();
+ if (isDead)
+ {
+ this.Xmark.enabled = true;
+ this.Background.color = Palette.HalfWhite;
+ }
+ if (voted)
+ {
+ this.votedMark.enabled = true;
+ }
+ }
+
+ public override void Reset()
+ {
+ this.Xmark.enabled = false;
+ this.votedMark.enabled = false;
+ this.Background.color = Color.white;
+ }
+}
diff --git a/Client/Assembly-CSharp/ChatController.cs b/Client/Assembly-CSharp/ChatController.cs
new file mode 100644
index 0000000..3658dd7
--- /dev/null
+++ b/Client/Assembly-CSharp/ChatController.cs
@@ -0,0 +1,326 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+
+public class ChatController : MonoBehaviour
+{
+ public bool IsOpen
+ {
+ get
+ {
+ return this.Content.activeInHierarchy;
+ }
+ }
+
+ public ObjectPoolBehavior chatBubPool;
+
+ public Transform TypingArea;
+
+ public SpriteRenderer TextBubble;
+
+ public TextBox TextArea;
+
+ public TextRenderer CharCount;
+
+ public int MaxChat = 15;
+
+ public Scroller scroller;
+
+ public GameObject Content;
+
+ public SpriteRenderer BackgroundImage;
+
+ public SpriteRenderer ChatNotifyDot;
+
+ public TextRenderer SendRateMessage;
+
+ public Vector3 SourcePos = new Vector3(0f, 0f, -10f);
+
+ public Vector3 TargetPos = new Vector3(-0.35f, 0.02f, -10f);
+
+ private const float MaxChatSendRate = 3f;
+
+ private float TimeSinceLastMessage = 3f;
+
+ public AudioClip MessageSound;
+
+ private bool animating;
+
+ private Coroutine notificationRoutine;
+
+ public BanMenu BanButton;
+
+ public void Toggle()
+ {
+ PlayerControl localPlayer = PlayerControl.LocalPlayer;
+ CustomNetworkTransform customNetworkTransform = (localPlayer != null) ? localPlayer.NetTransform : null;
+ if (this.animating || !customNetworkTransform)
+ {
+ return;
+ }
+ base.StopAllCoroutines();
+ if (this.IsOpen)
+ {
+ base.StartCoroutine(this.CoClose());
+ return;
+ }
+ this.Content.SetActive(true);
+ customNetworkTransform.Halt();
+ base.StartCoroutine(this.CoOpen());
+ }
+
+ public void SetVisible(bool visible)
+ {
+ Debug.Log("Chat is hidden: " + visible.ToString());
+ this.ForceClosed();
+ base.gameObject.SetActive(visible);
+ }
+
+ public void ForceClosed()
+ {
+ base.StopAllCoroutines();
+ this.Content.SetActive(false);
+ this.animating = false;
+ }
+
+ public IEnumerator CoOpen()
+ {
+ this.animating = true;
+ Vector3 scale = Vector3.one;
+ this.BanButton.Hide();
+ this.BanButton.SetVisible(true);
+ float targetScale = AspectSize.CalculateSize(base.transform.localPosition, this.BackgroundImage.sprite);
+ float timer = 0f;
+ while (timer < 0.15f)
+ {
+ timer += Time.deltaTime;
+ float num = Mathf.SmoothStep(0f, 1f, timer / 0.15f);
+ scale.y = (scale.x = Mathf.Lerp(0.1f, targetScale, num));
+ this.Content.transform.localScale = scale;
+ this.Content.transform.localPosition = Vector3.Lerp(this.SourcePos, this.TargetPos, num) * targetScale;
+ this.BanButton.transform.localPosition = new Vector3(0f, -num * 0.75f, -20f);
+ yield return null;
+ }
+ this.ChatNotifyDot.enabled = false;
+ this.animating = false;
+ this.GiveFocus();
+ yield break;
+ }
+
+ public IEnumerator CoClose()
+ {
+ this.animating = true;
+ this.BanButton.Hide();
+ Vector3 scale = Vector3.one;
+ float targetScale = AspectSize.CalculateSize(base.transform.localPosition, this.BackgroundImage.sprite);
+ for (float timer = 0f; timer < 0.15f; timer += Time.deltaTime)
+ {
+ float num = 1f - Mathf.SmoothStep(0f, 1f, timer / 0.15f);
+ scale.y = (scale.x = Mathf.Lerp(0.1f, targetScale, num));
+ this.Content.transform.localScale = scale;
+ this.Content.transform.localPosition = Vector3.Lerp(this.SourcePos, this.TargetPos, num) * targetScale;
+ this.BanButton.transform.localPosition = new Vector3(0f, -num * 0.75f, -20f);
+ yield return null;
+ }
+ this.BanButton.SetVisible(false);
+ this.Content.SetActive(false);
+ this.animating = false;
+ yield break;
+ }
+
+ public void SetPosition(MeetingHud meeting)
+ {
+ if (meeting)
+ {
+ base.transform.SetParent(meeting.transform);
+ base.transform.localPosition = new Vector3(3.1f, 2.2f, -10f);
+ return;
+ }
+ base.transform.SetParent(DestroyableSingleton<HudManager>.Instance.transform);
+ base.GetComponent<AspectPosition>().AdjustPosition();
+ }
+
+ public void UpdateCharCount()
+ {
+ Vector2 size = this.TextBubble.size;
+ size.y = Math.Max(0.62f, this.TextArea.TextHeight + 0.2f);
+ this.TextBubble.size = size;
+ Vector3 localPosition = this.TextBubble.transform.localPosition;
+ localPosition.y = (0.62f - size.y) / 2f;
+ this.TextBubble.transform.localPosition = localPosition;
+ Vector3 localPosition2 = this.TypingArea.localPosition;
+ localPosition2.y = -2.08f - localPosition.y * 2f;
+ this.TypingArea.localPosition = localPosition2;
+ int length = this.TextArea.text.Length;
+ this.CharCount.Text = length + "/100";
+ if (length < 75)
+ {
+ this.CharCount.Color = Color.black;
+ return;
+ }
+ if (length < 100)
+ {
+ this.CharCount.Color = new Color(1f, 1f, 0f, 1f);
+ return;
+ }
+ this.CharCount.Color = Color.red;
+ }
+
+ private void Update()
+ {
+ this.TimeSinceLastMessage += Time.deltaTime;
+ if (this.SendRateMessage.isActiveAndEnabled)
+ {
+ float num = 3f - this.TimeSinceLastMessage;
+ if (num < 0f)
+ {
+ this.SendRateMessage.gameObject.SetActive(false);
+ return;
+ }
+ this.SendRateMessage.Text = string.Format("Too fast. Wait {0} seconds", Mathf.CeilToInt(num));
+ }
+ }
+
+ public void SendChat()
+ {
+ float num = 3f - this.TimeSinceLastMessage;
+ if (num > 0f)
+ {
+ this.SendRateMessage.gameObject.SetActive(true);
+ this.SendRateMessage.Text = string.Format("Too fast. Wait {0} seconds", Mathf.CeilToInt(num));
+ return;
+ }
+ if (!PlayerControl.LocalPlayer.RpcSendChat(this.TextArea.text))
+ {
+ return;
+ }
+ this.TimeSinceLastMessage = 0f;
+ this.TextArea.Clear();
+ }
+
+ public void AddChatNote(GameData.PlayerInfo srcPlayer, ChatNoteTypes noteType)
+ {
+ if (srcPlayer == null)
+ {
+ return;
+ }
+ if (this.chatBubPool.NotInUse == 0)
+ {
+ this.chatBubPool.ReclaimOldest();
+ }
+ ChatBubble chatBubble = this.chatBubPool.Get<ChatBubble>();
+ PlayerControl.SetPlayerMaterialColors((int)srcPlayer.ColorId, chatBubble.ChatFace);
+ chatBubble.transform.SetParent(this.scroller.Inner);
+ chatBubble.transform.localScale = Vector3.one;
+ chatBubble.SetNotification();
+ if (noteType == ChatNoteTypes.DidVote)
+ {
+ int votesRemaining = MeetingHud.Instance.GetVotesRemaining();
+ chatBubble.SetName(string.Format("{0} has voted. {1} remaining.", srcPlayer.PlayerName, votesRemaining), false, true, Color.green);
+ }
+ chatBubble.TextArea.RefreshMesh();
+ chatBubble.Background.size = new Vector2(5.52f, 0.2f + chatBubble.NameText.Height);
+ Vector3 localPosition = chatBubble.Background.transform.localPosition;
+ localPosition.y = chatBubble.NameText.transform.localPosition.y - chatBubble.Background.size.y / 2f + 0.05f;
+ chatBubble.Background.transform.localPosition = localPosition;
+ this.AlignAllBubbles();
+ if (!this.IsOpen && this.notificationRoutine == null)
+ {
+ this.notificationRoutine = base.StartCoroutine(this.BounceDot());
+ }
+ if (srcPlayer.Object != PlayerControl.LocalPlayer)
+ {
+ SoundManager.Instance.PlaySound(this.MessageSound, false, 1f).pitch = 0.5f + (float)srcPlayer.PlayerId / 10f;
+ }
+ }
+
+ public void AddChat(PlayerControl sourcePlayer, string chatText)
+ {
+ if (!sourcePlayer || !PlayerControl.LocalPlayer)
+ {
+ return;
+ }
+ GameData.PlayerInfo data = PlayerControl.LocalPlayer.Data;
+ GameData.PlayerInfo data2 = sourcePlayer.Data;
+ if (data2 == null || data == null || (data2.IsDead && !data.IsDead))
+ {
+ return;
+ }
+ if (this.chatBubPool.NotInUse == 0)
+ {
+ this.chatBubPool.ReclaimOldest();
+ }
+ ChatBubble chatBubble = this.chatBubPool.Get<ChatBubble>();
+ try
+ {
+ chatBubble.transform.SetParent(this.scroller.Inner);
+ chatBubble.transform.localScale = Vector3.one;
+ bool flag = sourcePlayer == PlayerControl.LocalPlayer;
+ if (flag)
+ {
+ chatBubble.SetRight();
+ }
+ else
+ {
+ chatBubble.SetLeft();
+ }
+ bool flag2 = data.IsImpostor && data2.IsImpostor;
+ bool voted = MeetingHud.Instance && MeetingHud.Instance.DidVote(sourcePlayer.PlayerId);
+ PlayerControl.SetPlayerMaterialColors((int)data2.ColorId, chatBubble.ChatFace);
+ chatBubble.SetName(data2.PlayerName, data2.IsDead, voted, flag2 ? Palette.ImpostorRed : Color.white);
+ if (SaveManager.CensorChat)
+ {
+ chatText = BlockedWords.CensorWords(chatText);
+ }
+ chatBubble.TextArea.Text = chatText;
+ chatBubble.TextArea.RefreshMesh();
+ chatBubble.Background.size = new Vector2(5.52f, 0.2f + chatBubble.NameText.Height + chatBubble.TextArea.Height);
+ Vector3 localPosition = chatBubble.Background.transform.localPosition;
+ localPosition.y = chatBubble.NameText.transform.localPosition.y - chatBubble.Background.size.y / 2f + 0.05f;
+ chatBubble.Background.transform.localPosition = localPosition;
+ this.AlignAllBubbles();
+ if (!this.IsOpen && this.notificationRoutine == null)
+ {
+ this.notificationRoutine = base.StartCoroutine(this.BounceDot());
+ }
+ if (!flag)
+ {
+ SoundManager.Instance.PlaySound(this.MessageSound, false, 1f).pitch = 0.5f + (float)sourcePlayer.PlayerId / 10f;
+ }
+ }
+ catch
+ {
+ this.chatBubPool.Reclaim(chatBubble);
+ }
+ }
+
+ private void AlignAllBubbles()
+ {
+ float num = 0f;
+ List<PoolableBehavior> activeChildren = this.chatBubPool.activeChildren;
+ for (int i = activeChildren.Count - 1; i >= 0; i--)
+ {
+ ChatBubble chatBubble = activeChildren[i] as ChatBubble;
+ num += chatBubble.Background.size.y;
+ Vector3 localPosition = chatBubble.transform.localPosition;
+ localPosition.y = -1.85f + num;
+ chatBubble.transform.localPosition = localPosition;
+ num += 0.1f;
+ }
+ this.scroller.YBounds.min = Mathf.Min(0f, -num + this.scroller.HitBox.bounds.size.y);
+ }
+
+ private IEnumerator BounceDot()
+ {
+ this.ChatNotifyDot.enabled = true;
+ yield return Effects.Bounce(this.ChatNotifyDot.transform, 0.3f, 0.15f);
+ this.notificationRoutine = null;
+ yield break;
+ }
+
+ public void GiveFocus()
+ {
+ this.TextArea.GiveFocus();
+ }
+}
diff --git a/Client/Assembly-CSharp/ChatNoteTypes.cs b/Client/Assembly-CSharp/ChatNoteTypes.cs
new file mode 100644
index 0000000..c8e3514
--- /dev/null
+++ b/Client/Assembly-CSharp/ChatNoteTypes.cs
@@ -0,0 +1,6 @@
+using System;
+
+public enum ChatNoteTypes
+{
+ DidVote
+}
diff --git a/Client/Assembly-CSharp/CloudGenerator.cs b/Client/Assembly-CSharp/CloudGenerator.cs
new file mode 100644
index 0000000..e2821a2
--- /dev/null
+++ b/Client/Assembly-CSharp/CloudGenerator.cs
@@ -0,0 +1,159 @@
+using System;
+using UnityEngine;
+
+public class CloudGenerator : MonoBehaviour
+{
+ public Sprite[] CloudImages;
+
+ private Vector2[][] UvCache;
+
+ private Vector2[] ExtentCache;
+
+ public int NumClouds = 500;
+
+ public float Length = 25f;
+
+ public float Width = 25f;
+
+ public Vector2 Direction = new Vector2(1f, 0f);
+
+ private Vector2 NormDir = new Vector2(1f, 0f);
+
+ private Vector2 Tangent = new Vector2(0f, 1f);
+
+ private float tanLen;
+
+ public FloatRange Rates = new FloatRange(0.25f, 1f);
+
+ [HideInInspector]
+ private CloudGenerator.Cloud[] stars;
+
+ [HideInInspector]
+ private Vector3[] verts;
+
+ [HideInInspector]
+ private Mesh mesh;
+
+ private struct Cloud
+ {
+ public int CloudIdx;
+
+ public float Rate;
+
+ public float PositionX;
+
+ public float PositionY;
+ }
+
+ public void Start()
+ {
+ this.UvCache = new Vector2[this.CloudImages.Length][];
+ this.ExtentCache = new Vector2[this.CloudImages.Length];
+ for (int i = 0; i < this.CloudImages.Length; i++)
+ {
+ Sprite sprite = this.CloudImages[i];
+ this.UvCache[i] = sprite.uv;
+ this.ExtentCache[i] = sprite.bounds.extents;
+ }
+ this.stars = new CloudGenerator.Cloud[this.NumClouds];
+ this.verts = new Vector3[this.NumClouds * 4];
+ Vector2[] array = new Vector2[this.NumClouds * 4];
+ int[] array2 = new int[this.NumClouds * 6];
+ this.SetDirection(this.Direction);
+ MeshFilter component = base.GetComponent<MeshFilter>();
+ this.mesh = new Mesh();
+ this.mesh.MarkDynamic();
+ component.mesh = this.mesh;
+ Vector3 vector = default(Vector3);
+ for (int j = 0; j < this.stars.Length; j++)
+ {
+ CloudGenerator.Cloud cloud = this.stars[j];
+ int num = cloud.CloudIdx = this.CloudImages.RandomIdx<Sprite>();
+ Vector2 vector2 = this.ExtentCache[num];
+ Vector2[] array3 = this.UvCache[num];
+ float num2 = FloatRange.Next(-1f, 1f) * this.Length;
+ float num3 = FloatRange.Next(-1f, 1f) * this.Width;
+ float num4 = cloud.PositionX = num2 * this.NormDir.x + num3 * this.Tangent.x;
+ float num5 = cloud.PositionY = num2 * this.NormDir.y + num3 * this.Tangent.y;
+ cloud.Rate = this.Rates.Next();
+ this.stars[j] = cloud;
+ int num6 = j * 4;
+ vector.x = num4 - vector2.x;
+ vector.y = num5 + vector2.y;
+ this.verts[num6] = vector;
+ vector.x = num4 + vector2.x;
+ this.verts[num6 + 1] = vector;
+ vector.x = num4 - vector2.x;
+ vector.y = num5 - vector2.y;
+ this.verts[num6 + 2] = vector;
+ vector.x = num4 + vector2.x;
+ this.verts[num6 + 3] = vector;
+ array[num6] = array3[0];
+ array[num6 + 1] = array3[1];
+ array[num6 + 2] = array3[2];
+ array[num6 + 3] = array3[3];
+ int num7 = j * 6;
+ array2[num7] = num6;
+ array2[num7 + 1] = num6 + 1;
+ array2[num7 + 2] = num6 + 2;
+ array2[num7 + 3] = num6 + 2;
+ array2[num7 + 4] = num6 + 1;
+ array2[num7 + 5] = num6 + 3;
+ }
+ this.mesh.vertices = this.verts;
+ this.mesh.uv = array;
+ this.mesh.SetIndices(array2, MeshTopology.Triangles, 0);
+ }
+
+ private void FixedUpdate()
+ {
+ float num = -0.99f * this.Length;
+ Vector2 vector = this.Direction * Time.fixedDeltaTime;
+ Vector3 vector2 = default(Vector3);
+ for (int i = 0; i < this.stars.Length; i++)
+ {
+ int num2 = i * 4;
+ CloudGenerator.Cloud cloud = this.stars[i];
+ float num3 = cloud.PositionX;
+ float num4 = cloud.PositionY;
+ Vector2 vector3 = this.ExtentCache[cloud.CloudIdx];
+ float rate = cloud.Rate;
+ num3 += rate * vector.x;
+ num4 += rate * vector.y;
+ if (this.OrthoDistance(num3, num4) > this.Length)
+ {
+ float num5 = FloatRange.Next(-1f, 1f) * this.Width;
+ num3 = num * this.NormDir.x + num5 * this.Tangent.x;
+ num4 = num * this.NormDir.y + num5 * this.Tangent.y;
+ cloud.Rate = this.Rates.Next();
+ }
+ cloud.PositionX = num3;
+ cloud.PositionY = num4;
+ this.stars[i] = cloud;
+ vector2.x = num3 - vector3.x;
+ vector2.y = num4 + vector3.y;
+ this.verts[num2] = vector2;
+ vector2.x = num3 + vector3.x;
+ this.verts[num2 + 1] = vector2;
+ vector2.x = num3 - vector3.x;
+ vector2.y = num4 - vector3.y;
+ this.verts[num2 + 2] = vector2;
+ vector2.x = num3 + vector3.x;
+ this.verts[num2 + 3] = vector2;
+ }
+ this.mesh.vertices = this.verts;
+ }
+
+ public void SetDirection(Vector2 dir)
+ {
+ this.Direction = dir;
+ this.NormDir = this.Direction.normalized;
+ this.Tangent = new Vector2(-this.NormDir.y, this.NormDir.x);
+ this.tanLen = Mathf.Sqrt(this.Tangent.y * this.Tangent.y + this.Tangent.x * this.Tangent.x);
+ }
+
+ private float OrthoDistance(float pointx, float pointy)
+ {
+ return (this.Tangent.y * pointx - this.Tangent.x * pointy) / this.tanLen;
+ }
+}
diff --git a/Client/Assembly-CSharp/ColorChip.cs b/Client/Assembly-CSharp/ColorChip.cs
new file mode 100644
index 0000000..7f61630
--- /dev/null
+++ b/Client/Assembly-CSharp/ColorChip.cs
@@ -0,0 +1,11 @@
+using System;
+using UnityEngine;
+
+public class ColorChip : MonoBehaviour
+{
+ public SpriteRenderer Inner;
+
+ public GameObject InUseForeground;
+
+ public PassiveButton Button;
+}
diff --git a/Client/Assembly-CSharp/ConditionalHide.cs b/Client/Assembly-CSharp/ConditionalHide.cs
new file mode 100644
index 0000000..5915f4f
--- /dev/null
+++ b/Client/Assembly-CSharp/ConditionalHide.cs
@@ -0,0 +1,21 @@
+using System;
+using UnityEngine;
+
+public class ConditionalHide : MonoBehaviour
+{
+ public RuntimePlatform[] HideForPlatforms = new RuntimePlatform[]
+ {
+ RuntimePlatform.WindowsPlayer
+ };
+
+ private void Awake()
+ {
+ for (int i = 0; i < this.HideForPlatforms.Length; i++)
+ {
+ if (this.HideForPlatforms[i] == RuntimePlatform.WindowsPlayer)
+ {
+ base.gameObject.SetActive(false);
+ }
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/ConditionalStore.cs b/Client/Assembly-CSharp/ConditionalStore.cs
new file mode 100644
index 0000000..c549bff
--- /dev/null
+++ b/Client/Assembly-CSharp/ConditionalStore.cs
@@ -0,0 +1,9 @@
+using System;
+using UnityEngine;
+
+public class ConditionalStore : MonoBehaviour
+{
+ private void Awake()
+ {
+ }
+}
diff --git a/Client/Assembly-CSharp/Console.cs b/Client/Assembly-CSharp/Console.cs
new file mode 100644
index 0000000..31f9aca
--- /dev/null
+++ b/Client/Assembly-CSharp/Console.cs
@@ -0,0 +1,97 @@
+using System;
+using Assets.CoreScripts;
+using UnityEngine;
+
+public class Console : MonoBehaviour, IUsable
+{
+ public float UsableDistance
+ {
+ get
+ {
+ return this.usableDistance;
+ }
+ }
+
+ public float PercentCool
+ {
+ get
+ {
+ return 0f;
+ }
+ }
+
+ public float usableDistance = 1f;
+
+ public int ConsoleId;
+
+ public bool onlyFromBelow;
+
+ public bool GhostsIgnored;
+
+ public SystemTypes Room;
+
+ public TaskTypes[] TaskTypes;
+
+ public TaskSet[] ValidTasks;
+
+ public SpriteRenderer Image;
+
+ public void SetOutline(bool on, bool mainTarget)
+ {
+ if (this.Image)
+ {
+ this.Image.material.SetFloat("_Outline", (float)(on ? 1 : 0));
+ this.Image.material.SetColor("_OutlineColor", Color.yellow);
+ this.Image.material.SetColor("_AddColor", mainTarget ? Color.yellow : Color.clear);
+ }
+ }
+
+ public float CanUse(GameData.PlayerInfo pc, out bool canUse, out bool couldUse)
+ {
+ float num = float.MaxValue;
+ PlayerControl @object = pc.Object;
+ couldUse = ((!pc.IsDead || (PlayerControl.GameOptions.GhostsDoTasks && !this.GhostsIgnored)) && @object.CanMove && !pc.IsImpostor && (!this.onlyFromBelow || @object.transform.position.y < base.transform.position.y) && this.FindTask(@object));
+ canUse = couldUse;
+ if (canUse)
+ {
+ num = Vector2.Distance(@object.GetTruePosition(), base.transform.position);
+ canUse &= (num <= this.UsableDistance);
+ }
+ return num;
+ }
+
+ private PlayerTask FindTask(PlayerControl pc)
+ {
+ for (int i = 0; i < pc.myTasks.Count; i++)
+ {
+ PlayerTask playerTask = pc.myTasks[i];
+ if (!playerTask.IsComplete && playerTask.ValidConsole(this))
+ {
+ return playerTask;
+ }
+ }
+ return null;
+ }
+
+ public void Use()
+ {
+ bool flag;
+ bool flag2;
+ this.CanUse(PlayerControl.LocalPlayer.Data, out flag, out flag2);
+ if (!flag)
+ {
+ return;
+ }
+ PlayerControl localPlayer = PlayerControl.LocalPlayer;
+ PlayerTask playerTask = this.FindTask(localPlayer);
+ if (playerTask.MinigamePrefab)
+ {
+ Minigame minigame = UnityEngine.Object.Instantiate<Minigame>(playerTask.MinigamePrefab);
+ minigame.transform.SetParent(Camera.main.transform, false);
+ minigame.transform.localPosition = new Vector3(0f, 0f, -50f);
+ minigame.Console = this;
+ minigame.Begin(playerTask);
+ DestroyableSingleton<Telemetry>.Instance.WriteUse(localPlayer.PlayerId, playerTask.TaskType, base.transform.position);
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/Constants.cs b/Client/Assembly-CSharp/Constants.cs
new file mode 100644
index 0000000..5daa994
--- /dev/null
+++ b/Client/Assembly-CSharp/Constants.cs
@@ -0,0 +1,90 @@
+using System;
+using UnityEngine;
+
+public static class Constants
+{
+ public const string LocalNetAddress = "127.0.0.1";
+
+ public const int GamePlayPort = 22023;
+
+ public const int AnnouncementPort = 22024;
+
+ public const int ServersPort = 22025;
+
+ public const string InfinitySymbol = "∞";
+
+ public static readonly int ShipOnlyMask = LayerMask.GetMask(new string[]
+ {
+ "Ship"
+ });
+
+ public static readonly int ShipAndObjectsMask = LayerMask.GetMask(new string[]
+ {
+ "Ship",
+ "Objects"
+ });
+
+ public static readonly int ShipAndAllObjectsMask = LayerMask.GetMask(new string[]
+ {
+ "Ship",
+ "Objects",
+ "ShortObjects"
+ });
+
+ public static readonly int NotShipMask = ~LayerMask.GetMask(new string[]
+ {
+ "Ship"
+ });
+
+ public static readonly int Usables = ~LayerMask.GetMask(new string[]
+ {
+ "Ship",
+ "UI"
+ });
+
+ public static readonly int PlayersOnlyMask = LayerMask.GetMask(new string[]
+ {
+ "Players",
+ "Ghost"
+ });
+
+ public static readonly int ShadowMask = LayerMask.GetMask(new string[]
+ {
+ "Shadow",
+ "Objects",
+ "IlluminatedBlocking"
+ });
+
+ public static readonly int[] CompatVersions = new int[]
+ {
+ Constants.GetBroadcastVersion()
+ };
+
+ public const int Year = 2019;
+
+ public const int Month = 6;
+
+ public const int Day = 27;
+
+ public const int Revision = 0;
+
+ internal static int GetBroadcastVersion()
+ {
+ return 50487150;
+ }
+
+ internal static int GetVersion(int year, int month, int day, int rev)
+ {
+ return year * 25000 + month * 1800 + day * 50 + rev;
+ }
+
+ internal static byte[] GetBroadcastVersionBytes()
+ {
+ return BitConverter.GetBytes(Constants.GetBroadcastVersion());
+ }
+
+ public static bool ShouldPlaySfx()
+ {
+ return !AmongUsClient.Instance || AmongUsClient.Instance.GameMode != GameModes.LocalGame || DetectHeadset.Detect();
+ }
+}
diff --git a/Client/Assembly-CSharp/Controller.cs b/Client/Assembly-CSharp/Controller.cs
new file mode 100644
index 0000000..fd84caa
--- /dev/null
+++ b/Client/Assembly-CSharp/Controller.cs
@@ -0,0 +1,157 @@
+using System;
+using UnityEngine;
+
+public class Controller
+{
+ public bool AnyTouch
+ {
+ get
+ {
+ return this.Touches[0].IsDown || this.Touches[1].IsDown;
+ }
+ }
+
+ public bool AnyTouchDown
+ {
+ get
+ {
+ return this.Touches[0].TouchStart || this.Touches[1].TouchStart;
+ }
+ }
+
+ public bool AnyTouchUp
+ {
+ get
+ {
+ return this.Touches[0].TouchEnd || this.Touches[1].TouchEnd;
+ }
+ }
+
+ public bool FirstDown
+ {
+ get
+ {
+ return this.Touches[0].TouchStart;
+ }
+ }
+
+ public Vector2 DragPosition
+ {
+ get
+ {
+ return this.Touches[this.touchId].Position;
+ }
+ }
+
+ public Vector2 DragStartPosition
+ {
+ get
+ {
+ return this.Touches[this.touchId].DownAt;
+ }
+ }
+
+ public readonly Controller.TouchState[] Touches = new Controller.TouchState[2];
+
+ private Collider2D amTouching;
+
+ private int touchId = -1;
+
+ public class TouchState
+ {
+ public Vector2 DownAt;
+
+ public Vector2 Position;
+
+ public bool WasDown;
+
+ public bool IsDown;
+
+ public bool TouchStart;
+
+ public bool TouchEnd;
+ }
+
+ public Controller()
+ {
+ for (int i = 0; i < this.Touches.Length; i++)
+ {
+ this.Touches[i] = new Controller.TouchState();
+ }
+ }
+
+ public DragState CheckDrag(Collider2D coll, bool firstOnly = false)
+ {
+ if (!coll)
+ {
+ return DragState.NoTouch;
+ }
+ if (this.touchId <= -1)
+ {
+ if (firstOnly)
+ {
+ Controller.TouchState touchState = this.Touches[0];
+ if (touchState.TouchStart && coll.OverlapPoint(touchState.Position))
+ {
+ this.amTouching = coll;
+ this.touchId = 0;
+ return DragState.TouchStart;
+ }
+ }
+ else
+ {
+ for (int i = 0; i < this.Touches.Length; i++)
+ {
+ Controller.TouchState touchState2 = this.Touches[i];
+ if (touchState2.TouchStart && coll.OverlapPoint(touchState2.Position))
+ {
+ this.amTouching = coll;
+ this.touchId = i;
+ return DragState.TouchStart;
+ }
+ }
+ }
+ return DragState.NoTouch;
+ }
+ if (coll != this.amTouching)
+ {
+ return DragState.NoTouch;
+ }
+ if (this.Touches[this.touchId].IsDown)
+ {
+ return DragState.Dragging;
+ }
+ this.amTouching = null;
+ this.touchId = -1;
+ return DragState.Released;
+ }
+
+ public void Update()
+ {
+ Controller.TouchState touchState = this.Touches[0];
+ bool mouseButton = Input.GetMouseButton(0);
+ touchState.Position = Camera.main.ScreenToWorldPoint(Input.mousePosition);
+ touchState.TouchStart = (!touchState.IsDown && mouseButton);
+ if (touchState.TouchStart)
+ {
+ touchState.DownAt = touchState.Position;
+ }
+ touchState.TouchEnd = (touchState.IsDown && !mouseButton);
+ touchState.IsDown = mouseButton;
+ }
+
+ public void Reset()
+ {
+ for (int i = 0; i < this.Touches.Length; i++)
+ {
+ this.Touches[i] = new Controller.TouchState();
+ }
+ this.touchId = -1;
+ this.amTouching = null;
+ }
+
+ public Controller.TouchState GetTouch(int i)
+ {
+ return this.Touches[i];
+ }
+}
diff --git a/Client/Assembly-CSharp/CooldownHelpers.cs b/Client/Assembly-CSharp/CooldownHelpers.cs
new file mode 100644
index 0000000..5177c2c
--- /dev/null
+++ b/Client/Assembly-CSharp/CooldownHelpers.cs
@@ -0,0 +1,33 @@
+using System;
+using UnityEngine;
+
+public static class CooldownHelpers
+{
+ public static void SetCooldownNormalizedUvs(this SpriteRenderer myRend)
+ {
+ Vector2[] uv = myRend.sprite.uv;
+ Vector4 vector = new Vector4(2f, -1f, 2f, -1f);
+ for (int i = 0; i < uv.Length; i++)
+ {
+ if (vector.x > uv[i].x)
+ {
+ vector.x = uv[i].x;
+ }
+ if (vector.y < uv[i].x)
+ {
+ vector.y = uv[i].x;
+ }
+ if (vector.z > uv[i].y)
+ {
+ vector.z = uv[i].y;
+ }
+ if (vector.w < uv[i].y)
+ {
+ vector.w = uv[i].y;
+ }
+ }
+ vector.y -= vector.x;
+ vector.w -= vector.z;
+ myRend.material.SetVector("_NormalizedUvs", vector);
+ }
+}
diff --git a/Client/Assembly-CSharp/CounterArea.cs b/Client/Assembly-CSharp/CounterArea.cs
new file mode 100644
index 0000000..c399165
--- /dev/null
+++ b/Client/Assembly-CSharp/CounterArea.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+public class CounterArea : MonoBehaviour
+{
+ public SystemTypes RoomType;
+
+ public ObjectPoolBehavior pool;
+
+ private List<PoolableBehavior> myIcons = new List<PoolableBehavior>();
+
+ public float XOffset;
+
+ public float YOffset;
+
+ public int MaxWidth = 5;
+
+ public void UpdateCount(int cnt)
+ {
+ bool flag = this.myIcons.Count != cnt;
+ while (this.myIcons.Count < cnt)
+ {
+ PoolableBehavior item = this.pool.Get<PoolableBehavior>();
+ this.myIcons.Add(item);
+ }
+ while (this.myIcons.Count > cnt)
+ {
+ PoolableBehavior poolableBehavior = this.myIcons[this.myIcons.Count - 1];
+ this.myIcons.RemoveAt(this.myIcons.Count - 1);
+ poolableBehavior.OwnerPool.Reclaim(poolableBehavior);
+ }
+ if (flag)
+ {
+ for (int i = 0; i < this.myIcons.Count; i++)
+ {
+ int num = i % 5;
+ int num2 = i / 5;
+ float num3 = (float)(Mathf.Min(cnt - num2 * 5, 5) - 1) * this.XOffset / -2f;
+ this.myIcons[i].transform.position = base.transform.position + new Vector3(num3 + (float)num * this.XOffset, (float)num2 * this.YOffset, -1f);
+ }
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/CourseMinigame.cs b/Client/Assembly-CSharp/CourseMinigame.cs
new file mode 100644
index 0000000..562074b
--- /dev/null
+++ b/Client/Assembly-CSharp/CourseMinigame.cs
@@ -0,0 +1,206 @@
+using System;
+using System.Runtime.InteropServices;
+using UnityEngine;
+
+public class CourseMinigame : Minigame
+{
+ public CourseStarBehaviour StarPrefab;
+
+ public CourseStarBehaviour[] Stars;
+
+ public SpriteRenderer DotPrefab;
+
+ public Sprite DotLight;
+
+ public SpriteRenderer[] Dots;
+
+ public Collider2D Ship;
+
+ public CourseStarBehaviour Destination;
+
+ public Vector3[] PathPoints;
+
+ public int NumPoints;
+
+ public FloatRange XRange;
+
+ public FloatRange YRange;
+
+ public LineRenderer Path;
+
+ public Controller myController = new Controller();
+
+ public float lineTimer;
+
+ private CourseMinigame.UIntFloat Converter;
+
+ public AudioClip SetCourseSound;
+
+ public AudioClip SetCourseLastSound;
+
+ [StructLayout(LayoutKind.Explicit)]
+ private struct UIntFloat
+ {
+ [FieldOffset(0)]
+ public float FloatValue;
+
+ [FieldOffset(0)]
+ public int IntValue;
+
+ public float GetFloat(byte[] bytes)
+ {
+ this.IntValue = ((int)bytes[0] | (int)bytes[1] << 8 | (int)bytes[2] << 16 | (int)bytes[3] << 24);
+ return this.FloatValue;
+ }
+
+ public void GetBytes(float value, byte[] bytes)
+ {
+ this.FloatValue = value;
+ bytes[0] = (byte)(this.IntValue & 255);
+ bytes[1] = (byte)(this.IntValue >> 8 & 255);
+ bytes[2] = (byte)(this.IntValue >> 16 & 255);
+ bytes[3] = (byte)(this.IntValue >> 24 & 255);
+ }
+ }
+
+ public override void Begin(PlayerTask task)
+ {
+ base.Begin(task);
+ this.PathPoints = new Vector3[this.NumPoints];
+ this.Stars = new CourseStarBehaviour[this.NumPoints];
+ this.Dots = new SpriteRenderer[this.NumPoints];
+ for (int i = 0; i < this.PathPoints.Length; i++)
+ {
+ this.PathPoints[i].x = this.XRange.Lerp((float)i / ((float)this.PathPoints.Length - 1f));
+ do
+ {
+ this.PathPoints[i].y = this.YRange.Next();
+ }
+ while (i > 0 && Mathf.Abs(this.PathPoints[i - 1].y - this.PathPoints[i].y) < this.YRange.Width / 4f);
+ this.Dots[i] = UnityEngine.Object.Instantiate<SpriteRenderer>(this.DotPrefab, base.transform);
+ this.Dots[i].transform.localPosition = this.PathPoints[i];
+ if (i == 0)
+ {
+ this.Dots[i].sprite = this.DotLight;
+ }
+ else
+ {
+ if (i == 1)
+ {
+ this.Ship.transform.localPosition = this.PathPoints[0];
+ this.Ship.transform.eulerAngles = new Vector3(0f, 0f, Vector2.up.AngleSigned(this.PathPoints[1] - this.PathPoints[0]));
+ }
+ this.Stars[i] = UnityEngine.Object.Instantiate<CourseStarBehaviour>(this.StarPrefab, base.transform);
+ this.Stars[i].transform.localPosition = this.PathPoints[i];
+ if (i == this.PathPoints.Length - 1)
+ {
+ this.Destination.transform.localPosition = this.PathPoints[i];
+ }
+ }
+ }
+ this.Path.positionCount = this.PathPoints.Length;
+ this.Path.SetPositions(this.PathPoints);
+ }
+
+ public void FixedUpdate()
+ {
+ float num = this.Converter.GetFloat(this.MyNormTask.Data);
+ int num2 = (int)num;
+ Vector2 b = this.PathPoints[num2];
+ this.myController.Update();
+ DragState dragState = this.myController.CheckDrag(this.Ship, false);
+ if (dragState != DragState.NoTouch)
+ {
+ if (dragState == DragState.Dragging)
+ {
+ if (num < (float)(this.PathPoints.Length - 1))
+ {
+ Vector2 vector = this.PathPoints[num2 + 1] - b;
+ Vector2 a = new Vector2(1f, vector.y / vector.x);
+ Vector2 vector2 = base.transform.InverseTransformPoint(this.myController.DragPosition) - b;
+ if (vector2.x > 0f)
+ {
+ Vector2 vector3 = a * vector2.x;
+ if (Mathf.Abs(vector3.y - vector2.y) < 0.5f)
+ {
+ num = (float)num2 + Mathf.Min(1f, vector2.x / vector.x);
+ Vector3 localPosition = vector3 + b;
+ localPosition.z = -1f;
+ this.Ship.transform.localPosition = localPosition;
+ this.Ship.transform.localPosition = localPosition;
+ this.Ship.transform.eulerAngles = new Vector3(0f, 0f, Vector2.up.AngleSigned(vector));
+ }
+ else
+ {
+ this.myController.Reset();
+ }
+ }
+ }
+ else
+ {
+ Vector3 localPosition2 = this.PathPoints[this.PathPoints.Length - 1];
+ localPosition2.z = -1f;
+ this.Ship.transform.localPosition = localPosition2;
+ }
+ }
+ }
+ else if (num < (float)(this.PathPoints.Length - 1))
+ {
+ Vector2 vector4 = this.PathPoints[num2 + 1] - b;
+ Vector2 a2 = new Vector2(1f, vector4.y / vector4.x);
+ num = Mathf.Max((float)num2, Mathf.Lerp(num, (float)num2, Time.deltaTime * 5f));
+ Vector3 localPosition3 = a2 * (num - (float)num2) + b;
+ localPosition3.z = -1f;
+ this.Ship.transform.localPosition = localPosition3;
+ }
+ else
+ {
+ Vector3 localPosition4 = this.PathPoints[this.PathPoints.Length - 1];
+ localPosition4.z = -1f;
+ this.Ship.transform.localPosition = localPosition4;
+ }
+ if ((int)num > num2 && this.Stars[num2 + 1])
+ {
+ UnityEngine.Object.Destroy(this.Stars[num2 + 1].gameObject);
+ this.Dots[num2 + 1].sprite = this.DotLight;
+ if (num2 == this.PathPoints.Length - 2)
+ {
+ if (Constants.ShouldPlaySfx())
+ {
+ SoundManager.Instance.PlaySound(this.SetCourseLastSound, false, 1f).volume = 0.7f;
+ }
+ this.Destination.Speed *= 5f;
+ this.MyNormTask.NextStep();
+ base.StartCoroutine(base.CoStartClose(0.75f));
+ }
+ else if (Constants.ShouldPlaySfx())
+ {
+ SoundManager.Instance.PlaySound(this.SetCourseSound, false, 1f).volume = 0.7f;
+ }
+ }
+ this.Converter.GetBytes(num, this.MyNormTask.Data);
+ this.SetLineDivision(num);
+ }
+
+ private void SetLineDivision(float curVec)
+ {
+ int num = (int)curVec;
+ float num2 = 0f;
+ int num3 = 0;
+ while ((float)num3 <= curVec && num3 < this.PathPoints.Length - 1)
+ {
+ float num4 = Vector2.Distance(this.PathPoints[num3], this.PathPoints[num3 + 1]);
+ if (num3 == num)
+ {
+ num4 *= curVec - (float)num3;
+ }
+ num2 += num4;
+ num3++;
+ }
+ this.lineTimer -= Time.fixedDeltaTime;
+ Vector2 value = new Vector2(this.lineTimer, 0f);
+ this.Path.material.SetTextureOffset("_MainTex", value);
+ this.Path.material.SetTextureOffset("_AltTex", value);
+ this.Path.material.SetFloat("_Perc", num2 + this.lineTimer / 8f);
+ }
+}
diff --git a/Client/Assembly-CSharp/CourseStarBehaviour.cs b/Client/Assembly-CSharp/CourseStarBehaviour.cs
new file mode 100644
index 0000000..2f5dbe9
--- /dev/null
+++ b/Client/Assembly-CSharp/CourseStarBehaviour.cs
@@ -0,0 +1,17 @@
+using System;
+using UnityEngine;
+
+public class CourseStarBehaviour : MonoBehaviour
+{
+ public SpriteRenderer Upper;
+
+ public SpriteRenderer Lower;
+
+ public float Speed = 30f;
+
+ public void Update()
+ {
+ this.Upper.transform.Rotate(0f, 0f, Time.deltaTime * this.Speed);
+ this.Lower.transform.Rotate(0f, 0f, Time.deltaTime * this.Speed);
+ }
+}
diff --git a/Client/Assembly-CSharp/CreateGameOptions.cs b/Client/Assembly-CSharp/CreateGameOptions.cs
new file mode 100644
index 0000000..23251d4
--- /dev/null
+++ b/Client/Assembly-CSharp/CreateGameOptions.cs
@@ -0,0 +1,104 @@
+using System;
+using System.Collections;
+using InnerNet;
+using PowerTools;
+using UnityEngine;
+
+public class CreateGameOptions : MonoBehaviour, IConnectButton
+{
+ public AudioClip IntroMusic;
+
+ public GameObject Content;
+
+ public SpriteRenderer Foreground;
+
+ public SpriteAnim connectIcon;
+
+ public AnimationClip connectClip;
+
+ public void Show()
+ {
+ if (NameTextBehaviour.Instance.ShakeIfInvalid())
+ {
+ return;
+ }
+ if (StatsManager.Instance.AmBanned)
+ {
+ AmongUsClient.Instance.LastDisconnectReason = DisconnectReasons.IntentionalLeaving;
+ DestroyableSingleton<DisconnectPopup>.Instance.Show();
+ return;
+ }
+ base.gameObject.SetActive(true);
+ this.Content.SetActive(false);
+ base.StartCoroutine(this.CoShow());
+ }
+
+ private IEnumerator CoShow()
+ {
+ yield return Effects.ColorFade(this.Foreground, Color.clear, Color.black, 0.1f);
+ this.Content.SetActive(true);
+ yield return Effects.ColorFade(this.Foreground, Color.black, Color.clear, 0.1f);
+ yield break;
+ }
+
+ public void StartIcon()
+ {
+ if (!this.connectIcon)
+ {
+ return;
+ }
+ this.connectIcon.Play(this.connectClip, 1f);
+ }
+
+ public void StopIcon()
+ {
+ if (!this.connectIcon)
+ {
+ return;
+ }
+ this.connectIcon.Stop();
+ this.connectIcon.GetComponent<SpriteRenderer>().sprite = null;
+ }
+
+ public void Hide()
+ {
+ base.StartCoroutine(this.CoHide());
+ }
+
+ private IEnumerator CoHide()
+ {
+ yield return Effects.ColorFade(this.Foreground, Color.clear, Color.black, 0.1f);
+ this.Content.SetActive(false);
+ yield return Effects.ColorFade(this.Foreground, Color.black, Color.clear, 0.1f);
+ base.gameObject.SetActive(false);
+ yield break;
+ }
+
+ public void Confirm()
+ {
+ if (!DestroyableSingleton<MatchMaker>.Instance.Connecting(this))
+ {
+ return;
+ }
+ base.StartCoroutine(this.CoStartGame());
+ }
+
+ private IEnumerator CoStartGame()
+ {
+ SoundManager.Instance.CrossFadeSound("MainBG", null, 0.5f, 1.5f);
+ yield return Effects.ColorFade(this.Foreground, Color.clear, Color.black, 0.2f);
+ AmongUsClient.Instance.GameMode = GameModes.OnlineGame;
+ AmongUsClient.Instance.SetEndpoint(DestroyableSingleton<ServerManager>.Instance.OnlineNetAddress, 22023);
+ AmongUsClient.Instance.MainMenuScene = "MMOnline";
+ AmongUsClient.Instance.OnlineScene = "OnlineGame";
+ AmongUsClient.Instance.Connect(MatchMakerModes.HostAndClient);
+ yield return AmongUsClient.Instance.WaitForConnectionOrFail();
+ DestroyableSingleton<MatchMaker>.Instance.NotConnecting();
+ if (AmongUsClient.Instance.mode == MatchMakerModes.None)
+ {
+ SoundManager.Instance.CrossFadeSound("MainBG", this.IntroMusic, 0.5f, 1.5f);
+ yield return Effects.ColorFade(this.Foreground, Color.black, Color.clear, 0.2f);
+ }
+ yield break;
+ }
+}
diff --git a/Client/Assembly-CSharp/CreateOptionsPicker.cs b/Client/Assembly-CSharp/CreateOptionsPicker.cs
new file mode 100644
index 0000000..9210446
--- /dev/null
+++ b/Client/Assembly-CSharp/CreateOptionsPicker.cs
@@ -0,0 +1,165 @@
+using System;
+using InnerNet;
+using UnityEngine;
+
+public class CreateOptionsPicker : MonoBehaviour
+{
+ public SpriteRenderer[] MaxPlayerButtons;
+
+ public SpriteRenderer[] ImpostorButtons;
+
+ public SpriteRenderer[] LanguageButtons;
+
+ public SpriteRenderer[] MapButtons;
+
+ public SettingsMode mode;
+
+ public CrewVisualizer CrewArea;
+
+ public void Start()
+ {
+ this.MapButtons[1].gameObject.SetActive(false);
+ GameOptionsData targetOptions = this.GetTargetOptions();
+ this.UpdateImpostorsButtons(targetOptions.NumImpostors);
+ this.UpdateMaxPlayersButtons(targetOptions);
+ this.UpdateLanguageButtons(targetOptions.Keywords & GameKeywords.AllLanguages);
+ this.UpdateMapButtons((int)targetOptions.MapId);
+ }
+
+ private GameOptionsData GetTargetOptions()
+ {
+ if (this.mode == SettingsMode.Host)
+ {
+ return SaveManager.GameHostOptions;
+ }
+ GameOptionsData gameSearchOptions = SaveManager.GameSearchOptions;
+ if (gameSearchOptions.MapId == 0)
+ {
+ gameSearchOptions.ToggleMapFilter(0);
+ SaveManager.GameSearchOptions = gameSearchOptions;
+ }
+ return gameSearchOptions;
+ }
+
+ private void SetTargetOptions(GameOptionsData data)
+ {
+ if (this.mode == SettingsMode.Host)
+ {
+ SaveManager.GameHostOptions = data;
+ return;
+ }
+ SaveManager.GameSearchOptions = data;
+ }
+
+ public void SetMaxPlayersButtons(int maxPlayers)
+ {
+ GameOptionsData targetOptions = this.GetTargetOptions();
+ if (maxPlayers < GameOptionsData.MinPlayers[targetOptions.NumImpostors])
+ {
+ return;
+ }
+ targetOptions.MaxPlayers = maxPlayers;
+ this.SetTargetOptions(targetOptions);
+ if (DestroyableSingleton<FindAGameManager>.InstanceExists)
+ {
+ DestroyableSingleton<FindAGameManager>.Instance.ResetTimer();
+ }
+ this.UpdateMaxPlayersButtons(targetOptions);
+ }
+
+ private void UpdateMaxPlayersButtons(GameOptionsData opts)
+ {
+ if (this.CrewArea)
+ {
+ this.CrewArea.SetCrewSize(opts.MaxPlayers, opts.NumImpostors);
+ }
+ for (int i = 0; i < this.MaxPlayerButtons.Length; i++)
+ {
+ SpriteRenderer spriteRenderer = this.MaxPlayerButtons[i];
+ spriteRenderer.enabled = (spriteRenderer.name == opts.MaxPlayers.ToString());
+ spriteRenderer.GetComponentInChildren<TextRenderer>().Color = ((int.Parse(spriteRenderer.name) < GameOptionsData.MinPlayers[opts.NumImpostors]) ? Palette.DisabledGrey : Color.white);
+ }
+ }
+
+ public void SetImpostorButtons(int numImpostors)
+ {
+ GameOptionsData targetOptions = this.GetTargetOptions();
+ targetOptions.NumImpostors = numImpostors;
+ this.SetTargetOptions(targetOptions);
+ this.SetMaxPlayersButtons(Mathf.Max(targetOptions.MaxPlayers, GameOptionsData.MinPlayers[numImpostors]));
+ this.UpdateImpostorsButtons(numImpostors);
+ }
+
+ private void UpdateImpostorsButtons(int numImpostors)
+ {
+ for (int i = 0; i < this.ImpostorButtons.Length; i++)
+ {
+ SpriteRenderer spriteRenderer = this.ImpostorButtons[i];
+ spriteRenderer.enabled = (spriteRenderer.name == numImpostors.ToString());
+ }
+ }
+
+ public void SetMap(int mapid)
+ {
+ GameOptionsData targetOptions = this.GetTargetOptions();
+ if (this.mode == SettingsMode.Host)
+ {
+ targetOptions.MapId = (byte)mapid;
+ }
+ else
+ {
+ targetOptions.ToggleMapFilter((byte)mapid);
+ }
+ this.SetTargetOptions(targetOptions);
+ if (DestroyableSingleton<FindAGameManager>.InstanceExists)
+ {
+ DestroyableSingleton<FindAGameManager>.Instance.ResetTimer();
+ }
+ this.UpdateMapButtons(mapid);
+ }
+
+ private void UpdateMapButtons(int mapid)
+ {
+ if (this.mode == SettingsMode.Host)
+ {
+ if (this.CrewArea)
+ {
+ this.CrewArea.SetMap(mapid);
+ }
+ for (int i = 0; i < this.MapButtons.Length; i++)
+ {
+ SpriteRenderer spriteRenderer = this.MapButtons[i];
+ spriteRenderer.color = ((spriteRenderer.name == mapid.ToString()) ? Color.white : Palette.DisabledGrey);
+ }
+ return;
+ }
+ GameOptionsData targetOptions = this.GetTargetOptions();
+ for (int j = 0; j < this.MapButtons.Length; j++)
+ {
+ SpriteRenderer spriteRenderer2 = this.MapButtons[j];
+ spriteRenderer2.color = (targetOptions.FilterContainsMap(byte.Parse(spriteRenderer2.name)) ? Color.white : Palette.DisabledGrey);
+ }
+ }
+
+ public void SetLanguageFilter(int keyword)
+ {
+ GameOptionsData targetOptions = this.GetTargetOptions();
+ targetOptions.Keywords &= ~GameKeywords.AllLanguages;
+ targetOptions.Keywords |= (GameKeywords)keyword;
+ this.SetTargetOptions(targetOptions);
+ if (DestroyableSingleton<FindAGameManager>.InstanceExists)
+ {
+ DestroyableSingleton<FindAGameManager>.Instance.ResetTimer();
+ }
+ this.UpdateLanguageButtons((GameKeywords)keyword);
+ }
+
+ private void UpdateLanguageButtons(GameKeywords button)
+ {
+ for (int i = 0; i < this.LanguageButtons.Length; i++)
+ {
+ SpriteRenderer spriteRenderer = this.LanguageButtons[i];
+ spriteRenderer.enabled = (spriteRenderer.name == button.ToString());
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/CreateStoreButton.cs b/Client/Assembly-CSharp/CreateStoreButton.cs
new file mode 100644
index 0000000..b08c3d5
--- /dev/null
+++ b/Client/Assembly-CSharp/CreateStoreButton.cs
@@ -0,0 +1,16 @@
+using System;
+using UnityEngine;
+
+public class CreateStoreButton : MonoBehaviour
+{
+ public Transform Target;
+
+ public StoreMenu StorePrefab;
+
+ public void Click()
+ {
+ StoreMenu storeMenu = UnityEngine.Object.Instantiate<StoreMenu>(this.StorePrefab, this.Target);
+ storeMenu.transform.localPosition = new Vector3(0f, 0f, -100f);
+ storeMenu.transform.localScale = Vector3.zero;
+ }
+}
diff --git a/Client/Assembly-CSharp/CrewVisualizer.cs b/Client/Assembly-CSharp/CrewVisualizer.cs
new file mode 100644
index 0000000..31e26e7
--- /dev/null
+++ b/Client/Assembly-CSharp/CrewVisualizer.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+public class CrewVisualizer : MonoBehaviour
+{
+ public ObjectPoolBehavior CrewPool;
+
+ public SpriteRenderer Background;
+
+ public Sprite[] MapBackgrounds;
+
+ public float yOffset = 0.4f;
+
+ public FloatRange BgWidth;
+
+ public void SetCrewSize(int numPlayers, int numImpostors)
+ {
+ this.CrewPool.ReclaimAll();
+ int num = numPlayers / 2;
+ int num2 = Mathf.CeilToInt((float)numPlayers / 2f);
+ List<SpriteRenderer> list = new List<SpriteRenderer>();
+ Vector3 localPosition = new Vector3(0f, 0f, -1f);
+ for (int i = 0; i < numPlayers; i++)
+ {
+ SpriteRenderer component = this.CrewPool.Get<PoolableBehavior>().GetComponent<SpriteRenderer>();
+ component.color = Color.white;
+ list.Add(component);
+ if (i < num)
+ {
+ float num3 = Mathf.Clamp((float)num / 5f * 1.3f, 0f, 1f) * 0.85f;
+ localPosition.z = -1.5f;
+ localPosition.y = -this.yOffset;
+ localPosition.x = this.BgWidth.Lerp((float)i / ((float)num - 1f)) * num3;
+ }
+ else
+ {
+ float num4 = Mathf.Clamp((float)num2 / 5f * 1.3f, 0f, 1f);
+ localPosition.z = -1f;
+ localPosition.y = this.yOffset;
+ localPosition.x = this.BgWidth.Lerp((float)(i - num) / ((float)num2 - 1f)) * num4;
+ }
+ component.transform.localPosition = localPosition;
+ }
+ int j = 0;
+ int num5 = 0;
+ while (j < numImpostors)
+ {
+ if (BoolRange.Next(1f / (float)list.Count))
+ {
+ j++;
+ list[num5].color = Color.red;
+ list.RemoveAt(num5);
+ }
+ num5 = (num5 + 1) % list.Count;
+ }
+ }
+
+ public void SetMap(int mapid)
+ {
+ this.Background.sprite = this.MapBackgrounds[mapid];
+ }
+}
diff --git a/Client/Assembly-CSharp/CrossFadeImages.cs b/Client/Assembly-CSharp/CrossFadeImages.cs
new file mode 100644
index 0000000..912bf23
--- /dev/null
+++ b/Client/Assembly-CSharp/CrossFadeImages.cs
@@ -0,0 +1,20 @@
+using System;
+using UnityEngine;
+
+public class CrossFadeImages : MonoBehaviour
+{
+ public SpriteRenderer Image1;
+
+ public SpriteRenderer Image2;
+
+ public float Period = 5f;
+
+ private void Update()
+ {
+ Color white = Color.white;
+ white.a = Mathf.Clamp((Mathf.Sin(3.1415927f * Time.time / this.Period) + 0.75f) * 0.75f, 0f, 1f);
+ this.Image1.color = white;
+ white.a = 1f - white.a;
+ this.Image2.color = white;
+ }
+}
diff --git a/Client/Assembly-CSharp/CrossFader.cs b/Client/Assembly-CSharp/CrossFader.cs
new file mode 100644
index 0000000..39ccf82
--- /dev/null
+++ b/Client/Assembly-CSharp/CrossFader.cs
@@ -0,0 +1,74 @@
+using System;
+using UnityEngine;
+
+public class CrossFader : ISoundPlayer
+{
+ public string Name { get; set; }
+
+ public AudioSource Player { get; set; }
+
+ public float MaxVolume = 1f;
+
+ public AudioClip target;
+
+ public float Duration = 1.5f;
+
+ private float timer;
+
+ private bool didSwitch;
+
+ public void Update(float dt)
+ {
+ if (this.timer < this.Duration)
+ {
+ this.timer += dt;
+ float num = this.timer / this.Duration;
+ if (num < 0.5f)
+ {
+ this.Player.volume = (1f - num * 2f) * this.MaxVolume;
+ return;
+ }
+ if (!this.didSwitch)
+ {
+ this.didSwitch = true;
+ this.Player.Stop();
+ this.Player.clip = this.target;
+ if (this.target)
+ {
+ this.Player.Play();
+ }
+ }
+ this.Player.volume = (num - 0.5f) * 2f * this.MaxVolume;
+ }
+ }
+
+ public void SetTarget(AudioClip clip)
+ {
+ if (!this.Player.clip)
+ {
+ this.didSwitch = false;
+ this.Player.volume = 0f;
+ this.timer = 0.5f;
+ }
+ else
+ {
+ if (this.Player.clip == clip)
+ {
+ return;
+ }
+ if (this.didSwitch)
+ {
+ this.didSwitch = false;
+ if (this.timer >= this.Duration)
+ {
+ this.timer = 0f;
+ }
+ else
+ {
+ this.timer = this.Duration - this.timer;
+ }
+ }
+ }
+ this.target = clip;
+ }
+}
diff --git a/Client/Assembly-CSharp/CrystalBehaviour.cs b/Client/Assembly-CSharp/CrystalBehaviour.cs
new file mode 100644
index 0000000..ebb7b2b
--- /dev/null
+++ b/Client/Assembly-CSharp/CrystalBehaviour.cs
@@ -0,0 +1,110 @@
+using System;
+using System.Collections;
+using UnityEngine;
+
+public class CrystalBehaviour : MonoBehaviour
+{
+ public CrystalBehaviour Above;
+
+ public CrystalBehaviour Below;
+
+ public CrystalBehaviour.Parentness ParentSide;
+
+ public SpriteRenderer Renderer;
+
+ public BoxCollider2D Collider;
+
+ public FloatRange Padding;
+
+ private const float Speed = 15f;
+
+ private const float FloatMag = 0.05f;
+
+ private const float FloatSpeed = 0.35f;
+
+ public enum Parentness
+ {
+ None,
+ Above,
+ Below
+ }
+
+ private void Update()
+ {
+ Vector3 position = base.transform.position;
+ Vector3 vector;
+ if (this.ParentSide == CrystalBehaviour.Parentness.Above)
+ {
+ if (!this.Above)
+ {
+ return;
+ }
+ vector = this.Above.transform.position - new Vector3(0f, this.Above.Padding.min + this.Padding.max, 0f);
+ }
+ else
+ {
+ if (this.ParentSide != CrystalBehaviour.Parentness.Below)
+ {
+ return;
+ }
+ if (!this.Below)
+ {
+ return;
+ }
+ vector = this.Below.transform.position + new Vector3(0f, this.Below.Padding.max + this.Padding.min, 0f);
+ }
+ float num = Time.time / 0.35f;
+ vector.x += (Mathf.PerlinNoise(num, position.z * 20f) * 2f - 1f) * 0.05f;
+ vector.y += (Mathf.PerlinNoise(position.z * 20f, num) * 2f - 1f) * 0.05f;
+ vector.z = position.z;
+ position.x = Mathf.SmoothStep(position.x, vector.x, Time.deltaTime * 15f);
+ position.y = Mathf.SmoothStep(position.y, vector.y, Time.deltaTime * 15f);
+ base.transform.position = position;
+ }
+
+ public void FlashUp(float delay = 0f)
+ {
+ base.StopAllCoroutines();
+ base.StartCoroutine(CrystalBehaviour.Flash(this, delay));
+ if (this.Above)
+ {
+ this.Above.FlashUp(delay + 0.1f);
+ }
+ }
+
+ public void FlashDown(float delay = 0f)
+ {
+ base.StopAllCoroutines();
+ base.StartCoroutine(CrystalBehaviour.Flash(this, delay));
+ if (this.Below)
+ {
+ this.Below.FlashDown(delay + 0.1f);
+ }
+ }
+
+ private static IEnumerator Flash(CrystalBehaviour c, float delay)
+ {
+ for (float time = 0f; time < delay; time += Time.deltaTime)
+ {
+ yield return null;
+ }
+ Color col = Color.clear;
+ for (float time = 0f; time < 0.1f; time += Time.deltaTime)
+ {
+ float t = time / 0.1f;
+ col.r = (col.g = (col.b = Mathf.Lerp(0f, 1f, t)));
+ c.Renderer.material.SetColor("_AddColor", col);
+ yield return null;
+ }
+ for (float time = 0f; time < 0.1f; time += Time.deltaTime)
+ {
+ float t2 = time / 0.1f;
+ col.r = (col.g = (col.b = Mathf.Lerp(1f, 0f, t2)));
+ c.Renderer.material.SetColor("_AddColor", col);
+ yield return null;
+ }
+ col.r = (col.g = (col.b = 0f));
+ c.Renderer.material.SetColor("_AddColor", col);
+ yield break;
+ }
+}
diff --git a/Client/Assembly-CSharp/CrystalMinigame.cs b/Client/Assembly-CSharp/CrystalMinigame.cs
new file mode 100644
index 0000000..6e749f0
--- /dev/null
+++ b/Client/Assembly-CSharp/CrystalMinigame.cs
@@ -0,0 +1,120 @@
+using System;
+using UnityEngine;
+
+public class CrystalMinigame : Minigame
+{
+ public CrystalBehaviour[] CrystalPieces;
+
+ public FloatRange XRange;
+
+ public FloatRange YRange;
+
+ private Controller myController = new Controller();
+
+ public override void Begin(PlayerTask task)
+ {
+ base.Begin(task);
+ for (int i = 0; i < this.CrystalPieces.Length; i++)
+ {
+ this.CrystalPieces[i].transform.localPosition = new Vector3(this.XRange.Next(), this.YRange.Next(), ((float)i - (float)this.CrystalPieces.Length / 2f) / 100f);
+ }
+ }
+
+ public void Update()
+ {
+ this.myController.Update();
+ for (int i = 0; i < this.CrystalPieces.Length; i++)
+ {
+ CrystalBehaviour crystalBehaviour = this.CrystalPieces[i];
+ switch (this.myController.CheckDrag(crystalBehaviour.Collider, false))
+ {
+ case DragState.TouchStart:
+ this.Spread(i);
+ break;
+ case DragState.Dragging:
+ {
+ Vector3 position = this.myController.DragPosition;
+ position.z = base.transform.position.z;
+ crystalBehaviour.transform.position = position;
+ break;
+ }
+ case DragState.Released:
+ this.CheckSolution();
+ break;
+ }
+ }
+ }
+
+ private void Spread(int parent)
+ {
+ for (int i = 0; i < this.CrystalPieces.Length; i++)
+ {
+ if (i < parent)
+ {
+ this.CrystalPieces[i].ParentSide = CrystalBehaviour.Parentness.Below;
+ }
+ else if (i > parent)
+ {
+ this.CrystalPieces[i].ParentSide = CrystalBehaviour.Parentness.Above;
+ }
+ else
+ {
+ this.CrystalPieces[i].ParentSide = CrystalBehaviour.Parentness.None;
+ }
+ }
+ }
+
+ private void CheckSolution()
+ {
+ bool flag = false;
+ for (int i = 0; i < this.CrystalPieces.Length; i++)
+ {
+ CrystalBehaviour crystalBehaviour = this.CrystalPieces[i];
+ Vector3 position = crystalBehaviour.transform.position;
+ if (!crystalBehaviour.Above && i - 1 > -1)
+ {
+ CrystalBehaviour crystalBehaviour2 = this.CrystalPieces[i - 1];
+ if (CrystalMinigame.AreTouching(crystalBehaviour2.Collider, crystalBehaviour.Collider))
+ {
+ crystalBehaviour.Above = crystalBehaviour2;
+ crystalBehaviour2.Below = crystalBehaviour;
+ crystalBehaviour.FlashUp(0f);
+ }
+ else
+ {
+ flag = true;
+ }
+ }
+ if (!crystalBehaviour.Below && i + 1 < this.CrystalPieces.Length)
+ {
+ CrystalBehaviour crystalBehaviour3 = this.CrystalPieces[i + 1];
+ if (CrystalMinigame.AreTouching(crystalBehaviour3.Collider, crystalBehaviour.Collider))
+ {
+ crystalBehaviour.Below = crystalBehaviour3;
+ crystalBehaviour3.Above = crystalBehaviour;
+ crystalBehaviour.FlashDown(0f);
+ }
+ else
+ {
+ flag = true;
+ }
+ }
+ }
+ if (!flag)
+ {
+ this.MyNormTask.Complete();
+ base.StartCoroutine(base.CoStartClose(0.75f));
+ }
+ }
+
+ private static bool AreTouching(BoxCollider2D a, BoxCollider2D b)
+ {
+ Vector2 a2 = a.transform.position + a.offset;
+ Vector2 a3 = b.transform.position + b.offset;
+ Vector2 vector = a2 - a.size / 2f;
+ Vector2 vector2 = a3 - b.size / 2f;
+ Vector2 vector3 = a2 + a.size / 2f;
+ Vector2 vector4 = a3 + b.size / 2f;
+ return ((vector.y < vector4.y && vector.y > vector2.y) || (vector3.y < vector4.y && vector3.y > vector2.y)) && ((vector.x < vector4.x && vector.x > vector2.x) || (vector3.x < vector4.x && vector3.x > vector2.x));
+ }
+}
diff --git a/Client/Assembly-CSharp/CustomNetworkTransform.cs b/Client/Assembly-CSharp/CustomNetworkTransform.cs
new file mode 100644
index 0000000..98a1969
--- /dev/null
+++ b/Client/Assembly-CSharp/CustomNetworkTransform.cs
@@ -0,0 +1,265 @@
+using System;
+using Hazel;
+using InnerNet;
+using UnityEngine;
+
+//c 角色的位置和速度数据
+[DisallowMultipleComponent]
+public class CustomNetworkTransform : InnerNetObject
+{
+ private const float LocalMovementThreshold = 0.0001f;
+
+ private const float LocalVelocityThreshold = 0.0001f;
+
+ private const float MoveAheadRatio = 0.1f;
+
+ private readonly FloatRange XRange = new FloatRange(-40f, 40f);
+
+ private readonly FloatRange YRange = new FloatRange(-40f, 40f);
+
+ // 用来辅助设置位置
+ [SerializeField]
+ private float sendInterval = 0.1f;
+
+ [SerializeField]
+ private float snapThreshold = 5f;
+
+ [SerializeField]
+ private float interpolateMovement = 1f;
+
+ private Rigidbody2D body;
+
+ private Vector2 targetSyncPosition;
+
+ private Vector2 targetSyncVelocity;
+
+ private ushort lastSequenceId;
+
+ private Vector2 prevPosSent;
+
+ private Vector2 prevVelSent;
+
+ private enum RpcCalls
+ {
+ SnapTo
+ }
+
+ private void Awake()
+ {
+ this.body = base.GetComponent<Rigidbody2D>();
+ this.targetSyncPosition = (this.prevPosSent = base.transform.position);
+ this.targetSyncVelocity = (this.prevVelSent = Vector2.zero);
+ }
+
+ public void OnEnable()
+ {
+ base.SetDirtyBit(3U);
+ }
+
+ public void Halt()
+ {
+ ushort minSid = this.lastSequenceId + 1;
+ this.SnapTo(base.transform.position, minSid);
+ }
+
+ public void RpcSnapTo(Vector2 position)
+ {
+ ushort minSid = this.lastSequenceId + 5;
+ if (AmongUsClient.Instance.AmClient)
+ {
+ this.SnapTo(position, minSid);
+ }
+ MessageWriter messageWriter = AmongUsClient.Instance.StartRpc(this.NetId, 0, SendOption.Reliable);
+ this.WriteVector2(position, messageWriter);
+ messageWriter.Write(this.lastSequenceId);
+ messageWriter.EndMessage();
+ }
+
+ public void SnapTo(Vector2 position)
+ {
+ ushort minSid = this.lastSequenceId + 3;
+ this.SnapTo(position, minSid);
+ }
+
+ private void SnapTo(Vector2 position, ushort minSid)
+ {
+ if (!CustomNetworkTransform.SidGreaterThan(minSid, this.lastSequenceId))
+ {
+ return;
+ }
+ this.lastSequenceId = minSid;
+ Transform transform = base.transform;
+ this.body.position = position;
+ this.targetSyncPosition = position;
+ transform.position = position;
+ this.targetSyncVelocity = (this.body.velocity = Vector2.zero);
+ this.prevPosSent = position;
+ this.prevVelSent = Vector2.zero;
+ }
+
+ private void FixedUpdate()
+ {
+ if (base.AmOwner)
+ {
+ if (this.HasMoved())
+ {
+ base.SetDirtyBit(3U);
+ return;
+ }
+ }
+ else
+ {
+ if (this.interpolateMovement != 0f)
+ {
+ Vector2 vector = this.targetSyncPosition - this.body.position;
+ if (vector.sqrMagnitude >= 0.0001f)
+ {
+ float num = this.interpolateMovement / this.sendInterval;
+ vector.x *= num;
+ vector.y *= num;
+ if (PlayerControl.LocalPlayer)
+ {
+ vector = Vector2.ClampMagnitude(vector, PlayerControl.LocalPlayer.MyPhysics.TrueSpeed);
+ }
+ this.body.velocity = vector;
+ }
+ else
+ {
+ this.body.velocity = Vector2.zero;
+ }
+ }
+ this.targetSyncPosition += this.targetSyncVelocity * Time.fixedDeltaTime * 0.1f;
+ }
+ }
+
+ private bool HasMoved()
+ {
+ float num;
+ if (this.body != null)
+ {
+ num = Vector2.Distance(this.body.position, this.prevPosSent);
+ }
+ else
+ {
+ num = Vector2.Distance(base.transform.position, this.prevPosSent);
+ }
+ if (num > 0.0001f)
+ {
+ return true;
+ }
+ if (this.body != null)
+ {
+ num = Vector2.Distance(this.body.velocity, this.prevVelSent);
+ }
+ return num > 0.0001f;
+ }
+
+ public override void HandleRpc(byte callId, MessageReader reader)
+ {
+ if (!base.isActiveAndEnabled)
+ {
+ return;
+ }
+ if (callId == 0)
+ {
+ Vector2 position = this.ReadVector2(reader);
+ ushort minSid = reader.ReadUInt16();
+ this.SnapTo(position, minSid);
+ }
+ }
+
+ //c 序列化角色位置数据
+ public override bool Serialize(MessageWriter writer, bool initialState)
+ {
+ if (initialState)
+ {
+ writer.Write(this.lastSequenceId);
+ this.WriteVector2(this.body.position, writer);
+ this.WriteVector2(this.body.velocity, writer);
+ return true;
+ }
+ if (this.DirtyBits == 0U)
+ {
+ return false;
+ }
+ if (!base.isActiveAndEnabled)
+ {
+ this.DirtyBits = 0U;
+ return false;
+ }
+ this.lastSequenceId += 1;
+ writer.Write(this.lastSequenceId);
+ this.WriteVector2(this.body.position, writer);
+ this.WriteVector2(this.body.velocity, writer);
+ this.prevPosSent = this.body.position;
+ this.prevVelSent = this.body.velocity;
+ this.DirtyBits -= 1U;
+ return true;
+ }
+
+ //c 反序列化角色数据
+ public override void Deserialize(MessageReader reader, bool initialState)
+ {
+ if (initialState)
+ {
+ this.lastSequenceId = reader.ReadUInt16();
+ this.targetSyncPosition = (base.transform.position = this.ReadVector2(reader));
+ this.targetSyncVelocity = this.ReadVector2(reader);
+ return;
+ }
+ ushort newSid = reader.ReadUInt16();
+ if (!CustomNetworkTransform.SidGreaterThan(newSid, this.lastSequenceId))
+ {
+ return;
+ }
+ this.lastSequenceId = newSid;
+ if (!base.isActiveAndEnabled)
+ {
+ return;
+ }
+ this.targetSyncPosition = this.ReadVector2(reader);
+ this.targetSyncVelocity = this.ReadVector2(reader);
+ // 当本地计算的位置数据和网络传输的位置数据差别大于snapThreshold的时候直接用消息里的位置和速度数据
+ if (Vector2.Distance(this.body.position, this.targetSyncPosition) > this.snapThreshold)
+ {
+ if (this.body)
+ {
+ this.body.position = this.targetSyncPosition;
+ this.body.velocity = this.targetSyncVelocity;
+ }
+ else
+ {
+ base.transform.position = this.targetSyncPosition;
+ }
+ }
+ if (this.interpolateMovement == 0f && this.body)
+ {
+ this.body.position = this.targetSyncPosition;
+ }
+ }
+
+ private static bool SidGreaterThan(ushort newSid, ushort prevSid)
+ {
+ ushort num = prevSid + 32767;
+ if (prevSid < num)
+ {
+ return newSid > prevSid && newSid <= num;
+ }
+ return newSid > prevSid || newSid <= num;
+ }
+
+ private void WriteVector2(Vector2 vec, MessageWriter writer)
+ {
+ ushort value = (ushort)(this.XRange.ReverseLerp(vec.x) * 65535f);
+ ushort value2 = (ushort)(this.YRange.ReverseLerp(vec.y) * 65535f);
+ writer.Write(value);
+ writer.Write(value2);
+ }
+
+ private Vector2 ReadVector2(MessageReader reader)
+ {
+ float v = (float)reader.ReadUInt16() / 65535f;
+ float v2 = (float)reader.ReadUInt16() / 65535f;
+ return new Vector2(this.XRange.Lerp(v), this.YRange.Lerp(v2));
+ }
+}
diff --git a/Client/Assembly-CSharp/CustomPlayerMenu.cs b/Client/Assembly-CSharp/CustomPlayerMenu.cs
new file mode 100644
index 0000000..9213223
--- /dev/null
+++ b/Client/Assembly-CSharp/CustomPlayerMenu.cs
@@ -0,0 +1,41 @@
+using System;
+using UnityEngine;
+
+public class CustomPlayerMenu : MonoBehaviour
+{
+ public static CustomPlayerMenu Instance;
+
+ public TabButton[] Tabs;
+
+ public Sprite NormalColor;
+
+ public Sprite SelectedColor;
+
+ public void Start()
+ {
+ CustomPlayerMenu.Instance = this;
+ }
+
+ public void OpenTab(GameObject tab)
+ {
+ for (int i = 0; i < this.Tabs.Length; i++)
+ {
+ TabButton tabButton = this.Tabs[i];
+ if (tabButton.Tab == tab)
+ {
+ tabButton.Tab.SetActive(true);
+ tabButton.Button.sprite = this.SelectedColor;
+ }
+ else
+ {
+ tabButton.Tab.SetActive(false);
+ tabButton.Button.sprite = this.NormalColor;
+ }
+ }
+ }
+
+ public void Close(bool canMove)
+ {
+ UnityEngine.Object.Destroy(base.gameObject);
+ }
+}
diff --git a/Client/Assembly-CSharp/DataCollectScreen.cs b/Client/Assembly-CSharp/DataCollectScreen.cs
new file mode 100644
index 0000000..7f89568
--- /dev/null
+++ b/Client/Assembly-CSharp/DataCollectScreen.cs
@@ -0,0 +1,56 @@
+using System;
+using System.Collections;
+using UnityEngine;
+
+public class DataCollectScreen : MonoBehaviour
+{
+ public static DataCollectScreen Instance;
+
+ public ToggleButtonBehaviour SendNameButton;
+
+ public ToggleButtonBehaviour SendTelemButton;
+
+ public AdDataCollectScreen AdPolicy;
+
+ private void Start()
+ {
+ DataCollectScreen.Instance = this;
+ this.UpdateButtons();
+ }
+
+ public IEnumerator Show()
+ {
+ if (!SaveManager.SendDataScreen)
+ {
+ base.gameObject.SetActive(true);
+ while (base.gameObject.activeSelf)
+ {
+ yield return null;
+ }
+ }
+ yield break;
+ }
+
+ public void Close()
+ {
+ SaveManager.SendDataScreen = true;
+ }
+
+ public void ToggleSendTelemetry()
+ {
+ SaveManager.SendTelemetry = !SaveManager.SendTelemetry;
+ this.UpdateButtons();
+ }
+
+ public void ToggleSendName()
+ {
+ SaveManager.SendName = !SaveManager.SendName;
+ this.UpdateButtons();
+ }
+
+ public void UpdateButtons()
+ {
+ this.SendNameButton.UpdateText(SaveManager.SendName);
+ this.SendTelemButton.UpdateText(SaveManager.SendTelemetry);
+ }
+}
diff --git a/Client/Assembly-CSharp/DeadBody.cs b/Client/Assembly-CSharp/DeadBody.cs
new file mode 100644
index 0000000..5cab24a
--- /dev/null
+++ b/Client/Assembly-CSharp/DeadBody.cs
@@ -0,0 +1,35 @@
+using System;
+using UnityEngine;
+
+public class DeadBody : MonoBehaviour
+{
+ public Vector2 TruePosition
+ {
+ get
+ {
+ return base.transform.position + this.myCollider.offset;
+ }
+ }
+
+ public bool Reported;
+
+ public short KillIdx;
+
+ public byte ParentId;
+
+ public Collider2D myCollider;
+
+ public void OnClick()
+ {
+ if (this.Reported)
+ {
+ return;
+ }
+ if (!PhysicsHelpers.AnythingBetween(PlayerControl.LocalPlayer.GetTruePosition(), this.TruePosition, Constants.ShipAndObjectsMask, false))
+ {
+ this.Reported = true;
+ GameData.PlayerInfo playerById = GameData.Instance.GetPlayerById(this.ParentId);
+ PlayerControl.LocalPlayer.CmdReportDeadBody(playerById);
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/DeathReason.cs b/Client/Assembly-CSharp/DeathReason.cs
new file mode 100644
index 0000000..f5d99e5
--- /dev/null
+++ b/Client/Assembly-CSharp/DeathReason.cs
@@ -0,0 +1,8 @@
+using System;
+
+public enum DeathReason
+{
+ Exile,
+ Kill,
+ Disconnect
+}
diff --git a/Client/Assembly-CSharp/DeconSystem.cs b/Client/Assembly-CSharp/DeconSystem.cs
new file mode 100644
index 0000000..0eb0b9d
--- /dev/null
+++ b/Client/Assembly-CSharp/DeconSystem.cs
@@ -0,0 +1,163 @@
+using System;
+using Hazel;
+using UnityEngine;
+
+public class DeconSystem : MonoBehaviour, ISystemType
+{
+ private const byte HeadUpCmd = 1;
+
+ private const byte HeadDownCmd = 2;
+
+ private const byte HeadUpInsideCmd = 3;
+
+ private const byte HeadDownInsideCmd = 4;
+
+ public ManualDoor UpperDoor;
+
+ public ManualDoor LowerDoor;
+
+ public float DoorOpenTime = 5f;
+
+ public float DeconTime = 5f;
+
+ private DeconSystem.States curState;
+
+ private float timer;
+
+ public TextRenderer FloorText;
+
+ [Flags]
+ private enum States : byte
+ {
+ Idle = 0,
+ Enter = 1,
+ Closed = 2,
+ Exit = 4,
+ HeadingUp = 8
+ }
+
+ public bool Detoriorate(float dt)
+ {
+ int num = Mathf.CeilToInt(this.timer);
+ this.timer = Mathf.Max(0f, this.timer - dt);
+ int num2 = Mathf.CeilToInt(this.timer);
+ if (num != num2)
+ {
+ if (num2 == 0)
+ {
+ if (this.curState.HasFlag(DeconSystem.States.Enter))
+ {
+ this.curState = ((this.curState & ~DeconSystem.States.Enter) | DeconSystem.States.Closed);
+ this.timer = this.DeconTime;
+ }
+ else if (this.curState.HasFlag(DeconSystem.States.Closed))
+ {
+ this.curState = ((this.curState & ~DeconSystem.States.Closed) | DeconSystem.States.Exit);
+ this.timer = this.DoorOpenTime;
+ }
+ else if (this.curState.HasFlag(DeconSystem.States.Exit))
+ {
+ this.curState = DeconSystem.States.Idle;
+ }
+ }
+ this.UpdateDoorsViaState();
+ return true;
+ }
+ return false;
+ }
+
+ public void OpenDoor(bool upper)
+ {
+ if (this.curState == DeconSystem.States.Idle)
+ {
+ ShipStatus.Instance.RpcRepairSystem(SystemTypes.Decontamination, upper ? 2 : 1);
+ }
+ }
+
+ public void OpenFromInside(bool upper)
+ {
+ if (this.curState == DeconSystem.States.Idle)
+ {
+ ShipStatus.Instance.RpcRepairSystem(SystemTypes.Decontamination, upper ? 3 : 4);
+ }
+ }
+
+ public void RepairDamage(PlayerControl player, byte amount)
+ {
+ if (this.curState != DeconSystem.States.Idle)
+ {
+ return;
+ }
+ switch (amount)
+ {
+ case 1:
+ this.curState = (DeconSystem.States.Enter | DeconSystem.States.HeadingUp);
+ this.timer = this.DoorOpenTime;
+ break;
+ case 2:
+ this.curState = DeconSystem.States.Enter;
+ this.timer = this.DoorOpenTime;
+ break;
+ case 3:
+ this.curState = (DeconSystem.States.Exit | DeconSystem.States.HeadingUp);
+ this.timer = this.DoorOpenTime;
+ break;
+ case 4:
+ this.curState = DeconSystem.States.Exit;
+ this.timer = this.DoorOpenTime;
+ break;
+ }
+ this.UpdateDoorsViaState();
+ }
+
+ public void Serialize(MessageWriter writer, bool initialState)
+ {
+ writer.Write((byte)Mathf.CeilToInt(this.timer));
+ writer.Write((byte)this.curState);
+ }
+
+ public void Deserialize(MessageReader reader, bool initialState)
+ {
+ this.timer = (float)reader.ReadByte();
+ this.curState = (DeconSystem.States)reader.ReadByte();
+ this.UpdateDoorsViaState();
+ }
+
+ private void UpdateDoorsViaState()
+ {
+ int num = Mathf.CeilToInt(this.timer);
+ if (num > 0)
+ {
+ this.FloorText.Text = string.Join("\n", new object[]
+ {
+ num,
+ num,
+ num,
+ num
+ });
+ }
+ else
+ {
+ this.FloorText.Text = string.Empty;
+ }
+ if (this.curState.HasFlag(DeconSystem.States.Enter))
+ {
+ bool flag = this.curState.HasFlag(DeconSystem.States.HeadingUp);
+ this.LowerDoor.SetDoorway(flag);
+ this.UpperDoor.SetDoorway(!flag);
+ return;
+ }
+ if (this.curState.HasFlag(DeconSystem.States.Closed) || this.curState == DeconSystem.States.Idle)
+ {
+ this.LowerDoor.SetDoorway(false);
+ this.UpperDoor.SetDoorway(false);
+ return;
+ }
+ if (this.curState.HasFlag(DeconSystem.States.Exit))
+ {
+ bool flag2 = this.curState.HasFlag(DeconSystem.States.HeadingUp);
+ this.LowerDoor.SetDoorway(!flag2);
+ this.UpperDoor.SetDoorway(flag2);
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/DefaultPool.cs b/Client/Assembly-CSharp/DefaultPool.cs
new file mode 100644
index 0000000..676c666
--- /dev/null
+++ b/Client/Assembly-CSharp/DefaultPool.cs
@@ -0,0 +1,82 @@
+using System;
+using UnityEngine;
+
+public class DefaultPool : IObjectPool
+{
+ public override int InUse
+ {
+ get
+ {
+ return 0;
+ }
+ }
+
+ public override int NotInUse
+ {
+ get
+ {
+ return 0;
+ }
+ }
+
+ public static bool InstanceExists
+ {
+ get
+ {
+ return DefaultPool._instance;
+ }
+ }
+
+ public static DefaultPool Instance
+ {
+ get
+ {
+ object @lock = DefaultPool._lock;
+ DefaultPool instance;
+ lock (@lock)
+ {
+ if (DefaultPool._instance == null)
+ {
+ DefaultPool._instance = UnityEngine.Object.FindObjectOfType<DefaultPool>();
+ if (UnityEngine.Object.FindObjectsOfType<DefaultPool>().Length > 1)
+ {
+ Debug.LogError("[Singleton] Something went really wrong - there should never be more than 1 singleton! Reopening the scene might fix it.");
+ return DefaultPool._instance;
+ }
+ if (DefaultPool._instance == null)
+ {
+ GameObject gameObject = new GameObject();
+ DefaultPool._instance = gameObject.AddComponent<DefaultPool>();
+ gameObject.name = "(singleton) DefaultPool";
+ }
+ }
+ instance = DefaultPool._instance;
+ }
+ return instance;
+ }
+ }
+
+ private static DefaultPool _instance;
+
+ private static object _lock = new object();
+
+ public void OnDestroy()
+ {
+ object @lock = DefaultPool._lock;
+ lock (@lock)
+ {
+ DefaultPool._instance = null;
+ }
+ }
+
+ public override T Get<T>()
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void Reclaim(PoolableBehavior obj)
+ {
+ Debug.Log("Default Pool: Destroying this thing.");
+ UnityEngine.Object.Destroy(obj.gameObject);
+ }
+}
diff --git a/Client/Assembly-CSharp/DemoKeyboardStick.cs b/Client/Assembly-CSharp/DemoKeyboardStick.cs
new file mode 100644
index 0000000..bacd4d9
--- /dev/null
+++ b/Client/Assembly-CSharp/DemoKeyboardStick.cs
@@ -0,0 +1,25 @@
+using System;
+using UnityEngine;
+
+public class DemoKeyboardStick : VirtualJoystick
+{
+ public SpriteRenderer UpKey;
+
+ public SpriteRenderer DownKey;
+
+ public SpriteRenderer LeftKey;
+
+ public SpriteRenderer RightKey;
+
+ protected override void FixedUpdate()
+ {
+ }
+
+ public override void UpdateJoystick(FingerBehaviour finger, Vector2 velocity, bool syncFinger)
+ {
+ this.UpKey.enabled = (velocity.y > 0.1f);
+ this.DownKey.enabled = (velocity.y < -0.1f);
+ this.RightKey.enabled = (velocity.x > 0.1f);
+ this.LeftKey.enabled = (velocity.x < -0.1f);
+ }
+}
diff --git a/Client/Assembly-CSharp/DestroyableSingleton.cs b/Client/Assembly-CSharp/DestroyableSingleton.cs
new file mode 100644
index 0000000..bcd8319
--- /dev/null
+++ b/Client/Assembly-CSharp/DestroyableSingleton.cs
@@ -0,0 +1,58 @@
+using System;
+using UnityEngine;
+
+public class DestroyableSingleton<T> : MonoBehaviour where T : MonoBehaviour
+{
+ public static bool InstanceExists
+ {
+ get
+ {
+ return DestroyableSingleton<T>._instance;
+ }
+ }
+
+ public static T Instance
+ {
+ get
+ {
+ if (!DestroyableSingleton<T>._instance)
+ {
+ DestroyableSingleton<T>._instance = UnityEngine.Object.FindObjectOfType<T>();
+ if (!DestroyableSingleton<T>._instance)
+ {
+ DestroyableSingleton<T>._instance = new GameObject().AddComponent<T>();
+ }
+ }
+ return DestroyableSingleton<T>._instance;
+ }
+ }
+
+ private static T _instance;
+
+ public bool DontDestroy;
+
+ public virtual void Awake()
+ {
+ if (!DestroyableSingleton<T>._instance)
+ {
+ DestroyableSingleton<T>._instance = (this as T);
+ if (this.DontDestroy)
+ {
+ UnityEngine.Object.DontDestroyOnLoad(base.gameObject);
+ return;
+ }
+ }
+ else if (DestroyableSingleton<T>._instance != this)
+ {
+ UnityEngine.Object.Destroy(base.gameObject);
+ }
+ }
+
+ public virtual void OnDestroy()
+ {
+ if (!this.DontDestroy)
+ {
+ DestroyableSingleton<T>._instance = default(T);
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/DialBehaviour.cs b/Client/Assembly-CSharp/DialBehaviour.cs
new file mode 100644
index 0000000..d306e24
--- /dev/null
+++ b/Client/Assembly-CSharp/DialBehaviour.cs
@@ -0,0 +1,46 @@
+using System;
+using UnityEngine;
+
+public class DialBehaviour : MonoBehaviour
+{
+ public FloatRange DialRange;
+
+ public Collider2D collider;
+
+ public Controller myController = new Controller();
+
+ public float Value;
+
+ public bool Engaged;
+
+ public Transform DialTrans;
+
+ public Transform DialShadTrans;
+
+ public void Update()
+ {
+ this.Engaged = false;
+ this.myController.Update();
+ DragState dragState = this.myController.CheckDrag(this.collider, false);
+ if (dragState == DragState.Dragging)
+ {
+ Vector2 vector = this.myController.DragPosition - base.transform.position;
+ float num = Vector2.up.AngleSigned(vector);
+ if (num < -180f)
+ {
+ num += 360f;
+ }
+ num = this.DialRange.Clamp(num);
+ this.SetValue(num);
+ this.Engaged = true;
+ }
+ }
+
+ public void SetValue(float angle)
+ {
+ this.Value = angle;
+ Vector3 localEulerAngles = new Vector3(0f, 0f, angle);
+ this.DialTrans.localEulerAngles = localEulerAngles;
+ this.DialShadTrans.localEulerAngles = localEulerAngles;
+ }
+}
diff --git a/Client/Assembly-CSharp/DialogueBox.cs b/Client/Assembly-CSharp/DialogueBox.cs
new file mode 100644
index 0000000..63fe612
--- /dev/null
+++ b/Client/Assembly-CSharp/DialogueBox.cs
@@ -0,0 +1,27 @@
+using System;
+using UnityEngine;
+
+public class DialogueBox : MonoBehaviour
+{
+ public TextRenderer target;
+
+ public void Show(string dialogue)
+ {
+ this.target.Text = dialogue;
+ if (Minigame.Instance)
+ {
+ Minigame.Instance.Close();
+ Minigame.Instance.Close();
+ }
+ PlayerControl.LocalPlayer.moveable = false;
+ PlayerControl.LocalPlayer.NetTransform.Halt();
+ base.gameObject.SetActive(true);
+ }
+
+ public void Hide()
+ {
+ base.gameObject.SetActive(false);
+ PlayerControl.LocalPlayer.moveable = true;
+ Camera.main.GetComponent<FollowerCamera>().Locked = false;
+ }
+}
diff --git a/Client/Assembly-CSharp/DisconnectPopup.cs b/Client/Assembly-CSharp/DisconnectPopup.cs
new file mode 100644
index 0000000..6feeed5
--- /dev/null
+++ b/Client/Assembly-CSharp/DisconnectPopup.cs
@@ -0,0 +1,112 @@
+using System;
+using InnerNet;
+
+public class DisconnectPopup : DestroyableSingleton<DisconnectPopup>
+{
+ public TextRenderer TextArea;
+
+ public void Start()
+ {
+ if (DestroyableSingleton<DisconnectPopup>.Instance == this)
+ {
+ this.Show();
+ }
+ }
+
+ public void Show()
+ {
+ base.gameObject.SetActive(true);
+ this.DoShow();
+ }
+
+ private void DoShow()
+ {
+ if (DestroyableSingleton<WaitForHostPopup>.InstanceExists)
+ {
+ DestroyableSingleton<WaitForHostPopup>.Instance.Hide();
+ }
+ if (!AmongUsClient.Instance)
+ {
+ base.gameObject.SetActive(false);
+ return;
+ }
+ string text = InnerNetClient.IntToGameName(AmongUsClient.Instance.GameId);
+ string str = (text != null) ? (" from " + text) : " from the room";
+ DisconnectReasons lastDisconnectReason = AmongUsClient.Instance.LastDisconnectReason;
+ switch (lastDisconnectReason)
+ {
+ case DisconnectReasons.ExitGame:
+ case DisconnectReasons.Destroy:
+ base.gameObject.SetActive(false);
+ break;
+ case DisconnectReasons.GameFull:
+ this.TextArea.Text = "The game you tried to join is full.\r\n\r\nCheck with the host to see if you can join next round.";
+ return;
+ case DisconnectReasons.GameStarted:
+ this.TextArea.Text = "The game you tried to join already started.\r\n\r\nCheck with the host to see if you can join next round.";
+ return;
+ case DisconnectReasons.GameNotFound:
+ case DisconnectReasons.IncorrectGame:
+ this.TextArea.Text = "Could not find the game you're looking for.";
+ return;
+ case (DisconnectReasons)4:
+ case (DisconnectReasons)9:
+ case (DisconnectReasons)10:
+ case (DisconnectReasons)11:
+ case (DisconnectReasons)12:
+ case (DisconnectReasons)13:
+ case (DisconnectReasons)14:
+ case (DisconnectReasons)15:
+ break;
+ case DisconnectReasons.IncorrectVersion:
+ this.TextArea.Text = "You are running an older version of the game.\r\n\r\nPlease update to play with others.";
+ return;
+ case DisconnectReasons.Banned:
+ this.TextArea.Text = "You were banned" + str + ".\r\n\r\nYou cannot rejoin that room.";
+ return;
+ case DisconnectReasons.Kicked:
+ this.TextArea.Text = "You were kicked" + str + ".\r\n\r\nYou can rejoin if the room hasn't started.";
+ return;
+ case DisconnectReasons.Custom:
+ this.TextArea.Text = (AmongUsClient.Instance.LastCustomDisconnect ?? "An unknown error disconnected you from the server.");
+ return;
+ case DisconnectReasons.Error:
+ if (AmongUsClient.Instance.GameMode == GameModes.OnlineGame)
+ {
+ this.TextArea.Text = "You disconnected from the server.\r\nIf this happens often, check your network strength.\r\nThis may also be a server issue.";
+ return;
+ }
+ this.TextArea.Text = "You disconnected from the host.\r\n\r\nIf this happens often, check your WiFi strength.";
+ return;
+ case DisconnectReasons.ServerRequest:
+ this.TextArea.Text = "The server stopped this game. Possibly due to inactivity.";
+ return;
+ case DisconnectReasons.ServerFull:
+ this.TextArea.Text = "The Among Us servers are overloaded.\r\n\r\nSorry! Please try again later!";
+ return;
+ default:
+ if (lastDisconnectReason == DisconnectReasons.IntentionalLeaving)
+ {
+ this.TextArea.Text = string.Format("You may not join another game for another {0} minutes after intentionally disconnecting.", StatsManager.Instance.BanMinutesLeft);
+ return;
+ }
+ if (lastDisconnectReason != DisconnectReasons.FocusLost)
+ {
+ return;
+ }
+ this.TextArea.Text = "You were disconnected because Among Us was suspended by another app.";
+ return;
+ }
+ }
+
+ public void ShowCustom(string message)
+ {
+ base.gameObject.SetActive(true);
+ this.TextArea.Text = message;
+ }
+
+ public void Close()
+ {
+ base.gameObject.SetActive(false);
+ }
+}
diff --git a/Client/Assembly-CSharp/DiscordManager.cs b/Client/Assembly-CSharp/DiscordManager.cs
new file mode 100644
index 0000000..0ee1ea6
--- /dev/null
+++ b/Client/Assembly-CSharp/DiscordManager.cs
@@ -0,0 +1,207 @@
+using System;
+using System.Collections;
+using InnerNet;
+using UnityEngine;
+using UnityEngine.SceneManagement;
+
+public class DiscordManager : DestroyableSingleton<DiscordManager>
+{
+ private DiscordRpc.RichPresence presence = new DiscordRpc.RichPresence();
+
+ public DiscordRpc.DiscordUser joinRequest;
+
+ private DateTime? StartTime;
+
+ private static readonly DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+
+ public void Start()
+ {
+ if (DestroyableSingleton<DiscordManager>.Instance == this)
+ {
+ DiscordRpc.EventHandlers eventHandlers = default(DiscordRpc.EventHandlers);
+ eventHandlers.errorCallback = (DiscordRpc.ErrorCallback)Delegate.Combine(eventHandlers.errorCallback, new DiscordRpc.ErrorCallback(this.HandleError));
+ eventHandlers.disconnectedCallback = (DiscordRpc.DisconnectedCallback)Delegate.Combine(eventHandlers.disconnectedCallback, new DiscordRpc.DisconnectedCallback(this.HandleError));
+ eventHandlers.joinCallback = (DiscordRpc.JoinCallback)Delegate.Combine(eventHandlers.joinCallback, new DiscordRpc.JoinCallback(this.HandleJoinRequest));
+ eventHandlers.requestCallback = (DiscordRpc.RequestCallback)Delegate.Combine(eventHandlers.requestCallback, new DiscordRpc.RequestCallback(this.HandleAutoJoin));
+ DiscordRpc.Initialize("477175586805252107", ref eventHandlers, true, null);
+ this.SetInMenus();
+ SceneManager.sceneLoaded += delegate(Scene scene, LoadSceneMode mode)
+ {
+ this.OnSceneChange(scene.name);
+ };
+ }
+ }
+
+ private void HandleError(int errorCode, string message)
+ {
+ Debug.LogError(message ?? string.Format("No message: {0}", errorCode));
+ }
+
+ private void OnSceneChange(string name)
+ {
+ if (name == "MatchMaking" || name == "MMOnline" || name == "MainMenu")
+ {
+ this.SetInMenus();
+ }
+ }
+
+ public void FixedUpdate()
+ {
+ DiscordRpc.RunCallbacks();
+ }
+
+ public void SetInMenus()
+ {
+ this.ClearPresence();
+ this.StartTime = null;
+ this.presence.state = "In Menus";
+ this.presence.largeImageKey = "icon";
+ DiscordRpc.UpdatePresence(this.presence);
+ }
+
+ public void SetPlayingGame()
+ {
+ if (this.StartTime == null)
+ {
+ this.StartTime = new DateTime?(DateTime.UtcNow);
+ }
+ this.presence.state = "In Game";
+ this.presence.details = "Playing";
+ this.presence.largeImageKey = "icon";
+ this.presence.startTimestamp = DiscordManager.ToUnixTime(this.StartTime.Value);
+ DiscordRpc.UpdatePresence(this.presence);
+ }
+
+ public void SetHowToPlay()
+ {
+ this.ClearPresence();
+ this.presence.state = "In Freeplay";
+ this.presence.largeImageKey = "icon";
+ DiscordRpc.UpdatePresence(this.presence);
+ }
+
+ public void SetInLobbyClient()
+ {
+ if (this.StartTime == null)
+ {
+ this.StartTime = new DateTime?(DateTime.UtcNow);
+ }
+ this.ClearPresence();
+ this.presence.state = "In Lobby";
+ this.presence.largeImageKey = "icon";
+ this.presence.startTimestamp = DiscordManager.ToUnixTime(this.StartTime.Value);
+ DiscordRpc.UpdatePresence(this.presence);
+ }
+
+ private void ClearPresence()
+ {
+ this.presence.startTimestamp = 0L;
+ this.presence.details = null;
+ this.presence.partyId = null;
+ this.presence.matchSecret = null;
+ this.presence.joinSecret = null;
+ this.presence.partySize = 0;
+ this.presence.partyMax = 0;
+ }
+
+ public void SetInLobbyHost(int numPlayers, int gameId)
+ {
+ if (this.StartTime == null)
+ {
+ this.StartTime = new DateTime?(DateTime.UtcNow);
+ }
+ string text = InnerNetClient.IntToGameName(gameId);
+ this.presence.state = "In Lobby";
+ this.presence.details = "Hosting a game";
+ this.presence.partySize = numPlayers;
+ this.presence.partyMax = 10;
+ this.presence.smallImageKey = "icon";
+ this.presence.largeImageText = "Ask to play!";
+ this.presence.joinSecret = "join" + text;
+ this.presence.matchSecret = "match" + text;
+ this.presence.partyId = text;
+ DiscordRpc.UpdatePresence(this.presence);
+ }
+
+ private void HandleAutoJoin(ref DiscordRpc.DiscordUser requestUser)
+ {
+ Debug.Log("Discord: request from " + requestUser.username);
+ if (AmongUsClient.Instance.IsGameStarted)
+ {
+ this.RequestRespondNo();
+ return;
+ }
+ this.RequestRespondYes();
+ }
+
+ private void HandleJoinRequest(string joinSecret)
+ {
+ if (!joinSecret.StartsWith("join"))
+ {
+ Debug.LogWarning("Invalid join secret: " + joinSecret);
+ return;
+ }
+ if (!AmongUsClient.Instance)
+ {
+ Debug.LogWarning("Missing AmongUsClient");
+ return;
+ }
+ if (!DestroyableSingleton<DiscordManager>.InstanceExists)
+ {
+ Debug.LogWarning("Missing DiscordManager");
+ return;
+ }
+ if (AmongUsClient.Instance.mode != MatchMakerModes.None)
+ {
+ Debug.LogWarning("Already connected");
+ return;
+ }
+ AmongUsClient.Instance.GameMode = GameModes.OnlineGame;
+ AmongUsClient.Instance.GameId = InnerNetClient.GameNameToInt(joinSecret.Substring(4));
+ AmongUsClient.Instance.SetEndpoint(DestroyableSingleton<ServerManager>.Instance.OnlineNetAddress, 22023);
+ AmongUsClient.Instance.MainMenuScene = "MMOnline";
+ AmongUsClient.Instance.OnlineScene = "OnlineGame";
+ DestroyableSingleton<DiscordManager>.Instance.StopAllCoroutines();
+ DestroyableSingleton<DiscordManager>.Instance.StartCoroutine(DestroyableSingleton<DiscordManager>.Instance.CoJoinGame());
+ }
+
+ public IEnumerator CoJoinGame()
+ {
+ while (DataCollectScreen.Instance && DataCollectScreen.Instance.isActiveAndEnabled)
+ {
+ yield return null;
+ }
+ AmongUsClient.Instance.Connect(MatchMakerModes.Client);
+ yield return AmongUsClient.Instance.WaitForConnectionOrFail();
+ if (AmongUsClient.Instance.ClientId < 0)
+ {
+ SceneManager.LoadScene("MMOnline");
+ }
+ yield break;
+ }
+
+ public void RequestRespondYes()
+ {
+ DiscordRpc.Respond(this.joinRequest.userId, DiscordRpc.Reply.Yes);
+ }
+
+ public void RequestRespondNo()
+ {
+ Debug.Log("Discord: responding no to Ask to Join request");
+ DiscordRpc.Respond(this.joinRequest.userId, DiscordRpc.Reply.No);
+ }
+
+ public override void OnDestroy()
+ {
+ base.OnDestroy();
+ if (DestroyableSingleton<DiscordManager>.Instance == this)
+ {
+ DiscordRpc.Shutdown();
+ }
+ }
+
+ private static long ToUnixTime(DateTime time)
+ {
+ return (long)(time - DiscordManager.epoch).TotalSeconds;
+ }
+}
diff --git a/Client/Assembly-CSharp/DiscoveryState.cs b/Client/Assembly-CSharp/DiscoveryState.cs
new file mode 100644
index 0000000..4da2cfc
--- /dev/null
+++ b/Client/Assembly-CSharp/DiscoveryState.cs
@@ -0,0 +1,7 @@
+using System;
+
+public enum DiscoveryState
+{
+ Off,
+ Broadcast
+}
diff --git a/Client/Assembly-CSharp/DiscussBehaviour.cs b/Client/Assembly-CSharp/DiscussBehaviour.cs
new file mode 100644
index 0000000..9a99207
--- /dev/null
+++ b/Client/Assembly-CSharp/DiscussBehaviour.cs
@@ -0,0 +1,65 @@
+using System;
+using System.Collections;
+using UnityEngine;
+
+public class DiscussBehaviour : MonoBehaviour
+{
+ public SpriteRenderer LeftPlayer;
+
+ public SpriteRenderer RightPlayer;
+
+ public SpriteRenderer Text;
+
+ public FloatRange RotateRange = new FloatRange(-5f, 5f);
+
+ public Vector2Range TextTarget;
+
+ public AnimationCurve TextEasing;
+
+ public float Delay = 0.1f;
+
+ public float TextDuration = 0.5f;
+
+ public float HoldDuration = 2f;
+
+ private Vector3 vec;
+
+ public IEnumerator PlayAnimation()
+ {
+ this.Text.transform.localPosition = this.TextTarget.min;
+ yield return this.AnimateText();
+ yield return ShhhBehaviour.WaitWithInterrupt(this.HoldDuration);
+ yield break;
+ }
+
+ public void Update()
+ {
+ this.vec.Set(0f, 0f, this.RotateRange.Lerp(Mathf.PerlinNoise(1f, Time.time * 8f)));
+ this.LeftPlayer.transform.eulerAngles = this.vec;
+ this.vec.Set(0f, 0f, this.RotateRange.Lerp(Mathf.PerlinNoise(2f, Time.time * 8f)));
+ this.RightPlayer.transform.eulerAngles = this.vec;
+ }
+
+ private IEnumerator AnimateText()
+ {
+ for (float t = 0f; t < this.Delay; t += Time.deltaTime)
+ {
+ yield return null;
+ }
+ Vector3 vec = default(Vector3);
+ for (float t = 0f; t < this.TextDuration; t += Time.deltaTime)
+ {
+ float time = t / this.TextDuration;
+ this.UpdateText(ref vec, this.TextEasing.Evaluate(time));
+ yield return null;
+ }
+ this.UpdateText(ref vec, 1f);
+ yield break;
+ }
+
+ private void UpdateText(ref Vector3 vec, float p)
+ {
+ this.TextTarget.LerpUnclamped(ref vec, p, -7f);
+ this.Text.transform.localPosition = vec;
+ }
+}
diff --git a/Client/Assembly-CSharp/DivertPowerMetagame.cs b/Client/Assembly-CSharp/DivertPowerMetagame.cs
new file mode 100644
index 0000000..1be1bf3
--- /dev/null
+++ b/Client/Assembly-CSharp/DivertPowerMetagame.cs
@@ -0,0 +1,25 @@
+using System;
+using UnityEngine;
+
+public class DivertPowerMetagame : Minigame
+{
+ public Minigame DistributePrefab;
+
+ public Minigame ReceivePrefab;
+
+ public override void Begin(PlayerTask task)
+ {
+ base.Begin(task);
+ Minigame minigame;
+ if (this.MyNormTask.taskStep == 0)
+ {
+ minigame = UnityEngine.Object.Instantiate<Minigame>(this.DistributePrefab, base.transform.parent);
+ }
+ else
+ {
+ minigame = UnityEngine.Object.Instantiate<Minigame>(this.ReceivePrefab, base.transform.parent);
+ }
+ minigame.Begin(task);
+ UnityEngine.Object.Destroy(base.gameObject);
+ }
+}
diff --git a/Client/Assembly-CSharp/DivertPowerMinigame.cs b/Client/Assembly-CSharp/DivertPowerMinigame.cs
new file mode 100644
index 0000000..64aa786
--- /dev/null
+++ b/Client/Assembly-CSharp/DivertPowerMinigame.cs
@@ -0,0 +1,91 @@
+using System;
+using UnityEngine;
+
+public class DivertPowerMinigame : Minigame
+{
+ public SystemTypes[] SliderOrder = new SystemTypes[]
+ {
+ SystemTypes.LowerEngine,
+ SystemTypes.UpperEngine,
+ SystemTypes.Weapons,
+ SystemTypes.Shields,
+ SystemTypes.Nav,
+ SystemTypes.Comms,
+ SystemTypes.LifeSupp,
+ SystemTypes.Security
+ };
+
+ public Collider2D[] Sliders;
+
+ public LineRenderer[] Wires;
+
+ public VerticalGauge[] Gauges;
+
+ private int sliderId;
+
+ public FloatRange SliderY = new FloatRange(-1f, 1f);
+
+ private Controller myController = new Controller();
+
+ public override void Begin(PlayerTask task)
+ {
+ base.Begin(task);
+ DivertPowerTask powerTask = (DivertPowerTask)task;
+ this.sliderId = this.SliderOrder.IndexOf((SystemTypes t) => t == powerTask.TargetSystem);
+ for (int i = 0; i < this.Sliders.Length; i++)
+ {
+ if (i != this.sliderId)
+ {
+ this.Sliders[i].GetComponent<SpriteRenderer>().color = new Color(0.5f, 0.5f, 0.5f);
+ }
+ }
+ }
+
+ public void FixedUpdate()
+ {
+ this.myController.Update();
+ float num = 0f;
+ for (int i = 0; i < this.Sliders.Length; i++)
+ {
+ num += this.SliderY.ReverseLerp(this.Sliders[i].transform.localPosition.y) / (float)this.Sliders.Length;
+ }
+ for (int j = 0; j < this.Sliders.Length; j++)
+ {
+ float num2 = this.SliderY.ReverseLerp(this.Sliders[j].transform.localPosition.y);
+ float num3 = num2 / num / 1.6f;
+ this.Gauges[j].value = num3 + (Mathf.PerlinNoise((float)j, Time.time * 51f) - 0.5f) * 0.04f;
+ Color value = Color.Lerp(Color.gray, Color.yellow, num2 * num2);
+ value.a = (float)((num3 < 0.1f) ? 0 : 1);
+ Vector2 textureOffset = this.Wires[j].material.GetTextureOffset("_MainTex");
+ textureOffset.x -= Time.fixedDeltaTime * 3f * Mathf.Lerp(0.1f, 2f, num3);
+ this.Wires[j].material.SetTextureOffset("_MainTex", textureOffset);
+ this.Wires[j].material.SetColor("_Color", value);
+ }
+ if (this.sliderId < 0)
+ {
+ return;
+ }
+ Collider2D collider2D = this.Sliders[this.sliderId];
+ Vector2 vector = collider2D.transform.localPosition;
+ DragState dragState = this.myController.CheckDrag(collider2D, false);
+ if (dragState == DragState.Dragging)
+ {
+ Vector2 vector2 = this.myController.DragPosition - collider2D.transform.parent.position;
+ vector2.y = this.SliderY.Clamp(vector2.y);
+ vector.y = vector2.y;
+ collider2D.transform.localPosition = vector;
+ return;
+ }
+ if (dragState != DragState.Released)
+ {
+ return;
+ }
+ if (this.SliderY.max - vector.y < 0.05f)
+ {
+ this.MyNormTask.NextStep();
+ base.StartCoroutine(base.CoStartClose(0.75f));
+ this.sliderId = -1;
+ collider2D.GetComponent<SpriteRenderer>().color = new Color(0.5f, 0.5f, 0.5f);
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/DivertPowerTask.cs b/Client/Assembly-CSharp/DivertPowerTask.cs
new file mode 100644
index 0000000..f970ae3
--- /dev/null
+++ b/Client/Assembly-CSharp/DivertPowerTask.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Linq;
+using System.Text;
+
+public class DivertPowerTask : NormalPlayerTask
+{
+ public SystemTypes TargetSystem;
+
+ public override bool ValidConsole(global::Console console)
+ {
+ return (console.Room == this.TargetSystem && console.ValidTasks.Any((TaskSet set) => this.TaskType == set.taskType && set.taskStep.Contains(this.taskStep))) || (this.taskStep == 0 && console.TaskTypes.Contains(this.TaskType));
+ }
+
+ public override void AppendTaskText(StringBuilder sb)
+ {
+ if (this.taskStep > 0)
+ {
+ if (this.IsComplete)
+ {
+ sb.Append("[00DD00FF]");
+ }
+ else
+ {
+ sb.Append("[FFFF00FF]");
+ }
+ }
+ if (this.taskStep == 0)
+ {
+ sb.Append(DestroyableSingleton<TranslationController>.Instance.GetString(this.StartAt));
+ sb.Append(": ");
+ sb.Append(DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.DivertPowerTo, Array.Empty<object>()));
+ sb.Append(" ");
+ sb.Append(DestroyableSingleton<TranslationController>.Instance.GetString(this.TargetSystem));
+ }
+ else
+ {
+ sb.Append(DestroyableSingleton<TranslationController>.Instance.GetString(this.TargetSystem));
+ sb.Append(": ");
+ sb.Append(DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.AcceptDivertedPower, Array.Empty<object>()));
+ }
+ sb.Append(" (");
+ sb.Append(this.taskStep);
+ sb.Append("/");
+ sb.Append(this.MaxStep);
+ sb.AppendLine(")");
+ if (this.taskStep > 0)
+ {
+ sb.Append("[]");
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/DoorsSystemType.cs b/Client/Assembly-CSharp/DoorsSystemType.cs
new file mode 100644
index 0000000..e642713
--- /dev/null
+++ b/Client/Assembly-CSharp/DoorsSystemType.cs
@@ -0,0 +1,119 @@
+using System;
+using System.Linq;
+using Hazel;
+
+//c 管理场景中的门,同步门的开启状态
+public class DoorsSystemType : ISystemType, IActivatable
+{
+ public bool IsActive
+ {
+ get
+ {
+ return this.doors.Any((AutoOpenDoor b) => !b.Open);
+ }
+ }
+
+ private AutoOpenDoor[] doors; // 场景所有的门
+
+ private uint dirtyBits;
+
+ public void SetDoors(AutoOpenDoor[] doors)
+ {
+ this.doors = doors;
+ }
+
+ public bool Detoriorate(float deltaTime)
+ {
+ if (this.doors == null)
+ {
+ return false;
+ }
+ for (int i = 0; i < this.doors.Length; i++)
+ {
+ if (this.doors[i].DoUpdate(deltaTime))
+ {
+ this.dirtyBits |= 1U << i;
+ }
+ }
+ return this.dirtyBits > 0U;
+ }
+
+ public void RepairDamage(PlayerControl player, byte amount)
+ {
+ }
+
+ //c 同步场景中所有门的开启状态
+
+ public void Serialize(MessageWriter writer, bool initialState)
+ {
+ if (initialState)
+ {
+ for (int i = 0; i < this.doors.Length; i++)
+ {
+ this.doors[i].Serialize(writer);
+ }
+ return;
+ }
+ writer.WritePacked(this.dirtyBits);
+ for (int j = 0; j < this.doors.Length; j++)
+ {
+ if ((this.dirtyBits & 1U << j) != 0U)
+ {
+ this.doors[j].Serialize(writer);
+ }
+ }
+ this.dirtyBits = 0U;
+ }
+
+ public void Deserialize(MessageReader reader, bool initialState)
+ {
+ if (initialState)
+ {
+ for (int i = 0; i < this.doors.Length; i++)
+ {
+ this.doors[i].Deserialize(reader);
+ }
+ return;
+ }
+ uint num = reader.ReadPackedUInt32();
+ for (int j = 0; j < this.doors.Length; j++)
+ {
+ if ((num & 1U << j) != 0U)
+ {
+ this.doors[j].Deserialize(reader);
+ }
+ }
+ }
+
+ public void SetDoor(AutoOpenDoor door, bool open)
+ {
+ door.SetDoorway(open);
+ this.dirtyBits |= 1U << this.doors.IndexOf(door);
+ }
+
+ public void CloseDoorsOfType(SystemTypes room)
+ {
+ for (int i = 0; i < this.doors.Length; i++)
+ {
+ AutoOpenDoor autoOpenDoor = this.doors[i];
+ if (autoOpenDoor.Room == room)
+ {
+ autoOpenDoor.SetDoorway(false);
+ this.dirtyBits |= 1U << i;
+ }
+ }
+ }
+
+ public float GetTimer(SystemTypes room)
+ {
+ for (int i = 0; i < this.doors.Length; i++)
+ {
+ AutoOpenDoor autoOpenDoor = this.doors[i];
+ if (autoOpenDoor.Room == room)
+ {
+ return autoOpenDoor.CooldownTimer;
+ }
+ }
+ return 0f;
+ }
+}
diff --git a/Client/Assembly-CSharp/DotAligner.cs b/Client/Assembly-CSharp/DotAligner.cs
new file mode 100644
index 0000000..149f4c8
--- /dev/null
+++ b/Client/Assembly-CSharp/DotAligner.cs
@@ -0,0 +1,43 @@
+using System;
+using UnityEngine;
+
+public class DotAligner : MonoBehaviour
+{
+ public float Width = 2f;
+
+ public bool Even;
+
+ public void Start()
+ {
+ int num = 0;
+ for (int i = 0; i < base.transform.childCount; i++)
+ {
+ if (base.transform.GetChild(i).gameObject.activeSelf)
+ {
+ num++;
+ }
+ }
+ float num2;
+ float num3;
+ if (this.Even)
+ {
+ num2 = -this.Width * (float)(num - 1) / 2f;
+ num3 = this.Width;
+ }
+ else
+ {
+ num2 = -this.Width / 2f;
+ num3 = this.Width / (float)(num - 1);
+ }
+ int num4 = 0;
+ for (int j = 0; j < base.transform.childCount; j++)
+ {
+ Transform child = base.transform.GetChild(j);
+ if (child.gameObject.activeSelf)
+ {
+ child.transform.localPosition = new Vector3(num2 + (float)num4 * num3, 0f, 0f);
+ num4++;
+ }
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/DragState.cs b/Client/Assembly-CSharp/DragState.cs
new file mode 100644
index 0000000..6b99f71
--- /dev/null
+++ b/Client/Assembly-CSharp/DragState.cs
@@ -0,0 +1,9 @@
+using System;
+
+public enum DragState
+{
+ NoTouch,
+ TouchStart,
+ Dragging,
+ Released
+}
diff --git a/Client/Assembly-CSharp/DummyBehaviour.cs b/Client/Assembly-CSharp/DummyBehaviour.cs
new file mode 100644
index 0000000..58beb6f
--- /dev/null
+++ b/Client/Assembly-CSharp/DummyBehaviour.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Collections;
+using UnityEngine;
+
+public class DummyBehaviour : MonoBehaviour
+{
+ private PlayerControl myPlayer;
+
+ private FloatRange voteTime = new FloatRange(3f, 8f);
+
+ private bool voted;
+
+ public void Start()
+ {
+ this.myPlayer = base.GetComponent<PlayerControl>();
+ }
+
+ public void Update()
+ {
+ if (this.myPlayer.Data.IsDead)
+ {
+ return;
+ }
+ if (MeetingHud.Instance)
+ {
+ if (!this.voted)
+ {
+ this.voted = true;
+ base.StartCoroutine(this.DoVote());
+ return;
+ }
+ }
+ else
+ {
+ this.voted = false;
+ }
+ }
+
+ private IEnumerator DoVote()
+ {
+ yield return new WaitForSeconds(this.voteTime.Next());
+ sbyte suspectIdx = -1;
+ int num = 0;
+ while (num < 100 && num != 99)
+ {
+ int num2 = IntRange.Next(-1, GameData.Instance.PlayerCount);
+ if (num2 < 0)
+ {
+ suspectIdx = (sbyte)num2;
+ break;
+ }
+ GameData.PlayerInfo playerInfo = GameData.Instance.AllPlayers[num2];
+ if (!playerInfo.IsDead)
+ {
+ suspectIdx = (sbyte)playerInfo.PlayerId;
+ break;
+ }
+ num++;
+ }
+ MeetingHud.Instance.CmdCastVote(this.myPlayer.PlayerId, suspectIdx);
+ yield break;
+ }
+}
diff --git a/Client/Assembly-CSharp/DummyConsole.cs b/Client/Assembly-CSharp/DummyConsole.cs
new file mode 100644
index 0000000..cb053ae
--- /dev/null
+++ b/Client/Assembly-CSharp/DummyConsole.cs
@@ -0,0 +1,57 @@
+using System;
+using UnityEngine;
+
+public class DummyConsole : MonoBehaviour
+{
+ public int ConsoleId;
+
+ public PlayerAnimator[] Players;
+
+ public float UseDistance;
+
+ [HideInInspector]
+ private SpriteRenderer rend;
+
+ public void Start()
+ {
+ this.rend = base.GetComponent<SpriteRenderer>();
+ }
+
+ public void FixedUpdate()
+ {
+ this.rend.material.SetColor("_OutlineColor", Color.yellow);
+ float num = float.MaxValue;
+ for (int i = 0; i < this.Players.Length; i++)
+ {
+ PlayerAnimator playerAnimator = this.Players[i];
+ Vector2 vector = base.transform.position - playerAnimator.transform.position;
+ vector.y += 0.3636f;
+ float magnitude = vector.magnitude;
+ if (magnitude < num)
+ {
+ num = magnitude;
+ }
+ if (magnitude < this.UseDistance)
+ {
+ playerAnimator.NearbyConsoles |= 1 << this.ConsoleId;
+ }
+ else
+ {
+ playerAnimator.NearbyConsoles &= ~(1 << this.ConsoleId);
+ }
+ }
+ if (num >= this.UseDistance * 2f)
+ {
+ this.rend.material.SetFloat("_Outline", 0f);
+ this.rend.material.SetColor("_AddColor", Color.clear);
+ return;
+ }
+ this.rend.material.SetFloat("_Outline", 1f);
+ if (num < this.UseDistance)
+ {
+ this.rend.material.SetColor("_AddColor", Color.yellow);
+ return;
+ }
+ this.rend.material.SetColor("_AddColor", Color.clear);
+ }
+}
diff --git a/Client/Assembly-CSharp/DynamicSound.cs b/Client/Assembly-CSharp/DynamicSound.cs
new file mode 100644
index 0000000..f91f63d
--- /dev/null
+++ b/Client/Assembly-CSharp/DynamicSound.cs
@@ -0,0 +1,26 @@
+using System;
+using UnityEngine;
+
+public class DynamicSound : ISoundPlayer
+{
+ public string Name { get; set; }
+
+ public AudioSource Player { get; set; }
+
+ public DynamicSound.GetDynamicsFunction volumeFunc;
+
+ public delegate void GetDynamicsFunction(AudioSource source, float dt);
+
+ public void Update(float dt)
+ {
+ this.volumeFunc(this.Player, dt);
+ }
+
+ public void SetTarget(AudioClip clip, DynamicSound.GetDynamicsFunction volumeFunc)
+ {
+ this.volumeFunc = volumeFunc;
+ this.Player.clip = clip;
+ this.volumeFunc(this.Player, 1f);
+ this.Player.Play();
+ }
+}
diff --git a/Client/Assembly-CSharp/Effects.cs b/Client/Assembly-CSharp/Effects.cs
new file mode 100644
index 0000000..56b3efe
--- /dev/null
+++ b/Client/Assembly-CSharp/Effects.cs
@@ -0,0 +1,248 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+
+public static class Effects
+{
+ private static HashSet<Transform> activeShakes = new HashSet<Transform>();
+
+ public static IEnumerator Wait(float duration)
+ {
+ for (float t = 0f; t < duration; t += Time.deltaTime)
+ {
+ yield return null;
+ }
+ yield break;
+ }
+
+ public static IEnumerator All(params IEnumerator[] items)
+ {
+ Stack<IEnumerator>[] enums = new Stack<IEnumerator>[items.Length];
+ for (int i = 0; i < items.Length; i++)
+ {
+ enums[i] = new Stack<IEnumerator>();
+ enums[i].Push(items[i]);
+ }
+ int num;
+ for (int cap = 0; cap < 100000; cap = num)
+ {
+ bool flag = false;
+ for (int j = 0; j < enums.Length; j++)
+ {
+ if (enums[j].Count > 0)
+ {
+ flag = true;
+ IEnumerator enumerator = enums[j].Peek();
+ if (enumerator.MoveNext())
+ {
+ if (enumerator.Current is IEnumerator)
+ {
+ enums[j].Push((IEnumerator)enumerator.Current);
+ }
+ }
+ else
+ {
+ enums[j].Pop();
+ }
+ }
+ }
+ if (!flag)
+ {
+ break;
+ }
+ yield return null;
+ num = cap + 1;
+ }
+ yield break;
+ }
+
+ internal static IEnumerator ScaleIn(Transform self, float source, float target, float duration)
+ {
+ if (!self)
+ {
+ yield break;
+ }
+ Vector3 localScale = default(Vector3);
+ for (float t = 0f; t < duration; t += Time.deltaTime)
+ {
+ localScale.x = (localScale.y = (localScale.z = Mathf.SmoothStep(source, target, t / duration)));
+ self.localScale = localScale;
+ yield return null;
+ }
+ localScale.z = target;
+ localScale.y = target;
+ localScale.x = target;
+ self.localScale = localScale;
+ yield break;
+ }
+
+ internal static IEnumerator CycleColors(SpriteRenderer self, Color source, Color target, float rate, float duration)
+ {
+ if (!self)
+ {
+ yield break;
+ }
+ self.enabled = true;
+ for (float t = 0f; t < duration; t += Time.deltaTime)
+ {
+ float t2 = Mathf.Sin(t * 3.1415927f / rate) / 2f + 0.5f;
+ self.color = Color.Lerp(source, target, t2);
+ yield return null;
+ }
+ self.color = source;
+ yield break;
+ }
+
+ internal static IEnumerator PulseColor(SpriteRenderer self, Color source, Color target, float duration = 0.5f)
+ {
+ if (!self)
+ {
+ yield break;
+ }
+ self.enabled = true;
+ for (float t = 0f; t < duration; t += Time.deltaTime)
+ {
+ self.color = Color.Lerp(target, source, t / duration);
+ yield return null;
+ }
+ self.color = source;
+ yield break;
+ }
+
+ internal static IEnumerator PulseColor(TextRenderer self, Color source, Color target, float duration = 0.5f)
+ {
+ if (!self)
+ {
+ yield break;
+ }
+ for (float t = 0f; t < duration; t += Time.deltaTime)
+ {
+ self.Color = Color.Lerp(target, source, t / duration);
+ yield return null;
+ }
+ self.Color = source;
+ yield break;
+ }
+
+ public static IEnumerator ColorFade(SpriteRenderer self, Color source, Color target, float duration)
+ {
+ if (!self)
+ {
+ yield break;
+ }
+ self.enabled = true;
+ for (float t = 0f; t < duration; t += Time.deltaTime)
+ {
+ self.color = Color.Lerp(source, target, t / duration);
+ yield return null;
+ }
+ self.color = target;
+ yield break;
+ }
+
+ public static IEnumerator Rotate2D(Transform target, float source, float dest, float duration = 0.75f)
+ {
+ Vector3 temp = target.localEulerAngles;
+ for (float time = 0f; time < duration; time += Time.deltaTime)
+ {
+ float t = time / duration;
+ temp.z = Mathf.SmoothStep(source, dest, t);
+ target.localEulerAngles = temp;
+ yield return null;
+ }
+ temp.z = dest;
+ target.localEulerAngles = temp;
+ yield break;
+ }
+
+ public static IEnumerator Slide2D(Transform target, Vector2 source, Vector2 dest, float duration = 0.75f)
+ {
+ Vector3 temp = default(Vector3);
+ temp.z = target.localPosition.z;
+ for (float time = 0f; time < duration; time += Time.deltaTime)
+ {
+ float t = time / duration;
+ temp.x = Mathf.SmoothStep(source.x, dest.x, t);
+ temp.y = Mathf.SmoothStep(source.y, dest.y, t);
+ target.localPosition = temp;
+ yield return null;
+ }
+ temp.x = dest.x;
+ temp.y = dest.y;
+ target.localPosition = temp;
+ yield break;
+ }
+
+ public static IEnumerator Bounce(Transform target, float duration = 0.3f, float height = 0.15f)
+ {
+ if (!target)
+ {
+ yield break;
+ }
+ Vector3 origin = target.localPosition;
+ Vector3 temp = origin;
+ for (float timer = 0f; timer < duration; timer += Time.deltaTime)
+ {
+ float num = timer / duration;
+ float num2 = 1f - num;
+ temp.y = origin.y + height * Mathf.Abs(Mathf.Sin(num * 3.1415927f * 3f)) * num2;
+ if (!target)
+ {
+ yield break;
+ }
+ target.localPosition = temp;
+ yield return null;
+ }
+ if (target)
+ {
+ target.transform.localPosition = origin;
+ }
+ yield break;
+ }
+
+ public static IEnumerator Shake(Transform target, float duration = 0.75f, float halfWidth = 0.25f)
+ {
+ if (Effects.activeShakes.Add(target))
+ {
+ Vector3 origin = target.localPosition;
+ for (float timer = 0f; timer < duration; timer += Time.deltaTime)
+ {
+ float num = timer / duration;
+ target.localPosition = origin + Vector3.right * (halfWidth * Mathf.Sin(num * 30f) * (1f - num));
+ yield return null;
+ }
+ target.transform.localPosition = origin;
+ Effects.activeShakes.Remove(target);
+ origin = default(Vector3);
+ }
+ yield break;
+ }
+
+ public static IEnumerator Bloop(float delay, Transform target, float duration = 0.5f)
+ {
+ for (float t = 0f; t < delay; t += Time.deltaTime)
+ {
+ yield return null;
+ }
+ Vector3 localScale = default(Vector3);
+ for (float t = 0f; t < duration; t += Time.deltaTime)
+ {
+ float z = Effects.ElasticOut(t, duration);
+ localScale.x = (localScale.y = (localScale.z = z));
+ target.localScale = localScale;
+ yield return null;
+ }
+ localScale.x = (localScale.y = (localScale.z = 1f));
+ target.localScale = localScale;
+ yield break;
+ }
+
+ private static float ElasticOut(float time, float duration)
+ {
+ time /= duration;
+ float num = time * time;
+ float num2 = num * time;
+ return 33f * num2 * num + -106f * num * num + 126f * num2 + -67f * num + 15f * time;
+ }
+}
diff --git a/Client/Assembly-CSharp/ElectricTask.cs b/Client/Assembly-CSharp/ElectricTask.cs
new file mode 100644
index 0000000..1480462
--- /dev/null
+++ b/Client/Assembly-CSharp/ElectricTask.cs
@@ -0,0 +1,82 @@
+using System;
+using System.Linq;
+using System.Text;
+using UnityEngine;
+
+public class ElectricTask : SabotageTask
+{
+ public override int TaskStep
+ {
+ get
+ {
+ if (!this.isComplete)
+ {
+ return 0;
+ }
+ return 1;
+ }
+ }
+
+ public override bool IsComplete
+ {
+ get
+ {
+ return this.isComplete;
+ }
+ }
+
+ private bool isComplete;
+
+ private SwitchSystem system;
+
+ private bool even;
+
+ public override void Initialize()
+ {
+ ShipStatus instance = ShipStatus.Instance;
+ this.system = (SwitchSystem)instance.Systems[SystemTypes.Electrical];
+ base.SetupArrows();
+ }
+
+ private void FixedUpdate()
+ {
+ if (this.isComplete)
+ {
+ return;
+ }
+ if (this.system.ExpectedSwitches == this.system.ActualSwitches)
+ {
+ this.Complete();
+ }
+ }
+
+ public override bool ValidConsole(global::Console console)
+ {
+ return console.TaskTypes.Contains(TaskTypes.FixLights);
+ }
+
+ public override void Complete()
+ {
+ this.isComplete = true;
+ PlayerControl.LocalPlayer.RemoveTask(this);
+ if (this.didContribute)
+ {
+ StatsManager instance = StatsManager.Instance;
+ uint sabsFixed = instance.SabsFixed;
+ instance.SabsFixed = sabsFixed + 1U;
+ }
+ }
+
+ public override void AppendTaskText(StringBuilder sb)
+ {
+ this.even = !this.even;
+ Color color = this.even ? Color.yellow : Color.red;
+ sb.Append(color.ToTextColor());
+ sb.Append(DestroyableSingleton<TranslationController>.Instance.GetString(TaskTypes.FixLights));
+ sb.AppendLine(" (%" + (int)(this.system.Level * 100f) + ")[]");
+ for (int i = 0; i < this.Arrows.Length; i++)
+ {
+ this.Arrows[i].image.color = color;
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/EmergencyMinigame.cs b/Client/Assembly-CSharp/EmergencyMinigame.cs
new file mode 100644
index 0000000..5b6bff6
--- /dev/null
+++ b/Client/Assembly-CSharp/EmergencyMinigame.cs
@@ -0,0 +1,120 @@
+using System;
+using System.Collections;
+using System.Linq;
+using UnityEngine;
+
+public class EmergencyMinigame : Minigame
+{
+ public SpriteRenderer ClosedLid;
+
+ public SpriteRenderer OpenLid;
+
+ public Transform meetingButton;
+
+ public TextRenderer StatusText;
+
+ public TextRenderer NumberText;
+
+ public bool ButtonActive = true;
+
+ public AudioClip ButtonSound;
+
+ private int state;
+
+ public const int MinEmergencyTime = 15;
+
+ public override void Begin(PlayerTask task)
+ {
+ base.Begin(task);
+ this.Update();
+ }
+
+ public void Update()
+ {
+ if (ShipStatus.Instance.Timer < 15f || ShipStatus.Instance.EmergencyCooldown > 0f)
+ {
+ int num = Mathf.CeilToInt(15f - ShipStatus.Instance.Timer);
+ num = Mathf.Max(Mathf.CeilToInt(ShipStatus.Instance.EmergencyCooldown), num);
+ this.ButtonActive = false;
+ this.StatusText.Text = DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.EmergencyNotReady, Array.Empty<object>());
+ this.NumberText.Text = num + "s";
+ this.ClosedLid.gameObject.SetActive(true);
+ this.OpenLid.gameObject.SetActive(false);
+ return;
+ }
+ if (!PlayerControl.LocalPlayer.myTasks.Any(new Func<PlayerTask, bool>(PlayerTask.TaskIsEmergency)))
+ {
+ if (this.state == 1)
+ {
+ return;
+ }
+ this.state = 1;
+ int remainingEmergencies = PlayerControl.LocalPlayer.RemainingEmergencies;
+ this.StatusText.Text = DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.EmergencyCount, new object[]
+ {
+ PlayerControl.LocalPlayer.Data.PlayerName
+ });
+ this.NumberText.Text = remainingEmergencies.ToString();
+ this.ButtonActive = (remainingEmergencies > 0);
+ this.ClosedLid.gameObject.SetActive(!this.ButtonActive);
+ this.OpenLid.gameObject.SetActive(this.ButtonActive);
+ return;
+ }
+ else
+ {
+ if (this.state == 2)
+ {
+ return;
+ }
+ this.state = 2;
+ this.ButtonActive = false;
+ this.StatusText.Text = DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.EmergencyDuringCrisis, Array.Empty<object>());
+ this.NumberText.Text = string.Empty;
+ this.ClosedLid.gameObject.SetActive(true);
+ this.OpenLid.gameObject.SetActive(false);
+ return;
+ }
+ }
+
+ public void CallMeeting()
+ {
+ if (!PlayerControl.LocalPlayer.myTasks.Any(new Func<PlayerTask, bool>(PlayerTask.TaskIsEmergency)) && PlayerControl.LocalPlayer.RemainingEmergencies > 0 && this.ButtonActive)
+ {
+ this.StatusText.Text = DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.EmergencyRequested, Array.Empty<object>());
+ if (Constants.ShouldPlaySfx())
+ {
+ SoundManager.Instance.PlaySound(this.ButtonSound, false, 1f);
+ }
+ PlayerControl.LocalPlayer.CmdReportDeadBody(null);
+ this.ButtonActive = false;
+ }
+ }
+
+ private float easeOutElastic(float t)
+ {
+ float num = 0.3f;
+ return Mathf.Pow(2f, -10f * t) * Mathf.Sin((t - num / 4f) * 6.2831855f / num) + 1f;
+ }
+
+ protected override IEnumerator CoAnimateOpen()
+ {
+ for (float timer = 0f; timer < 0.2f; timer += Time.deltaTime)
+ {
+ float t = timer / 0.2f;
+ base.transform.localPosition = new Vector3(0f, Mathf.SmoothStep(-8f, 0f, t), -50f);
+ yield return null;
+ }
+ base.transform.localPosition = new Vector3(0f, 0f, -50f);
+ Vector3 meetingPos = this.meetingButton.localPosition;
+ for (float timer = 0f; timer < 0.1f; timer += Time.deltaTime)
+ {
+ float num = timer / 0.1f;
+ meetingPos.y = Mathf.Sin(3.1415927f * num) * 1f / (num * 5f + 4f) - 0.882f;
+ this.meetingButton.localPosition = meetingPos;
+ yield return null;
+ }
+ meetingPos.y = -0.882f;
+ this.meetingButton.localPosition = meetingPos;
+ yield break;
+ }
+}
diff --git a/Client/Assembly-CSharp/EmptyGarbageMinigame.cs b/Client/Assembly-CSharp/EmptyGarbageMinigame.cs
new file mode 100644
index 0000000..679d683
--- /dev/null
+++ b/Client/Assembly-CSharp/EmptyGarbageMinigame.cs
@@ -0,0 +1,195 @@
+using System;
+using System.Collections;
+using System.Linq;
+using UnityEngine;
+
+public class EmptyGarbageMinigame : Minigame
+{
+ private const float GrinderVolume = 0.8f;
+
+ public FloatRange HandleRange = new FloatRange(-0.65f, 0.65f);
+
+ public Vector2Range SpawnRange;
+
+ public Collider2D Blocker;
+
+ public AreaEffector2D Popper;
+
+ public Collider2D Handle;
+
+ public SpriteRenderer Bars;
+
+ private Controller controller = new Controller();
+
+ private bool finished;
+
+ public int NumObjects = 15;
+
+ private SpriteRenderer[] Objects;
+
+ public SpriteRenderer[] GarbagePrefabs;
+
+ public SpriteRenderer[] LeafPrefabs;
+
+ public SpriteRenderer[] SpecialObjectPrefabs;
+
+ public AudioClip LeverDown;
+
+ public AudioClip LeverUp;
+
+ public AudioClip GrinderStart;
+
+ public AudioClip GrinderLoop;
+
+ public AudioClip GrinderEnd;
+
+ public override void Begin(PlayerTask task)
+ {
+ base.Begin(task);
+ int i = 0;
+ this.Objects = new SpriteRenderer[this.NumObjects];
+ RandomFill<SpriteRenderer> randomFill = new RandomFill<SpriteRenderer>();
+ NormalPlayerTask myNormTask = this.MyNormTask;
+ if (myNormTask != null && myNormTask.taskStep == 0)
+ {
+ if (this.MyNormTask.StartAt == SystemTypes.Cafeteria)
+ {
+ randomFill.Set(this.GarbagePrefabs);
+ }
+ else
+ {
+ randomFill.Set(this.LeafPrefabs);
+ }
+ }
+ else
+ {
+ randomFill.Set(this.GarbagePrefabs.Union(this.LeafPrefabs));
+ while (i < this.SpecialObjectPrefabs.Length)
+ {
+ SpriteRenderer spriteRenderer = this.Objects[i] = UnityEngine.Object.Instantiate<SpriteRenderer>(this.SpecialObjectPrefabs[i]);
+ spriteRenderer.transform.SetParent(base.transform);
+ spriteRenderer.transform.localPosition = this.SpawnRange.Next();
+ i++;
+ }
+ }
+ while (i < this.Objects.Length)
+ {
+ SpriteRenderer spriteRenderer2 = this.Objects[i] = UnityEngine.Object.Instantiate<SpriteRenderer>(randomFill.Get());
+ spriteRenderer2.transform.SetParent(base.transform);
+ Vector3 vector = this.SpawnRange.Next();
+ vector.z = FloatRange.Next(-0.5f, 0.5f);
+ spriteRenderer2.transform.localPosition = vector;
+ spriteRenderer2.GetComponent<SpriteRenderer>().color = Color.Lerp(Color.white, Color.black, (vector.z + 0.5f) * 0.7f);
+ i++;
+ }
+ }
+
+ public void Update()
+ {
+ if (this.amClosing != Minigame.CloseState.None)
+ {
+ return;
+ }
+ this.controller.Update();
+ Vector3 localPosition = this.Handle.transform.localPosition;
+ float num = this.HandleRange.ReverseLerp(localPosition.y);
+ switch (this.controller.CheckDrag(this.Handle, false))
+ {
+ case DragState.NoTouch:
+ localPosition.y = Mathf.Lerp(localPosition.y, this.HandleRange.max, num + Time.deltaTime * 15f);
+ break;
+ case DragState.Dragging:
+ if (!this.finished)
+ {
+ if (num > 0.5f)
+ {
+ Vector2 vector = this.controller.DragPosition - base.transform.position;
+ float num2 = this.HandleRange.ReverseLerp(this.HandleRange.Clamp(vector.y));
+ localPosition.y = this.HandleRange.Lerp(num2 / 2f + 0.5f);
+ }
+ else
+ {
+ localPosition.y = Mathf.Lerp(localPosition.y, this.HandleRange.min, num + Time.deltaTime * 15f);
+ if (this.Blocker.enabled)
+ {
+ if (Constants.ShouldPlaySfx())
+ {
+ SoundManager.Instance.PlaySound(this.LeverDown, false, 1f);
+ SoundManager.Instance.PlaySound(this.GrinderStart, false, 0.8f);
+ SoundManager.Instance.StopSound(this.GrinderEnd);
+ SoundManager.Instance.StopSound(this.GrinderLoop);
+ }
+ this.Blocker.enabled = false;
+ base.StopAllCoroutines();
+ base.StartCoroutine(this.PopObjects());
+ base.StartCoroutine(this.AnimateObjects());
+ }
+ }
+ }
+ break;
+ case DragState.Released:
+ if (!this.Blocker.enabled)
+ {
+ this.Blocker.enabled = true;
+ if (Constants.ShouldPlaySfx())
+ {
+ SoundManager.Instance.PlaySound(this.LeverUp, false, 1f);
+ SoundManager.Instance.StopSound(this.GrinderStart);
+ SoundManager.Instance.StopSound(this.GrinderLoop);
+ SoundManager.Instance.PlaySound(this.GrinderEnd, false, 0.8f);
+ }
+ }
+ if (!this.finished)
+ {
+ if (this.Objects.All((SpriteRenderer o) => !o))
+ {
+ this.finished = true;
+ this.MyNormTask.NextStep();
+ base.StartCoroutine(base.CoStartClose(0.75f));
+ }
+ }
+ break;
+ }
+ if (Constants.ShouldPlaySfx() && !this.Blocker.enabled && !SoundManager.Instance.SoundIsPlaying(this.GrinderStart))
+ {
+ SoundManager.Instance.PlaySound(this.GrinderLoop, true, 0.8f);
+ }
+ this.Handle.transform.localPosition = localPosition;
+ Vector3 localScale = this.Bars.transform.localScale;
+ localScale.y = this.HandleRange.ChangeRange(localPosition.y, -1f, 1f);
+ this.Bars.transform.localScale = localScale;
+ }
+
+ private IEnumerator PopObjects()
+ {
+ this.Popper.enabled = true;
+ yield return new WaitForSeconds(0.05f);
+ this.Popper.enabled = false;
+ yield break;
+ }
+
+ private IEnumerator AnimateObjects()
+ {
+ Vector3 pos = base.transform.localPosition;
+ for (float t = 3f; t > 0f; t -= Time.deltaTime)
+ {
+ float d = t / 3f;
+ base.transform.localPosition = pos + Vector2Range.NextEdge() * d * 0.1f;
+ yield return null;
+ }
+ yield break;
+ }
+
+ public override void Close()
+ {
+ SoundManager.Instance.StopSound(this.GrinderStart);
+ SoundManager.Instance.StopSound(this.GrinderLoop);
+ SoundManager.Instance.StopSound(this.GrinderEnd);
+ if (this.MyNormTask && this.MyNormTask.IsComplete)
+ {
+ ShipStatus.Instance.OpenHatch();
+ PlayerControl.LocalPlayer.RpcPlayAnimation((byte)this.MyTask.TaskType);
+ }
+ base.Close();
+ }
+}
diff --git a/Client/Assembly-CSharp/EndGameManager.cs b/Client/Assembly-CSharp/EndGameManager.cs
new file mode 100644
index 0000000..7380913
--- /dev/null
+++ b/Client/Assembly-CSharp/EndGameManager.cs
@@ -0,0 +1,236 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using Assets.CoreScripts;
+using InnerNet;
+using UnityEngine;
+
+public class EndGameManager : DestroyableSingleton<EndGameManager>
+{
+ public TextRenderer WinText;
+
+ public MeshRenderer BackgroundBar;
+
+ public MeshRenderer Foreground;
+
+ public FloatRange ForegroundRadius;
+
+ public SpriteRenderer FrontMost;
+
+ public PoolablePlayer PlayerPrefab;
+
+ public Sprite GhostSprite;
+
+ public SpriteRenderer PlayAgainButton;
+
+ public SpriteRenderer ExitButton;
+
+ public AudioClip DisconnectStinger;
+
+ public AudioClip CrewStinger;
+
+ public AudioClip ImpostorStinger;
+
+ public float BaseY = -0.25f;
+
+ private float stingerTime;
+
+ public void Start()
+ {
+ if (TempData.showAd && !SaveManager.BoughtNoAds)
+ {
+ AdPlayer.RequestInterstitial();
+ }
+ this.SetEverythingUp();
+ base.StartCoroutine(this.CoBegin());
+ base.Invoke("ShowButtons", 1.1f);
+ }
+
+ private void ShowButtons()
+ {
+ this.FrontMost.gameObject.SetActive(false);
+ this.PlayAgainButton.gameObject.SetActive(true);
+ this.ExitButton.gameObject.SetActive(true);
+ }
+
+ private void SetEverythingUp()
+ {
+ StatsManager instance = StatsManager.Instance;
+ uint gamesFinished = instance.GamesFinished;
+ instance.GamesFinished = gamesFinished + 1U;
+ bool flag = TempData.DidHumansWin(TempData.EndReason);
+ if (TempData.EndReason == GameOverReason.ImpostorDisconnect)
+ {
+ StatsManager.Instance.AddDrawReason(TempData.EndReason);
+ this.WinText.Text = DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.ImpostorDisconnected, Array.Empty<object>());
+ SoundManager.Instance.PlaySound(this.DisconnectStinger, false, 1f);
+ }
+ else if (TempData.EndReason == GameOverReason.HumansDisconnect)
+ {
+ StatsManager.Instance.AddDrawReason(TempData.EndReason);
+ this.WinText.Text = DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.CrewmatesDisconnected, Array.Empty<object>());
+ SoundManager.Instance.PlaySound(this.DisconnectStinger, false, 1f);
+ }
+ else
+ {
+ if (TempData.winners.Any((WinningPlayerData h) => h.IsYou))
+ {
+ StatsManager.Instance.AddWinReason(TempData.EndReason);
+ this.WinText.Text = DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.Victory, Array.Empty<object>());
+ this.BackgroundBar.material.SetColor("_Color", Palette.CrewmateBlue);
+ WinningPlayerData winningPlayerData = TempData.winners.FirstOrDefault((WinningPlayerData h) => h.IsYou);
+ if (winningPlayerData != null)
+ {
+ DestroyableSingleton<Telemetry>.Instance.WonGame(winningPlayerData.ColorId, winningPlayerData.HatId);
+ }
+ }
+ else
+ {
+ StatsManager.Instance.AddLoseReason(TempData.EndReason);
+ this.WinText.Text = DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.Defeat, Array.Empty<object>());
+ this.WinText.Color = Color.red;
+ }
+ if (flag)
+ {
+ SoundManager.Instance.PlayDynamicSound("Stinger", this.CrewStinger, false, new DynamicSound.GetDynamicsFunction(this.GetStingerVol), false);
+ }
+ else
+ {
+ SoundManager.Instance.PlayDynamicSound("Stinger", this.ImpostorStinger, false, new DynamicSound.GetDynamicsFunction(this.GetStingerVol), false);
+ }
+ }
+ List<WinningPlayerData> list = TempData.winners.OrderBy(delegate(WinningPlayerData b)
+ {
+ if (!b.IsYou)
+ {
+ return 0;
+ }
+ return -1;
+ }).ToList<WinningPlayerData>();
+ for (int i = 0; i < list.Count; i++)
+ {
+ WinningPlayerData winningPlayerData2 = list[i];
+ int num = (i % 2 == 0) ? -1 : 1;
+ int num2 = (i + 1) / 2;
+ float num3 = 1f - (float)num2 * 0.075f;
+ float num4 = 1f - (float)num2 * 0.035f;
+ float num5 = (float)((i == 0) ? -8 : -1);
+ PoolablePlayer poolablePlayer = UnityEngine.Object.Instantiate<PoolablePlayer>(this.PlayerPrefab, base.transform);
+ poolablePlayer.transform.localPosition = new Vector3(0.8f * (float)num * (float)num2 * num4, this.BaseY - 0.25f + (float)num2 * 0.1f, num5 + (float)num2 * 0.01f) * 1.25f;
+ Vector3 vector = new Vector3(num3, num3, num3) * 1.25f;
+ poolablePlayer.transform.localScale = vector;
+ if (winningPlayerData2.IsDead)
+ {
+ poolablePlayer.Body.sprite = this.GhostSprite;
+ poolablePlayer.SetDeadFlipX(i % 2 != 0);
+ }
+ else
+ {
+ poolablePlayer.SetFlipX(i % 2 == 0);
+ }
+ if (!winningPlayerData2.IsDead)
+ {
+ DestroyableSingleton<HatManager>.Instance.SetSkin(poolablePlayer.SkinSlot, winningPlayerData2.SkinId);
+ }
+ else
+ {
+ poolablePlayer.HatSlot.color = new Color(1f, 1f, 1f, 0.5f);
+ }
+ PlayerControl.SetPlayerMaterialColors(winningPlayerData2.ColorId, poolablePlayer.Body);
+ PlayerControl.SetHatImage(winningPlayerData2.HatId, poolablePlayer.HatSlot);
+ PlayerControl.SetPetImage(winningPlayerData2.PetId, winningPlayerData2.ColorId, poolablePlayer.PetSlot);
+ if (flag)
+ {
+ poolablePlayer.NameText.gameObject.SetActive(false);
+ }
+ else
+ {
+ poolablePlayer.NameText.Text = winningPlayerData2.Name;
+ if (winningPlayerData2.IsImpostor)
+ {
+ poolablePlayer.NameText.Color = Palette.ImpostorRed;
+ }
+ poolablePlayer.NameText.transform.localScale = vector.Inv();
+ }
+ }
+ }
+
+ private void GetStingerVol(AudioSource source, float dt)
+ {
+ this.stingerTime += dt * 0.75f;
+ source.volume = Mathf.Clamp(1f / this.stingerTime, 0f, 1f);
+ }
+
+ public IEnumerator CoBegin()
+ {
+ Color c = this.WinText.Color;
+ Color fade = Color.black;
+ Color white = Color.white;
+ Vector3 titlePos = this.WinText.transform.localPosition;
+ float timer = 0f;
+ while (timer < 3f)
+ {
+ timer += Time.deltaTime;
+ float num = Mathf.Min(1f, timer / 3f);
+ this.Foreground.material.SetFloat("_Rad", this.ForegroundRadius.ExpOutLerp(num * 2f));
+ fade.a = Mathf.Lerp(1f, 0f, num * 3f);
+ this.FrontMost.color = fade;
+ c.a = Mathf.Clamp(FloatRange.ExpOutLerp(num, 0f, 1f), 0f, 1f);
+ this.WinText.Color = c;
+ titlePos.y = 2.7f - num * 0.3f;
+ this.WinText.transform.localPosition = titlePos;
+ yield return null;
+ }
+ this.FrontMost.gameObject.SetActive(false);
+ yield break;
+ }
+
+ public void NextGame()
+ {
+ this.PlayAgainButton.gameObject.SetActive(false);
+ this.ExitButton.gameObject.SetActive(false);
+ if (TempData.showAd && !SaveManager.BoughtNoAds)
+ {
+ TempData.showAd = false;
+ AdPlayer.ShowInterstitial(this, true);
+ return;
+ }
+ base.StartCoroutine(this.CoJoinGame());
+ }
+
+ public IEnumerator CoJoinGame()
+ {
+ AmongUsClient.Instance.JoinGame();
+ yield return EndGameManager.WaitWithTimeout(() => AmongUsClient.Instance.ClientId >= 0);
+ if (AmongUsClient.Instance.ClientId < 0)
+ {
+ AmongUsClient.Instance.ExitGame(AmongUsClient.Instance.LastDisconnectReason);
+ }
+ yield break;
+ }
+
+ public void Exit()
+ {
+ this.PlayAgainButton.gameObject.SetActive(false);
+ this.ExitButton.gameObject.SetActive(false);
+ if (TempData.showAd && !SaveManager.BoughtNoAds)
+ {
+ TempData.showAd = false;
+ AdPlayer.ShowInterstitial(this, false);
+ return;
+ }
+ AmongUsClient.Instance.ExitGame(DisconnectReasons.ExitGame);
+ }
+
+ public static IEnumerator WaitWithTimeout(Func<bool> success)
+ {
+ float timer = 0f;
+ while (timer < 5f && !success())
+ {
+ yield return null;
+ timer += Time.deltaTime;
+ }
+ yield break;
+ }
+}
diff --git a/Client/Assembly-CSharp/EngineBehaviour.cs b/Client/Assembly-CSharp/EngineBehaviour.cs
new file mode 100644
index 0000000..87bdccf
--- /dev/null
+++ b/Client/Assembly-CSharp/EngineBehaviour.cs
@@ -0,0 +1,48 @@
+using System;
+using UnityEngine;
+
+public class EngineBehaviour : MonoBehaviour
+{
+ public AudioClip ElectricSound;
+
+ public AudioClip SteamSound;
+
+ public float SoundDistance = 5f;
+
+ public void PlayElectricSound()
+ {
+ if (Constants.ShouldPlaySfx())
+ {
+ SoundManager.Instance.PlayDynamicSound("EngineShock" + base.name, this.ElectricSound, false, new DynamicSound.GetDynamicsFunction(this.GetSoundDistance), false);
+ }
+ }
+
+ public void PlaySteamSound()
+ {
+ if (Constants.ShouldPlaySfx())
+ {
+ float pitch = FloatRange.Next(0.7f, 1.1f);
+ SoundManager.Instance.PlayDynamicSound("EngineSteam" + base.name, this.SteamSound, false, delegate(AudioSource p, float d)
+ {
+ this.GetSoundDistance(p, d, pitch);
+ }, false);
+ }
+ }
+
+ private void GetSoundDistance(AudioSource player, float dt)
+ {
+ this.GetSoundDistance(player, dt, 1f);
+ }
+
+ private void GetSoundDistance(AudioSource player, float dt, float pitch)
+ {
+ float num = 1f;
+ if (PlayerControl.LocalPlayer)
+ {
+ float num2 = Vector2.Distance(base.transform.position, PlayerControl.LocalPlayer.GetTruePosition());
+ num = 1f - num2 / this.SoundDistance;
+ }
+ player.volume = num * 0.8f;
+ player.pitch = pitch;
+ }
+}
diff --git a/Client/Assembly-CSharp/EnterCodeMinigame.cs b/Client/Assembly-CSharp/EnterCodeMinigame.cs
new file mode 100644
index 0000000..4bbc0ef
--- /dev/null
+++ b/Client/Assembly-CSharp/EnterCodeMinigame.cs
@@ -0,0 +1,121 @@
+using System;
+using System.Collections;
+using UnityEngine;
+
+public class EnterCodeMinigame : Minigame
+{
+ public TextRenderer NumberText;
+
+ public TextRenderer TargetText;
+
+ public int number;
+
+ public string numString = string.Empty;
+
+ private bool animating;
+
+ public SpriteRenderer AcceptButton;
+
+ private bool done;
+
+ private int targetNumber;
+
+ public void EnterDigit(int i)
+ {
+ if (this.animating)
+ {
+ return;
+ }
+ if (this.done)
+ {
+ return;
+ }
+ if (this.NumberText.Text.Length >= 5)
+ {
+ base.StartCoroutine(this.BlinkAccept());
+ return;
+ }
+ this.numString += i;
+ this.number = this.number * 10 + i;
+ this.NumberText.Text = this.numString;
+ }
+
+ public void ClearDigits()
+ {
+ if (this.animating)
+ {
+ return;
+ }
+ this.number = 0;
+ this.numString = string.Empty;
+ this.NumberText.Text = string.Empty;
+ }
+
+ public void AcceptDigits()
+ {
+ if (this.animating)
+ {
+ return;
+ }
+ base.StartCoroutine(this.Animate());
+ }
+
+ public override void Begin(PlayerTask task)
+ {
+ base.Begin(task);
+ this.targetNumber = BitConverter.ToInt32(this.MyNormTask.Data, 0);
+ this.NumberText.Text = string.Empty;
+ this.TargetText.Text = this.targetNumber.ToString("D5");
+ }
+
+ private IEnumerator BlinkAccept()
+ {
+ int num;
+ for (int i = 0; i < 5; i = num)
+ {
+ this.AcceptButton.color = Color.gray;
+ yield return null;
+ yield return null;
+ this.AcceptButton.color = Color.white;
+ yield return null;
+ yield return null;
+ num = i + 1;
+ }
+ yield break;
+ }
+
+ private IEnumerator Animate()
+ {
+ this.animating = true;
+ WaitForSeconds wait = new WaitForSeconds(0.1f);
+ yield return wait;
+ this.NumberText.Text = string.Empty;
+ yield return wait;
+ if (this.targetNumber == this.number)
+ {
+ this.done = true;
+ this.NumberText.Text = "OK";
+ yield return wait;
+ this.NumberText.Text = string.Empty;
+ yield return wait;
+ this.NumberText.Text = "OK";
+ yield return wait;
+ this.NumberText.Text = string.Empty;
+ yield return base.CoStartClose(0.5f);
+ }
+ else
+ {
+ this.NumberText.Text = "Bad";
+ yield return wait;
+ this.NumberText.Text = string.Empty;
+ yield return wait;
+ this.NumberText.Text = "Bad";
+ yield return wait;
+ this.numString = string.Empty;
+ this.number = 0;
+ this.NumberText.Text = this.numString;
+ }
+ this.animating = false;
+ yield break;
+ }
+}
diff --git a/Client/Assembly-CSharp/ExileController.cs b/Client/Assembly-CSharp/ExileController.cs
new file mode 100644
index 0000000..4a52a4d
--- /dev/null
+++ b/Client/Assembly-CSharp/ExileController.cs
@@ -0,0 +1,160 @@
+using System;
+using System.Collections;
+using System.Linq;
+using UnityEngine;
+
+public class ExileController : MonoBehaviour
+{
+ public static ExileController Instance;
+
+ public TextRenderer ImpostorText;
+
+ public TextRenderer Text;
+
+ public SpriteRenderer Player;
+
+ public SpriteRenderer PlayerHat;
+
+ public SpriteRenderer PlayerSkin;
+
+ public AnimationCurve LerpCurve;
+
+ public float Duration = 7f;
+
+ public AudioClip TextSound;
+
+ private string completeString = string.Empty;
+
+ private GameData.PlayerInfo exiled;
+
+ public void Begin(GameData.PlayerInfo exiled, bool tie)
+ {
+ ExileController.Instance = this;
+ this.exiled = exiled;
+ this.Text.gameObject.SetActive(false);
+ this.Text.Text = string.Empty;
+ int num = GameData.Instance.AllPlayers.Count((GameData.PlayerInfo p) => p.IsImpostor && !p.IsDead && !p.Disconnected);
+ if (exiled != null)
+ {
+ int num2 = GameData.Instance.AllPlayers.Count((GameData.PlayerInfo p) => p.IsImpostor);
+ if (exiled.IsImpostor)
+ {
+ if (num2 > 1)
+ {
+ this.completeString = DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.ExileTextPP, new object[]
+ {
+ exiled.PlayerName
+ });
+ }
+ else
+ {
+ this.completeString = DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.ExileTextSP, new object[]
+ {
+ exiled.PlayerName
+ });
+ }
+ }
+ else if (num2 > 1)
+ {
+ this.completeString = DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.ExileTextPN, new object[]
+ {
+ exiled.PlayerName
+ });
+ }
+ else
+ {
+ this.completeString = DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.ExileTextSN, new object[]
+ {
+ exiled.PlayerName
+ });
+ }
+ PlayerControl.SetPlayerMaterialColors((int)exiled.ColorId, this.Player);
+ PlayerControl.SetHatImage(exiled.HatId, this.PlayerHat);
+ this.PlayerSkin.sprite = DestroyableSingleton<HatManager>.Instance.GetSkinById(exiled.SkinId).EjectFrame;
+ if (exiled.IsImpostor)
+ {
+ num--;
+ }
+ }
+ else
+ {
+ if (tie)
+ {
+ this.completeString = DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.NoExileTie, Array.Empty<object>());
+ }
+ else
+ {
+ this.completeString = DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.NoExileSkip, Array.Empty<object>());
+ }
+ this.Player.gameObject.SetActive(false);
+ }
+ if (num == 1)
+ {
+ this.ImpostorText.Text = DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.ImpostorsRemainS, new object[]
+ {
+ num
+ });
+ }
+ else
+ {
+ this.ImpostorText.Text = DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.ImpostorsRemainP, new object[]
+ {
+ num
+ });
+ }
+ base.StartCoroutine(this.Animate());
+ }
+
+ private IEnumerator Animate()
+ {
+ yield return DestroyableSingleton<HudManager>.Instance.CoFadeFullScreen(Color.black, Color.clear, 0.2f);
+ yield return new WaitForSeconds(1f);
+ float d = Camera.main.orthographicSize * Camera.main.aspect + 1f;
+ Vector2 left = Vector2.left * d;
+ Vector2 right = Vector2.right * d;
+ for (float t = 0f; t <= this.Duration; t += Time.deltaTime)
+ {
+ float num = t / this.Duration;
+ this.Player.transform.localPosition = Vector2.Lerp(left, right, this.LerpCurve.Evaluate(num));
+ float z = (t + 0.75f) * 25f / Mathf.Exp(t * 0.75f + 1f);
+ this.Player.transform.Rotate(new Vector3(0f, 0f, z));
+ if (num >= 0.3f)
+ {
+ int num2 = (int)(Mathf.Min(1f, (num - 0.3f) / 0.3f) * (float)this.completeString.Length);
+ if (num2 > this.Text.Text.Length)
+ {
+ this.Text.Text = this.completeString.Substring(0, num2);
+ this.Text.gameObject.SetActive(true);
+ if (this.completeString[num2 - 1] != ' ')
+ {
+ SoundManager.Instance.PlaySoundImmediate(this.TextSound, false, 0.8f, 1f);
+ }
+ }
+ }
+ yield return null;
+ }
+ this.Text.Text = this.completeString;
+ this.ImpostorText.gameObject.SetActive(true);
+ yield return Effects.Bloop(0f, this.ImpostorText.transform, 0.5f);
+ yield return new WaitForSeconds(0.5f);
+ yield return DestroyableSingleton<HudManager>.Instance.CoFadeFullScreen(Color.clear, Color.black, 0.2f);
+ if (this.exiled != null)
+ {
+ PlayerControl @object = this.exiled.Object;
+ if (@object != null)
+ {
+ @object.Exiled();
+ }
+ }
+ if (DestroyableSingleton<TutorialManager>.InstanceExists || !ShipStatus.Instance.IsGameOverDueToDeath())
+ {
+ DestroyableSingleton<HudManager>.Instance.StartCoroutine(DestroyableSingleton<HudManager>.Instance.CoFadeFullScreen(Color.black, Color.clear, 0.2f));
+ PlayerControl.LocalPlayer.SetKillTimer(PlayerControl.GameOptions.KillCooldown);
+ ShipStatus.Instance.EmergencyCooldown = (float)PlayerControl.GameOptions.EmergencyCooldown;
+ Camera.main.GetComponent<FollowerCamera>().Locked = false;
+ DestroyableSingleton<HudManager>.Instance.SetHudActive(true);
+ }
+ UnityEngine.Object.Destroy(base.gameObject);
+ yield break;
+ }
+}
diff --git a/Client/Assembly-CSharp/ExitGameButton.cs b/Client/Assembly-CSharp/ExitGameButton.cs
new file mode 100644
index 0000000..6fbbc35
--- /dev/null
+++ b/Client/Assembly-CSharp/ExitGameButton.cs
@@ -0,0 +1,24 @@
+using System;
+using InnerNet;
+using UnityEngine;
+
+public class ExitGameButton : MonoBehaviour
+{
+ public void Start()
+ {
+ if (!DestroyableSingleton<HudManager>.InstanceExists)
+ {
+ base.gameObject.SetActive(false);
+ }
+ }
+
+ public void OnClick()
+ {
+ if (AmongUsClient.Instance)
+ {
+ AmongUsClient.Instance.ExitGame(DisconnectReasons.ExitGame);
+ return;
+ }
+ SceneChanger.ChangeScene("MainMenu");
+ }
+}
diff --git a/Client/Assembly-CSharp/Extensions.cs b/Client/Assembly-CSharp/Extensions.cs
new file mode 100644
index 0000000..a653cea
--- /dev/null
+++ b/Client/Assembly-CSharp/Extensions.cs
@@ -0,0 +1,409 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using UnityEngine;
+
+public static class Extensions
+{
+ private static string[] ByteHex = (from x in Enumerable.Range(0, 256)
+ select x.ToString("X2")).ToArray<string>();
+
+ public static void TrimEnd(this StringBuilder self)
+ {
+ for (int i = self.Length - 1; i >= 0; i--)
+ {
+ char c = self[i];
+ if (c != ' ' && c != '\t' && c != '\n' && c != '\r')
+ {
+ break;
+ }
+ int length = self.Length;
+ self.Length = length - 1;
+ }
+ }
+
+ public static void AddUnique<T>(this IList<T> self, T item)
+ {
+ if (!self.Contains(item))
+ {
+ self.Add(item);
+ }
+ }
+
+ public static string ToTextColor(this Color c)
+ {
+ return string.Concat(new string[]
+ {
+ "[",
+ Extensions.ByteHex[(int)((byte)(c.r * 255f))],
+ Extensions.ByteHex[(int)((byte)(c.g * 255f))],
+ Extensions.ByteHex[(int)((byte)(c.b * 255f))],
+ Extensions.ByteHex[(int)((byte)(c.a * 255f))],
+ "]"
+ });
+ }
+
+ public static int ToInteger(this Color c, bool alpha)
+ {
+ if (alpha)
+ {
+ return (int)((byte)(c.r * 256f)) << 24 | (int)((byte)(c.g * 256f)) << 16 | (int)((byte)(c.b * 256f)) << 8 | (int)((byte)(c.a * 256f));
+ }
+ return (int)((byte)(c.r * 256f)) << 16 | (int)((byte)(c.g * 256f)) << 8 | (int)((byte)(c.b * 256f));
+ }
+
+ public static bool HasAnyBit(this int self, int bit)
+ {
+ return (self & bit) != 0;
+ }
+
+ public static bool HasAnyBit(this byte self, byte bit)
+ {
+ return (self & bit) > 0;
+ }
+
+ public static bool HasBit(this byte self, byte bit)
+ {
+ return (self & bit) == bit;
+ }
+
+ public static int BitCount(this byte self)
+ {
+ int num = 0;
+ for (int i = 0; i < 8; i++)
+ {
+ if ((1 << i & (int)self) != 0)
+ {
+ num++;
+ }
+ }
+ return num;
+ }
+
+ public static int IndexOf<T>(this T[] self, T item) where T : class
+ {
+ for (int i = 0; i < self.Length; i++)
+ {
+ if (self[i] == item)
+ {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public static int IndexOfMin<T>(this T[] self, Func<T, float> comparer)
+ {
+ float num = float.MaxValue;
+ int result = -1;
+ for (int i = 0; i < self.Length; i++)
+ {
+ float num2 = comparer(self[i]);
+ if (num2 <= num)
+ {
+ result = i;
+ num = num2;
+ }
+ }
+ return result;
+ }
+
+ public static int IndexOfMax<T>(this T[] self, Func<T, int> comparer, out bool tie)
+ {
+ tie = false;
+ int num = int.MinValue;
+ int result = -1;
+ for (int i = 0; i < self.Length; i++)
+ {
+ int num2 = comparer(self[i]);
+ if (num2 > num)
+ {
+ result = i;
+ num = num2;
+ tie = false;
+ }
+ else if (num2 == num)
+ {
+ tie = true;
+ result = -1;
+ }
+ }
+ return result;
+ }
+
+ public static void SetAll<T>(this IList<T> self, T value)
+ {
+ for (int i = 0; i < self.Count; i++)
+ {
+ self[i] = value;
+ }
+ }
+
+ public static void AddAll<T>(this List<T> self, IList<T> other)
+ {
+ int num = self.Count + other.Count;
+ if (self.Capacity < num)
+ {
+ self.Capacity = num;
+ }
+ for (int i = 0; i < other.Count; i++)
+ {
+ self.Add(other[i]);
+ }
+ }
+
+ public static void RemoveDupes<T>(this IList<T> self) where T : class
+ {
+ for (int i = 0; i < self.Count; i++)
+ {
+ T t = self[i];
+ for (int j = self.Count - 1; j > i; j--)
+ {
+ if (self[j] == t)
+ {
+ self.RemoveAt(j);
+ }
+ }
+ }
+ }
+
+ public static void Shuffle<T>(this IList<T> self)
+ {
+ for (int i = 0; i < self.Count - 1; i++)
+ {
+ T value = self[i];
+ int index = UnityEngine.Random.Range(i, self.Count);
+ self[i] = self[index];
+ self[index] = value;
+ }
+ }
+
+ public static void Shuffle<T>(this System.Random r, IList<T> self)
+ {
+ for (int i = 0; i < self.Count; i++)
+ {
+ T value = self[i];
+ int index = r.Next(self.Count);
+ self[i] = self[index];
+ self[index] = value;
+ }
+ }
+
+ public static T[] RandomSet<T>(this IList<T> self, int length)
+ {
+ T[] array = new T[length];
+ self.RandomFill(array);
+ return array;
+ }
+
+ public static void RandomFill<T>(this IList<T> self, T[] target)
+ {
+ HashSet<int> hashSet = new HashSet<int>();
+ for (int i = 0; i < target.Length; i++)
+ {
+ int num;
+ do
+ {
+ num = self.RandomIdx<T>();
+ }
+ while (hashSet.Contains(num));
+ target[i] = self[num];
+ hashSet.Add(num);
+ if (hashSet.Count == self.Count)
+ {
+ return;
+ }
+ }
+ }
+
+ public static int RandomIdx<T>(this IList<T> self)
+ {
+ return UnityEngine.Random.Range(0, self.Count);
+ }
+
+ public static int RandomIdx<T>(this IEnumerable<T> self)
+ {
+ return UnityEngine.Random.Range(0, self.Count<T>());
+ }
+
+ public static T Random<T>(this IEnumerable<T> self)
+ {
+ return self.ToArray<T>().Random<T>();
+ }
+
+ public static T Random<T>(this IList<T> self)
+ {
+ if (self.Count > 0)
+ {
+ return self[UnityEngine.Random.Range(0, self.Count)];
+ }
+ return default(T);
+ }
+
+ public static Vector2 Div(this Vector2 a, Vector2 b)
+ {
+ return new Vector2(a.x / b.x, a.y / b.y);
+ }
+
+ public static Vector2 Mul(this Vector2 a, Vector2 b)
+ {
+ return new Vector2(a.x * b.x, a.y * b.y);
+ }
+
+ public static Vector3 Mul(this Vector3 a, Vector3 b)
+ {
+ return new Vector3(a.x * b.x, a.y * b.y, a.z * b.z);
+ }
+
+ public static Vector3 Inv(this Vector3 a)
+ {
+ return new Vector3(1f / a.x, 1f / a.y, 1f / a.z);
+ }
+
+ public static Rect Lerp(this Rect source, Rect target, float t)
+ {
+ return new Rect
+ {
+ position = Vector2.Lerp(source.position, target.position, t),
+ size = Vector2.Lerp(source.size, target.size, t)
+ };
+ }
+
+ public static void ForEach<T>(this IList<T> self, Action<T> todo)
+ {
+ for (int i = 0; i < self.Count; i++)
+ {
+ todo(self[i]);
+ }
+ }
+
+ public static T Max<T>(this IList<T> self, Func<T, float> comparer)
+ {
+ T t = self.First<T>();
+ float num = comparer(t);
+ for (int i = 0; i < self.Count; i++)
+ {
+ T t2 = self[i];
+ float num2 = comparer(t2);
+ if (num < num2 || (num == num2 && UnityEngine.Random.value > 0.5f))
+ {
+ num = num2;
+ t = t2;
+ }
+ }
+ return t;
+ }
+
+ public static T Max<T>(this IList<T> self, Func<T, decimal> comparer)
+ {
+ T t = self.First<T>();
+ decimal d = comparer(t);
+ for (int i = 0; i < self.Count; i++)
+ {
+ T t2 = self[i];
+ decimal num = comparer(t2);
+ if (d < num || (d == num && UnityEngine.Random.value > 0.5f))
+ {
+ d = num;
+ t = t2;
+ }
+ }
+ return t;
+ }
+
+ public static int Wrap(this int self, int max)
+ {
+ if (self >= 0)
+ {
+ return self % max;
+ }
+ return (self + -(self / max) * max + max) % max;
+ }
+
+ public static int IndexOf<T>(this T[] self, Predicate<T> pred)
+ {
+ for (int i = 0; i < self.Length; i++)
+ {
+ if (pred(self[i]))
+ {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public static Vector2 MapToRectangle(this Vector2 del, Vector2 widthAndHeight)
+ {
+ del = del.normalized;
+ if (Mathf.Abs(del.x) > Mathf.Abs(del.y))
+ {
+ return new Vector2(Mathf.Sign(del.x) * widthAndHeight.x, del.y * widthAndHeight.y / 0.70710677f);
+ }
+ return new Vector2(del.x * widthAndHeight.x / 0.70710677f, Mathf.Sign(del.y) * widthAndHeight.y);
+ }
+
+ public static float AngleSignedRad(this Vector2 vector1, Vector2 vector2)
+ {
+ return Mathf.Atan2(vector2.y, vector2.x) - Mathf.Atan2(vector1.y, vector1.x);
+ }
+
+ public static float AngleSigned(this Vector2 vector1, Vector2 vector2)
+ {
+ return vector1.AngleSignedRad(vector2) * 57.29578f;
+ }
+
+ public static Vector2 Rotate(this Vector2 self, float degrees)
+ {
+ float f = 0.017453292f * degrees;
+ float num = Mathf.Cos(f);
+ float num2 = Mathf.Sin(f);
+ return new Vector2(self.x * num - num2 * self.y, self.x * num2 + num * self.y);
+ }
+
+ public static Vector3 RotateY(this Vector3 self, float degrees)
+ {
+ float f = 0.017453292f * degrees;
+ float num = Mathf.Cos(f);
+ float num2 = Mathf.Sin(f);
+ return new Vector3(self.x * num - num2 * self.z, self.y, self.x * num2 + num * self.z);
+ }
+
+ public static bool TryToEnum<TEnum>(this string strEnumValue, out TEnum enumValue)
+ {
+ enumValue = default(TEnum);
+ if (!Enum.IsDefined(typeof(TEnum), strEnumValue))
+ {
+ return false;
+ }
+ enumValue = (TEnum)((object)Enum.Parse(typeof(TEnum), strEnumValue));
+ return true;
+ }
+
+ public static TEnum ToEnum<TEnum>(this string strEnumValue)
+ {
+ if (!Enum.IsDefined(typeof(TEnum), strEnumValue))
+ {
+ return default(TEnum);
+ }
+ return (TEnum)((object)Enum.Parse(typeof(TEnum), strEnumValue));
+ }
+
+ public static TEnum ToEnum<TEnum>(this string strEnumValue, TEnum defaultValue)
+ {
+ if (!Enum.IsDefined(typeof(TEnum), strEnumValue))
+ {
+ return defaultValue;
+ }
+ return (TEnum)((object)Enum.Parse(typeof(TEnum), strEnumValue));
+ }
+
+ public static bool IsNullOrWhiteSpace(this string s)
+ {
+ if (s == null)
+ {
+ return true;
+ }
+ return !s.Any((char c) => !char.IsWhiteSpace(c));
+ }
+}
diff --git a/Client/Assembly-CSharp/FindAGameManager.cs b/Client/Assembly-CSharp/FindAGameManager.cs
new file mode 100644
index 0000000..b428861
--- /dev/null
+++ b/Client/Assembly-CSharp/FindAGameManager.cs
@@ -0,0 +1,124 @@
+using System;
+using System.Collections.Generic;
+using InnerNet;
+using UnityEngine;
+using UnityEngine.SceneManagement;
+
+public class FindAGameManager : DestroyableSingleton<FindAGameManager>, IGameListHandler
+{
+ private const float RefreshTime = 5f;
+
+ private float timer;
+
+ public ObjectPoolBehavior buttonPool;
+
+ public SpinAnimator RefreshSpinner;
+
+ public Scroller TargetArea;
+
+ public float ButtonStart = 1.75f;
+
+ public float ButtonHeight = 0.6f;
+
+ public const bool showPrivate = false;
+
+ private class GameSorter : IComparer<GameListing>
+ {
+ public static readonly FindAGameManager.GameSorter Instance = new FindAGameManager.GameSorter();
+
+ public int Compare(GameListing x, GameListing y)
+ {
+ return -x.PlayerCount.CompareTo(y.PlayerCount);
+ }
+ }
+
+ public void ResetTimer()
+ {
+ this.timer = 5f;
+ this.RefreshSpinner.Appear();
+ this.RefreshSpinner.StartPulse();
+ }
+
+ public void Start()
+ {
+ if (!AmongUsClient.Instance)
+ {
+ AmongUsClient.Instance = UnityEngine.Object.FindObjectOfType<AmongUsClient>();
+ if (!AmongUsClient.Instance)
+ {
+ SceneManager.LoadScene("MMOnline");
+ return;
+ }
+ }
+ AmongUsClient.Instance.GameListHandlers.Add(this);
+ AmongUsClient.Instance.RequestGameList(false, SaveManager.GameSearchOptions);
+ }
+
+ public void Update()
+ {
+ this.timer += Time.deltaTime;
+ GameOptionsData gameSearchOptions = SaveManager.GameSearchOptions;
+ if ((this.timer < 0f || this.timer > 5f) && gameSearchOptions.MapId != 0)
+ {
+ this.RefreshSpinner.Appear();
+ }
+ else
+ {
+ this.RefreshSpinner.Disappear();
+ }
+ if (Input.GetKeyUp(KeyCode.Escape))
+ {
+ this.ExitGame();
+ }
+ }
+
+ public void RefreshList()
+ {
+ if (this.timer > 5f)
+ {
+ this.timer = -5f;
+ this.RefreshSpinner.Play();
+ AmongUsClient.Instance.RequestGameList(false, SaveManager.GameSearchOptions);
+ }
+ }
+
+ public override void OnDestroy()
+ {
+ if (AmongUsClient.Instance)
+ {
+ AmongUsClient.Instance.GameListHandlers.Remove(this);
+ }
+ base.OnDestroy();
+ }
+
+ public void HandleList(int totalGames, List<GameListing> availableGames)
+ {
+ Debug.Log(string.Format("TotalGames: {0}\tAvailable: {1}", totalGames, availableGames.Count));
+ this.RefreshSpinner.Disappear();
+ this.timer = 0f;
+ availableGames.Sort(FindAGameManager.GameSorter.Instance);
+ while (this.buttonPool.activeChildren.Count > availableGames.Count)
+ {
+ PoolableBehavior poolableBehavior = this.buttonPool.activeChildren[this.buttonPool.activeChildren.Count - 1];
+ poolableBehavior.OwnerPool.Reclaim(poolableBehavior);
+ }
+ while (this.buttonPool.activeChildren.Count < availableGames.Count)
+ {
+ this.buttonPool.Get<PoolableBehavior>().transform.SetParent(this.TargetArea.Inner);
+ }
+ Vector3 vector = new Vector3(0f, this.ButtonStart, -1f);
+ for (int i = 0; i < this.buttonPool.activeChildren.Count; i++)
+ {
+ MatchMakerGameButton matchMakerGameButton = (MatchMakerGameButton)this.buttonPool.activeChildren[i];
+ matchMakerGameButton.SetGame(availableGames[i]);
+ matchMakerGameButton.transform.localPosition = vector;
+ vector.y -= this.ButtonHeight;
+ }
+ this.TargetArea.YBounds.max = Mathf.Max(0f, -vector.y - this.ButtonStart);
+ }
+
+ public void ExitGame()
+ {
+ AmongUsClient.Instance.ExitGame(DisconnectReasons.ExitGame);
+ }
+}
diff --git a/Client/Assembly-CSharp/FindGameButton.cs b/Client/Assembly-CSharp/FindGameButton.cs
new file mode 100644
index 0000000..4b3eef9
--- /dev/null
+++ b/Client/Assembly-CSharp/FindGameButton.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Collections;
+using InnerNet;
+using PowerTools;
+using UnityEngine;
+using UnityEngine.SceneManagement;
+
+public class FindGameButton : MonoBehaviour, IConnectButton
+{
+ public SpriteAnim connectIcon;
+
+ public AnimationClip connectClip;
+
+ public void OnClick()
+ {
+ if (NameTextBehaviour.Instance.ShakeIfInvalid())
+ {
+ return;
+ }
+ if (StatsManager.Instance.AmBanned)
+ {
+ AmongUsClient.Instance.LastDisconnectReason = DisconnectReasons.IntentionalLeaving;
+ DestroyableSingleton<DisconnectPopup>.Instance.Show();
+ return;
+ }
+ if (!DestroyableSingleton<MatchMaker>.Instance.Connecting(this))
+ {
+ return;
+ }
+ AmongUsClient.Instance.GameMode = GameModes.OnlineGame;
+ AmongUsClient.Instance.MainMenuScene = "MMOnline";
+ base.StartCoroutine(this.ConnectForFindGame());
+ }
+
+ private IEnumerator ConnectForFindGame()
+ {
+ AmongUsClient.Instance.SetEndpoint(DestroyableSingleton<ServerManager>.Instance.OnlineNetAddress, 22023);
+ AmongUsClient.Instance.OnlineScene = "OnlineGame";
+ AmongUsClient.Instance.mode = MatchMakerModes.Client;
+ yield return AmongUsClient.Instance.CoConnect();
+ if (AmongUsClient.Instance.LastDisconnectReason != DisconnectReasons.ExitGame)
+ {
+ DestroyableSingleton<MatchMaker>.Instance.NotConnecting();
+ }
+ else
+ {
+ AmongUsClient.Instance.HostId = AmongUsClient.Instance.ClientId;
+ SceneManager.LoadScene("FindAGame");
+ }
+ yield break;
+ }
+
+ public void StartIcon()
+ {
+ this.connectIcon.Play(this.connectClip, 1f);
+ }
+
+ public void StopIcon()
+ {
+ this.connectIcon.Stop();
+ this.connectIcon.GetComponent<SpriteRenderer>().sprite = null;
+ }
+}
diff --git a/Client/Assembly-CSharp/FingerBehaviour.cs b/Client/Assembly-CSharp/FingerBehaviour.cs
new file mode 100644
index 0000000..b39ad65
--- /dev/null
+++ b/Client/Assembly-CSharp/FingerBehaviour.cs
@@ -0,0 +1,101 @@
+using System;
+using System.Collections;
+using UnityEngine;
+
+public class FingerBehaviour : MonoBehaviour
+{
+ public SpriteRenderer Finger;
+
+ public SpriteRenderer Click;
+
+ public float liftedAngle = -20f;
+
+ public static class Quadratic
+ {
+ public static float InOut(float k)
+ {
+ if (k < 0f)
+ {
+ k = 0f;
+ }
+ if (k > 1f)
+ {
+ k = 1f;
+ }
+ if ((k *= 2f) < 1f)
+ {
+ return 0.5f * k * k;
+ }
+ return -0.5f * ((k -= 1f) * (k - 2f) - 1f);
+ }
+ }
+
+ public IEnumerator DoClick(float duration)
+ {
+ for (float time = 0f; time < duration; time += Time.deltaTime)
+ {
+ float num = time / duration;
+ if (num < 0.4f)
+ {
+ float num2 = num / 0.4f;
+ num2 = num2 * 2f - 1f;
+ if (num2 < 0f)
+ {
+ float fingerAngle = Mathf.Lerp(this.liftedAngle, this.liftedAngle * 2f, 1f + Mathf.Abs(num2));
+ this.SetFingerAngle(fingerAngle);
+ }
+ else
+ {
+ float fingerAngle2 = Mathf.Lerp(this.liftedAngle * 2f, 0f, num2);
+ this.SetFingerAngle(fingerAngle2);
+ }
+ }
+ else if (num < 0.7f)
+ {
+ this.ClickOn();
+ }
+ else
+ {
+ float t = (num - 0.7f) / 0.3f;
+ this.Click.enabled = false;
+ float fingerAngle3 = Mathf.Lerp(0f, this.liftedAngle, t);
+ this.SetFingerAngle(fingerAngle3);
+ }
+ yield return null;
+ }
+ this.ClickOff();
+ yield break;
+ }
+
+ private void SetFingerAngle(float angle)
+ {
+ this.Finger.transform.localRotation = Quaternion.Euler(0f, 0f, angle);
+ }
+
+ public void ClickOff()
+ {
+ this.Click.enabled = false;
+ this.SetFingerAngle(this.liftedAngle);
+ }
+
+ public void ClickOn()
+ {
+ this.Click.enabled = true;
+ this.SetFingerAngle(0f);
+ }
+
+ public IEnumerator MoveTo(Vector2 target, float duration)
+ {
+ Vector3 startPos = base.transform.position;
+ Vector3 targetPos = target;
+ targetPos.z = startPos.z;
+ for (float time = 0f; time < duration; time += Time.deltaTime)
+ {
+ float t = time / duration;
+ base.transform.position = Vector3.Lerp(startPos, targetPos, t);
+ yield return null;
+ }
+ base.transform.position = targetPos;
+ yield break;
+ }
+}
diff --git a/Client/Assembly-CSharp/FixedActionConsole.cs b/Client/Assembly-CSharp/FixedActionConsole.cs
new file mode 100644
index 0000000..63707bb
--- /dev/null
+++ b/Client/Assembly-CSharp/FixedActionConsole.cs
@@ -0,0 +1,64 @@
+using System;
+using UnityEngine;
+using UnityEngine.UI;
+
+public class FixedActionConsole : MonoBehaviour, IUsable
+{
+ public float UsableDistance
+ {
+ get
+ {
+ return this.usableDistance;
+ }
+ }
+
+ public float PercentCool
+ {
+ get
+ {
+ return 0f;
+ }
+ }
+
+ public float usableDistance = 1f;
+
+ public SpriteRenderer Image;
+
+ public Button.ButtonClickedEvent OnUse;
+
+ public void SetOutline(bool on, bool mainTarget)
+ {
+ if (this.Image)
+ {
+ this.Image.material.SetFloat("_Outline", (float)(on ? 1 : 0));
+ this.Image.material.SetColor("_OutlineColor", Color.white);
+ this.Image.material.SetColor("_AddColor", mainTarget ? Color.white : Color.clear);
+ }
+ }
+
+ public float CanUse(GameData.PlayerInfo pc, out bool canUse, out bool couldUse)
+ {
+ float num = float.MaxValue;
+ PlayerControl @object = pc.Object;
+ couldUse = (pc.Object.CanMove && !pc.IsDead);
+ canUse = couldUse;
+ if (canUse)
+ {
+ num = Vector2.Distance(@object.GetTruePosition(), base.transform.position);
+ canUse &= (num <= this.UsableDistance);
+ }
+ return num;
+ }
+
+ public void Use()
+ {
+ bool flag;
+ bool flag2;
+ this.CanUse(PlayerControl.LocalPlayer.Data, out flag, out flag2);
+ if (!flag)
+ {
+ return;
+ }
+ this.OnUse.Invoke();
+ }
+}
diff --git a/Client/Assembly-CSharp/FlatWaveBehaviour.cs b/Client/Assembly-CSharp/FlatWaveBehaviour.cs
new file mode 100644
index 0000000..4f10d5a
--- /dev/null
+++ b/Client/Assembly-CSharp/FlatWaveBehaviour.cs
@@ -0,0 +1,75 @@
+using System;
+using UnityEngine;
+
+[RequireComponent(typeof(MeshFilter))]
+[RequireComponent(typeof(MeshRenderer))]
+public class FlatWaveBehaviour : MonoBehaviour
+{
+ public int NumPoints = 128;
+
+ public FloatRange Width;
+
+ public FloatRange Delta;
+
+ public float Center;
+
+ private Mesh mesh;
+
+ private Vector3[] vecs;
+
+ public float TickRate = 0.1f;
+
+ private float timer;
+
+ public int Skip = 3;
+
+ [Range(0f, 1f)]
+ public float NoiseP = 0.5f;
+
+ public void Start()
+ {
+ this.mesh = new Mesh();
+ base.GetComponent<MeshFilter>().mesh = this.mesh;
+ this.mesh.MarkDynamic();
+ this.vecs = new Vector3[this.NumPoints];
+ int[] array = new int[this.NumPoints];
+ for (int i = 0; i < this.vecs.Length; i++)
+ {
+ Vector3 vector = this.vecs[i];
+ vector.x = this.Width.Lerp((float)i / (float)this.vecs.Length);
+ vector.y = this.Center;
+ if (BoolRange.Next(this.NoiseP))
+ {
+ vector.y += this.Delta.Next();
+ }
+ this.vecs[i] = vector;
+ array[i] = i;
+ }
+ this.mesh.vertices = this.vecs;
+ this.mesh.SetIndices(array, MeshTopology.LineStrip, 0);
+ }
+
+ public void Update()
+ {
+ this.timer += Time.deltaTime;
+ if (this.timer > this.TickRate)
+ {
+ this.timer = 0f;
+ for (int i = 0; i < this.vecs.Length - this.Skip; i++)
+ {
+ this.vecs[i].y = this.vecs[i + this.Skip].y;
+ }
+ for (int j = 1; j <= this.Skip; j++)
+ {
+ this.vecs[this.vecs.Length - j].y = this.Center;
+ if (BoolRange.Next(this.NoiseP))
+ {
+ Vector3[] array = this.vecs;
+ int num = this.vecs.Length - j;
+ array[num].y = array[num].y + this.Delta.Next();
+ }
+ }
+ this.mesh.vertices = this.vecs;
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/FloatRange.cs b/Client/Assembly-CSharp/FloatRange.cs
new file mode 100644
index 0000000..ca7cfbd
--- /dev/null
+++ b/Client/Assembly-CSharp/FloatRange.cs
@@ -0,0 +1,159 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+[Serializable]
+public class FloatRange
+{
+ public float Last { get; private set; }
+
+ public float Width
+ {
+ get
+ {
+ return this.max - this.min;
+ }
+ }
+
+ public float min;
+
+ public float max;
+
+ public FloatRange(float min, float max)
+ {
+ this.min = min;
+ this.max = max;
+ }
+
+ public float ChangeRange(float y, float min, float max)
+ {
+ return Mathf.Lerp(min, max, (y - this.min) / this.Width);
+ }
+
+ public float Clamp(float value)
+ {
+ return Mathf.Clamp(value, this.min, this.max);
+ }
+
+ public bool Contains(float t)
+ {
+ return this.min <= t && this.max >= t;
+ }
+
+ public float CubicLerp(float v)
+ {
+ if (this.min >= this.max)
+ {
+ return this.min;
+ }
+ v = Mathf.Clamp(0f, 1f, v);
+ return v * v * v * (this.max - this.min) + this.min;
+ }
+
+ public float EitherOr()
+ {
+ if (UnityEngine.Random.value <= 0.5f)
+ {
+ return this.max;
+ }
+ return this.min;
+ }
+
+ public float LerpUnclamped(float v)
+ {
+ return Mathf.LerpUnclamped(this.min, this.max, v);
+ }
+
+ public float Lerp(float v)
+ {
+ return Mathf.Lerp(this.min, this.max, v);
+ }
+
+ public float ExpOutLerp(float v)
+ {
+ return this.Lerp(1f - Mathf.Pow(2f, -10f * v));
+ }
+
+ public static float ExpOutLerp(float v, float min, float max)
+ {
+ return Mathf.Lerp(min, max, 1f - Mathf.Pow(2f, -10f * v));
+ }
+
+ public static float Next(float min, float max)
+ {
+ return UnityEngine.Random.Range(min, max);
+ }
+
+ public float Next()
+ {
+ return this.Last = UnityEngine.Random.Range(this.min, this.max);
+ }
+
+ public IEnumerable<float> Range(int numStops)
+ {
+ float num;
+ for (float i = 0f; i <= (float)numStops; i = num)
+ {
+ yield return Mathf.Lerp(this.min, this.max, i / (float)numStops);
+ num = i + 1f;
+ }
+ yield break;
+ }
+
+ public IEnumerable<float> RandomRange(int numStops)
+ {
+ float num;
+ for (float i = 0f; i <= (float)numStops; i = num)
+ {
+ yield return this.Next();
+ num = i + 1f;
+ }
+ yield break;
+ }
+
+ internal float ReverseLerp(float t)
+ {
+ return Mathf.Clamp((t - this.min) / this.Width, 0f, 1f);
+ }
+
+ public static float ReverseLerp(float t, float min, float max)
+ {
+ float num = max - min;
+ return Mathf.Clamp((t - min) / num, 0f, 1f);
+ }
+
+ public IEnumerable<float> SpreadToEdges(int stops)
+ {
+ return FloatRange.SpreadToEdges(this.min, this.max, stops);
+ }
+
+ public IEnumerable<float> SpreadEvenly(int stops)
+ {
+ return FloatRange.SpreadEvenly(this.min, this.max, stops);
+ }
+
+ public static IEnumerable<float> SpreadToEdges(float min, float max, int stops)
+ {
+ if (stops == 1)
+ {
+ yield break;
+ }
+ int num;
+ for (int i = 0; i < stops; i = num)
+ {
+ yield return Mathf.Lerp(min, max, (float)i / ((float)stops - 1f));
+ num = i + 1;
+ }
+ yield break;
+ }
+
+ public static IEnumerable<float> SpreadEvenly(float min, float max, int stops)
+ {
+ float step = 1f / ((float)stops + 1f);
+ for (float i = step; i < 1f; i += step)
+ {
+ yield return Mathf.Lerp(min, max, i);
+ }
+ yield break;
+ }
+}
diff --git a/Client/Assembly-CSharp/FollowerCamera.cs b/Client/Assembly-CSharp/FollowerCamera.cs
new file mode 100644
index 0000000..3184542
--- /dev/null
+++ b/Client/Assembly-CSharp/FollowerCamera.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Collections;
+using UnityEngine;
+
+internal class FollowerCamera : MonoBehaviour
+{
+ public MonoBehaviour Target;
+
+ public Vector2 Offset;
+
+ public bool Locked;
+
+ public float shakeAmount;
+
+ public float shakePeriod = 1f;
+
+ public void FixedUpdate()
+ {
+ if (this.Target && !this.Locked)
+ {
+ base.transform.position = Vector3.Lerp(base.transform.position, this.Target.transform.position + this.Offset, 5f * Time.deltaTime);
+ if (this.shakeAmount > 0f)
+ {
+ float num = Mathf.PerlinNoise(0.5f, Time.time * this.shakePeriod) * 2f - 1f;
+ float num2 = Mathf.PerlinNoise(Time.time * this.shakePeriod, 0.5f) * 2f - 1f;
+ base.transform.Translate(num * this.shakeAmount, num2 * this.shakeAmount, 0f);
+ }
+ }
+ }
+
+ public void ShakeScreen(float duration, float severity)
+ {
+ base.StartCoroutine(this.CoShakeScreen(duration, severity));
+ }
+
+ private IEnumerator CoShakeScreen(float duration, float severity)
+ {
+ WaitForFixedUpdate wait = new WaitForFixedUpdate();
+ for (float t = duration; t > 0f; t -= Time.fixedDeltaTime)
+ {
+ float d = t / duration;
+ this.Offset = UnityEngine.Random.insideUnitCircle * d * severity;
+ yield return wait;
+ }
+ this.Offset = Vector2.zero;
+ yield break;
+ }
+
+ internal void SetTarget(MonoBehaviour target)
+ {
+ this.Target = target;
+ base.transform.position = this.Target.transform.position + this.Offset;
+ }
+}
diff --git a/Client/Assembly-CSharp/FontCache.cs b/Client/Assembly-CSharp/FontCache.cs
new file mode 100644
index 0000000..91dc7a0
--- /dev/null
+++ b/Client/Assembly-CSharp/FontCache.cs
@@ -0,0 +1,78 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+public class FontCache : MonoBehaviour
+{
+ public static FontCache Instance;
+
+ private Dictionary<string, FontData> cache = new Dictionary<string, FontData>();
+
+ public List<FontExtensionData> extraData = new List<FontExtensionData>();
+
+ public List<TextAsset> DefaultFonts = new List<TextAsset>();
+
+ public List<Material> DefaultFontMaterials = new List<Material>();
+
+ public void OnEnable()
+ {
+ if (!FontCache.Instance)
+ {
+ FontCache.Instance = this;
+ UnityEngine.Object.DontDestroyOnLoad(base.gameObject);
+ return;
+ }
+ if (FontCache.Instance != null)
+ {
+ UnityEngine.Object.Destroy(base.gameObject);
+ }
+ }
+
+ public void SetFont(TextRenderer self, string name)
+ {
+ if (self.FontData.name == name)
+ {
+ return;
+ }
+ for (int i = 0; i < this.DefaultFonts.Count; i++)
+ {
+ if (this.DefaultFonts[i].name == name)
+ {
+ MeshRenderer component = self.GetComponent<MeshRenderer>();
+ Material material = component.material;
+ self.FontData = this.DefaultFonts[i];
+ component.sharedMaterial = this.DefaultFontMaterials[i];
+ component.material.SetColor("_OutlineColor", material.GetColor("_OutlineColor"));
+ component.material.SetInt("_Mask", material.GetInt("_Mask"));
+ return;
+ }
+ }
+ }
+
+ public FontData LoadFont(TextAsset dataSrc)
+ {
+ if (this.cache == null)
+ {
+ this.cache = new Dictionary<string, FontData>();
+ }
+ FontData fontData;
+ if (this.cache.TryGetValue(dataSrc.name, out fontData))
+ {
+ return fontData;
+ }
+ int num = this.extraData.FindIndex((FontExtensionData ed) => ed.FontName.Equals(dataSrc.name, StringComparison.OrdinalIgnoreCase));
+ FontExtensionData eData = null;
+ if (num >= 0)
+ {
+ eData = this.extraData[num];
+ }
+ fontData = FontCache.LoadFontUncached(dataSrc, eData);
+ this.cache[dataSrc.name] = fontData;
+ return fontData;
+ }
+
+ public static FontData LoadFontUncached(TextAsset dataSrc, FontExtensionData eData = null)
+ {
+ return FontLoader.FromBinary(dataSrc, eData);
+ }
+}
diff --git a/Client/Assembly-CSharp/FontData.cs b/Client/Assembly-CSharp/FontData.cs
new file mode 100644
index 0000000..896d74c
--- /dev/null
+++ b/Client/Assembly-CSharp/FontData.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+[Serializable]
+public class FontData
+{
+ public Vector2 TextureSize = new Vector2(256f, 256f);
+
+ public List<Vector4> bounds = new List<Vector4>();
+
+ public List<Vector3> offsets = new List<Vector3>();
+
+ public List<Vector4> Channels = new List<Vector4>();
+
+ public Dictionary<int, int> charMap;
+
+ public float LineHeight;
+
+ public Dictionary<int, Dictionary<int, float>> kernings;
+
+ public float GetKerning(int last, int cur)
+ {
+ Dictionary<int, float> dictionary;
+ float result;
+ if (this.kernings.TryGetValue(last, out dictionary) && dictionary.TryGetValue(cur, out result))
+ {
+ return result;
+ }
+ return 0f;
+ }
+}
diff --git a/Client/Assembly-CSharp/FontExtensionData.cs b/Client/Assembly-CSharp/FontExtensionData.cs
new file mode 100644
index 0000000..8978b88
--- /dev/null
+++ b/Client/Assembly-CSharp/FontExtensionData.cs
@@ -0,0 +1,56 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+[CreateAssetMenu]
+public class FontExtensionData : ScriptableObject
+{
+ public string FontName;
+
+ public List<KerningPair> kernings = new List<KerningPair>();
+
+ public List<OffsetAdjustment> Offsets = new List<OffsetAdjustment>();
+
+ public void AdjustKernings(FontData target)
+ {
+ for (int i = 0; i < this.kernings.Count; i++)
+ {
+ KerningPair kerningPair = this.kernings[i];
+ Dictionary<int, float> dictionary;
+ if (target.kernings.TryGetValue((int)kerningPair.First, out dictionary))
+ {
+ float num;
+ if (dictionary.TryGetValue((int)kerningPair.Second, out num))
+ {
+ dictionary[(int)kerningPair.Second] = num + (float)kerningPair.Pixels;
+ }
+ else
+ {
+ dictionary[(int)kerningPair.Second] = (float)kerningPair.Pixels;
+ }
+ }
+ else
+ {
+ Dictionary<int, float> dictionary2 = new Dictionary<int, float>();
+ dictionary2[(int)kerningPair.Second] = (float)kerningPair.Pixels;
+ target.kernings[(int)kerningPair.First] = dictionary2;
+ }
+ }
+ }
+
+ public void AdjustOffsets(FontData target)
+ {
+ for (int i = 0; i < this.Offsets.Count; i++)
+ {
+ OffsetAdjustment offsetAdjustment = this.Offsets[i];
+ int index;
+ if (target.charMap.TryGetValue((int)offsetAdjustment.Char, out index))
+ {
+ Vector3 value = target.offsets[index];
+ value.x += (float)offsetAdjustment.OffsetX;
+ value.y += (float)offsetAdjustment.OffsetY;
+ target.offsets[index] = value;
+ }
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/FontLoader.cs b/Client/Assembly-CSharp/FontLoader.cs
new file mode 100644
index 0000000..c65a693
--- /dev/null
+++ b/Client/Assembly-CSharp/FontLoader.cs
@@ -0,0 +1,106 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using UnityEngine;
+
+public static class FontLoader
+{
+ public static FontData FromBinary(TextAsset dataSrc, FontExtensionData eData)
+ {
+ FontData fontData = new FontData();
+ using (MemoryStream memoryStream = new MemoryStream(dataSrc.bytes))
+ {
+ using (BinaryReader binaryReader = new BinaryReader(memoryStream))
+ {
+ if (memoryStream.ReadByte() != 66 || memoryStream.ReadByte() != 77 || memoryStream.ReadByte() != 70 || memoryStream.ReadByte() != 3)
+ {
+ throw new InvalidDataException("Wrong format font.");
+ }
+ long num = -1L;
+ while (memoryStream.Position < memoryStream.Length)
+ {
+ if (num == memoryStream.Position)
+ {
+ throw new InvalidDataException("Bad font.");
+ }
+ num = memoryStream.Position;
+ byte b = binaryReader.ReadByte();
+ int num2 = binaryReader.ReadInt32();
+ long position = memoryStream.Position;
+ switch (b)
+ {
+ default:
+ memoryStream.Position += (long)num2;
+ break;
+ case 2:
+ fontData.LineHeight = (float)binaryReader.ReadUInt16();
+ memoryStream.Position += 2L;
+ fontData.TextureSize = new Vector2((float)binaryReader.ReadUInt16(), (float)binaryReader.ReadUInt16());
+ memoryStream.Position = position + (long)num2;
+ break;
+ case 4:
+ {
+ int num3 = num2 / 20;
+ fontData.charMap = new Dictionary<int, int>(num3);
+ fontData.bounds.Capacity = num3;
+ fontData.offsets.Capacity = num3;
+ fontData.Channels.Capacity = num3;
+ fontData.kernings = new Dictionary<int, Dictionary<int, float>>(256);
+ for (int i = 0; i < num3; i++)
+ {
+ int key = binaryReader.ReadInt32();
+ int num4 = (int)binaryReader.ReadUInt16();
+ int num5 = (int)binaryReader.ReadUInt16();
+ int num6 = (int)binaryReader.ReadUInt16();
+ int num7 = (int)binaryReader.ReadUInt16();
+ int num8 = (int)binaryReader.ReadInt16();
+ int num9 = (int)binaryReader.ReadInt16();
+ int num10 = (int)binaryReader.ReadInt16();
+ binaryReader.ReadByte();
+ int input = (int)binaryReader.ReadByte();
+ fontData.charMap.Add(key, fontData.bounds.Count);
+ fontData.bounds.Add(new Vector4((float)num4, (float)num5, (float)num6, (float)num7));
+ fontData.offsets.Add(new Vector3((float)num8, (float)num9, (float)num10));
+ fontData.Channels.Add(FontLoader.IntToChannels(input));
+ }
+ break;
+ }
+ case 5:
+ while (memoryStream.Position < position + (long)num2)
+ {
+ int key2 = binaryReader.ReadInt32();
+ int key3 = binaryReader.ReadInt32();
+ int num11 = (int)binaryReader.ReadInt16();
+ Dictionary<int, float> dictionary;
+ if (!fontData.kernings.TryGetValue(key2, out dictionary))
+ {
+ fontData.kernings.Add(key2, dictionary = new Dictionary<int, float>(256));
+ }
+ dictionary.Add(key3, (float)num11);
+ }
+ break;
+ }
+ }
+ }
+ }
+ if (eData != null)
+ {
+ eData.AdjustKernings(fontData);
+ eData.AdjustOffsets(fontData);
+ }
+ return fontData;
+ }
+
+ private static Vector4 IntToChannels(int input)
+ {
+ Vector4 result = default(Vector4);
+ for (int i = 0; i < 4; i++)
+ {
+ if ((input >> i & 1) == 1)
+ {
+ result[i] = 1f;
+ }
+ }
+ return result;
+ }
+}
diff --git a/Client/Assembly-CSharp/GameData.cs b/Client/Assembly-CSharp/GameData.cs
new file mode 100644
index 0000000..4941bd6
--- /dev/null
+++ b/Client/Assembly-CSharp/GameData.cs
@@ -0,0 +1,556 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Assets.CoreScripts;
+using Hazel;
+using InnerNet;
+using UnityEngine;
+
+public class GameData : InnerNetObject, IDisconnectHandler
+{
+ public int PlayerCount
+ {
+ get
+ {
+ return this.AllPlayers.Count;
+ }
+ }
+
+ public static GameData Instance;
+
+ // 所有角色数据
+ public List<GameData.PlayerInfo> AllPlayers = new List<GameData.PlayerInfo>();
+
+ public int TotalTasks;
+
+ public int CompletedTasks;
+
+ public const byte InvalidPlayerId = 255;
+
+ public const byte DisconnectedPlayerId = 254;
+
+ public class TaskInfo
+ {
+ public uint Id;
+
+ public bool Complete;
+
+ public void Serialize(MessageWriter writer)
+ {
+ writer.WritePacked(this.Id);
+ writer.Write(this.Complete);
+ }
+
+ public void Deserialize(MessageReader reader)
+ {
+ this.Id = reader.ReadPackedUInt32();
+ this.Complete = reader.ReadBoolean();
+ }
+ }
+
+ //c 角色数据,包括外观、装饰、是否是imposter、是否死亡
+ public class PlayerInfo
+ {
+ public PlayerControl Object
+ {
+ get
+ {
+ if (!this._object)
+ {
+ this._object = PlayerControl.AllPlayerControls.FirstOrDefault((PlayerControl p) => p.PlayerId == this.PlayerId);
+ }
+ return this._object;
+ }
+ }
+
+ public readonly byte PlayerId;
+
+ public string PlayerName = string.Empty;
+
+ public byte ColorId;
+
+ public uint HatId;
+
+ public uint PetId;
+
+ public uint SkinId;
+
+ public bool Disconnected;
+
+ public List<GameData.TaskInfo> Tasks;
+
+ public bool IsImpostor;
+
+ public bool IsDead;
+
+ private PlayerControl _object;
+
+ public PlayerInfo(byte playerId)
+ {
+ this.PlayerId = playerId;
+ }
+
+ public PlayerInfo(PlayerControl pc) : this(pc.PlayerId)
+ {
+ this._object = pc;
+ }
+
+ public void Serialize(MessageWriter writer)
+ {
+ writer.Write(this.PlayerName);
+ writer.Write(this.ColorId);
+ writer.WritePacked(this.HatId);
+ writer.WritePacked(this.PetId);
+ writer.WritePacked(this.SkinId);
+ byte b = 0;
+ if (this.Disconnected)
+ {
+ b |= 1;
+ }
+ if (this.IsImpostor)
+ {
+ b |= 2;
+ }
+ if (this.IsDead)
+ {
+ b |= 4;
+ }
+ writer.Write(b);
+ if (this.Tasks != null)
+ {
+ writer.Write((byte)this.Tasks.Count);
+ for (int i = 0; i < this.Tasks.Count; i++)
+ {
+ this.Tasks[i].Serialize(writer);
+ }
+ return;
+ }
+ writer.Write(0);
+ }
+
+ public void Deserialize(MessageReader reader)
+ {
+ this.PlayerName = reader.ReadString();
+ this.ColorId = reader.ReadByte();
+ this.HatId = reader.ReadPackedUInt32();
+ this.PetId = reader.ReadPackedUInt32();
+ this.SkinId = reader.ReadPackedUInt32();
+ byte b = reader.ReadByte();
+ this.Disconnected = ((b & 1) > 0);
+ this.IsImpostor = ((b & 2) > 0);
+ this.IsDead = ((b & 4) > 0);
+ byte b2 = reader.ReadByte();
+ this.Tasks = new List<GameData.TaskInfo>((int)b2);
+ for (int i = 0; i < (int)b2; i++)
+ {
+ this.Tasks.Add(new GameData.TaskInfo());
+ this.Tasks[i].Deserialize(reader);
+ }
+ }
+
+ public GameData.TaskInfo FindTaskById(uint taskId)
+ {
+ for (int i = 0; i < this.Tasks.Count; i++)
+ {
+ if (this.Tasks[i].Id == taskId)
+ {
+ return this.Tasks[i];
+ }
+ }
+ return null;
+ }
+ }
+
+ private enum RpcCalls
+ {
+ SetTasks
+ }
+
+ public void Awake()
+ {
+ if (GameData.Instance && GameData.Instance != this)
+ {
+ UnityEngine.Object.Destroy(base.gameObject);
+ return;
+ }
+ GameData.Instance = this;
+ if (AmongUsClient.Instance)
+ {
+ AmongUsClient.Instance.DisconnectHandlers.AddUnique(this);
+ }
+ }
+
+ internal void SetDirty()
+ {
+ base.SetDirtyBit(uint.MaxValue);
+ }
+
+ public GameData.PlayerInfo GetHost()
+ {
+ ClientData host = AmongUsClient.Instance.GetHost();
+ if (host != null && host.Character)
+ {
+ return host.Character.Data;
+ }
+ return null;
+ }
+
+ public sbyte GetAvailableId()
+ {
+ sbyte i;
+ sbyte j;
+ for (i = 0; i < 10; i = j + 1)
+ {
+ if (!this.AllPlayers.Any((GameData.PlayerInfo p) => p.PlayerId == (byte)i))
+ {
+ return i;
+ }
+ j = i;
+ }
+ return -1;
+ }
+
+ public GameData.PlayerInfo GetPlayerById(byte id)
+ {
+ if (id == 255)
+ {
+ return null;
+ }
+ for (int i = 0; i < this.AllPlayers.Count; i++)
+ {
+ if (this.AllPlayers[i].PlayerId == id)
+ {
+ return this.AllPlayers[i];
+ }
+ }
+ return null;
+ }
+
+ public void UpdateName(byte playerId, string name)
+ {
+ GameData.PlayerInfo playerById = this.GetPlayerById(playerId);
+ if (playerById != null)
+ {
+ playerById.PlayerName = name;
+ }
+ base.SetDirtyBit(1U << (int)playerId);
+ }
+
+ public void UpdateColor(byte playerId, byte color)
+ {
+ GameData.PlayerInfo playerById = this.GetPlayerById(playerId);
+ if (playerById != null)
+ {
+ playerById.ColorId = color;
+ }
+ base.SetDirtyBit(1U << (int)playerId);
+ }
+
+ public void UpdateHat(byte playerId, uint hat)
+ {
+ GameData.PlayerInfo playerById = this.GetPlayerById(playerId);
+ if (playerById != null)
+ {
+ playerById.HatId = hat;
+ }
+ base.SetDirtyBit(1U << (int)playerId);
+ }
+
+ public void UpdatePet(byte playerId, uint petId)
+ {
+ GameData.PlayerInfo playerById = this.GetPlayerById(playerId);
+ if (playerById != null)
+ {
+ playerById.PetId = petId;
+ }
+ base.SetDirtyBit(1U << (int)playerId);
+ }
+
+ public void UpdateSkin(byte playerId, uint skin)
+ {
+ GameData.PlayerInfo playerById = this.GetPlayerById(playerId);
+ if (playerById != null)
+ {
+ playerById.SkinId = skin;
+ }
+ base.SetDirtyBit(1U << (int)playerId);
+ }
+
+ public void AddPlayer(PlayerControl pc)
+ {
+ GameData.PlayerInfo item = new GameData.PlayerInfo(pc);
+ this.AllPlayers.Add(item);
+ base.SetDirtyBit(1U << (int)pc.PlayerId);
+ }
+
+ public bool RemovePlayer(byte playerId)
+ {
+ for (int i = 0; i < this.AllPlayers.Count; i++)
+ {
+ if (this.AllPlayers[i].PlayerId == playerId)
+ {
+ this.SetDirty();
+ this.AllPlayers.RemoveAt(i);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void RecomputeTaskCounts()
+ {
+ this.TotalTasks = 0;
+ this.CompletedTasks = 0;
+ for (int i = 0; i < this.AllPlayers.Count; i++)
+ {
+ GameData.PlayerInfo playerInfo = this.AllPlayers[i];
+ if (!playerInfo.Disconnected && playerInfo.Tasks != null && playerInfo.Object && (PlayerControl.GameOptions.GhostsDoTasks || !playerInfo.IsDead) && !playerInfo.IsImpostor)
+ {
+ for (int j = 0; j < playerInfo.Tasks.Count; j++)
+ {
+ this.TotalTasks++;
+ if (playerInfo.Tasks[j].Complete)
+ {
+ this.CompletedTasks++;
+ }
+ }
+ }
+ }
+ }
+
+ public void TutOnlyRemoveTask(byte playerId, uint taskId)
+ {
+ GameData.PlayerInfo playerById = this.GetPlayerById(playerId);
+ GameData.TaskInfo item = playerById.FindTaskById(taskId);
+ playerById.Tasks.Remove(item);
+ this.RecomputeTaskCounts();
+ }
+
+ public void TutOnlyAddTask(byte playerId, uint taskId)
+ {
+ this.GetPlayerById(playerId).Tasks.Add(new GameData.TaskInfo
+ {
+ Id = taskId
+ });
+ this.TotalTasks++;
+ }
+
+ private void SetTasks(byte playerId, byte[] taskTypeIds)
+ {
+ GameData.PlayerInfo playerById = this.GetPlayerById(playerId);
+ if (playerById == null)
+ {
+ Debug.Log("Could not set tasks for player id: " + playerId);
+ return;
+ }
+ if (playerById.Disconnected)
+ {
+ return;
+ }
+ if (!playerById.Object)
+ {
+ Debug.Log(string.Concat(new object[]
+ {
+ "Could not set tasks for player (",
+ playerById.PlayerName,
+ "): ",
+ playerId
+ }));
+ return;
+ }
+ playerById.Tasks = new List<GameData.TaskInfo>(taskTypeIds.Length);
+ for (int i = 0; i < taskTypeIds.Length; i++)
+ {
+ playerById.Tasks.Add(new GameData.TaskInfo());
+ playerById.Tasks[i].Id = (uint)i;
+ }
+ playerById.Object.SetTasks(taskTypeIds);
+ base.SetDirtyBit(1U << (int)playerById.PlayerId);
+ }
+
+ public void CompleteTask(PlayerControl pc, uint taskId)
+ {
+ GameData.TaskInfo taskInfo = pc.Data.FindTaskById(taskId);
+ if (taskInfo == null)
+ {
+ Debug.LogWarning("Couldn't find task: " + taskId);
+ return;
+ }
+ if (!taskInfo.Complete)
+ {
+ taskInfo.Complete = true;
+ this.CompletedTasks++;
+ return;
+ }
+ Debug.LogWarning("Double complete task: " + taskId);
+ }
+
+ public void HandleDisconnect(PlayerControl player, DisconnectReasons reason)
+ {
+ if (!player)
+ {
+ return;
+ }
+ GameData.PlayerInfo playerById = this.GetPlayerById(player.PlayerId);
+ if (playerById == null)
+ {
+ return;
+ }
+ if (AmongUsClient.Instance.IsGameStarted)
+ {
+ if (!playerById.Disconnected)
+ {
+ playerById.Disconnected = true;
+ TempData.LastDeathReason = DeathReason.Disconnect;
+ this.ShowNotification(playerById.PlayerName, reason);
+ }
+ }
+ else if (this.RemovePlayer(player.PlayerId))
+ {
+ this.ShowNotification(playerById.PlayerName, reason);
+ }
+ this.RecomputeTaskCounts();
+ }
+
+ private void ShowNotification(string playerName, DisconnectReasons reason)
+ {
+ if (string.IsNullOrEmpty(playerName))
+ {
+ return;
+ }
+ if (reason <= DisconnectReasons.Banned)
+ {
+ if (reason == DisconnectReasons.ExitGame)
+ {
+ DestroyableSingleton<HudManager>.Instance.Notifier.AddItem(playerName + " left the game.");
+ return;
+ }
+ if (reason == DisconnectReasons.Banned)
+ {
+ GameData.PlayerInfo data = AmongUsClient.Instance.GetHost().Character.Data;
+ DestroyableSingleton<HudManager>.Instance.Notifier.AddItem(playerName + " was banned by " + data.PlayerName + ".");
+ return;
+ }
+ }
+ else if (reason == DisconnectReasons.Kicked)
+ {
+ GameData.PlayerInfo data2 = AmongUsClient.Instance.GetHost().Character.Data;
+ DestroyableSingleton<HudManager>.Instance.Notifier.AddItem(playerName + " was kicked by " + data2.PlayerName + ".");
+ return;
+ }
+ DestroyableSingleton<HudManager>.Instance.Notifier.AddItem(playerName + " left the game due to error.");
+ }
+
+ public void HandleDisconnect()
+ {
+ if (!AmongUsClient.Instance.IsGameStarted)
+ {
+ for (int i = this.AllPlayers.Count - 1; i >= 0; i--)
+ {
+ if (!this.AllPlayers[i].Object)
+ {
+ this.AllPlayers.RemoveAt(i);
+ }
+ }
+ }
+ }
+
+ public override bool Serialize(MessageWriter writer, bool initialState)
+ {
+ if (initialState)
+ {
+ if (!DestroyableSingleton<Telemetry>.Instance.IsInitialized)
+ {
+ DestroyableSingleton<Telemetry>.Instance.Initialize();
+ }
+ writer.WriteBytesAndSize(DestroyableSingleton<Telemetry>.Instance.CurrentGuid.ToByteArray());
+ writer.WritePacked(this.AllPlayers.Count);
+ for (int i = 0; i < this.AllPlayers.Count; i++)
+ {
+ GameData.PlayerInfo playerInfo = this.AllPlayers[i];
+ writer.Write(playerInfo.PlayerId);
+ playerInfo.Serialize(writer);
+ }
+ }
+ else
+ {
+ int position = writer.Position;
+ byte b = 0;
+ writer.Write(b);
+ for (int j = 0; j < this.AllPlayers.Count; j++)
+ {
+ GameData.PlayerInfo playerInfo2 = this.AllPlayers[j];
+ if ((this.DirtyBits & 1U << (int)playerInfo2.PlayerId) != 0U)
+ {
+ writer.Write(playerInfo2.PlayerId);
+ playerInfo2.Serialize(writer);
+ b += 1;
+ }
+ }
+ writer.Position = position;
+ writer.Write(b);
+ writer.Position = writer.Length;
+ this.DirtyBits = 0U;
+ }
+ return true;
+ }
+
+ public override void Deserialize(MessageReader reader, bool initialState)
+ {
+ if (initialState)
+ {
+ Guid gameGuid = new Guid(reader.ReadBytesAndSize());
+ if (!DestroyableSingleton<Telemetry>.Instance.IsInitialized)
+ {
+ DestroyableSingleton<Telemetry>.Instance.Initialize(gameGuid);
+ }
+ int num = reader.ReadPackedInt32();
+ for (int i = 0; i < num; i++)
+ {
+ GameData.PlayerInfo playerInfo = new GameData.PlayerInfo(reader.ReadByte());
+ playerInfo.Deserialize(reader);
+ this.AllPlayers.Add(playerInfo);
+ }
+ }
+ else
+ {
+ byte b = reader.ReadByte();
+ for (int j = 0; j < (int)b; j++)
+ {
+ byte b2 = reader.ReadByte();
+ GameData.PlayerInfo playerInfo2 = this.GetPlayerById(b2);
+ if (playerInfo2 != null)
+ {
+ playerInfo2.Deserialize(reader);
+ }
+ else
+ {
+ playerInfo2 = new GameData.PlayerInfo(b2);
+ playerInfo2.Deserialize(reader);
+ this.AllPlayers.Add(playerInfo2);
+ }
+ }
+ }
+ this.RecomputeTaskCounts();
+ }
+
+ public void RpcSetTasks(byte playerId, byte[] taskTypeIds)
+ {
+ if (AmongUsClient.Instance.AmClient)
+ {
+ this.SetTasks(playerId, taskTypeIds);
+ }
+ MessageWriter messageWriter = AmongUsClient.Instance.StartRpc(this.NetId, 0, SendOption.Reliable);
+ messageWriter.Write(playerId);
+ messageWriter.WriteBytesAndSize(taskTypeIds);
+ messageWriter.EndMessage();
+ }
+
+ public override void HandleRpc(byte callId, MessageReader reader)
+ {
+ if (callId == 0)
+ {
+ this.SetTasks(reader.ReadByte(), reader.ReadBytesAndSize());
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/GameDiscovery.cs b/Client/Assembly-CSharp/GameDiscovery.cs
new file mode 100644
index 0000000..5742e43
--- /dev/null
+++ b/Client/Assembly-CSharp/GameDiscovery.cs
@@ -0,0 +1,83 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Hazel.Udp;
+using InnerNet;
+using UnityEngine;
+
+public class GameDiscovery : MonoBehaviour
+{
+ public JoinGameButton ButtonPrefab;
+
+ public Transform ItemLocation;
+
+ public float YStart = 0.56f;
+
+ public float YOffset = -0.75f;
+
+ private Dictionary<string, JoinGameButton> received = new Dictionary<string, JoinGameButton>();
+
+ public void Start()
+ {
+ InnerDiscover component = base.GetComponent<InnerDiscover>();
+ component.OnPacketGet += this.Receive;
+ component.StartAsClient();
+ }
+
+ public void Update()
+ {
+ float time = Time.time;
+ string[] array = this.received.Keys.ToArray<string>();
+ int num = 0;
+ foreach (string key in array)
+ {
+ JoinGameButton joinGameButton = this.received[key];
+ if (time - joinGameButton.timeRecieved > 3f)
+ {
+ this.received.Remove(key);
+ UnityEngine.Object.Destroy(joinGameButton.gameObject);
+ }
+ else
+ {
+ joinGameButton.transform.localPosition = new Vector3(0f, this.YStart + (float)num * this.YOffset, -1f);
+ num++;
+ }
+ }
+ }
+
+ private void Receive(BroadcastPacket packet)
+ {
+ string[] array = packet.Data.Split(new char[]
+ {
+ '~'
+ });
+ string address = packet.GetAddress();
+ JoinGameButton joinGameButton;
+ if (this.received.TryGetValue(address, out joinGameButton))
+ {
+ joinGameButton.timeRecieved = Time.time;
+ joinGameButton.SetGameName(array);
+ return;
+ }
+ if (array[1].Equals("Open"))
+ {
+ this.CreateButtonForAddess(address, array);
+ }
+ }
+
+ private void CreateButtonForAddess(string fromAddress, string[] gameNameParts)
+ {
+ JoinGameButton joinGameButton;
+ if (this.received.TryGetValue(fromAddress, out joinGameButton))
+ {
+ UnityEngine.Object.Destroy(joinGameButton.gameObject);
+ }
+ JoinGameButton joinGameButton2 = UnityEngine.Object.Instantiate<JoinGameButton>(this.ButtonPrefab, this.ItemLocation);
+ joinGameButton2.transform.localPosition = new Vector3(0f, this.YStart + (float)(this.ItemLocation.childCount - 1) * this.YOffset, -1f);
+ joinGameButton2.netAddress = fromAddress;
+ joinGameButton2.timeRecieved = Time.time;
+ joinGameButton2.SetGameName(gameNameParts);
+ joinGameButton2.GetComponentInChildren<MeshRenderer>().material.SetInt("_Mask", 4);
+ this.received[fromAddress] = joinGameButton2;
+ }
+}
diff --git a/Client/Assembly-CSharp/GameModes.cs b/Client/Assembly-CSharp/GameModes.cs
new file mode 100644
index 0000000..67c0bac
--- /dev/null
+++ b/Client/Assembly-CSharp/GameModes.cs
@@ -0,0 +1,8 @@
+using System;
+
+public enum GameModes
+{
+ LocalGame,
+ OnlineGame,
+ FreePlay
+}
diff --git a/Client/Assembly-CSharp/GameObjectExtensions.cs b/Client/Assembly-CSharp/GameObjectExtensions.cs
new file mode 100644
index 0000000..57795eb
--- /dev/null
+++ b/Client/Assembly-CSharp/GameObjectExtensions.cs
@@ -0,0 +1,77 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+public static class GameObjectExtensions
+{
+ public static T Find<T>(this List<T> self, GameObject toFind) where T : MonoBehaviour
+ {
+ for (int i = 0; i < self.Count; i++)
+ {
+ T t = self[i];
+ if (t.gameObject == toFind)
+ {
+ return t;
+ }
+ }
+ return default(T);
+ }
+
+ public static void SetZ(this Transform self, float z)
+ {
+ Vector3 localPosition = self.localPosition;
+ localPosition.z = z;
+ self.localPosition = localPosition;
+ }
+
+ public static void LookAt2d(this Transform self, Vector3 target)
+ {
+ Vector3 vector = target - self.transform.position;
+ vector.Normalize();
+ float num = Mathf.Atan2(vector.y, vector.x);
+ self.transform.rotation = Quaternion.Euler(0f, 0f, num * 57.29578f);
+ }
+
+ public static void LookAt2d(this Transform self, Transform target)
+ {
+ self.LookAt2d(target.transform.position);
+ }
+
+ public static void DestroyChildren(this Transform self)
+ {
+ for (int i = self.childCount - 1; i > -1; i--)
+ {
+ Transform child = self.GetChild(i);
+ child.transform.SetParent(null);
+ UnityEngine.Object.Destroy(child.gameObject);
+ }
+ }
+
+ public static void DestroyChildren(this MonoBehaviour self)
+ {
+ for (int i = self.transform.childCount - 1; i > -1; i--)
+ {
+ UnityEngine.Object.Destroy(self.transform.GetChild(i).gameObject);
+ }
+ }
+
+ public static void ForEachChild(this GameObject self, Action<GameObject> todo)
+ {
+ for (int i = self.transform.childCount - 1; i > -1; i--)
+ {
+ todo(self.transform.GetChild(i).gameObject);
+ }
+ }
+
+ public static void ForEachChildBehavior<T>(this MonoBehaviour self, Action<T> todo) where T : MonoBehaviour
+ {
+ for (int i = self.transform.childCount - 1; i > -1; i--)
+ {
+ T component = self.transform.GetChild(i).GetComponent<T>();
+ if (component)
+ {
+ todo(component);
+ }
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/GameOptionsData.cs b/Client/Assembly-CSharp/GameOptionsData.cs
new file mode 100644
index 0000000..b3ef55f
--- /dev/null
+++ b/Client/Assembly-CSharp/GameOptionsData.cs
@@ -0,0 +1,332 @@
+using System;
+using System.IO;
+using System.Text;
+using InnerNet;
+using UnityEngine;
+
+public class GameOptionsData : IBytesSerializable
+{
+ private const byte GameDataVersion = 1;
+
+ public static readonly string[] MapNames = new string[]
+ {
+ "The Skeld",
+ "???",
+ "???"
+ };
+
+ public static readonly float[] KillDistances = new float[]
+ {
+ 1f,
+ 1.8f,
+ 2.5f
+ };
+
+ public static readonly string[] KillDistanceStrings = new string[]
+ {
+ "Short",
+ "Normal",
+ "Long"
+ };
+
+ public int MaxPlayers = 10;
+
+ public GameKeywords Keywords = GameKeywords.English;
+
+ public byte MapId;
+
+ public float PlayerSpeedMod = 1f;
+
+ public float CrewLightMod = 1f;
+
+ public float ImpostorLightMod = 1.5f;
+
+ public float KillCooldown = 15f;
+
+ public int NumCommonTasks = 1;
+
+ public int NumLongTasks = 1;
+
+ public int NumShortTasks = 2;
+
+ public int NumEmergencyMeetings = 1;
+
+ public int EmergencyCooldown = 15;
+
+ public int NumImpostors = 1;
+
+ public bool GhostsDoTasks = true;
+
+ public int KillDistance = 1;
+
+ public int DiscussionTime = 15;
+
+ public int VotingTime = 120;
+
+ public bool isDefaults = true;
+
+ private static readonly int[] RecommendedKillCooldown = new int[]
+ {
+ 0,
+ 0,
+ 0,
+ 0,
+ 45,
+ 30,
+ 15,
+ 35,
+ 30,
+ 25,
+ 20
+ };
+
+ private static readonly int[] RecommendedImpostors = new int[]
+ {
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 2,
+ 2,
+ 2,
+ 2
+ };
+
+ private static readonly int[] MaxImpostors = new int[]
+ {
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 2,
+ 2,
+ 3,
+ 3
+ };
+
+ public static readonly int[] MinPlayers = new int[]
+ {
+ 4,
+ 4,
+ 7,
+ 9
+ };
+
+ public void ToggleMapFilter(byte newId)
+ {
+ byte b = (byte)(((int)this.MapId ^ 1 << (int)newId) & 3);
+ if (b != 0)
+ {
+ this.MapId = b;
+ }
+ }
+
+ public bool FilterContainsMap(byte newId)
+ {
+ int num = 1 << (int)newId;
+ return ((int)this.MapId & num) == num;
+ }
+
+ public GameOptionsData()
+ {
+ try
+ {
+ SystemLanguage systemLanguage = Application.systemLanguage;
+ if (systemLanguage <= SystemLanguage.Portuguese)
+ {
+ if (systemLanguage != SystemLanguage.Korean)
+ {
+ if (systemLanguage == SystemLanguage.Portuguese)
+ {
+ this.Keywords = GameKeywords.Portuguese;
+ }
+ }
+ else
+ {
+ this.Keywords = GameKeywords.Korean;
+ }
+ }
+ else if (systemLanguage != SystemLanguage.Russian)
+ {
+ if (systemLanguage == SystemLanguage.Spanish)
+ {
+ this.Keywords = GameKeywords.Spanish;
+ }
+ }
+ else
+ {
+ this.Keywords = GameKeywords.Russian;
+ }
+ }
+ catch
+ {
+ }
+ }
+
+ public void SetRecommendations(int numPlayers, GameModes modes)
+ {
+ numPlayers = Mathf.Clamp(numPlayers, 4, 10);
+ this.PlayerSpeedMod = 1f;
+ this.CrewLightMod = 1f;
+ this.ImpostorLightMod = 1.5f;
+ this.KillCooldown = (float)GameOptionsData.RecommendedKillCooldown[numPlayers];
+ this.NumCommonTasks = 1;
+ this.NumLongTasks = 1;
+ this.NumShortTasks = 2;
+ this.NumEmergencyMeetings = 1;
+ if (modes != GameModes.OnlineGame)
+ {
+ this.NumImpostors = GameOptionsData.RecommendedImpostors[numPlayers];
+ }
+ this.KillDistance = 1;
+ this.DiscussionTime = 15;
+ this.VotingTime = 120;
+ this.isDefaults = true;
+ this.EmergencyCooldown = ((modes == GameModes.OnlineGame) ? 15 : 0);
+ }
+
+ public void Serialize(BinaryWriter writer)
+ {
+ writer.Write(1);
+ writer.Write((byte)this.MaxPlayers);
+ writer.Write((uint)this.Keywords);
+ writer.Write(this.MapId);
+ writer.Write(this.PlayerSpeedMod);
+ writer.Write(this.CrewLightMod);
+ writer.Write(this.ImpostorLightMod);
+ writer.Write(this.KillCooldown);
+ writer.Write((byte)this.NumCommonTasks);
+ writer.Write((byte)this.NumLongTasks);
+ writer.Write((byte)this.NumShortTasks);
+ writer.Write(this.NumEmergencyMeetings);
+ writer.Write((byte)this.NumImpostors);
+ writer.Write((byte)this.KillDistance);
+ writer.Write(this.DiscussionTime);
+ writer.Write(this.VotingTime);
+ writer.Write(this.isDefaults);
+ writer.Write((byte)this.EmergencyCooldown);
+ }
+
+ public static GameOptionsData Deserialize(BinaryReader reader)
+ {
+ try
+ {
+ byte b = reader.ReadByte();
+ if (b != 1 && b != 2)
+ {
+ return null;
+ }
+ GameOptionsData gameOptionsData = new GameOptionsData();
+ gameOptionsData.MaxPlayers = (int)reader.ReadByte();
+ gameOptionsData.Keywords = (GameKeywords)reader.ReadUInt32();
+ gameOptionsData.MapId = reader.ReadByte();
+ gameOptionsData.PlayerSpeedMod = reader.ReadSingle();
+ gameOptionsData.CrewLightMod = reader.ReadSingle();
+ gameOptionsData.ImpostorLightMod = reader.ReadSingle();
+ gameOptionsData.KillCooldown = reader.ReadSingle();
+ gameOptionsData.NumCommonTasks = (int)reader.ReadByte();
+ gameOptionsData.NumLongTasks = (int)reader.ReadByte();
+ gameOptionsData.NumShortTasks = (int)reader.ReadByte();
+ gameOptionsData.NumEmergencyMeetings = reader.ReadInt32();
+ gameOptionsData.NumImpostors = (int)reader.ReadByte();
+ gameOptionsData.KillDistance = (int)reader.ReadByte();
+ gameOptionsData.DiscussionTime = reader.ReadInt32();
+ gameOptionsData.VotingTime = reader.ReadInt32();
+ gameOptionsData.isDefaults = reader.ReadBoolean();
+ try
+ {
+ gameOptionsData.EmergencyCooldown = (int)reader.ReadByte();
+ }
+ catch
+ {
+ }
+ return gameOptionsData;
+ }
+ catch
+ {
+ }
+ return null;
+ }
+
+ public byte[] ToBytes()
+ {
+ byte[] result;
+ using (MemoryStream memoryStream = new MemoryStream())
+ {
+ using (BinaryWriter binaryWriter = new BinaryWriter(memoryStream))
+ {
+ this.Serialize(binaryWriter);
+ binaryWriter.Flush();
+ memoryStream.Position = 0L;
+ result = memoryStream.ToArray();
+ }
+ }
+ return result;
+ }
+
+ public static GameOptionsData FromBytes(byte[] bytes)
+ {
+ GameOptionsData result;
+ using (MemoryStream memoryStream = new MemoryStream(bytes))
+ {
+ using (BinaryReader binaryReader = new BinaryReader(memoryStream))
+ {
+ result = (GameOptionsData.Deserialize(binaryReader) ?? new GameOptionsData());
+ }
+ }
+ return result;
+ }
+
+ public override string ToString()
+ {
+ return this.ToHudString(10);
+ }
+
+ public string ToHudString(int numPlayers)
+ {
+ numPlayers = Mathf.Clamp(numPlayers, 0, 10);
+ StringBuilder stringBuilder = new StringBuilder(256);
+ stringBuilder.AppendLine(DestroyableSingleton<TranslationController>.Instance.GetString(this.isDefaults ? StringNames.GameRecommendedSettings : StringNames.GameCustomSettings, Array.Empty<object>()));
+ int num = GameOptionsData.MaxImpostors[numPlayers];
+ stringBuilder.AppendLine(DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.GameMapName, Array.Empty<object>()) + ": " + GameOptionsData.MapNames[(int)this.MapId]);
+ stringBuilder.Append(string.Format("{0}: {1}", DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.GameNumImpostors, Array.Empty<object>()), this.NumImpostors));
+ if (this.NumImpostors > num)
+ {
+ stringBuilder.Append(string.Format(" ({0}: {1})", DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.Limit, Array.Empty<object>()), num));
+ }
+ stringBuilder.AppendLine();
+ stringBuilder.AppendLine(string.Format("{0}: {1}", DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.GameNumMeetings, Array.Empty<object>()), this.NumEmergencyMeetings));
+ stringBuilder.AppendLine(string.Format("{0}: {1}s", DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.GameEmergencyCooldown, Array.Empty<object>()), this.EmergencyCooldown));
+ stringBuilder.AppendLine(string.Format("Discussion Time: {0}s", this.DiscussionTime));
+ if (this.VotingTime > 0)
+ {
+ stringBuilder.AppendLine(string.Format("{0}: {1}s", DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.GameVotingTime, Array.Empty<object>()), this.VotingTime));
+ }
+ else
+ {
+ stringBuilder.AppendLine(DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.GameVotingTime, Array.Empty<object>()) + ": ∞s");
+ }
+ stringBuilder.AppendLine(string.Format("{0}: {1}x", DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.GamePlayerSpeed, Array.Empty<object>()), this.PlayerSpeedMod));
+ stringBuilder.AppendLine(string.Format("{0}: {1}x", DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.GameCrewLight, Array.Empty<object>()), this.CrewLightMod));
+ stringBuilder.AppendLine(string.Format("{0}: {1}x", DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.GameImpostorLight, Array.Empty<object>()), this.ImpostorLightMod));
+ stringBuilder.AppendLine(string.Format("{0}: {1}s", DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.GameKillCooldown, Array.Empty<object>()), this.KillCooldown));
+ stringBuilder.AppendLine(DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.GameKillDistance, Array.Empty<object>()) + ": " + GameOptionsData.KillDistanceStrings[this.KillDistance]);
+ stringBuilder.AppendLine(string.Format("{0}: {1}", DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.GameCommonTasks, Array.Empty<object>()), this.NumCommonTasks));
+ stringBuilder.AppendLine(string.Format("{0}: {1}", DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.GameLongTasks, Array.Empty<object>()), this.NumLongTasks));
+ stringBuilder.Append(string.Format("{0}: {1}", DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.GameShortTasks, Array.Empty<object>()), this.NumShortTasks));
+ return stringBuilder.ToString();
+ }
+
+ public int GetAdjustedNumImpostors(int playerCount)
+ {
+ int numImpostors = PlayerControl.GameOptions.NumImpostors;
+ int max = GameOptionsData.MaxImpostors[GameData.Instance.PlayerCount];
+ return Mathf.Clamp(numImpostors, 1, max);
+ }
+}
diff --git a/Client/Assembly-CSharp/GameOptionsMenu.cs b/Client/Assembly-CSharp/GameOptionsMenu.cs
new file mode 100644
index 0000000..1c44375
--- /dev/null
+++ b/Client/Assembly-CSharp/GameOptionsMenu.cs
@@ -0,0 +1,133 @@
+using System;
+using UnityEngine;
+
+public class GameOptionsMenu : MonoBehaviour
+{
+ private GameOptionsData cachedData;
+
+ public GameObject ResetButton;
+
+ private OptionBehaviour[] Children;
+
+ public void Start()
+ {
+ this.Children = base.GetComponentsInChildren<OptionBehaviour>();
+ this.cachedData = PlayerControl.GameOptions;
+ for (int i = 0; i < this.Children.Length; i++)
+ {
+ OptionBehaviour optionBehaviour = this.Children[i];
+ optionBehaviour.OnValueChanged = new Action<OptionBehaviour>(this.ValueChanged);
+ if (AmongUsClient.Instance && !AmongUsClient.Instance.AmHost)
+ {
+ optionBehaviour.SetAsPlayer();
+ }
+ }
+ }
+
+ public void Update()
+ {
+ if (this.cachedData != PlayerControl.GameOptions)
+ {
+ this.cachedData = PlayerControl.GameOptions;
+ this.RefreshChildren();
+ }
+ }
+
+ private void RefreshChildren()
+ {
+ for (int i = 0; i < this.Children.Length; i++)
+ {
+ OptionBehaviour optionBehaviour = this.Children[i];
+ optionBehaviour.enabled = false;
+ optionBehaviour.enabled = true;
+ }
+ }
+
+ public void ValueChanged(OptionBehaviour option)
+ {
+ if (!AmongUsClient.Instance || !AmongUsClient.Instance.AmHost)
+ {
+ return;
+ }
+ if (option.Title == StringNames.GameRecommendedSettings)
+ {
+ if (this.cachedData.isDefaults)
+ {
+ this.cachedData.isDefaults = false;
+ }
+ else
+ {
+ this.cachedData.SetRecommendations(GameData.Instance.PlayerCount, AmongUsClient.Instance.GameMode);
+ }
+ this.RefreshChildren();
+ }
+ else
+ {
+ GameOptionsData gameOptions = PlayerControl.GameOptions;
+ StringNames title = option.Title;
+ switch (title)
+ {
+ case StringNames.GameMapName:
+ gameOptions.MapId = (byte)option.GetInt();
+ break;
+ case StringNames.GameNumImpostors:
+ gameOptions.NumImpostors = option.GetInt();
+ break;
+ case StringNames.GameNumMeetings:
+ gameOptions.NumEmergencyMeetings = option.GetInt();
+ break;
+ case StringNames.GameDiscussTime:
+ gameOptions.DiscussionTime = option.GetInt();
+ break;
+ case StringNames.GameVotingTime:
+ gameOptions.VotingTime = option.GetInt();
+ break;
+ case StringNames.GamePlayerSpeed:
+ gameOptions.PlayerSpeedMod = option.GetFloat();
+ break;
+ case StringNames.GameCrewLight:
+ gameOptions.CrewLightMod = option.GetFloat();
+ break;
+ case StringNames.GameImpostorLight:
+ gameOptions.ImpostorLightMod = option.GetFloat();
+ break;
+ case StringNames.GameKillCooldown:
+ gameOptions.KillCooldown = option.GetFloat();
+ break;
+ case StringNames.GameKillDistance:
+ gameOptions.KillDistance = option.GetInt();
+ break;
+ case StringNames.GameCommonTasks:
+ gameOptions.NumCommonTasks = option.GetInt();
+ break;
+ case StringNames.GameLongTasks:
+ gameOptions.NumLongTasks = option.GetInt();
+ break;
+ case StringNames.GameShortTasks:
+ gameOptions.NumShortTasks = option.GetInt();
+ break;
+ default:
+ if (title != StringNames.GameEmergencyCooldown)
+ {
+ Debug.Log("Ono, unrecognized setting: " + option.Title);
+ }
+ else
+ {
+ gameOptions.EmergencyCooldown = option.GetInt();
+ }
+ break;
+ }
+ if (gameOptions.isDefaults && option.Title != StringNames.GameMapName)
+ {
+ gameOptions.isDefaults = false;
+ this.RefreshChildren();
+ }
+ }
+ PlayerControl localPlayer = PlayerControl.LocalPlayer;
+ if (localPlayer == null)
+ {
+ return;
+ }
+ localPlayer.RpcSyncSettings(PlayerControl.GameOptions);
+ }
+}
diff --git a/Client/Assembly-CSharp/GameOverReason.cs b/Client/Assembly-CSharp/GameOverReason.cs
new file mode 100644
index 0000000..6f11220
--- /dev/null
+++ b/Client/Assembly-CSharp/GameOverReason.cs
@@ -0,0 +1,12 @@
+using System;
+
+public enum GameOverReason
+{
+ HumansByVote,
+ HumansByTask,
+ ImpostorByVote,
+ ImpostorByKill,
+ ImpostorBySabotage,
+ ImpostorDisconnect,
+ HumansDisconnect
+}
diff --git a/Client/Assembly-CSharp/GameSettingMenu.cs b/Client/Assembly-CSharp/GameSettingMenu.cs
new file mode 100644
index 0000000..d0d80dc
--- /dev/null
+++ b/Client/Assembly-CSharp/GameSettingMenu.cs
@@ -0,0 +1,37 @@
+using System;
+using UnityEngine;
+
+public class GameSettingMenu : MonoBehaviour
+{
+ public Transform[] AllItems;
+
+ public float YStart;
+
+ public float YOffset;
+
+ public Transform[] HideForOnline;
+
+ private void OnEnable()
+ {
+ int num = 0;
+ for (int i = 0; i < this.AllItems.Length; i++)
+ {
+ Transform transform = this.AllItems[i];
+ if (transform.gameObject.activeSelf)
+ {
+ if ((AmongUsClient.Instance.GameMode == GameModes.OnlineGame && this.HideForOnline.IndexOf(transform) != -1) || transform.name == "MapName")
+ {
+ transform.gameObject.SetActive(false);
+ }
+ else
+ {
+ Vector3 localPosition = transform.localPosition;
+ localPosition.y = this.YStart - (float)num * this.YOffset;
+ transform.localPosition = localPosition;
+ num++;
+ }
+ }
+ }
+ base.GetComponent<Scroller>().YBounds.max = (float)num * this.YOffset / 2f + 0.1f;
+ }
+}
diff --git a/Client/Assembly-CSharp/GameStartManager.cs b/Client/Assembly-CSharp/GameStartManager.cs
new file mode 100644
index 0000000..69f1967
--- /dev/null
+++ b/Client/Assembly-CSharp/GameStartManager.cs
@@ -0,0 +1,215 @@
+using System;
+using InnerNet;
+using UnityEngine;
+
+public class GameStartManager : DestroyableSingleton<GameStartManager>, IDisconnectHandler
+{
+ public int MinPlayers = 4;
+
+ public TextRenderer PlayerCounter;
+
+ private int LastPlayerCount = -1;
+
+ public GameObject GameSizePopup;
+
+ public TextRenderer GameRoomName;
+
+ public LobbyBehaviour LobbyPrefab;
+
+ public TextRenderer GameStartText;
+
+ public SpriteRenderer StartButton;
+
+ public SpriteRenderer MakePublicButton;
+
+ public Sprite PublicGameImage;
+
+ public Sprite PrivateGameImage;
+
+ private GameStartManager.StartingStates startState;
+
+ private float countDownTimer;
+
+ private enum StartingStates
+ {
+ NotStarting,
+ Countdown,
+ Starting
+ }
+
+ public void Start()
+ {
+ if (DestroyableSingleton<TutorialManager>.InstanceExists)
+ {
+ UnityEngine.Object.Destroy(base.gameObject);
+ return;
+ }
+ string text = InnerNetClient.IntToGameName(AmongUsClient.Instance.GameId);
+ if (text != null)
+ {
+ this.GameRoomName.Text = DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.RoomCode, Array.Empty<object>()) + "\r\n" + text;
+ }
+ else
+ {
+ this.StartButton.transform.localPosition = new Vector3(0f, -0.2f, 0f);
+ this.PlayerCounter.transform.localPosition = new Vector3(0f, -0.8f, 0f);
+ }
+ AmongUsClient.Instance.DisconnectHandlers.AddUnique(this);
+ if (!AmongUsClient.Instance.AmHost)
+ {
+ this.StartButton.gameObject.SetActive(false);
+ }
+ else
+ {
+ LobbyBehaviour.Instance = UnityEngine.Object.Instantiate<LobbyBehaviour>(this.LobbyPrefab);
+ AmongUsClient.Instance.Spawn(LobbyBehaviour.Instance, -2, SpawnFlags.None);
+ }
+ this.MakePublicButton.gameObject.SetActive(AmongUsClient.Instance.GameMode == GameModes.OnlineGame);
+ }
+
+ public void MakePublic()
+ {
+ if (AmongUsClient.Instance.AmHost)
+ {
+ AmongUsClient.Instance.ChangeGamePublic(!AmongUsClient.Instance.IsGamePublic);
+ }
+ }
+
+ public void Update()
+ {
+ if (!GameData.Instance)
+ {
+ return;
+ }
+ this.MakePublicButton.sprite = (AmongUsClient.Instance.IsGamePublic ? this.PublicGameImage : this.PrivateGameImage);
+ if (GameData.Instance.PlayerCount != this.LastPlayerCount)
+ {
+ this.LastPlayerCount = GameData.Instance.PlayerCount;
+ string arg = "[FF0000FF]";
+ if (this.LastPlayerCount > this.MinPlayers)
+ {
+ arg = "[00FF00FF]";
+ }
+ if (this.LastPlayerCount == this.MinPlayers)
+ {
+ arg = "[FFFF00FF]";
+ }
+ this.PlayerCounter.Text = string.Format("{0}{1}/{2}", arg, this.LastPlayerCount, PlayerControl.GameOptions.MaxPlayers);
+ this.StartButton.color = ((this.LastPlayerCount >= this.MinPlayers) ? Palette.EnabledColor : Palette.DisabledColor);
+ if (DestroyableSingleton<DiscordManager>.InstanceExists)
+ {
+ if (AmongUsClient.Instance.AmHost && AmongUsClient.Instance.GameMode == GameModes.OnlineGame)
+ {
+ DestroyableSingleton<DiscordManager>.Instance.SetInLobbyHost(this.LastPlayerCount, AmongUsClient.Instance.GameId);
+ }
+ else
+ {
+ DestroyableSingleton<DiscordManager>.Instance.SetInLobbyClient();
+ }
+ }
+ }
+ if (AmongUsClient.Instance.AmHost)
+ {
+ if (this.startState == GameStartManager.StartingStates.Countdown)
+ {
+ int num = Mathf.CeilToInt(this.countDownTimer);
+ this.countDownTimer -= Time.deltaTime;
+ int num2 = Mathf.CeilToInt(this.countDownTimer);
+ this.GameStartText.Text = string.Format("Starting in {0}", num2);
+ if (num != num2)
+ {
+ PlayerControl.LocalPlayer.RpcSetStartCounter(num2);
+ }
+ if (num2 <= 0)
+ {
+ this.FinallyBegin();
+ return;
+ }
+ }
+ else
+ {
+ this.GameStartText.Text = string.Empty;
+ }
+ }
+ }
+
+ public void ResetStartState()
+ {
+ this.startState = GameStartManager.StartingStates.NotStarting;
+ if (this.StartButton && this.StartButton.gameObject)
+ {
+ this.StartButton.gameObject.SetActive(AmongUsClient.Instance.AmHost);
+ }
+ PlayerControl.LocalPlayer.RpcSetStartCounter(-1);
+ }
+
+ public void SetStartCounter(sbyte sec)
+ {
+ if (sec == -1)
+ {
+ this.GameStartText.Text = string.Empty;
+ return;
+ }
+ this.GameStartText.Text = string.Format("Starting in {0}", sec);
+ }
+
+ public void BeginGame()
+ {
+ if (this.startState != GameStartManager.StartingStates.NotStarting)
+ {
+ return;
+ }
+ if (SaveManager.ShowMinPlayerWarning && GameData.Instance.PlayerCount == this.MinPlayers)
+ {
+ this.GameSizePopup.SetActive(true);
+ return;
+ }
+ if (GameData.Instance.PlayerCount < this.MinPlayers)
+ {
+ base.StartCoroutine(Effects.Shake(this.PlayerCounter.transform, 0.75f, 0.25f));
+ return;
+ }
+ this.ReallyBegin(false);
+ }
+
+ public void ReallyBegin(bool neverShow)
+ {
+ this.startState = GameStartManager.StartingStates.Countdown;
+ if (neverShow)
+ {
+ SaveManager.ShowMinPlayerWarning = false;
+ }
+ this.StartButton.gameObject.SetActive(false);
+ this.countDownTimer = 10.0001f;
+ this.startState = GameStartManager.StartingStates.Countdown;
+ }
+
+ public void FinallyBegin()
+ {
+ if (this.startState != GameStartManager.StartingStates.Countdown)
+ {
+ return;
+ }
+ this.startState = GameStartManager.StartingStates.Starting;
+ AmongUsClient.Instance.StartGame();
+ AmongUsClient.Instance.DisconnectHandlers.Remove(this);
+ UnityEngine.Object.Destroy(base.gameObject);
+ }
+
+ public void HandleDisconnect(PlayerControl pc, DisconnectReasons reason)
+ {
+ if (AmongUsClient.Instance.AmHost)
+ {
+ this.LastPlayerCount = -1;
+ if (this.StartButton)
+ {
+ this.StartButton.gameObject.SetActive(true);
+ }
+ }
+ }
+
+ public void HandleDisconnect()
+ {
+ this.HandleDisconnect(null, DisconnectReasons.ExitGame);
+ }
+}
diff --git a/Client/Assembly-CSharp/GarbageBehaviour.cs b/Client/Assembly-CSharp/GarbageBehaviour.cs
new file mode 100644
index 0000000..8fbc191
--- /dev/null
+++ b/Client/Assembly-CSharp/GarbageBehaviour.cs
@@ -0,0 +1,13 @@
+using System;
+using UnityEngine;
+
+public class GarbageBehaviour : MonoBehaviour
+{
+ public void FixedUpdate()
+ {
+ if (base.transform.localPosition.y < -3.49f)
+ {
+ UnityEngine.Object.Destroy(base.gameObject);
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/GoogleMobileAds/Api/AdErrorEventArgs.cs b/Client/Assembly-CSharp/GoogleMobileAds/Api/AdErrorEventArgs.cs
new file mode 100644
index 0000000..ab105d0
--- /dev/null
+++ b/Client/Assembly-CSharp/GoogleMobileAds/Api/AdErrorEventArgs.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace GoogleMobileAds.Api
+{
+ public class AdErrorEventArgs : EventArgs
+ {
+ public string Message { get; set; }
+ }
+}
diff --git a/Client/Assembly-CSharp/GoogleMobileAds/Api/AdFailedToLoadEventArgs.cs b/Client/Assembly-CSharp/GoogleMobileAds/Api/AdFailedToLoadEventArgs.cs
new file mode 100644
index 0000000..42389fc
--- /dev/null
+++ b/Client/Assembly-CSharp/GoogleMobileAds/Api/AdFailedToLoadEventArgs.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace GoogleMobileAds.Api
+{
+ public class AdFailedToLoadEventArgs : EventArgs
+ {
+ public string Message { get; set; }
+ }
+}
diff --git a/Client/Assembly-CSharp/GoogleMobileAds/Api/AdLoader.cs b/Client/Assembly-CSharp/GoogleMobileAds/Api/AdLoader.cs
new file mode 100644
index 0000000..3116a90
--- /dev/null
+++ b/Client/Assembly-CSharp/GoogleMobileAds/Api/AdLoader.cs
@@ -0,0 +1,93 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using GoogleMobileAds.Common;
+
+namespace GoogleMobileAds.Api
+{
+ public class AdLoader
+ {
+ public Dictionary<string, Action<CustomNativeTemplateAd, string>> CustomNativeTemplateClickHandlers { get; private set; }
+
+ public string AdUnitId { get; private set; }
+
+ public HashSet<NativeAdType> AdTypes { get; private set; }
+
+ public HashSet<string> TemplateIds { get; private set; }
+
+ public event EventHandler<AdFailedToLoadEventArgs> OnAdFailedToLoad;
+
+ public event EventHandler<CustomNativeEventArgs> OnCustomNativeTemplateAdLoaded;
+
+ private IAdLoaderClient adLoaderClient;
+
+ public class Builder
+ {
+ internal string AdUnitId { get; private set; }
+
+ internal HashSet<NativeAdType> AdTypes { get; private set; }
+
+ internal HashSet<string> TemplateIds { get; private set; }
+
+ internal Dictionary<string, Action<CustomNativeTemplateAd, string>> CustomNativeTemplateClickHandlers { get; private set; }
+
+ public Builder(string adUnitId)
+ {
+ this.AdUnitId = adUnitId;
+ this.AdTypes = new HashSet<NativeAdType>();
+ this.TemplateIds = new HashSet<string>();
+ this.CustomNativeTemplateClickHandlers = new Dictionary<string, Action<CustomNativeTemplateAd, string>>();
+ }
+
+ public AdLoader.Builder ForCustomNativeAd(string templateId)
+ {
+ this.TemplateIds.Add(templateId);
+ this.AdTypes.Add(NativeAdType.CustomTemplate);
+ return this;
+ }
+
+ public AdLoader.Builder ForCustomNativeAd(string templateId, Action<CustomNativeTemplateAd, string> callback)
+ {
+ this.TemplateIds.Add(templateId);
+ this.CustomNativeTemplateClickHandlers[templateId] = callback;
+ this.AdTypes.Add(NativeAdType.CustomTemplate);
+ return this;
+ }
+
+ public AdLoader Build()
+ {
+ return new AdLoader(this);
+ }
+ }
+
+ private AdLoader(AdLoader.Builder builder)
+ {
+ this.AdUnitId = string.Copy(builder.AdUnitId);
+ this.CustomNativeTemplateClickHandlers = new Dictionary<string, Action<CustomNativeTemplateAd, string>>(builder.CustomNativeTemplateClickHandlers);
+ this.TemplateIds = new HashSet<string>(builder.TemplateIds);
+ this.AdTypes = new HashSet<NativeAdType>(builder.AdTypes);
+ MethodInfo method = Type.GetType("GoogleMobileAds.GoogleMobileAdsClientFactory,Assembly-CSharp").GetMethod("BuildAdLoaderClient", BindingFlags.Static | BindingFlags.Public);
+ this.adLoaderClient = (IAdLoaderClient)method.Invoke(null, new object[]
+ {
+ this
+ });
+ Utils.CheckInitialization();
+ this.adLoaderClient.OnCustomNativeTemplateAdLoaded += delegate(object sender, CustomNativeEventArgs args)
+ {
+ this.OnCustomNativeTemplateAdLoaded(this, args);
+ };
+ this.adLoaderClient.OnAdFailedToLoad += delegate(object sender, AdFailedToLoadEventArgs args)
+ {
+ if (this.OnAdFailedToLoad != null)
+ {
+ this.OnAdFailedToLoad(this, args);
+ }
+ };
+ }
+
+ public void LoadAd(AdRequest request)
+ {
+ this.adLoaderClient.LoadAd(request);
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/GoogleMobileAds/Api/AdPosition.cs b/Client/Assembly-CSharp/GoogleMobileAds/Api/AdPosition.cs
new file mode 100644
index 0000000..6bc2a49
--- /dev/null
+++ b/Client/Assembly-CSharp/GoogleMobileAds/Api/AdPosition.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace GoogleMobileAds.Api
+{
+ public enum AdPosition
+ {
+ Top,
+ Bottom,
+ TopLeft,
+ TopRight,
+ BottomLeft,
+ BottomRight,
+ Center
+ }
+}
diff --git a/Client/Assembly-CSharp/GoogleMobileAds/Api/AdRequest.cs b/Client/Assembly-CSharp/GoogleMobileAds/Api/AdRequest.cs
new file mode 100644
index 0000000..a303ea9
--- /dev/null
+++ b/Client/Assembly-CSharp/GoogleMobileAds/Api/AdRequest.cs
@@ -0,0 +1,113 @@
+using System;
+using System.Collections.Generic;
+using GoogleMobileAds.Api.Mediation;
+
+namespace GoogleMobileAds.Api
+{
+ public class AdRequest
+ {
+ public List<string> TestDevices { get; private set; }
+
+ public HashSet<string> Keywords { get; private set; }
+
+ public DateTime? Birthday { get; private set; }
+
+ public Gender? Gender { get; private set; }
+
+ public bool? TagForChildDirectedTreatment { get; private set; }
+
+ public Dictionary<string, string> Extras { get; private set; }
+
+ public List<MediationExtras> MediationExtras { get; private set; }
+
+ public const string Version = "3.17.0";
+
+ public const string TestDeviceSimulator = "SIMULATOR";
+
+ public class Builder
+ {
+ internal List<string> TestDevices { get; private set; }
+
+ internal HashSet<string> Keywords { get; private set; }
+
+ internal DateTime? Birthday { get; private set; }
+
+ internal Gender? Gender { get; private set; }
+
+ internal bool? ChildDirectedTreatmentTag { get; private set; }
+
+ internal Dictionary<string, string> Extras { get; private set; }
+
+ internal List<MediationExtras> MediationExtras { get; private set; }
+
+ public Builder()
+ {
+ this.TestDevices = new List<string>();
+ this.Keywords = new HashSet<string>();
+ this.Birthday = null;
+ this.Gender = null;
+ this.ChildDirectedTreatmentTag = null;
+ this.Extras = new Dictionary<string, string>();
+ this.MediationExtras = new List<MediationExtras>();
+ }
+
+ public AdRequest.Builder AddKeyword(string keyword)
+ {
+ this.Keywords.Add(keyword);
+ return this;
+ }
+
+ public AdRequest.Builder AddTestDevice(string deviceId)
+ {
+ this.TestDevices.Add(deviceId);
+ return this;
+ }
+
+ public AdRequest Build()
+ {
+ return new AdRequest(this);
+ }
+
+ public AdRequest.Builder SetBirthday(DateTime birthday)
+ {
+ this.Birthday = new DateTime?(birthday);
+ return this;
+ }
+
+ public AdRequest.Builder SetGender(Gender gender)
+ {
+ this.Gender = new Gender?(gender);
+ return this;
+ }
+
+ public AdRequest.Builder AddMediationExtras(MediationExtras extras)
+ {
+ this.MediationExtras.Add(extras);
+ return this;
+ }
+
+ public AdRequest.Builder TagForChildDirectedTreatment(bool tagForChildDirectedTreatment)
+ {
+ this.ChildDirectedTreatmentTag = new bool?(tagForChildDirectedTreatment);
+ return this;
+ }
+
+ public AdRequest.Builder AddExtra(string key, string value)
+ {
+ this.Extras.Add(key, value);
+ return this;
+ }
+ }
+
+ private AdRequest(AdRequest.Builder builder)
+ {
+ this.TestDevices = new List<string>(builder.TestDevices);
+ this.Keywords = new HashSet<string>(builder.Keywords);
+ this.Birthday = builder.Birthday;
+ this.Gender = builder.Gender;
+ this.TagForChildDirectedTreatment = builder.ChildDirectedTreatmentTag;
+ this.Extras = new Dictionary<string, string>(builder.Extras);
+ this.MediationExtras = builder.MediationExtras;
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/GoogleMobileAds/Api/AdSize.cs b/Client/Assembly-CSharp/GoogleMobileAds/Api/AdSize.cs
new file mode 100644
index 0000000..ec5f3f1
--- /dev/null
+++ b/Client/Assembly-CSharp/GoogleMobileAds/Api/AdSize.cs
@@ -0,0 +1,88 @@
+using System;
+
+namespace GoogleMobileAds.Api
+{
+ public class AdSize
+ {
+ public int Width
+ {
+ get
+ {
+ return this.width;
+ }
+ }
+
+ public int Height
+ {
+ get
+ {
+ return this.height;
+ }
+ }
+
+ public bool IsSmartBanner
+ {
+ get
+ {
+ return this.isSmartBanner;
+ }
+ }
+
+ private bool isSmartBanner;
+
+ private int width;
+
+ private int height;
+
+ public static readonly AdSize Banner = new AdSize(320, 50);
+
+ public static readonly AdSize MediumRectangle = new AdSize(300, 250);
+
+ public static readonly AdSize IABBanner = new AdSize(468, 60);
+
+ public static readonly AdSize Leaderboard = new AdSize(728, 90);
+
+ public static readonly AdSize SmartBanner = new AdSize(true);
+
+ public static readonly int FullWidth = -1;
+
+ public AdSize(int width, int height)
+ {
+ this.isSmartBanner = false;
+ this.width = width;
+ this.height = height;
+ }
+
+ private AdSize(bool isSmartBanner) : this(0, 0)
+ {
+ this.isSmartBanner = isSmartBanner;
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj == null || base.GetType() != obj.GetType())
+ {
+ return false;
+ }
+ AdSize adSize = (AdSize)obj;
+ return this.width == adSize.width && this.height == adSize.height && this.isSmartBanner == adSize.isSmartBanner;
+ }
+
+ public static bool operator ==(AdSize a, AdSize b)
+ {
+ return a.Equals(b);
+ }
+
+ public static bool operator !=(AdSize a, AdSize b)
+ {
+ return !a.Equals(b);
+ }
+
+ public override int GetHashCode()
+ {
+ int num = 71;
+ int num2 = 11;
+ return ((num * num2 ^ this.width.GetHashCode()) * num2 ^ this.height.GetHashCode()) * num2 ^ this.isSmartBanner.GetHashCode();
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/GoogleMobileAds/Api/AdapterState.cs b/Client/Assembly-CSharp/GoogleMobileAds/Api/AdapterState.cs
new file mode 100644
index 0000000..471e180
--- /dev/null
+++ b/Client/Assembly-CSharp/GoogleMobileAds/Api/AdapterState.cs
@@ -0,0 +1,10 @@
+using System;
+
+namespace GoogleMobileAds.Api
+{
+ public enum AdapterState
+ {
+ NotReady,
+ Ready
+ }
+}
diff --git a/Client/Assembly-CSharp/GoogleMobileAds/Api/AdapterStatus.cs b/Client/Assembly-CSharp/GoogleMobileAds/Api/AdapterStatus.cs
new file mode 100644
index 0000000..31451e3
--- /dev/null
+++ b/Client/Assembly-CSharp/GoogleMobileAds/Api/AdapterStatus.cs
@@ -0,0 +1,20 @@
+using System;
+
+namespace GoogleMobileAds.Api
+{
+ public class AdapterStatus
+ {
+ public AdapterState InitializationState { get; private set; }
+
+ public string Description { get; private set; }
+
+ public int Latency { get; private set; }
+
+ internal AdapterStatus(AdapterState state, string description, int latency)
+ {
+ this.InitializationState = state;
+ this.Description = description;
+ this.Latency = latency;
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/GoogleMobileAds/Api/BannerView.cs b/Client/Assembly-CSharp/GoogleMobileAds/Api/BannerView.cs
new file mode 100644
index 0000000..7ad2724
--- /dev/null
+++ b/Client/Assembly-CSharp/GoogleMobileAds/Api/BannerView.cs
@@ -0,0 +1,121 @@
+using System;
+using System.Reflection;
+using GoogleMobileAds.Common;
+
+namespace GoogleMobileAds.Api
+{
+ public class BannerView
+ {
+ public event EventHandler<EventArgs> OnAdLoaded;
+
+ public event EventHandler<AdFailedToLoadEventArgs> OnAdFailedToLoad;
+
+ public event EventHandler<EventArgs> OnAdOpening;
+
+ public event EventHandler<EventArgs> OnAdClosed;
+
+ public event EventHandler<EventArgs> OnAdLeavingApplication;
+
+ private IBannerClient client;
+
+ public BannerView(string adUnitId, AdSize adSize, AdPosition position)
+ {
+ MethodInfo method = Type.GetType("GoogleMobileAds.GoogleMobileAdsClientFactory,Assembly-CSharp").GetMethod("BuildBannerClient", BindingFlags.Static | BindingFlags.Public);
+ this.client = (IBannerClient)method.Invoke(null, null);
+ this.client.CreateBannerView(adUnitId, adSize, position);
+ this.ConfigureBannerEvents();
+ }
+
+ public BannerView(string adUnitId, AdSize adSize, int x, int y)
+ {
+ MethodInfo method = Type.GetType("GoogleMobileAds.GoogleMobileAdsClientFactory,Assembly-CSharp").GetMethod("BuildBannerClient", BindingFlags.Static | BindingFlags.Public);
+ this.client = (IBannerClient)method.Invoke(null, null);
+ this.client.CreateBannerView(adUnitId, adSize, x, y);
+ this.ConfigureBannerEvents();
+ }
+
+ public void LoadAd(AdRequest request)
+ {
+ this.client.LoadAd(request);
+ }
+
+ public void Hide()
+ {
+ this.client.HideBannerView();
+ }
+
+ public void Show()
+ {
+ this.client.ShowBannerView();
+ }
+
+ public void Destroy()
+ {
+ this.client.DestroyBannerView();
+ }
+
+ public float GetHeightInPixels()
+ {
+ return this.client.GetHeightInPixels();
+ }
+
+ public float GetWidthInPixels()
+ {
+ return this.client.GetWidthInPixels();
+ }
+
+ public void SetPosition(AdPosition adPosition)
+ {
+ this.client.SetPosition(adPosition);
+ }
+
+ public void SetPosition(int x, int y)
+ {
+ this.client.SetPosition(x, y);
+ }
+
+ private void ConfigureBannerEvents()
+ {
+ this.client.OnAdLoaded += delegate(object sender, EventArgs args)
+ {
+ if (this.OnAdLoaded != null)
+ {
+ this.OnAdLoaded(this, args);
+ }
+ };
+ this.client.OnAdFailedToLoad += delegate(object sender, AdFailedToLoadEventArgs args)
+ {
+ if (this.OnAdFailedToLoad != null)
+ {
+ this.OnAdFailedToLoad(this, args);
+ }
+ };
+ this.client.OnAdOpening += delegate(object sender, EventArgs args)
+ {
+ if (this.OnAdOpening != null)
+ {
+ this.OnAdOpening(this, args);
+ }
+ };
+ this.client.OnAdClosed += delegate(object sender, EventArgs args)
+ {
+ if (this.OnAdClosed != null)
+ {
+ this.OnAdClosed(this, args);
+ }
+ };
+ this.client.OnAdLeavingApplication += delegate(object sender, EventArgs args)
+ {
+ if (this.OnAdLeavingApplication != null)
+ {
+ this.OnAdLeavingApplication(this, args);
+ }
+ };
+ }
+
+ public string MediationAdapterClassName()
+ {
+ return this.client.MediationAdapterClassName();
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/GoogleMobileAds/Api/CustomNativeEventArgs.cs b/Client/Assembly-CSharp/GoogleMobileAds/Api/CustomNativeEventArgs.cs
new file mode 100644
index 0000000..692b54b
--- /dev/null
+++ b/Client/Assembly-CSharp/GoogleMobileAds/Api/CustomNativeEventArgs.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace GoogleMobileAds.Api
+{
+ public class CustomNativeEventArgs : EventArgs
+ {
+ public CustomNativeTemplateAd nativeAd { get; set; }
+ }
+}
diff --git a/Client/Assembly-CSharp/GoogleMobileAds/Api/CustomNativeTemplateAd.cs b/Client/Assembly-CSharp/GoogleMobileAds/Api/CustomNativeTemplateAd.cs
new file mode 100644
index 0000000..159341b
--- /dev/null
+++ b/Client/Assembly-CSharp/GoogleMobileAds/Api/CustomNativeTemplateAd.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Collections.Generic;
+using GoogleMobileAds.Common;
+using UnityEngine;
+
+namespace GoogleMobileAds.Api
+{
+ public class CustomNativeTemplateAd
+ {
+ private ICustomNativeTemplateClient client;
+
+ internal CustomNativeTemplateAd(ICustomNativeTemplateClient client)
+ {
+ this.client = client;
+ }
+
+ public List<string> GetAvailableAssetNames()
+ {
+ return this.client.GetAvailableAssetNames();
+ }
+
+ public string GetCustomTemplateId()
+ {
+ return this.client.GetTemplateId();
+ }
+
+ public Texture2D GetTexture2D(string key)
+ {
+ byte[] imageByteArray = this.client.GetImageByteArray(key);
+ if (imageByteArray == null)
+ {
+ return null;
+ }
+ return Utils.GetTexture2DFromByteArray(imageByteArray);
+ }
+
+ public string GetText(string key)
+ {
+ return this.client.GetText(key);
+ }
+
+ public void PerformClick(string assetName)
+ {
+ this.client.PerformClick(assetName);
+ }
+
+ public void RecordImpression()
+ {
+ this.client.RecordImpression();
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/GoogleMobileAds/Api/Gender.cs b/Client/Assembly-CSharp/GoogleMobileAds/Api/Gender.cs
new file mode 100644
index 0000000..afdd035
--- /dev/null
+++ b/Client/Assembly-CSharp/GoogleMobileAds/Api/Gender.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace GoogleMobileAds.Api
+{
+ public enum Gender
+ {
+ Unknown,
+ Male,
+ Female
+ }
+}
diff --git a/Client/Assembly-CSharp/GoogleMobileAds/Api/InterstitialAd.cs b/Client/Assembly-CSharp/GoogleMobileAds/Api/InterstitialAd.cs
new file mode 100644
index 0000000..e9817bb
--- /dev/null
+++ b/Client/Assembly-CSharp/GoogleMobileAds/Api/InterstitialAd.cs
@@ -0,0 +1,88 @@
+using System;
+using System.Reflection;
+using GoogleMobileAds.Common;
+
+namespace GoogleMobileAds.Api
+{
+ public class InterstitialAd
+ {
+ public event EventHandler<EventArgs> OnAdLoaded;
+
+ public event EventHandler<AdFailedToLoadEventArgs> OnAdFailedToLoad;
+
+ public event EventHandler<EventArgs> OnAdOpening;
+
+ public event EventHandler<EventArgs> OnAdClosed;
+
+ public event EventHandler<EventArgs> OnAdLeavingApplication;
+
+ private IInterstitialClient client;
+
+ public InterstitialAd(string adUnitId)
+ {
+ MethodInfo method = Type.GetType("GoogleMobileAds.GoogleMobileAdsClientFactory,Assembly-CSharp").GetMethod("BuildInterstitialClient", BindingFlags.Static | BindingFlags.Public);
+ this.client = (IInterstitialClient)method.Invoke(null, null);
+ this.client.CreateInterstitialAd(adUnitId);
+ this.client.OnAdLoaded += delegate(object sender, EventArgs args)
+ {
+ if (this.OnAdLoaded != null)
+ {
+ this.OnAdLoaded(this, args);
+ }
+ };
+ this.client.OnAdFailedToLoad += delegate(object sender, AdFailedToLoadEventArgs args)
+ {
+ if (this.OnAdFailedToLoad != null)
+ {
+ this.OnAdFailedToLoad(this, args);
+ }
+ };
+ this.client.OnAdOpening += delegate(object sender, EventArgs args)
+ {
+ if (this.OnAdOpening != null)
+ {
+ this.OnAdOpening(this, args);
+ }
+ };
+ this.client.OnAdClosed += delegate(object sender, EventArgs args)
+ {
+ if (this.OnAdClosed != null)
+ {
+ this.OnAdClosed(this, args);
+ }
+ };
+ this.client.OnAdLeavingApplication += delegate(object sender, EventArgs args)
+ {
+ if (this.OnAdLeavingApplication != null)
+ {
+ this.OnAdLeavingApplication(this, args);
+ }
+ };
+ }
+
+ public void LoadAd(AdRequest request)
+ {
+ this.client.LoadAd(request);
+ }
+
+ public bool IsLoaded()
+ {
+ return this.client.IsLoaded();
+ }
+
+ public void Show()
+ {
+ this.client.ShowInterstitial();
+ }
+
+ public void Destroy()
+ {
+ this.client.DestroyInterstitial();
+ }
+
+ public string MediationAdapterClassName()
+ {
+ return this.client.MediationAdapterClassName();
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/GoogleMobileAds/Api/Mediation/MediationExtras.cs b/Client/Assembly-CSharp/GoogleMobileAds/Api/Mediation/MediationExtras.cs
new file mode 100644
index 0000000..1604ec7
--- /dev/null
+++ b/Client/Assembly-CSharp/GoogleMobileAds/Api/Mediation/MediationExtras.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+
+namespace GoogleMobileAds.Api.Mediation
+{
+ public abstract class MediationExtras
+ {
+ public Dictionary<string, string> Extras { get; protected set; }
+
+ public abstract string AndroidMediationExtraBuilderClassName { get; }
+
+ public abstract string IOSMediationExtraBuilderClassName { get; }
+
+ public MediationExtras()
+ {
+ this.Extras = new Dictionary<string, string>();
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/GoogleMobileAds/Api/MobileAds.cs b/Client/Assembly-CSharp/GoogleMobileAds/Api/MobileAds.cs
new file mode 100644
index 0000000..38199b3
--- /dev/null
+++ b/Client/Assembly-CSharp/GoogleMobileAds/Api/MobileAds.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Reflection;
+using GoogleMobileAds.Common;
+
+namespace GoogleMobileAds.Api
+{
+ public class MobileAds
+ {
+ private static readonly IMobileAdsClient client = MobileAds.GetMobileAdsClient();
+
+ public static void Initialize(string appId)
+ {
+ MobileAds.client.Initialize(appId);
+ MobileAdsEventExecutor.Initialize();
+ }
+
+ public static void SetApplicationMuted(bool muted)
+ {
+ MobileAds.client.SetApplicationMuted(muted);
+ }
+
+ public static void SetApplicationVolume(float volume)
+ {
+ MobileAds.client.SetApplicationVolume(volume);
+ }
+
+ public static void SetiOSAppPauseOnBackground(bool pause)
+ {
+ MobileAds.client.SetiOSAppPauseOnBackground(pause);
+ }
+
+ private static IMobileAdsClient GetMobileAdsClient()
+ {
+ return (IMobileAdsClient)Type.GetType("GoogleMobileAds.GoogleMobileAdsClientFactory,Assembly-CSharp").GetMethod("MobileAdsInstance", BindingFlags.Static | BindingFlags.Public).Invoke(null, null);
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/GoogleMobileAds/Api/NativeAdType.cs b/Client/Assembly-CSharp/GoogleMobileAds/Api/NativeAdType.cs
new file mode 100644
index 0000000..98c1e39
--- /dev/null
+++ b/Client/Assembly-CSharp/GoogleMobileAds/Api/NativeAdType.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace GoogleMobileAds.Api
+{
+ public enum NativeAdType
+ {
+ CustomTemplate
+ }
+}
diff --git a/Client/Assembly-CSharp/GoogleMobileAds/Api/Reward.cs b/Client/Assembly-CSharp/GoogleMobileAds/Api/Reward.cs
new file mode 100644
index 0000000..1e0beda
--- /dev/null
+++ b/Client/Assembly-CSharp/GoogleMobileAds/Api/Reward.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace GoogleMobileAds.Api
+{
+ public class Reward : EventArgs
+ {
+ public string Type { get; set; }
+
+ public double Amount { get; set; }
+ }
+}
diff --git a/Client/Assembly-CSharp/GoogleMobileAds/Api/RewardBasedVideoAd.cs b/Client/Assembly-CSharp/GoogleMobileAds/Api/RewardBasedVideoAd.cs
new file mode 100644
index 0000000..90f069a
--- /dev/null
+++ b/Client/Assembly-CSharp/GoogleMobileAds/Api/RewardBasedVideoAd.cs
@@ -0,0 +1,125 @@
+using System;
+using System.Reflection;
+using GoogleMobileAds.Common;
+
+namespace GoogleMobileAds.Api
+{
+ public class RewardBasedVideoAd
+ {
+ public static RewardBasedVideoAd Instance
+ {
+ get
+ {
+ return RewardBasedVideoAd.instance;
+ }
+ }
+
+ public event EventHandler<EventArgs> OnAdLoaded;
+
+ public event EventHandler<AdFailedToLoadEventArgs> OnAdFailedToLoad;
+
+ public event EventHandler<EventArgs> OnAdOpening;
+
+ public event EventHandler<EventArgs> OnAdStarted;
+
+ public event EventHandler<EventArgs> OnAdClosed;
+
+ public event EventHandler<Reward> OnAdRewarded;
+
+ public event EventHandler<EventArgs> OnAdLeavingApplication;
+
+ public event EventHandler<EventArgs> OnAdCompleted;
+
+ private IRewardBasedVideoAdClient client;
+
+ private static readonly RewardBasedVideoAd instance = new RewardBasedVideoAd();
+
+ private RewardBasedVideoAd()
+ {
+ MethodInfo method = Type.GetType("GoogleMobileAds.GoogleMobileAdsClientFactory,Assembly-CSharp").GetMethod("BuildRewardBasedVideoAdClient", BindingFlags.Static | BindingFlags.Public);
+ this.client = (IRewardBasedVideoAdClient)method.Invoke(null, null);
+ this.client.CreateRewardBasedVideoAd();
+ this.client.OnAdLoaded += delegate(object sender, EventArgs args)
+ {
+ if (this.OnAdLoaded != null)
+ {
+ this.OnAdLoaded(this, args);
+ }
+ };
+ this.client.OnAdFailedToLoad += delegate(object sender, AdFailedToLoadEventArgs args)
+ {
+ if (this.OnAdFailedToLoad != null)
+ {
+ this.OnAdFailedToLoad(this, args);
+ }
+ };
+ this.client.OnAdOpening += delegate(object sender, EventArgs args)
+ {
+ if (this.OnAdOpening != null)
+ {
+ this.OnAdOpening(this, args);
+ }
+ };
+ this.client.OnAdStarted += delegate(object sender, EventArgs args)
+ {
+ if (this.OnAdStarted != null)
+ {
+ this.OnAdStarted(this, args);
+ }
+ };
+ this.client.OnAdClosed += delegate(object sender, EventArgs args)
+ {
+ if (this.OnAdClosed != null)
+ {
+ this.OnAdClosed(this, args);
+ }
+ };
+ this.client.OnAdLeavingApplication += delegate(object sender, EventArgs args)
+ {
+ if (this.OnAdLeavingApplication != null)
+ {
+ this.OnAdLeavingApplication(this, args);
+ }
+ };
+ this.client.OnAdRewarded += delegate(object sender, Reward args)
+ {
+ if (this.OnAdRewarded != null)
+ {
+ this.OnAdRewarded(this, args);
+ }
+ };
+ this.client.OnAdCompleted += delegate(object sender, EventArgs args)
+ {
+ if (this.OnAdCompleted != null)
+ {
+ this.OnAdCompleted(this, args);
+ }
+ };
+ }
+
+ public void LoadAd(AdRequest request, string adUnitId)
+ {
+ this.client.LoadAd(request, adUnitId);
+ }
+
+ public bool IsLoaded()
+ {
+ return this.client.IsLoaded();
+ }
+
+ public void Show()
+ {
+ this.client.ShowRewardBasedVideoAd();
+ }
+
+ public void SetUserId(string userId)
+ {
+ this.client.SetUserId(userId);
+ }
+
+ public string MediationAdapterClassName()
+ {
+ return this.client.MediationAdapterClassName();
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/GoogleMobileAds/Api/RewardedAd.cs b/Client/Assembly-CSharp/GoogleMobileAds/Api/RewardedAd.cs
new file mode 100644
index 0000000..48a43ff
--- /dev/null
+++ b/Client/Assembly-CSharp/GoogleMobileAds/Api/RewardedAd.cs
@@ -0,0 +1,97 @@
+using System;
+using System.Reflection;
+using GoogleMobileAds.Common;
+
+namespace GoogleMobileAds.Api
+{
+ public class RewardedAd
+ {
+ public event EventHandler<EventArgs> OnAdLoaded;
+
+ public event EventHandler<AdErrorEventArgs> OnAdFailedToLoad;
+
+ public event EventHandler<AdErrorEventArgs> OnAdFailedToShow;
+
+ public event EventHandler<EventArgs> OnAdOpening;
+
+ public event EventHandler<EventArgs> OnAdClosed;
+
+ public event EventHandler<Reward> OnUserEarnedReward;
+
+ private IRewardedAdClient client;
+
+ public RewardedAd(string adUnitId)
+ {
+ MethodInfo method = Type.GetType("GoogleMobileAds.GoogleMobileAdsClientFactory,Assembly-CSharp").GetMethod("BuildRewardedAdClient", BindingFlags.Static | BindingFlags.Public);
+ this.client = (IRewardedAdClient)method.Invoke(null, null);
+ this.client.CreateRewardedAd(adUnitId);
+ this.client.OnAdLoaded += delegate(object sender, EventArgs args)
+ {
+ if (this.OnAdLoaded != null)
+ {
+ this.OnAdLoaded(this, args);
+ }
+ };
+ this.client.OnAdFailedToLoad += delegate(object sender, AdErrorEventArgs args)
+ {
+ if (this.OnAdFailedToLoad != null)
+ {
+ this.OnAdFailedToLoad(this, args);
+ }
+ };
+ this.client.OnAdFailedToShow += delegate(object sender, AdErrorEventArgs args)
+ {
+ if (this.OnAdFailedToShow != null)
+ {
+ this.OnAdFailedToShow(this, args);
+ }
+ };
+ this.client.OnAdOpening += delegate(object sender, EventArgs args)
+ {
+ if (this.OnAdOpening != null)
+ {
+ this.OnAdOpening(this, args);
+ }
+ };
+ this.client.OnAdClosed += delegate(object sender, EventArgs args)
+ {
+ if (this.OnAdClosed != null)
+ {
+ this.OnAdClosed(this, args);
+ }
+ };
+ this.client.OnUserEarnedReward += delegate(object sender, Reward args)
+ {
+ if (this.OnUserEarnedReward != null)
+ {
+ this.OnUserEarnedReward(this, args);
+ }
+ };
+ }
+
+ public void LoadAd(AdRequest request)
+ {
+ this.client.LoadAd(request);
+ }
+
+ public bool IsLoaded()
+ {
+ return this.client.IsLoaded();
+ }
+
+ public void Show()
+ {
+ this.client.Show();
+ }
+
+ public void SetServerSideVerificationOptions(ServerSideVerificationOptions serverSideVerificationOptions)
+ {
+ this.client.SetServerSideVerificationOptions(serverSideVerificationOptions);
+ }
+
+ public string MediationAdapterClassName()
+ {
+ return this.client.MediationAdapterClassName();
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/GoogleMobileAds/Api/ServerSideVerificationOptions.cs b/Client/Assembly-CSharp/GoogleMobileAds/Api/ServerSideVerificationOptions.cs
new file mode 100644
index 0000000..6f0e920
--- /dev/null
+++ b/Client/Assembly-CSharp/GoogleMobileAds/Api/ServerSideVerificationOptions.cs
@@ -0,0 +1,41 @@
+using System;
+
+namespace GoogleMobileAds.Api
+{
+ public class ServerSideVerificationOptions
+ {
+ public string UserId { get; private set; }
+
+ public string CustomData { get; private set; }
+
+ public class Builder
+ {
+ internal string UserId { get; private set; }
+
+ internal string CustomData { get; private set; }
+
+ public ServerSideVerificationOptions.Builder SetUserId(string userId)
+ {
+ this.UserId = userId;
+ return this;
+ }
+
+ public ServerSideVerificationOptions.Builder SetCustomData(string customData)
+ {
+ this.CustomData = customData;
+ return this;
+ }
+
+ public ServerSideVerificationOptions Build()
+ {
+ return new ServerSideVerificationOptions(this);
+ }
+ }
+
+ private ServerSideVerificationOptions(ServerSideVerificationOptions.Builder builder)
+ {
+ this.UserId = builder.UserId;
+ this.CustomData = builder.CustomData;
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/GoogleMobileAds/Common/DummyClient.cs b/Client/Assembly-CSharp/GoogleMobileAds/Common/DummyClient.cs
new file mode 100644
index 0000000..91587cc
--- /dev/null
+++ b/Client/Assembly-CSharp/GoogleMobileAds/Common/DummyClient.cs
@@ -0,0 +1,185 @@
+using System;
+using System.Reflection;
+using GoogleMobileAds.Api;
+using UnityEngine;
+
+namespace GoogleMobileAds.Common
+{
+ public class DummyClient : IBannerClient, IInterstitialClient, IRewardBasedVideoAdClient, IAdLoaderClient, IMobileAdsClient
+ {
+ public string UserId
+ {
+ get
+ {
+ Debug.Log("Dummy " + MethodBase.GetCurrentMethod().Name);
+ return "UserId";
+ }
+ set
+ {
+ Debug.Log("Dummy " + MethodBase.GetCurrentMethod().Name);
+ }
+ }
+
+ public event EventHandler<EventArgs> OnAdLoaded;
+
+ public event EventHandler<AdFailedToLoadEventArgs> OnAdFailedToLoad;
+
+ public event EventHandler<EventArgs> OnAdOpening;
+
+ public event EventHandler<EventArgs> OnAdStarted;
+
+ public event EventHandler<EventArgs> OnAdClosed;
+
+ public event EventHandler<Reward> OnAdRewarded;
+
+ public event EventHandler<EventArgs> OnAdLeavingApplication;
+
+ public event EventHandler<EventArgs> OnAdCompleted;
+
+ public event EventHandler<CustomNativeEventArgs> OnCustomNativeTemplateAdLoaded;
+
+ public DummyClient()
+ {
+ Debug.Log("Dummy " + MethodBase.GetCurrentMethod().Name);
+ }
+
+ public void Initialize(string appId)
+ {
+ Debug.Log("Dummy " + MethodBase.GetCurrentMethod().Name);
+ }
+
+ public void SetApplicationMuted(bool muted)
+ {
+ Debug.Log("Dummy " + MethodBase.GetCurrentMethod().Name);
+ }
+
+ public void SetApplicationVolume(float volume)
+ {
+ Debug.Log("Dummy " + MethodBase.GetCurrentMethod().Name);
+ }
+
+ public void SetiOSAppPauseOnBackground(bool pause)
+ {
+ Debug.Log("Dummy " + MethodBase.GetCurrentMethod().Name);
+ }
+
+ public void CreateBannerView(string adUnitId, AdSize adSize, AdPosition position)
+ {
+ Debug.Log("Dummy " + MethodBase.GetCurrentMethod().Name);
+ }
+
+ public void CreateBannerView(string adUnitId, AdSize adSize, int positionX, int positionY)
+ {
+ Debug.Log("Dummy " + MethodBase.GetCurrentMethod().Name);
+ }
+
+ public void LoadAd(AdRequest request)
+ {
+ Debug.Log("Dummy " + MethodBase.GetCurrentMethod().Name);
+ }
+
+ public void ShowBannerView()
+ {
+ Debug.Log("Dummy " + MethodBase.GetCurrentMethod().Name);
+ }
+
+ public void HideBannerView()
+ {
+ Debug.Log("Dummy " + MethodBase.GetCurrentMethod().Name);
+ }
+
+ public void DestroyBannerView()
+ {
+ Debug.Log("Dummy " + MethodBase.GetCurrentMethod().Name);
+ }
+
+ public float GetHeightInPixels()
+ {
+ Debug.Log("Dummy " + MethodBase.GetCurrentMethod().Name);
+ return 0f;
+ }
+
+ public float GetWidthInPixels()
+ {
+ Debug.Log("Dummy " + MethodBase.GetCurrentMethod().Name);
+ return 0f;
+ }
+
+ public void SetPosition(AdPosition adPosition)
+ {
+ Debug.Log("Dummy " + MethodBase.GetCurrentMethod().Name);
+ }
+
+ public void SetPosition(int x, int y)
+ {
+ Debug.Log("Dummy " + MethodBase.GetCurrentMethod().Name);
+ }
+
+ public void CreateInterstitialAd(string adUnitId)
+ {
+ Debug.Log("Dummy " + MethodBase.GetCurrentMethod().Name);
+ }
+
+ public bool IsLoaded()
+ {
+ Debug.Log("Dummy " + MethodBase.GetCurrentMethod().Name);
+ return true;
+ }
+
+ public void ShowInterstitial()
+ {
+ Debug.Log("Dummy " + MethodBase.GetCurrentMethod().Name);
+ }
+
+ public void DestroyInterstitial()
+ {
+ Debug.Log("Dummy " + MethodBase.GetCurrentMethod().Name);
+ }
+
+ public void CreateRewardBasedVideoAd()
+ {
+ Debug.Log("Dummy " + MethodBase.GetCurrentMethod().Name);
+ }
+
+ public void SetUserId(string userId)
+ {
+ Debug.Log("Dummy " + MethodBase.GetCurrentMethod().Name);
+ }
+
+ public void LoadAd(AdRequest request, string adUnitId)
+ {
+ Debug.Log("Dummy " + MethodBase.GetCurrentMethod().Name);
+ }
+
+ public void DestroyRewardBasedVideoAd()
+ {
+ Debug.Log("Dummy " + MethodBase.GetCurrentMethod().Name);
+ }
+
+ public void ShowRewardBasedVideoAd()
+ {
+ Debug.Log("Dummy " + MethodBase.GetCurrentMethod().Name);
+ }
+
+ public void CreateAdLoader(AdLoader.Builder builder)
+ {
+ Debug.Log("Dummy " + MethodBase.GetCurrentMethod().Name);
+ }
+
+ public void Load(AdRequest request)
+ {
+ Debug.Log("Dummy " + MethodBase.GetCurrentMethod().Name);
+ }
+
+ public void SetAdSize(AdSize adSize)
+ {
+ Debug.Log("Dummy " + MethodBase.GetCurrentMethod().Name);
+ }
+
+ public string MediationAdapterClassName()
+ {
+ Debug.Log("Dummy " + MethodBase.GetCurrentMethod().Name);
+ return null;
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/GoogleMobileAds/Common/IAdLoaderClient.cs b/Client/Assembly-CSharp/GoogleMobileAds/Common/IAdLoaderClient.cs
new file mode 100644
index 0000000..4f9f332
--- /dev/null
+++ b/Client/Assembly-CSharp/GoogleMobileAds/Common/IAdLoaderClient.cs
@@ -0,0 +1,14 @@
+using System;
+using GoogleMobileAds.Api;
+
+namespace GoogleMobileAds.Common
+{
+ public interface IAdLoaderClient
+ {
+ event EventHandler<AdFailedToLoadEventArgs> OnAdFailedToLoad;
+
+ event EventHandler<CustomNativeEventArgs> OnCustomNativeTemplateAdLoaded;
+
+ void LoadAd(AdRequest request);
+ }
+}
diff --git a/Client/Assembly-CSharp/GoogleMobileAds/Common/IBannerClient.cs b/Client/Assembly-CSharp/GoogleMobileAds/Common/IBannerClient.cs
new file mode 100644
index 0000000..96f2d37
--- /dev/null
+++ b/Client/Assembly-CSharp/GoogleMobileAds/Common/IBannerClient.cs
@@ -0,0 +1,40 @@
+using System;
+using GoogleMobileAds.Api;
+
+namespace GoogleMobileAds.Common
+{
+ public interface IBannerClient
+ {
+ event EventHandler<EventArgs> OnAdLoaded;
+
+ event EventHandler<AdFailedToLoadEventArgs> OnAdFailedToLoad;
+
+ event EventHandler<EventArgs> OnAdOpening;
+
+ event EventHandler<EventArgs> OnAdClosed;
+
+ event EventHandler<EventArgs> OnAdLeavingApplication;
+
+ void CreateBannerView(string adUnitId, AdSize adSize, AdPosition position);
+
+ void CreateBannerView(string adUnitId, AdSize adSize, int x, int y);
+
+ void LoadAd(AdRequest request);
+
+ void ShowBannerView();
+
+ void HideBannerView();
+
+ void DestroyBannerView();
+
+ float GetHeightInPixels();
+
+ float GetWidthInPixels();
+
+ void SetPosition(AdPosition adPosition);
+
+ void SetPosition(int x, int y);
+
+ string MediationAdapterClassName();
+ }
+}
diff --git a/Client/Assembly-CSharp/GoogleMobileAds/Common/ICustomNativeTemplateClient.cs b/Client/Assembly-CSharp/GoogleMobileAds/Common/ICustomNativeTemplateClient.cs
new file mode 100644
index 0000000..8c5ae57
--- /dev/null
+++ b/Client/Assembly-CSharp/GoogleMobileAds/Common/ICustomNativeTemplateClient.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+
+namespace GoogleMobileAds.Common
+{
+ public interface ICustomNativeTemplateClient
+ {
+ string GetTemplateId();
+
+ byte[] GetImageByteArray(string key);
+
+ List<string> GetAvailableAssetNames();
+
+ string GetText(string key);
+
+ void PerformClick(string assetName);
+
+ void RecordImpression();
+ }
+}
diff --git a/Client/Assembly-CSharp/GoogleMobileAds/Common/IInterstitialClient.cs b/Client/Assembly-CSharp/GoogleMobileAds/Common/IInterstitialClient.cs
new file mode 100644
index 0000000..cd4e35a
--- /dev/null
+++ b/Client/Assembly-CSharp/GoogleMobileAds/Common/IInterstitialClient.cs
@@ -0,0 +1,30 @@
+using System;
+using GoogleMobileAds.Api;
+
+namespace GoogleMobileAds.Common
+{
+ public interface IInterstitialClient
+ {
+ event EventHandler<EventArgs> OnAdLoaded;
+
+ event EventHandler<AdFailedToLoadEventArgs> OnAdFailedToLoad;
+
+ event EventHandler<EventArgs> OnAdOpening;
+
+ event EventHandler<EventArgs> OnAdClosed;
+
+ event EventHandler<EventArgs> OnAdLeavingApplication;
+
+ void CreateInterstitialAd(string adUnitId);
+
+ void LoadAd(AdRequest request);
+
+ bool IsLoaded();
+
+ void ShowInterstitial();
+
+ void DestroyInterstitial();
+
+ string MediationAdapterClassName();
+ }
+}
diff --git a/Client/Assembly-CSharp/GoogleMobileAds/Common/IMobileAdsClient.cs b/Client/Assembly-CSharp/GoogleMobileAds/Common/IMobileAdsClient.cs
new file mode 100644
index 0000000..a7735f2
--- /dev/null
+++ b/Client/Assembly-CSharp/GoogleMobileAds/Common/IMobileAdsClient.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace GoogleMobileAds.Common
+{
+ public interface IMobileAdsClient
+ {
+ void Initialize(string appId);
+
+ void SetApplicationVolume(float volume);
+
+ void SetApplicationMuted(bool muted);
+
+ void SetiOSAppPauseOnBackground(bool pause);
+ }
+}
diff --git a/Client/Assembly-CSharp/GoogleMobileAds/Common/IRewardBasedVideoAdClient.cs b/Client/Assembly-CSharp/GoogleMobileAds/Common/IRewardBasedVideoAdClient.cs
new file mode 100644
index 0000000..2e4589d
--- /dev/null
+++ b/Client/Assembly-CSharp/GoogleMobileAds/Common/IRewardBasedVideoAdClient.cs
@@ -0,0 +1,36 @@
+using System;
+using GoogleMobileAds.Api;
+
+namespace GoogleMobileAds.Common
+{
+ public interface IRewardBasedVideoAdClient
+ {
+ event EventHandler<EventArgs> OnAdLoaded;
+
+ event EventHandler<AdFailedToLoadEventArgs> OnAdFailedToLoad;
+
+ event EventHandler<EventArgs> OnAdOpening;
+
+ event EventHandler<EventArgs> OnAdStarted;
+
+ event EventHandler<Reward> OnAdRewarded;
+
+ event EventHandler<EventArgs> OnAdClosed;
+
+ event EventHandler<EventArgs> OnAdLeavingApplication;
+
+ event EventHandler<EventArgs> OnAdCompleted;
+
+ void CreateRewardBasedVideoAd();
+
+ void LoadAd(AdRequest request, string adUnitId);
+
+ bool IsLoaded();
+
+ string MediationAdapterClassName();
+
+ void ShowRewardBasedVideoAd();
+
+ void SetUserId(string userId);
+ }
+}
diff --git a/Client/Assembly-CSharp/GoogleMobileAds/Common/IRewardedAdClient.cs b/Client/Assembly-CSharp/GoogleMobileAds/Common/IRewardedAdClient.cs
new file mode 100644
index 0000000..10a8a6c
--- /dev/null
+++ b/Client/Assembly-CSharp/GoogleMobileAds/Common/IRewardedAdClient.cs
@@ -0,0 +1,32 @@
+using System;
+using GoogleMobileAds.Api;
+
+namespace GoogleMobileAds.Common
+{
+ public interface IRewardedAdClient
+ {
+ event EventHandler<EventArgs> OnAdLoaded;
+
+ event EventHandler<AdErrorEventArgs> OnAdFailedToLoad;
+
+ event EventHandler<AdErrorEventArgs> OnAdFailedToShow;
+
+ event EventHandler<EventArgs> OnAdOpening;
+
+ event EventHandler<Reward> OnUserEarnedReward;
+
+ event EventHandler<EventArgs> OnAdClosed;
+
+ void CreateRewardedAd(string adUnitId);
+
+ void LoadAd(AdRequest request);
+
+ bool IsLoaded();
+
+ string MediationAdapterClassName();
+
+ void Show();
+
+ void SetServerSideVerificationOptions(ServerSideVerificationOptions serverSideVerificationOptions);
+ }
+}
diff --git a/Client/Assembly-CSharp/GoogleMobileAds/Common/MobileAdsEventExecutor.cs b/Client/Assembly-CSharp/GoogleMobileAds/Common/MobileAdsEventExecutor.cs
new file mode 100644
index 0000000..8afa69c
--- /dev/null
+++ b/Client/Assembly-CSharp/GoogleMobileAds/Common/MobileAdsEventExecutor.cs
@@ -0,0 +1,72 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace GoogleMobileAds.Common
+{
+ public class MobileAdsEventExecutor : MonoBehaviour
+ {
+ public static MobileAdsEventExecutor instance = null;
+
+ private static List<Action> adEventsQueue = new List<Action>();
+
+ private static volatile bool adEventsQueueEmpty = true;
+
+ public static void Initialize()
+ {
+ if (MobileAdsEventExecutor.IsActive())
+ {
+ return;
+ }
+ GameObject gameObject = new GameObject("MobileAdsMainThreadExecuter");
+ gameObject.hideFlags = HideFlags.HideAndDontSave;
+ UnityEngine.Object.DontDestroyOnLoad(gameObject);
+ MobileAdsEventExecutor.instance = gameObject.AddComponent<MobileAdsEventExecutor>();
+ }
+
+ public static bool IsActive()
+ {
+ return MobileAdsEventExecutor.instance != null;
+ }
+
+ public void Awake()
+ {
+ UnityEngine.Object.DontDestroyOnLoad(base.gameObject);
+ }
+
+ public static void ExecuteInUpdate(Action action)
+ {
+ List<Action> obj = MobileAdsEventExecutor.adEventsQueue;
+ lock (obj)
+ {
+ MobileAdsEventExecutor.adEventsQueue.Add(action);
+ MobileAdsEventExecutor.adEventsQueueEmpty = false;
+ }
+ }
+
+ public void Update()
+ {
+ if (MobileAdsEventExecutor.adEventsQueueEmpty)
+ {
+ return;
+ }
+ List<Action> list = new List<Action>();
+ List<Action> obj = MobileAdsEventExecutor.adEventsQueue;
+ lock (obj)
+ {
+ list.AddRange(MobileAdsEventExecutor.adEventsQueue);
+ MobileAdsEventExecutor.adEventsQueue.Clear();
+ MobileAdsEventExecutor.adEventsQueueEmpty = true;
+ }
+ foreach (Action action in list)
+ {
+ action();
+ }
+ }
+
+ public void OnDisable()
+ {
+ MobileAdsEventExecutor.instance = null;
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/GoogleMobileAds/Common/RewardedAdDummyClient.cs b/Client/Assembly-CSharp/GoogleMobileAds/Common/RewardedAdDummyClient.cs
new file mode 100644
index 0000000..1271c6d
--- /dev/null
+++ b/Client/Assembly-CSharp/GoogleMobileAds/Common/RewardedAdDummyClient.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Reflection;
+using GoogleMobileAds.Api;
+using UnityEngine;
+
+namespace GoogleMobileAds.Common
+{
+ public class RewardedAdDummyClient : IRewardedAdClient
+ {
+ public event EventHandler<EventArgs> OnAdLoaded;
+
+ public event EventHandler<AdErrorEventArgs> OnAdFailedToLoad;
+
+ public event EventHandler<AdErrorEventArgs> OnAdFailedToShow;
+
+ public event EventHandler<EventArgs> OnAdOpening;
+
+ public event EventHandler<EventArgs> OnAdClosed;
+
+ public event EventHandler<Reward> OnUserEarnedReward;
+
+ public RewardedAdDummyClient()
+ {
+ Debug.Log("Dummy " + MethodBase.GetCurrentMethod().Name);
+ }
+
+ public void CreateRewardedAd(string adUnitId)
+ {
+ Debug.Log("Dummy " + MethodBase.GetCurrentMethod().Name);
+ }
+
+ public void LoadAd(AdRequest request)
+ {
+ Debug.Log("Dummy " + MethodBase.GetCurrentMethod().Name);
+ }
+
+ public bool IsLoaded()
+ {
+ Debug.Log("Dummy " + MethodBase.GetCurrentMethod().Name);
+ return true;
+ }
+
+ public void Show()
+ {
+ Debug.Log("Dummy " + MethodBase.GetCurrentMethod().Name);
+ }
+
+ public string MediationAdapterClassName()
+ {
+ Debug.Log("Dummy " + MethodBase.GetCurrentMethod().Name);
+ return null;
+ }
+
+ public void SetServerSideVerificationOptions(ServerSideVerificationOptions serverSideVerificationOptions)
+ {
+ Debug.Log("Dummy " + MethodBase.GetCurrentMethod().Name);
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/GoogleMobileAds/Common/Utils.cs b/Client/Assembly-CSharp/GoogleMobileAds/Common/Utils.cs
new file mode 100644
index 0000000..cd20f95
--- /dev/null
+++ b/Client/Assembly-CSharp/GoogleMobileAds/Common/Utils.cs
@@ -0,0 +1,27 @@
+using System;
+using UnityEngine;
+
+namespace GoogleMobileAds.Common
+{
+ internal class Utils
+ {
+ public static void CheckInitialization()
+ {
+ if (!MobileAdsEventExecutor.IsActive())
+ {
+ Debug.Log("You intitialized an ad object but have not yet called MobileAds.Initialize(). We highly recommend you call MobileAds.Initialize() before interacting with the Google Mobile Ads SDK.");
+ }
+ MobileAdsEventExecutor.Initialize();
+ }
+
+ public static Texture2D GetTexture2DFromByteArray(byte[] img)
+ {
+ Texture2D texture2D = new Texture2D(1, 1);
+ if (!texture2D.LoadImage(img))
+ {
+ throw new InvalidOperationException("Could not load custom native template\n image asset as texture");
+ }
+ return texture2D;
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/GoogleMobileAds/GoogleMobileAdsClientFactory.cs b/Client/Assembly-CSharp/GoogleMobileAds/GoogleMobileAdsClientFactory.cs
new file mode 100644
index 0000000..1e5fcaa
--- /dev/null
+++ b/Client/Assembly-CSharp/GoogleMobileAds/GoogleMobileAdsClientFactory.cs
@@ -0,0 +1,39 @@
+using System;
+using GoogleMobileAds.Api;
+using GoogleMobileAds.Common;
+
+namespace GoogleMobileAds
+{
+ public class GoogleMobileAdsClientFactory
+ {
+ public static IBannerClient BuildBannerClient()
+ {
+ return new DummyClient();
+ }
+
+ public static IInterstitialClient BuildInterstitialClient()
+ {
+ return new DummyClient();
+ }
+
+ public static IRewardBasedVideoAdClient BuildRewardBasedVideoAdClient()
+ {
+ return new DummyClient();
+ }
+
+ public static IRewardedAdClient BuildRewardedAdClient()
+ {
+ return new RewardedAdDummyClient();
+ }
+
+ public static IAdLoaderClient BuildAdLoaderClient(AdLoader adLoader)
+ {
+ return new DummyClient();
+ }
+
+ public static IMobileAdsClient MobileAdsInstance()
+ {
+ return new DummyClient();
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/HashRandom.cs b/Client/Assembly-CSharp/HashRandom.cs
new file mode 100644
index 0000000..7d226b1
--- /dev/null
+++ b/Client/Assembly-CSharp/HashRandom.cs
@@ -0,0 +1,36 @@
+using System;
+
+public static class HashRandom
+{
+ private static XXHash src = new XXHash((int)DateTime.UtcNow.Ticks);
+
+ private static int cnt = 0;
+
+ public static uint Next()
+ {
+ return HashRandom.src.GetHash(HashRandom.cnt++);
+ }
+
+ public static int FastNext(int maxInt)
+ {
+ return (int)((ulong)HashRandom.Next() % (ulong)((long)maxInt));
+ }
+
+ public static int Next(int maxInt)
+ {
+ uint num = (uint)(-1 / maxInt);
+ uint num2 = num * (uint)maxInt;
+ uint num3;
+ do
+ {
+ num3 = HashRandom.Next();
+ }
+ while (num3 > num2);
+ return (int)(num3 / num);
+ }
+
+ public static int Next(int minInt, int maxInt)
+ {
+ return HashRandom.Next(maxInt - minInt) + minInt;
+ }
+}
diff --git a/Client/Assembly-CSharp/HatBehaviour.cs b/Client/Assembly-CSharp/HatBehaviour.cs
new file mode 100644
index 0000000..95d4b25
--- /dev/null
+++ b/Client/Assembly-CSharp/HatBehaviour.cs
@@ -0,0 +1,34 @@
+using System;
+using UnityEngine;
+
+[CreateAssetMenu]
+public class HatBehaviour : ScriptableObject, IBuyable
+{
+ public string ProdId
+ {
+ get
+ {
+ return this.ProductId;
+ }
+ }
+
+ public Sprite MainImage;
+
+ public Sprite FloorImage;
+
+ public bool InFront;
+
+ public bool Free;
+
+ public int LimitedMonth;
+
+ public int LimitedYear;
+
+ public SkinData RelatedSkin;
+
+ public string StoreName;
+
+ public string ProductId;
+
+ public int Order;
+}
diff --git a/Client/Assembly-CSharp/HatManager.cs b/Client/Assembly-CSharp/HatManager.cs
new file mode 100644
index 0000000..4c57c44
--- /dev/null
+++ b/Client/Assembly-CSharp/HatManager.cs
@@ -0,0 +1,89 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+
+public class HatManager : DestroyableSingleton<HatManager>
+{
+ public HatBehaviour NoneHat;
+
+ public List<PetBehaviour> AllPets = new List<PetBehaviour>();
+
+ public List<HatBehaviour> AllHats = new List<HatBehaviour>();
+
+ public List<SkinData> AllSkins = new List<SkinData>();
+
+ internal PetBehaviour GetPetById(uint petId)
+ {
+ if ((ulong)petId >= (ulong)((long)this.AllPets.Count))
+ {
+ return this.AllPets[0];
+ }
+ return this.AllPets[(int)petId];
+ }
+
+ public uint GetIdFromPet(PetBehaviour pet)
+ {
+ return (uint)this.AllPets.FindIndex((PetBehaviour p) => p.idleClip == pet.idleClip);
+ }
+
+ public PetBehaviour[] GetUnlockedPets()
+ {
+ return (from h in this.AllPets
+ where h.Free || SaveManager.GetPurchase(h.ProductId)
+ select h).ToArray<PetBehaviour>();
+ }
+
+ public HatBehaviour GetHatById(uint hatId)
+ {
+ if ((ulong)hatId >= (ulong)((long)this.AllHats.Count))
+ {
+ return this.NoneHat;
+ }
+ return this.AllHats[(int)hatId];
+ }
+
+ public HatBehaviour[] GetUnlockedHats()
+ {
+ return (from h in this.AllHats
+ where h.LimitedMonth == 0 || SaveManager.GetPurchase(h.ProductId)
+ select h into o
+ orderby o.Order descending, o.name
+ select o).ToArray<HatBehaviour>();
+ }
+
+ public uint GetIdFromHat(HatBehaviour hat)
+ {
+ return (uint)this.AllHats.IndexOf(hat);
+ }
+
+ public SkinData[] GetUnlockedSkins()
+ {
+ return (from o in this.AllSkins
+ orderby o.Order descending, o.name
+ select o).ToArray<SkinData>();
+ }
+
+ public uint GetIdFromSkin(SkinData skin)
+ {
+ return (uint)this.AllSkins.IndexOf(skin);
+ }
+
+ internal SkinData GetSkinById(uint skinId)
+ {
+ if ((ulong)skinId >= (ulong)((long)this.AllSkins.Count))
+ {
+ return this.AllSkins[0];
+ }
+ return this.AllSkins[(int)skinId];
+ }
+
+ internal void SetSkin(SpriteRenderer skinRend, uint skinId)
+ {
+ SkinData skinById = this.GetSkinById(skinId);
+ if (skinById)
+ {
+ skinRend.sprite = skinById.IdleFrame;
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/HatsTab.cs b/Client/Assembly-CSharp/HatsTab.cs
new file mode 100644
index 0000000..10f7b3a
--- /dev/null
+++ b/Client/Assembly-CSharp/HatsTab.cs
@@ -0,0 +1,83 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+public class HatsTab : MonoBehaviour
+{
+ public ColorChip ColorTabPrefab;
+
+ public SpriteRenderer DemoImage;
+
+ public SpriteRenderer HatImage;
+
+ public SpriteRenderer SkinImage;
+
+ public SpriteRenderer PetImage;
+
+ public FloatRange XRange = new FloatRange(1.5f, 3f);
+
+ public float YStart = 0.8f;
+
+ public float YOffset = 0.8f;
+
+ public int NumPerRow = 4;
+
+ public Scroller scroller;
+
+ private List<ColorChip> ColorChips = new List<ColorChip>();
+
+ public void OnEnable()
+ {
+ PlayerControl.SetPlayerMaterialColors((int)PlayerControl.LocalPlayer.Data.ColorId, this.DemoImage);
+ PlayerControl.SetHatImage(SaveManager.LastHat, this.HatImage);
+ PlayerControl.SetSkinImage(SaveManager.LastSkin, this.SkinImage);
+ PlayerControl.SetPetImage(SaveManager.LastPet, (int)PlayerControl.LocalPlayer.Data.ColorId, this.PetImage);
+ HatBehaviour[] unlockedHats = DestroyableSingleton<HatManager>.Instance.GetUnlockedHats();
+ for (int i = 0; i < unlockedHats.Length; i++)
+ {
+ HatBehaviour hat = unlockedHats[i];
+ float x = this.XRange.Lerp((float)(i % this.NumPerRow) / ((float)this.NumPerRow - 1f));
+ float y = this.YStart - (float)(i / this.NumPerRow) * this.YOffset;
+ ColorChip colorChip = UnityEngine.Object.Instantiate<ColorChip>(this.ColorTabPrefab, this.scroller.Inner);
+ colorChip.transform.localPosition = new Vector3(x, y, -1f);
+ colorChip.Button.OnClick.AddListener(delegate()
+ {
+ this.SelectHat(hat);
+ });
+ colorChip.Inner.sprite = hat.MainImage;
+ this.ColorChips.Add(colorChip);
+ }
+ this.scroller.YBounds.max = -(this.YStart - (float)(unlockedHats.Length / this.NumPerRow) * this.YOffset) - 3f;
+ }
+
+ public void OnDisable()
+ {
+ for (int i = 0; i < this.ColorChips.Count; i++)
+ {
+ UnityEngine.Object.Destroy(this.ColorChips[i].gameObject);
+ }
+ this.ColorChips.Clear();
+ }
+
+ public void Update()
+ {
+ PlayerControl.SetPlayerMaterialColors((int)PlayerControl.LocalPlayer.Data.ColorId, this.DemoImage);
+ HatBehaviour hatById = DestroyableSingleton<HatManager>.Instance.GetHatById(SaveManager.LastHat);
+ for (int i = 0; i < this.ColorChips.Count; i++)
+ {
+ ColorChip colorChip = this.ColorChips[i];
+ colorChip.InUseForeground.SetActive(hatById.MainImage == colorChip.Inner.sprite);
+ }
+ }
+
+ private void SelectHat(HatBehaviour hat)
+ {
+ uint idFromHat = DestroyableSingleton<HatManager>.Instance.GetIdFromHat(hat);
+ SaveManager.LastHat = idFromHat;
+ PlayerControl.SetHatImage(idFromHat, this.HatImage);
+ if (PlayerControl.LocalPlayer)
+ {
+ PlayerControl.LocalPlayer.RpcSetHat(idFromHat);
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/HorizontalGauge.cs b/Client/Assembly-CSharp/HorizontalGauge.cs
new file mode 100644
index 0000000..2995903
--- /dev/null
+++ b/Client/Assembly-CSharp/HorizontalGauge.cs
@@ -0,0 +1,26 @@
+using System;
+using UnityEngine;
+
+public class HorizontalGauge : MonoBehaviour
+{
+ public float Value = 0.5f;
+
+ public float MaxValue = 1f;
+
+ public float maskScale = 1f;
+
+ public SpriteMask Mask;
+
+ private float lastValue = float.MinValue;
+
+ public void Update()
+ {
+ if (this.MaxValue != 0f && this.lastValue != this.Value)
+ {
+ this.lastValue = this.Value;
+ float num = this.lastValue / this.MaxValue * this.maskScale;
+ this.Mask.transform.localScale = new Vector3(num, 1f, 1f);
+ this.Mask.transform.localPosition = new Vector3(-this.Mask.sprite.bounds.size.x * (this.maskScale - num) / 2f, 0f, 0f);
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/HostGameButton.cs b/Client/Assembly-CSharp/HostGameButton.cs
new file mode 100644
index 0000000..486dc8e
--- /dev/null
+++ b/Client/Assembly-CSharp/HostGameButton.cs
@@ -0,0 +1,135 @@
+using System;
+using System.Collections;
+using InnerNet;
+using PowerTools;
+using UnityEngine;
+
+public class HostGameButton : MonoBehaviour, IConnectButton
+{
+ public AudioClip IntroMusic;
+
+ public string targetScene;
+
+ public SpriteRenderer FillScreen;
+
+ public SpriteAnim connectIcon;
+
+ public AnimationClip connectClip;
+
+ public GameModes GameMode;
+
+ public void Start()
+ {
+ if (DestroyableSingleton<MatchMaker>.InstanceExists)
+ {
+ DestroyableSingleton<MatchMaker>.Instance.NotConnecting();
+ }
+ }
+
+ public void OnClick()
+ {
+ if (this.GameMode == GameModes.FreePlay)
+ {
+ if (!NameTextBehaviour.IsValidName(SaveManager.PlayerName))
+ {
+ SaveManager.PlayerName = "Player";
+ }
+ }
+ else
+ {
+ if (NameTextBehaviour.Instance.ShakeIfInvalid())
+ {
+ return;
+ }
+ if (StatsManager.Instance.AmBanned)
+ {
+ AmongUsClient.Instance.LastDisconnectReason = DisconnectReasons.IntentionalLeaving;
+ DestroyableSingleton<DisconnectPopup>.Instance.Show();
+ return;
+ }
+ if (!DestroyableSingleton<MatchMaker>.Instance.Connecting(this))
+ {
+ return;
+ }
+ }
+ base.StartCoroutine(this.CoStartGame());
+ }
+
+ public void StartIcon()
+ {
+ if (!this.connectIcon)
+ {
+ return;
+ }
+ this.connectIcon.Play(this.connectClip, 1f);
+ }
+
+ public void StopIcon()
+ {
+ if (!this.connectIcon)
+ {
+ return;
+ }
+ this.connectIcon.Stop();
+ this.connectIcon.GetComponent<SpriteRenderer>().sprite = null;
+ }
+
+ private IEnumerator CoStartGame()
+ {
+ try
+ {
+ SoundManager.Instance.StopAllSound();
+ AmongUsClient.Instance.GameMode = this.GameMode;
+ switch (this.GameMode)
+ {
+ case GameModes.LocalGame:
+ DestroyableSingleton<InnerNetServer>.Instance.StartAsServer();
+ AmongUsClient.Instance.SetEndpoint("127.0.0.1", 22023);
+ AmongUsClient.Instance.MainMenuScene = "MatchMaking";
+ break;
+ case GameModes.OnlineGame:
+ AmongUsClient.Instance.SetEndpoint(DestroyableSingleton<ServerManager>.Instance.OnlineNetAddress, 22023);
+ AmongUsClient.Instance.MainMenuScene = "MMOnline";
+ break;
+ case GameModes.FreePlay:
+ DestroyableSingleton<InnerNetServer>.Instance.StartAsServer();
+ AmongUsClient.Instance.SetEndpoint("127.0.0.1", 22023);
+ AmongUsClient.Instance.MainMenuScene = "MainMenu";
+ break;
+ }
+ }
+ catch (Exception ex)
+ {
+ DestroyableSingleton<DisconnectPopup>.Instance.ShowCustom(ex.Message);
+ DestroyableSingleton<MatchMaker>.Instance.NotConnecting();
+ yield break;
+ }
+ yield return new WaitForSeconds(0.1f);
+ if (this.FillScreen)
+ {
+ SoundManager.Instance.CrossFadeSound("MainBG", null, 0.5f, 1.5f);
+ this.FillScreen.gameObject.SetActive(true);
+ for (float time = 0f; time < 0.25f; time += Time.deltaTime)
+ {
+ this.FillScreen.color = Color.Lerp(Color.clear, Color.black, time / 0.25f);
+ yield return null;
+ }
+ this.FillScreen.color = Color.black;
+ }
+ AmongUsClient.Instance.OnlineScene = this.targetScene;
+ AmongUsClient.Instance.Connect(MatchMakerModes.HostAndClient);
+ yield return AmongUsClient.Instance.WaitForConnectionOrFail();
+ DestroyableSingleton<MatchMaker>.Instance.NotConnecting();
+ if (AmongUsClient.Instance.mode == MatchMakerModes.None && this.FillScreen)
+ {
+ SoundManager.Instance.CrossFadeSound("MainBG", this.IntroMusic, 0.5f, 1.5f);
+ for (float time = 0f; time < 0.25f; time += Time.deltaTime)
+ {
+ this.FillScreen.color = Color.Lerp(Color.black, Color.clear, time / 0.25f);
+ yield return null;
+ }
+ this.FillScreen.color = Color.clear;
+ }
+ yield break;
+ }
+}
diff --git a/Client/Assembly-CSharp/HowToPlayController.cs b/Client/Assembly-CSharp/HowToPlayController.cs
new file mode 100644
index 0000000..2274478
--- /dev/null
+++ b/Client/Assembly-CSharp/HowToPlayController.cs
@@ -0,0 +1,67 @@
+using System;
+using UnityEngine;
+using UnityEngine.SceneManagement;
+
+public class HowToPlayController : MonoBehaviour
+{
+ public Transform DotParent;
+
+ public SpriteRenderer leftButton;
+
+ public SpriteRenderer rightButton;
+
+ public SceneController PCMove;
+
+ public SceneController[] Scenes;
+
+ public int SceneNum;
+
+ public void Start()
+ {
+ this.Scenes[2] = this.PCMove;
+ this.PCMove.gameObject.SetActive(false);
+ for (int i = 1; i < this.Scenes.Length; i++)
+ {
+ this.Scenes[i].gameObject.SetActive(false);
+ }
+ for (int j = 0; j < this.DotParent.childCount; j++)
+ {
+ this.DotParent.GetChild(j).localScale = Vector3.one;
+ }
+ this.ChangeScene(0);
+ }
+
+ public void Update()
+ {
+ if (Input.GetKeyUp(KeyCode.Escape))
+ {
+ this.Close();
+ }
+ }
+
+ public void NextScene()
+ {
+ this.ChangeScene(1);
+ }
+
+ public void PreviousScene()
+ {
+ this.ChangeScene(-1);
+ }
+
+ public void Close()
+ {
+ SceneManager.LoadScene("MainMenu");
+ }
+
+ private void ChangeScene(int del)
+ {
+ this.Scenes[this.SceneNum].gameObject.SetActive(false);
+ this.DotParent.GetChild(this.SceneNum).localScale = Vector3.one;
+ this.SceneNum = Mathf.Clamp(this.SceneNum + del, 0, this.Scenes.Length - 1);
+ this.Scenes[this.SceneNum].gameObject.SetActive(true);
+ this.DotParent.GetChild(this.SceneNum).localScale = new Vector3(1.5f, 1.5f, 1.5f);
+ this.leftButton.gameObject.SetActive(this.SceneNum > 0);
+ this.rightButton.gameObject.SetActive(this.SceneNum < this.Scenes.Length - 1);
+ }
+}
diff --git a/Client/Assembly-CSharp/HudManager.cs b/Client/Assembly-CSharp/HudManager.cs
new file mode 100644
index 0000000..d3905ea
--- /dev/null
+++ b/Client/Assembly-CSharp/HudManager.cs
@@ -0,0 +1,312 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Text;
+using InnerNet;
+using UnityEngine;
+
+public class HudManager : DestroyableSingleton<HudManager>
+{
+ public Coroutine ReactorFlash { get; set; }
+
+ public Coroutine OxyFlash { get; set; }
+
+ public MeetingHud MeetingPrefab;
+
+ public KillButtonManager KillButton;
+
+ public UseButtonManager UseButton;
+
+ public ReportButtonManager ReportButton;
+
+ public TextRenderer GameSettings;
+
+ public GameObject TaskStuff;
+
+ public ChatController Chat;
+
+ public DialogueBox Dialogue;
+
+ public TextRenderer TaskText;
+
+ public Transform TaskCompleteOverlay;
+
+ private float taskDirtyTimer;
+
+ public MeshRenderer ShadowQuad;
+
+ public SpriteRenderer FullScreen;
+
+ public SpriteRenderer MapButton;
+
+ public KillOverlay KillOverlay;
+
+ public IVirtualJoystick joystick;
+
+ public MonoBehaviour[] Joysticks;
+
+ public DiscussBehaviour discussEmblem;
+
+ public ShhhBehaviour shhhEmblem;
+
+ public IntroCutscene IntroPrefab;
+
+ public OptionsMenuBehaviour GameMenu;
+
+ public NotificationPopper Notifier;
+
+ public RoomTracker roomTracker;
+
+ public AudioClip SabotageSound;
+
+ public AudioClip TaskCompleteSound;
+
+ public AudioClip TaskUpdateSound;
+
+ private StringBuilder tasksString = new StringBuilder();
+
+ public void Start()
+ {
+ this.SetTouchType(SaveManager.TouchConfig);
+ }
+
+ public void ShowTaskComplete()
+ {
+ base.StartCoroutine(this.CoTaskComplete());
+ }
+
+ private IEnumerator CoTaskComplete()
+ {
+ if (Constants.ShouldPlaySfx())
+ {
+ SoundManager.Instance.PlaySound(this.TaskCompleteSound, false, 1f);
+ }
+ this.TaskCompleteOverlay.gameObject.SetActive(true);
+ yield return Effects.Slide2D(this.TaskCompleteOverlay, new Vector2(0f, -8f), Vector2.zero, 0.25f);
+ for (float time = 0f; time < 0.75f; time += Time.deltaTime)
+ {
+ yield return null;
+ }
+ yield return Effects.Slide2D(this.TaskCompleteOverlay, Vector2.zero, new Vector2(0f, 8f), 0.25f);
+ this.TaskCompleteOverlay.gameObject.SetActive(false);
+ yield break;
+ }
+
+ public void SetJoystickSize(float size)
+ {
+ if (this.joystick != null && this.joystick is VirtualJoystick)
+ {
+ VirtualJoystick virtualJoystick = (VirtualJoystick)this.joystick;
+ virtualJoystick.transform.localScale = new Vector3(size, size, 1f);
+ AspectPosition component = virtualJoystick.GetComponent<AspectPosition>();
+ float num = Mathf.Lerp(0.65f, 1.1f, FloatRange.ReverseLerp(size, 0.5f, 1.5f));
+ component.DistanceFromEdge = new Vector3(num, num, -10f);
+ component.AdjustPosition();
+ }
+ }
+
+ public void SetTouchType(int type)
+ {
+ if (this.joystick != null && !(this.joystick is KeyboardJoystick))
+ {
+ UnityEngine.Object.Destroy((this.joystick as MonoBehaviour).gameObject);
+ }
+ MonoBehaviour monoBehaviour = UnityEngine.Object.Instantiate<MonoBehaviour>(this.Joysticks[Mathf.Clamp(type + 1, 1, this.Joysticks.Length)]);
+ monoBehaviour.transform.SetParent(base.transform, false);
+ this.joystick = monoBehaviour.GetComponent<IVirtualJoystick>();
+ }
+
+ public void OpenMap()
+ {
+ this.ShowMap(delegate(MapBehaviour m)
+ {
+ m.ShowNormalMap();
+ });
+ }
+
+ public void ShowMap(Action<MapBehaviour> mapAction)
+ {
+ if (!ShipStatus.Instance)
+ {
+ return;
+ }
+ if (!MapBehaviour.Instance)
+ {
+ MapBehaviour.Instance = UnityEngine.Object.Instantiate<MapBehaviour>(ShipStatus.Instance.MapPrefab, base.transform);
+ MapBehaviour.Instance.gameObject.SetActive(false);
+ }
+ mapAction(MapBehaviour.Instance);
+ }
+
+ public void SetHudActive(bool isActive)
+ {
+ DestroyableSingleton<HudManager>.Instance.UseButton.gameObject.SetActive(isActive);
+ DestroyableSingleton<HudManager>.Instance.UseButton.Refresh();
+ DestroyableSingleton<HudManager>.Instance.ReportButton.gameObject.SetActive(isActive);
+ GameData.PlayerInfo data = PlayerControl.LocalPlayer.Data;
+ DestroyableSingleton<HudManager>.Instance.KillButton.gameObject.SetActive(isActive && data.IsImpostor && !data.IsDead);
+ DestroyableSingleton<HudManager>.Instance.TaskText.transform.parent.gameObject.SetActive(isActive);
+ }
+
+ public void FixedUpdate()
+ {
+ this.taskDirtyTimer += Time.fixedDeltaTime;
+ if (this.taskDirtyTimer > 0.25f)
+ {
+ this.taskDirtyTimer = 0f;
+ if (!PlayerControl.LocalPlayer)
+ {
+ this.TaskText.Text = string.Empty;
+ return;
+ }
+ GameData.PlayerInfo data = PlayerControl.LocalPlayer.Data;
+ if (data == null)
+ {
+ return;
+ }
+ bool isImpostor = data.IsImpostor;
+ this.tasksString.Clear();
+ if (PlayerControl.LocalPlayer.myTasks.Count == 0)
+ {
+ this.tasksString.Append("None");
+ }
+ else
+ {
+ for (int i = 0; i < PlayerControl.LocalPlayer.myTasks.Count; i++)
+ {
+ PlayerTask playerTask = PlayerControl.LocalPlayer.myTasks[i];
+ if (playerTask)
+ {
+ if (playerTask.TaskType == TaskTypes.FixComms && !isImpostor)
+ {
+ this.tasksString.Clear();
+ playerTask.AppendTaskText(this.tasksString);
+ break;
+ }
+ playerTask.AppendTaskText(this.tasksString);
+ }
+ }
+ this.tasksString.TrimEnd();
+ }
+ this.TaskText.Text = this.tasksString.ToString();
+ }
+ }
+
+ public IEnumerator ShowEmblem(bool shhh)
+ {
+ if (shhh)
+ {
+ this.shhhEmblem.gameObject.SetActive(true);
+ yield return this.shhhEmblem.PlayAnimation();
+ this.shhhEmblem.gameObject.SetActive(false);
+ }
+ else
+ {
+ this.discussEmblem.gameObject.SetActive(true);
+ yield return this.discussEmblem.PlayAnimation();
+ this.discussEmblem.gameObject.SetActive(false);
+ }
+ yield break;
+ }
+
+ public void StartReactorFlash()
+ {
+ if (this.ReactorFlash == null)
+ {
+ this.ReactorFlash = base.StartCoroutine(this.CoReactorFlash());
+ }
+ }
+
+ public void StartOxyFlash()
+ {
+ if (this.OxyFlash == null)
+ {
+ this.OxyFlash = base.StartCoroutine(this.CoReactorFlash());
+ }
+ }
+
+ public void ShowPopUp(string text)
+ {
+ this.Dialogue.Show(text);
+ }
+
+ public void StopReactorFlash()
+ {
+ if (this.ReactorFlash != null)
+ {
+ base.StopCoroutine(this.ReactorFlash);
+ this.FullScreen.enabled = false;
+ this.ReactorFlash = null;
+ }
+ }
+
+ public void StopOxyFlash()
+ {
+ if (this.OxyFlash != null)
+ {
+ base.StopCoroutine(this.OxyFlash);
+ this.FullScreen.enabled = false;
+ this.OxyFlash = null;
+ }
+ }
+
+ public IEnumerator CoFadeFullScreen(Color source, Color target, float duration = 0.2f)
+ {
+ if (this.FullScreen.enabled && this.FullScreen.color == target)
+ {
+ yield break;
+ }
+ this.FullScreen.enabled = true;
+ for (float t = 0f; t < duration; t += Time.deltaTime)
+ {
+ this.FullScreen.color = Color.Lerp(source, target, t / duration);
+ yield return null;
+ }
+ this.FullScreen.color = target;
+ if (target.a < 0.05f)
+ {
+ this.FullScreen.enabled = false;
+ }
+ yield break;
+ }
+
+ private IEnumerator CoReactorFlash()
+ {
+ WaitForSeconds wait = new WaitForSeconds(1f);
+ this.FullScreen.color = new Color(1f, 0f, 0f, 0.37254903f);
+ for (;;)
+ {
+ this.FullScreen.enabled = !this.FullScreen.enabled;
+ SoundManager.Instance.PlaySound(this.SabotageSound, false, 1f);
+ yield return wait;
+ }
+ yield break;
+ }
+
+ public IEnumerator CoShowIntro(List<PlayerControl> yourTeam)
+ {
+ DestroyableSingleton<HudManager>.Instance.FullScreen.transform.localPosition = new Vector3(0f, 0f, -250f);
+ yield return DestroyableSingleton<HudManager>.Instance.ShowEmblem(true);
+ IntroCutscene introCutscene = UnityEngine.Object.Instantiate<IntroCutscene>(this.IntroPrefab, base.transform);
+ yield return introCutscene.CoBegin(yourTeam, PlayerControl.LocalPlayer.Data.IsImpostor);
+ PlayerControl.LocalPlayer.SetKillTimer(10f);
+ ((SabotageSystemType)ShipStatus.Instance.Systems[SystemTypes.Sabotage]).ForceSabTime(10f);
+ yield return this.CoFadeFullScreen(Color.black, Color.clear, 0.2f);
+ DestroyableSingleton<HudManager>.Instance.FullScreen.transform.localPosition = new Vector3(0f, 0f, -500f);
+ yield break;
+ }
+
+ public void OpenMeetingRoom(PlayerControl reporter)
+ {
+ if (MeetingHud.Instance)
+ {
+ return;
+ }
+ Debug.Log("Opening meeting room: " + reporter);
+ ShipStatus.Instance.RepairSystem(SystemTypes.Reactor, PlayerControl.LocalPlayer, 16);
+ ShipStatus.Instance.RepairSystem(SystemTypes.LifeSupp, PlayerControl.LocalPlayer, 16);
+ MeetingHud.Instance = UnityEngine.Object.Instantiate<MeetingHud>(this.MeetingPrefab);
+ MeetingHud.Instance.ServerStart(reporter.PlayerId);
+ AmongUsClient.Instance.Spawn(MeetingHud.Instance, -2, SpawnFlags.None);
+ }
+}
diff --git a/Client/Assembly-CSharp/HudOverrideSystemType.cs b/Client/Assembly-CSharp/HudOverrideSystemType.cs
new file mode 100644
index 0000000..82558cb
--- /dev/null
+++ b/Client/Assembly-CSharp/HudOverrideSystemType.cs
@@ -0,0 +1,40 @@
+using System;
+using Hazel;
+
+internal class HudOverrideSystemType : ISystemType, IActivatable
+{
+ public bool IsActive { get; private set; }
+
+ public const byte DamageBit = 128;
+
+ public const byte TaskMask = 127;
+
+ public bool Detoriorate(float deltaTime)
+ {
+ if (this.IsActive && !PlayerTask.PlayerHasHudTask(PlayerControl.LocalPlayer))
+ {
+ PlayerControl.LocalPlayer.AddSystemTask(SystemTypes.Comms);
+ }
+ return false;
+ }
+
+ public void RepairDamage(PlayerControl player, byte amount)
+ {
+ if ((amount & 128) > 0)
+ {
+ this.IsActive = true;
+ return;
+ }
+ this.IsActive = false;
+ }
+
+ public void Serialize(MessageWriter writer, bool initialState)
+ {
+ writer.Write(this.IsActive);
+ }
+
+ public void Deserialize(MessageReader reader, bool initialState)
+ {
+ this.IsActive = reader.ReadBoolean();
+ }
+}
diff --git a/Client/Assembly-CSharp/HudOverrideTask.cs b/Client/Assembly-CSharp/HudOverrideTask.cs
new file mode 100644
index 0000000..eb47b87
--- /dev/null
+++ b/Client/Assembly-CSharp/HudOverrideTask.cs
@@ -0,0 +1,82 @@
+using System;
+using System.Linq;
+using System.Text;
+using UnityEngine;
+
+public class HudOverrideTask : SabotageTask
+{
+ public override int TaskStep
+ {
+ get
+ {
+ if (!this.isComplete)
+ {
+ return 0;
+ }
+ return 1;
+ }
+ }
+
+ public override bool IsComplete
+ {
+ get
+ {
+ return this.isComplete;
+ }
+ }
+
+ private bool isComplete;
+
+ private HudOverrideSystemType system;
+
+ private bool even;
+
+ public override void Initialize()
+ {
+ ShipStatus instance = ShipStatus.Instance;
+ this.system = (instance.Systems[SystemTypes.Comms] as HudOverrideSystemType);
+ base.SetupArrows();
+ }
+
+ private void FixedUpdate()
+ {
+ if (this.isComplete)
+ {
+ return;
+ }
+ if (!this.system.IsActive)
+ {
+ this.Complete();
+ }
+ }
+
+ public override bool ValidConsole(global::Console console)
+ {
+ return console.TaskTypes.Contains(TaskTypes.FixComms);
+ }
+
+ public override void Complete()
+ {
+ this.isComplete = true;
+ PlayerControl.LocalPlayer.RemoveTask(this);
+ if (this.didContribute)
+ {
+ StatsManager instance = StatsManager.Instance;
+ uint sabsFixed = instance.SabsFixed;
+ instance.SabsFixed = sabsFixed + 1U;
+ }
+ }
+
+ public override void AppendTaskText(StringBuilder sb)
+ {
+ this.even = !this.even;
+ Color color = this.even ? Color.yellow : Color.red;
+ sb.Append(color.ToTextColor());
+ sb.Append(DestroyableSingleton<TranslationController>.Instance.GetString(TaskTypes.FixComms));
+ sb.Append("[]");
+ for (int i = 0; i < this.Arrows.Length; i++)
+ {
+ this.Arrows[i].image.color = color;
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/IActivatable.cs b/Client/Assembly-CSharp/IActivatable.cs
new file mode 100644
index 0000000..fd4ecbf
--- /dev/null
+++ b/Client/Assembly-CSharp/IActivatable.cs
@@ -0,0 +1,6 @@
+using System;
+
+public interface IActivatable
+{
+ bool IsActive { get; }
+}
diff --git a/Client/Assembly-CSharp/IBuyable.cs b/Client/Assembly-CSharp/IBuyable.cs
new file mode 100644
index 0000000..b19fbb4
--- /dev/null
+++ b/Client/Assembly-CSharp/IBuyable.cs
@@ -0,0 +1,6 @@
+using System;
+
+public interface IBuyable
+{
+ string ProdId { get; }
+}
diff --git a/Client/Assembly-CSharp/IBytesSerializable.cs b/Client/Assembly-CSharp/IBytesSerializable.cs
new file mode 100644
index 0000000..e256d7e
--- /dev/null
+++ b/Client/Assembly-CSharp/IBytesSerializable.cs
@@ -0,0 +1,6 @@
+using System;
+
+public interface IBytesSerializable
+{
+ byte[] ToBytes();
+}
diff --git a/Client/Assembly-CSharp/IConnectButton.cs b/Client/Assembly-CSharp/IConnectButton.cs
new file mode 100644
index 0000000..5f2848a
--- /dev/null
+++ b/Client/Assembly-CSharp/IConnectButton.cs
@@ -0,0 +1,8 @@
+using System;
+
+public interface IConnectButton
+{
+ void StartIcon();
+
+ void StopIcon();
+}
diff --git a/Client/Assembly-CSharp/IDisconnectHandler.cs b/Client/Assembly-CSharp/IDisconnectHandler.cs
new file mode 100644
index 0000000..c2482fe
--- /dev/null
+++ b/Client/Assembly-CSharp/IDisconnectHandler.cs
@@ -0,0 +1,9 @@
+using System;
+using InnerNet;
+
+public interface IDisconnectHandler
+{
+ void HandleDisconnect(PlayerControl pc, DisconnectReasons reason);
+
+ void HandleDisconnect();
+}
diff --git a/Client/Assembly-CSharp/IFocusHolder.cs b/Client/Assembly-CSharp/IFocusHolder.cs
new file mode 100644
index 0000000..1342fa8
--- /dev/null
+++ b/Client/Assembly-CSharp/IFocusHolder.cs
@@ -0,0 +1,11 @@
+using System;
+using UnityEngine;
+
+public interface IFocusHolder
+{
+ void GiveFocus();
+
+ void LoseFocus();
+
+ bool CheckCollision(Vector2 pt);
+}
diff --git a/Client/Assembly-CSharp/IGameListHandler.cs b/Client/Assembly-CSharp/IGameListHandler.cs
new file mode 100644
index 0000000..4be55da
--- /dev/null
+++ b/Client/Assembly-CSharp/IGameListHandler.cs
@@ -0,0 +1,8 @@
+using System;
+using System.Collections.Generic;
+using InnerNet;
+
+public interface IGameListHandler
+{
+ void HandleList(int totalGames, List<GameListing> availableGames);
+}
diff --git a/Client/Assembly-CSharp/ILocationActivate.cs b/Client/Assembly-CSharp/ILocationActivate.cs
new file mode 100644
index 0000000..2d9bb88
--- /dev/null
+++ b/Client/Assembly-CSharp/ILocationActivate.cs
@@ -0,0 +1,6 @@
+using System;
+
+internal interface ILocationActivate
+{
+ void LocationUse(PlayerControl pc);
+}
diff --git a/Client/Assembly-CSharp/IObjectPool.cs b/Client/Assembly-CSharp/IObjectPool.cs
new file mode 100644
index 0000000..312a917
--- /dev/null
+++ b/Client/Assembly-CSharp/IObjectPool.cs
@@ -0,0 +1,13 @@
+using System;
+using UnityEngine;
+
+public abstract class IObjectPool : MonoBehaviour
+{
+ public abstract int InUse { get; }
+
+ public abstract int NotInUse { get; }
+
+ public abstract T Get<T>() where T : PoolableBehavior;
+
+ public abstract void Reclaim(PoolableBehavior obj);
+}
diff --git a/Client/Assembly-CSharp/ISoundPlayer.cs b/Client/Assembly-CSharp/ISoundPlayer.cs
new file mode 100644
index 0000000..0b5d3ca
--- /dev/null
+++ b/Client/Assembly-CSharp/ISoundPlayer.cs
@@ -0,0 +1,11 @@
+using System;
+using UnityEngine;
+
+public interface ISoundPlayer
+{
+ string Name { get; set; }
+
+ AudioSource Player { get; set; }
+
+ void Update(float dt);
+}
diff --git a/Client/Assembly-CSharp/ISystemType.cs b/Client/Assembly-CSharp/ISystemType.cs
new file mode 100644
index 0000000..6e6d4fd
--- /dev/null
+++ b/Client/Assembly-CSharp/ISystemType.cs
@@ -0,0 +1,15 @@
+using System;
+using Hazel;
+
+// 需要同步的内容,比如门DoorsSystemType
+
+public interface ISystemType
+{
+ bool Detoriorate(float deltaTime);
+
+ void RepairDamage(PlayerControl player, byte amount);
+
+ void Serialize(MessageWriter writer, bool initialState);
+
+ void Deserialize(MessageReader reader, bool initialState);
+}
diff --git a/Client/Assembly-CSharp/ITranslatedText.cs b/Client/Assembly-CSharp/ITranslatedText.cs
new file mode 100644
index 0000000..f6f1857
--- /dev/null
+++ b/Client/Assembly-CSharp/ITranslatedText.cs
@@ -0,0 +1,6 @@
+using System;
+
+public interface ITranslatedText
+{
+ void ResetText();
+}
diff --git a/Client/Assembly-CSharp/IUsable.cs b/Client/Assembly-CSharp/IUsable.cs
new file mode 100644
index 0000000..12053be
--- /dev/null
+++ b/Client/Assembly-CSharp/IUsable.cs
@@ -0,0 +1,14 @@
+using System;
+
+public interface IUsable
+{
+ float UsableDistance { get; }
+
+ float PercentCool { get; }
+
+ void SetOutline(bool on, bool mainTarget);
+
+ float CanUse(GameData.PlayerInfo pc, out bool canUse, out bool couldUse);
+
+ void Use();
+}
diff --git a/Client/Assembly-CSharp/IVirtualJoystick.cs b/Client/Assembly-CSharp/IVirtualJoystick.cs
new file mode 100644
index 0000000..fdfeef8
--- /dev/null
+++ b/Client/Assembly-CSharp/IVirtualJoystick.cs
@@ -0,0 +1,7 @@
+using System;
+using UnityEngine;
+
+public interface IVirtualJoystick
+{
+ Vector2 Delta { get; }
+}
diff --git a/Client/Assembly-CSharp/ImageData.cs b/Client/Assembly-CSharp/ImageData.cs
new file mode 100644
index 0000000..762189b
--- /dev/null
+++ b/Client/Assembly-CSharp/ImageData.cs
@@ -0,0 +1,10 @@
+using System;
+using UnityEngine;
+
+[Serializable]
+public struct ImageData
+{
+ public ImageNames Name;
+
+ public Sprite Sprite;
+}
diff --git a/Client/Assembly-CSharp/ImageNames.cs b/Client/Assembly-CSharp/ImageNames.cs
new file mode 100644
index 0000000..e70e8a8
--- /dev/null
+++ b/Client/Assembly-CSharp/ImageNames.cs
@@ -0,0 +1,12 @@
+using System;
+
+public enum ImageNames
+{
+ LocalButton,
+ OnlineButton,
+ HowToPlayButton,
+ FreeplayButton,
+ HostHeader,
+ PublicHeader,
+ PrivateHeader
+}
diff --git a/Client/Assembly-CSharp/ImageTranslator.cs b/Client/Assembly-CSharp/ImageTranslator.cs
new file mode 100644
index 0000000..7ea508b
--- /dev/null
+++ b/Client/Assembly-CSharp/ImageTranslator.cs
@@ -0,0 +1,24 @@
+using System;
+using UnityEngine;
+
+[RequireComponent(typeof(SpriteRenderer))]
+public class ImageTranslator : MonoBehaviour, ITranslatedText
+{
+ public ImageNames TargetImage;
+
+ public void ResetText()
+ {
+ base.GetComponent<SpriteRenderer>().sprite = DestroyableSingleton<TranslationController>.Instance.GetImage(this.TargetImage);
+ }
+
+ public void Start()
+ {
+ DestroyableSingleton<TranslationController>.Instance.ActiveTexts.Add(this);
+ this.ResetText();
+ }
+
+ public void OnDestroy()
+ {
+ DestroyableSingleton<TranslationController>.Instance.ActiveTexts.Remove(this);
+ }
+}
diff --git a/Client/Assembly-CSharp/ImportantTextTask.cs b/Client/Assembly-CSharp/ImportantTextTask.cs
new file mode 100644
index 0000000..7eb28b7
--- /dev/null
+++ b/Client/Assembly-CSharp/ImportantTextTask.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Text;
+
+public class ImportantTextTask : PlayerTask
+{
+ public override int TaskStep
+ {
+ get
+ {
+ return 0;
+ }
+ }
+
+ public override bool IsComplete
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ public string Text;
+
+ public override void Initialize()
+ {
+ }
+
+ public override bool ValidConsole(global::Console console)
+ {
+ return false;
+ }
+
+ public override void Complete()
+ {
+ }
+
+ public override void AppendTaskText(StringBuilder sb)
+ {
+ sb.AppendLine("[FF0000FF]" + this.Text + "[]");
+ }
+}
diff --git a/Client/Assembly-CSharp/InfectedOverlay.cs b/Client/Assembly-CSharp/InfectedOverlay.cs
new file mode 100644
index 0000000..53d7942
--- /dev/null
+++ b/Client/Assembly-CSharp/InfectedOverlay.cs
@@ -0,0 +1,51 @@
+using System;
+using UnityEngine;
+
+public class InfectedOverlay : MonoBehaviour
+{
+ public bool CanUseDoors
+ {
+ get
+ {
+ return !this.SabSystem.AnyActive;
+ }
+ }
+
+ public bool CanUseSpecial
+ {
+ get
+ {
+ return this.SabSystem.Timer <= 0f && !this.doors.IsActive && !this.SabSystem.AnyActive;
+ }
+ }
+
+ public MapRoom[] rooms;
+
+ private IActivatable doors;
+
+ private SabotageSystemType SabSystem;
+
+ public void Start()
+ {
+ for (int i = 0; i < this.rooms.Length; i++)
+ {
+ this.rooms[i].Parent = this;
+ }
+ this.SabSystem = (SabotageSystemType)ShipStatus.Instance.Systems[SystemTypes.Sabotage];
+ this.doors = (IActivatable)ShipStatus.Instance.Systems[SystemTypes.Doors];
+ }
+
+ private void FixedUpdate()
+ {
+ if (this.doors == null)
+ {
+ return;
+ }
+ float specialActive = this.doors.IsActive ? 1f : this.SabSystem.PercentCool;
+ for (int i = 0; i < this.rooms.Length; i++)
+ {
+ this.rooms[i].SetSpecialActive(specialActive);
+ this.rooms[i].OOBUpdate();
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/InnerNet/AlterGameTags.cs b/Client/Assembly-CSharp/InnerNet/AlterGameTags.cs
new file mode 100644
index 0000000..730126b
--- /dev/null
+++ b/Client/Assembly-CSharp/InnerNet/AlterGameTags.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace InnerNet
+{
+ public static class AlterGameTags
+ {
+ public const byte ChangePrivacy = 1;
+ }
+}
diff --git a/Client/Assembly-CSharp/InnerNet/ClientData.cs b/Client/Assembly-CSharp/InnerNet/ClientData.cs
new file mode 100644
index 0000000..780d6ae
--- /dev/null
+++ b/Client/Assembly-CSharp/InnerNet/ClientData.cs
@@ -0,0 +1,21 @@
+using System;
+
+namespace InnerNet
+{
+ [Serializable]
+ public class ClientData
+ {
+ public int Id;
+
+ public bool InScene;
+
+ public bool IsReady;
+
+ public PlayerControl Character;
+
+ public ClientData(int id)
+ {
+ this.Id = id;
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/InnerNet/DisconnectReasons.cs b/Client/Assembly-CSharp/InnerNet/DisconnectReasons.cs
new file mode 100644
index 0000000..1d9c4a6
--- /dev/null
+++ b/Client/Assembly-CSharp/InnerNet/DisconnectReasons.cs
@@ -0,0 +1,25 @@
+using System;
+
+namespace InnerNet
+{
+ public enum DisconnectReasons
+ {
+ ExitGame,
+ GameFull,
+ GameStarted,
+ GameNotFound,
+ IncorrectVersion = 5,
+ Banned,
+ Kicked,
+ Custom,
+ Destroy = 16,
+ Error,
+ IncorrectGame,
+ ServerRequest,
+ ServerFull,
+ IntentionalLeaving = 208,
+ FocusLostBackground = 207,
+ FocusLost = 209,
+ NewConnection
+ }
+}
diff --git a/Client/Assembly-CSharp/InnerNet/GameKeywords.cs b/Client/Assembly-CSharp/InnerNet/GameKeywords.cs
new file mode 100644
index 0000000..76eaabc
--- /dev/null
+++ b/Client/Assembly-CSharp/InnerNet/GameKeywords.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace InnerNet
+{
+ [Flags]
+ public enum GameKeywords : uint
+ {
+ All = 0U,
+ AllLanguages = 31U,
+ English = 1U,
+ Spanish = 2U,
+ Korean = 4U,
+ Russian = 8U,
+ Portuguese = 16U
+ }
+}
diff --git a/Client/Assembly-CSharp/InnerNet/GameListing.cs b/Client/Assembly-CSharp/InnerNet/GameListing.cs
new file mode 100644
index 0000000..d10ea34
--- /dev/null
+++ b/Client/Assembly-CSharp/InnerNet/GameListing.cs
@@ -0,0 +1,30 @@
+using System;
+
+namespace InnerNet
+{
+ [Serializable]
+ public struct GameListing
+ {
+ public int GameId;
+
+ public byte PlayerCount;
+
+ public byte ImpostorCount;
+
+ public byte MaxPlayers;
+
+ public int Age;
+
+ public string HostName;
+
+ public GameListing(int id, byte numImpostors, byte playerCount, byte maxPlayers, int age, string host)
+ {
+ this.GameId = id;
+ this.ImpostorCount = numImpostors;
+ this.PlayerCount = playerCount;
+ this.MaxPlayers = maxPlayers;
+ this.Age = age;
+ this.HostName = host;
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/InnerNet/GameStates.cs b/Client/Assembly-CSharp/InnerNet/GameStates.cs
new file mode 100644
index 0000000..812ffa2
--- /dev/null
+++ b/Client/Assembly-CSharp/InnerNet/GameStates.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace InnerNet
+{
+ public enum GameStates : byte
+ {
+ NotStarted,
+ Started,
+ Ended,
+ Destroyed
+ }
+}
diff --git a/Client/Assembly-CSharp/InnerNet/InnerDiscover.cs b/Client/Assembly-CSharp/InnerNet/InnerDiscover.cs
new file mode 100644
index 0000000..b596ef5
--- /dev/null
+++ b/Client/Assembly-CSharp/InnerNet/InnerDiscover.cs
@@ -0,0 +1,114 @@
+using System;
+using System.Collections;
+using System.Net.Sockets;
+using Hazel.Udp;
+using UnityEngine;
+
+namespace InnerNet
+{
+ public class InnerDiscover : DestroyableSingleton<InnerDiscover>
+ {
+ public event Action<BroadcastPacket> OnPacketGet;
+
+ private UdpBroadcastListener listener;
+
+ private UdpBroadcaster sender;
+
+ public int Port = 47777;
+
+ public float Interval = 1f;
+
+ public void StartAsServer(string data)
+ {
+ bool flag = this.sender == null;
+ if (flag)
+ {
+ this.sender = new UdpBroadcaster(this.Port);
+ }
+ this.sender.SetData(data);
+ if (flag)
+ {
+ base.StartCoroutine(this.RunServer());
+ }
+ }
+
+ private IEnumerator RunServer()
+ {
+ while (this.sender != null)
+ {
+ this.sender.Broadcast();
+ for (float timer = 0f; timer < this.Interval; timer += Time.deltaTime)
+ {
+ yield return null;
+ }
+ }
+ yield break;
+ }
+
+ public void StopServer()
+ {
+ if (this.sender != null)
+ {
+ this.sender.Dispose();
+ this.sender = null;
+ }
+ }
+
+ public void StartAsClient()
+ {
+ if (this.listener == null)
+ {
+ try
+ {
+ this.listener = new UdpBroadcastListener(this.Port);
+ this.listener.StartListen();
+ base.StartCoroutine(this.RunClient());
+ }
+ catch (SocketException)
+ {
+ AmongUsClient.Instance.LastDisconnectReason = DisconnectReasons.Custom;
+ AmongUsClient.Instance.LastCustomDisconnect = "Couldn't start local network listener. You may need to restart Among Us.";
+ DestroyableSingleton<DisconnectPopup>.Instance.Show();
+ }
+ }
+ }
+
+ private IEnumerator RunClient()
+ {
+ while (this.listener != null)
+ {
+ if (!this.listener.Running)
+ {
+ this.listener.StartListen();
+ }
+ BroadcastPacket[] packets = this.listener.GetPackets();
+ for (int i = 0; i < packets.Length; i++)
+ {
+ Action<BroadcastPacket> onPacketGet = this.OnPacketGet;
+ if (onPacketGet != null)
+ {
+ onPacketGet(packets[i]);
+ }
+ }
+ yield return null;
+ }
+ yield break;
+ }
+
+ public void StopClient()
+ {
+ if (this.listener != null)
+ {
+ this.listener.Dispose();
+ this.listener = null;
+ }
+ }
+
+ public override void OnDestroy()
+ {
+ this.StopServer();
+ this.StopClient();
+ base.OnDestroy();
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/InnerNet/InnerNetClient.cs b/Client/Assembly-CSharp/InnerNet/InnerNetClient.cs
new file mode 100644
index 0000000..7cff87c
--- /dev/null
+++ b/Client/Assembly-CSharp/InnerNet/InnerNetClient.cs
@@ -0,0 +1,1726 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Threading;
+using Assets.CoreScripts;
+using Hazel;
+using Hazel.Udp;
+using UnityEngine;
+using UnityEngine.SceneManagement;
+
+namespace InnerNet
+{
+ public abstract class InnerNetClient : MonoBehaviour
+ {
+ private bool AmConnected
+ {
+ get
+ {
+ return this.connection != null;
+ }
+ }
+
+ public int Ping
+ {
+ get
+ {
+ if (this.connection == null)
+ {
+ return 0;
+ }
+ return (int)this.connection.AveragePingMs;
+ }
+ }
+
+ public bool AmHost
+ {
+ get
+ {
+ return this.HostId == this.ClientId;
+ }
+ }
+
+ public bool AmClient
+ {
+ get
+ {
+ return this.ClientId > 0;
+ }
+ }
+
+ public bool IsGamePublic { get; private set; }
+
+ public bool IsGameStarted
+ {
+ get
+ {
+ return this.GameState == InnerNetClient.GameStates.Started;
+ }
+ }
+
+ public bool IsGameOver
+ {
+ get
+ {
+ return this.GameState == InnerNetClient.GameStates.Ended;
+ }
+ }
+
+ private static readonly DisconnectReasons[] disconnectReasons = new DisconnectReasons[]
+ {
+ DisconnectReasons.Error,
+ DisconnectReasons.GameFull,
+ DisconnectReasons.GameStarted,
+ DisconnectReasons.GameNotFound,
+ DisconnectReasons.IncorrectVersion,
+ DisconnectReasons.Banned,
+ DisconnectReasons.Kicked,
+ DisconnectReasons.ServerFull,
+ DisconnectReasons.Custom
+ };
+
+ public const int NoClientId = -1;
+
+ private string networkAddress = "127.0.0.1";
+
+ private int networkPort;
+
+ private UdpClientConnection connection;
+
+ public MatchMakerModes mode;
+
+ public int GameId = 32;
+
+ public int HostId;
+
+ public int ClientId = -1;
+
+ public List<ClientData> allClients = new List<ClientData>();
+
+ public DisconnectReasons LastDisconnectReason;
+
+ public string LastCustomDisconnect;
+
+ private readonly List<Action> PreSpawnDispatcher = new List<Action>();
+
+ //c 网络消息队列,网络线程写入,主线程调用
+ private readonly List<Action> Dispatcher = new List<Action>();
+
+ public InnerNetClient.GameStates GameState;
+
+ private List<Action> TempQueue = new List<Action>();
+
+ private volatile bool appPaused;
+
+ public const int CurrentClient = -3;
+
+ public const int InvalidClient = -2;
+
+ internal const byte DataFlag = 1;
+
+ internal const byte RpcFlag = 2;
+
+ internal const byte SpawnFlag = 4;
+
+ internal const byte DespawnFlag = 5;
+
+ internal const byte SceneChangeFlag = 6;
+
+ internal const byte ReadyFlag = 7;
+
+ internal const byte ChangeSettingsFlag = 8;
+
+ // 每次发送数据的间隔,每过0.1s发送一次
+ public float MinSendInterval = 0.1f;
+
+ private uint NetIdCnt = 1U;
+
+ private float timer;
+
+ public InnerNetObject[] SpawnableObjects;
+
+ private bool InOnlineScene;
+
+ private HashSet<uint> DestroyedObjects = new HashSet<uint>();
+
+ // 所有要同步的数据,包括InnerNetObject的所有派生类,场景内所有的对象的数据
+ public List<InnerNetObject> allObjects = new List<InnerNetObject>();
+
+ private Dictionary<uint, InnerNetObject> allObjectsFast = new Dictionary<uint, InnerNetObject>();
+
+ private MessageWriter[] Streams;
+
+ public enum GameStates
+ {
+ NotJoined,
+ Joined,
+ Started,
+ Ended
+ }
+
+ public void SetEndpoint(string addr, ushort port)
+ {
+ this.networkAddress = addr;
+ this.networkPort = (int)port;
+ }
+
+ public virtual void Start()
+ {
+ SceneManager.activeSceneChanged += delegate(Scene oldScene, Scene scene)
+ {
+ this.SendSceneChange(scene.name);
+ };
+ this.ClientId = -1;
+ this.GameId = 32;
+ }
+
+ private void SendOrDisconnect(MessageWriter msg)
+ {
+ try
+ {
+ this.connection.Send(msg);
+ }
+ catch
+ {
+ this.EnqueueDisconnect(DisconnectReasons.Error, "Failed to send message");
+ }
+ }
+
+ public ClientData GetHost()
+ {
+ List<ClientData> obj = this.allClients;
+ lock (obj)
+ {
+ for (int i = 0; i < this.allClients.Count; i++)
+ {
+ ClientData clientData = this.allClients[i];
+ if (clientData.Id == this.HostId)
+ {
+ return clientData;
+ }
+ }
+ }
+ return null;
+ }
+
+ public int GetClientIdFromCharacter(InnerNetObject character)
+ {
+ if (!character)
+ {
+ return -1;
+ }
+ List<ClientData> obj = this.allClients;
+ lock (obj)
+ {
+ for (int i = 0; i < this.allClients.Count; i++)
+ {
+ ClientData clientData = this.allClients[i];
+ if (clientData.Character == character)
+ {
+ return clientData.Id;
+ }
+ }
+ }
+ return -1;
+ }
+
+ public virtual void OnDestroy()
+ {
+ if (this.AmConnected)
+ {
+ this.DisconnectInternal(DisconnectReasons.Destroy, null);
+ }
+ }
+
+ public IEnumerator CoConnect()
+ {
+ if (this.AmConnected)
+ {
+ yield break;
+ }
+ DestroyableSingleton<DisconnectPopup>.Instance.Close();
+ this.LastDisconnectReason = DisconnectReasons.ExitGame;
+ this.NetIdCnt = 1U;
+ this.DestroyedObjects.Clear();
+ if (this.Streams == null)
+ {
+ this.Streams = new MessageWriter[2];
+ for (int i = 0; i < this.Streams.Length; i++)
+ {
+ this.Streams[i] = MessageWriter.Get((SendOption)i);
+ }
+ }
+ for (int j = 0; j < this.Streams.Length; j++)
+ {
+ MessageWriter messageWriter = this.Streams[j];
+ messageWriter.Clear((SendOption)j);
+ messageWriter.StartMessage(5);
+ messageWriter.Write(this.GameId);
+ }
+ IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Parse(this.networkAddress), this.networkPort);
+ this.connection = new UdpClientConnection(remoteEndPoint, IPMode.IPv4);
+ this.connection.KeepAliveInterval = 1500;
+ this.connection.DisconnectTimeout = 7500;
+ this.connection.ResendPingMultiplier = 2f;
+ this.connection.DataReceived += this.OnMessageReceived; // 注册到网络线程的消息处理函数
+ this.connection.Disconnected += this.OnDisconnect; // 连接断开时的回调函数
+ this.connection.ConnectAsync(this.GetConnectionData(), 5000);
+
+ yield return this.WaitWithTimeout(() => this.connection == null || this.connection.State == ConnectionState.Connected);
+
+ yield break;
+ }
+
+ private void Connection_DataReceivedRaw(byte[] data)
+ {
+ Debug.Log("Client Got: " + string.Join(" ", from b in data
+ select b.ToString()));
+ }
+
+ private void Connection_DataSentRaw(byte[] data, int length)
+ {
+ Debug.Log("Client Sent: " + string.Join(" ", (from b in data
+ select b.ToString()).ToArray<string>(), 0, length));
+ }
+
+ public void Connect(MatchMakerModes mode)
+ {
+ base.StartCoroutine(this.CoConnect(mode));
+ }
+
+ private IEnumerator CoConnect(MatchMakerModes mode)
+ {
+ if (this.mode != MatchMakerModes.None)
+ {
+ this.DisconnectInternal(DisconnectReasons.NewConnection, null);
+ }
+ this.mode = mode;
+ yield return this.CoConnect();
+ if (!this.AmConnected)
+ {
+ yield break;
+ }
+ MatchMakerModes matchMakerModes = this.mode;
+ if (matchMakerModes == MatchMakerModes.Client)
+ {
+ this.JoinGame();
+ yield return this.WaitWithTimeout(() => this.ClientId >= 0);
+ bool amConnected = this.AmConnected;
+ yield break;
+ }
+ if (matchMakerModes != MatchMakerModes.HostAndClient)
+ {
+ yield break;
+ }
+ this.GameId = 0;
+ PlayerControl.GameOptions = SaveManager.GameHostOptions;
+ this.HostGame(PlayerControl.GameOptions);
+ yield return this.WaitWithTimeout(() => this.GameId != 0);
+ if (!this.AmConnected)
+ {
+ yield break;
+ }
+ this.JoinGame();
+ yield return this.WaitWithTimeout(() => this.ClientId >= 0);
+ bool amConnected2 = this.AmConnected;
+ yield break;
+ }
+
+ public IEnumerator WaitForConnectionOrFail()
+ {
+ while (this.AmConnected)
+ {
+ switch (this.mode)
+ {
+ case MatchMakerModes.None:
+ goto IL_5F;
+ case MatchMakerModes.Client:
+ if (this.ClientId >= 0)
+ {
+ yield break;
+ }
+ break;
+ case MatchMakerModes.HostAndClient:
+ if (this.GameId != 0 && this.ClientId >= 0)
+ {
+ yield break;
+ }
+ break;
+ default:
+ goto IL_5F;
+ }
+ yield return null;
+ continue;
+ IL_5F:
+ yield break;
+ }
+ yield break;
+ }
+
+ private IEnumerator WaitWithTimeout(Func<bool> success)
+ {
+ bool failed = true;
+ for (float timer = 0f; timer < 5f; timer += Time.deltaTime)
+ {
+ if (success())
+ {
+ failed = false;
+ break;
+ }
+ if (!this.AmConnected)
+ {
+ yield break;
+ }
+ yield return null;
+ }
+ if (failed)
+ {
+ this.DisconnectInternal(DisconnectReasons.Error, null);
+ }
+ yield break;
+ }
+
+ //c 执行从网络线程收到的回调Dispatcher
+ public void Update()
+ {
+ if (Input.GetKeyDown(KeyCode.Return) && (Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt)))
+ {
+ ResolutionManager.ToggleFullscreen();
+ }
+ this.TempQueue.Clear();
+ List<Action> obj = this.Dispatcher;
+ lock (obj)
+ {
+ this.TempQueue.AddAll(this.Dispatcher);
+ this.Dispatcher.Clear();
+ }
+ for (int i = 0; i < this.TempQueue.Count; i++)
+ {
+ Action action = this.TempQueue[i];
+ try
+ {
+ action();
+ }
+ catch (Exception exception)
+ {
+ Debug.LogException(exception);
+ }
+ }
+ if (this.InOnlineScene)
+ {
+ this.TempQueue.Clear();
+ obj = this.PreSpawnDispatcher;
+ lock (obj)
+ {
+ this.TempQueue.AddAll(this.PreSpawnDispatcher);
+ this.PreSpawnDispatcher.Clear();
+ }
+ for (int j = 0; j < this.TempQueue.Count; j++)
+ {
+ Action action2 = this.TempQueue[j];
+ try
+ {
+ action2();
+ }
+ catch (Exception exception2)
+ {
+ Debug.LogException(exception2);
+ }
+ }
+ }
+ }
+
+ private void OnDisconnect(object sender, DisconnectedEventArgs e)
+ {
+ if (!e.Reason.Contains("The remote sent a"))
+ {
+ this.LastCustomDisconnect = "You disconnected from the server.\r\n\r\n" + e.Reason;
+ this.EnqueueDisconnect(DisconnectReasons.Custom, e.Reason);
+ return;
+ }
+ this.EnqueueDisconnect(DisconnectReasons.Error, e.Reason);
+ }
+
+ public void HandleDisconnect(DisconnectReasons reason, string stringReason = null)
+ {
+ base.StopAllCoroutines();
+ DestroyableSingleton<Telemetry>.Instance.WriteDisconnect(this.LastDisconnectReason);
+ this.DisconnectInternal(reason, stringReason);
+ this.OnDisconnected();
+ }
+
+ protected void EnqueueDisconnect(DisconnectReasons reason, string stringReason = null)
+ {
+ UdpClientConnection udpClientConnection = this.connection;
+ List<Action> dispatcher = this.Dispatcher;
+ lock (dispatcher)
+ {
+ this.Dispatcher.Add(delegate
+ {
+ this.HandleDisconnect(reason, stringReason);
+ });
+ }
+ }
+
+ protected void DisconnectInternal(DisconnectReasons reason, string stringReason = null)
+ {
+ if (reason != DisconnectReasons.NewConnection && reason != DisconnectReasons.FocusLostBackground)
+ {
+ this.LastDisconnectReason = reason;
+ if (reason != DisconnectReasons.ExitGame && DestroyableSingleton<DisconnectPopup>.InstanceExists)
+ {
+ DestroyableSingleton<DisconnectPopup>.Instance.Show();
+ }
+ }
+ if (this.mode == MatchMakerModes.HostAndClient)
+ {
+ this.GameId = 0;
+ }
+ if (this.mode == MatchMakerModes.Client || this.mode == MatchMakerModes.HostAndClient)
+ {
+ this.ClientId = -1;
+ }
+ this.mode = MatchMakerModes.None;
+ this.GameState = InnerNetClient.GameStates.NotJoined;
+ UdpClientConnection udpClientConnection = this.connection;
+ this.connection = null;
+ if (udpClientConnection != null)
+ {
+ try
+ {
+ udpClientConnection.Dispose();
+ }
+ catch (Exception exception)
+ {
+ Debug.LogException(exception);
+ }
+ }
+ if (DestroyableSingleton<InnerNetServer>.InstanceExists)
+ {
+ DestroyableSingleton<InnerNetServer>.Instance.StopServer();
+ }
+ List<Action> obj = this.Dispatcher;
+ lock (obj)
+ {
+ this.Dispatcher.Clear();
+ }
+ obj = this.PreSpawnDispatcher;
+ lock (obj)
+ {
+ this.PreSpawnDispatcher.Clear();
+ }
+ if (reason != DisconnectReasons.Error)
+ {
+ this.TempQueue.Clear();
+ }
+ this.allObjects.Clear();
+ this.allClients.Clear();
+ this.allObjectsFast.Clear();
+ }
+
+ public void HostGame(IBytesSerializable settings)
+ {
+ this.IsGamePublic = false;
+ MessageWriter messageWriter = MessageWriter.Get(SendOption.Reliable);
+ messageWriter.StartMessage(0);
+ messageWriter.WriteBytesAndSize(settings.ToBytes());
+ messageWriter.EndMessage();
+ this.SendOrDisconnect(messageWriter);
+ messageWriter.Recycle();
+ Debug.Log("Client requesting new game.");
+ }
+
+ public void JoinGame()
+ {
+ this.ClientId = -1;
+ if (!this.AmConnected)
+ {
+ this.HandleDisconnect(DisconnectReasons.Error, null);
+ return;
+ }
+ Debug.Log("Client joining game: " + InnerNetClient.IntToGameName(this.GameId));
+ MessageWriter messageWriter = MessageWriter.Get(SendOption.Reliable);
+ messageWriter.StartMessage(1);
+ messageWriter.Write(this.GameId);
+ messageWriter.EndMessage();
+ this.SendOrDisconnect(messageWriter);
+ messageWriter.Recycle();
+ }
+
+ public bool CanBan()
+ {
+ return this.AmHost && !this.IsGameStarted;
+ }
+
+ public bool CanKick()
+ {
+ return this.IsGameStarted || this.AmHost;
+ }
+
+ public void KickPlayer(int clientId, bool ban)
+ {
+ if (!this.AmHost)
+ {
+ return;
+ }
+ MessageWriter messageWriter = MessageWriter.Get(SendOption.Reliable);
+ messageWriter.StartMessage(11);
+ messageWriter.Write(this.GameId);
+ messageWriter.WritePacked(clientId);
+ messageWriter.Write(ban);
+ messageWriter.EndMessage();
+ this.SendOrDisconnect(messageWriter);
+ messageWriter.Recycle();
+ }
+
+ public MessageWriter StartEndGame()
+ {
+ MessageWriter messageWriter = MessageWriter.Get(SendOption.Reliable);
+ messageWriter.StartMessage(8);
+ messageWriter.Write(this.GameId);
+ return messageWriter;
+ }
+
+ public void FinishEndGame(MessageWriter msg)
+ {
+ msg.EndMessage();
+ this.SendOrDisconnect(msg);
+ msg.Recycle();
+ }
+
+ protected void SendLateRejection(int targetId, DisconnectReasons reason)
+ {
+ MessageWriter messageWriter = MessageWriter.Get(SendOption.Reliable);
+ messageWriter.StartMessage(4);
+ messageWriter.Write(this.GameId);
+ messageWriter.WritePacked(targetId);
+ messageWriter.Write((byte)reason);
+ messageWriter.EndMessage();
+ this.SendOrDisconnect(messageWriter);
+ messageWriter.Recycle();
+ }
+
+ protected void SendClientReady()
+ {
+ if (!this.AmHost)
+ {
+ MessageWriter messageWriter = MessageWriter.Get(SendOption.Reliable);
+ messageWriter.StartMessage(5);
+ messageWriter.Write(this.GameId);
+ messageWriter.StartMessage(7);
+ messageWriter.WritePacked(this.ClientId);
+ messageWriter.EndMessage();
+ messageWriter.EndMessage();
+ this.SendOrDisconnect(messageWriter);
+ messageWriter.Recycle();
+ return;
+ }
+ ClientData clientData = this.FindClientById(this.ClientId);
+ if (clientData == null)
+ {
+ this.HandleDisconnect(DisconnectReasons.Error, "Couldn't find self as host");
+ return;
+ }
+ clientData.IsReady = true;
+ }
+
+ protected void SendStartGame()
+ {
+ MessageWriter messageWriter = MessageWriter.Get(SendOption.Reliable);
+ messageWriter.StartMessage(2);
+ messageWriter.Write(this.GameId);
+ messageWriter.EndMessage();
+ this.SendOrDisconnect(messageWriter);
+ messageWriter.Recycle();
+ }
+
+ public void RequestGameList(bool includePrivate, IBytesSerializable settings)
+ {
+ MessageWriter messageWriter = MessageWriter.Get(SendOption.Reliable);
+ messageWriter.StartMessage(9);
+ messageWriter.Write(includePrivate);
+ messageWriter.WriteBytesAndSize(settings.ToBytes());
+ messageWriter.EndMessage();
+ this.SendOrDisconnect(messageWriter);
+ messageWriter.Recycle();
+ }
+
+ public void ChangeGamePublic(bool isPublic)
+ {
+ if (this.AmHost)
+ {
+ MessageWriter messageWriter = MessageWriter.Get(SendOption.Reliable);
+ messageWriter.StartMessage(10);
+ messageWriter.Write(this.GameId);
+ messageWriter.Write(1);
+ messageWriter.Write(isPublic);
+ messageWriter.EndMessage();
+ this.SendOrDisconnect(messageWriter);
+ messageWriter.Recycle();
+ this.IsGamePublic = isPublic;
+ }
+ }
+
+ //c 收到数据,在网络线程
+ private void OnMessageReceived(DataReceivedEventArgs e)
+ {
+ MessageReader message = e.Message;
+ try
+ {
+ while (message.Position < message.Length)
+ {
+ this.HandleMessage(message.ReadMessage());
+ }
+ }
+ finally
+ {
+ message.Recycle();
+ }
+ }
+
+
+ //c 从imposter搞过来的
+ //c 后来发现Tags.cs里有
+ private static readonly Dictionary<byte, string> TagMap = new Dictionary<byte, string>
+ {
+ {0, "HostGame"},
+ {1, "JoinGame"},
+ {2, "StartGame"},
+ {3, "RemoveGame"},
+ {4, "RemovePlayer"},
+ {5, "GameData"},
+ {6, "GameDataTo"},
+ {7, "JoinedGame"},
+ {8, "EndGame"},
+ {9, "GetGameList"},
+ {10, "AlterGame"},
+ {11, "KickPlayer"},
+ {12, "WaitForHost"},
+ {13, "Redirect"},
+ {14, "ReselectServer"},
+ {16, "GetGameListV2"}
+ };
+
+
+ private enum TagAlias
+ {
+ HostGame = 0, // 创建
+ StartGame = 2, // 开始游戏
+ Disconnect = 3, // 断开
+ SubMessage = 6, //
+ JoinGame = 7, // 加入游戏
+ Gameover = 8, // 游戏结束
+ }
+
+ //c 处理收到的数据
+ private void HandleMessage(MessageReader reader)
+ {
+ List<Action> obj;
+ switch (reader.Tag)
+ {
+ case (int)TagAlias.HostGame:
+ this.GameId = reader.ReadInt32();
+ Debug.Log("Client hosting game: " + InnerNetClient.IntToGameName(this.GameId));
+ obj = this.Dispatcher;
+ lock (obj)
+ {
+ this.Dispatcher.Add(delegate
+ {
+ this.OnGameCreated(InnerNetClient.IntToGameName(this.GameId));
+ });
+ return;
+ }
+ break;
+ case 1:
+ goto IL_2F5;
+ case (int)TagAlias.StartGame:
+ this.GameState = InnerNetClient.GameStates.Started;
+ obj = this.Dispatcher;
+ lock (obj)
+ {
+ this.Dispatcher.Add(delegate
+ {
+ this.OnStartGame();
+ });
+ return;
+ }
+ goto IL_675;
+ case (int)TagAlias.Disconnect:
+ {
+ DisconnectReasons reason3 = DisconnectReasons.ServerRequest;
+ if (reader.Position < reader.Length)
+ {
+ reason3 = (DisconnectReasons)reader.ReadByte();
+ }
+ this.EnqueueDisconnect(reason3, null);
+ return;
+ }
+ case 4:
+ break;
+ case 5:
+ case (int)TagAlias.SubMessage: // 把这类消息存在队列里,主线程后续调用
+ {
+ int num = reader.ReadInt32();
+ if (this.GameId == num)
+ {
+ if (reader.Tag == 6)
+ {
+ int num2 = reader.ReadPackedInt32();
+ if (this.ClientId != num2)
+ {
+ Debug.LogWarning(string.Format("Got data meant for {0}", num2));
+ return;
+ }
+ }
+ MessageReader subReader;
+ if (this.InOnlineScene)
+ {
+ subReader = MessageReader.Get(reader);
+ obj = this.Dispatcher;
+ lock (obj)
+ {
+ this.Dispatcher.Add(delegate
+ {
+ this.HandleGameData(subReader, 0); // 在主线程调用
+ });
+ return;
+ }
+ }
+ Debug.Log("Stored early game data");
+ subReader = MessageReader.Get(reader);
+ obj = this.PreSpawnDispatcher;
+ lock (obj)
+ {
+ this.PreSpawnDispatcher.Add(delegate
+ {
+ this.HandleGameData(subReader, 0); // 在主线程调用
+ });
+ return;
+ }
+ goto IL_517;
+ }
+ return;
+ }
+ case (int)TagAlias.JoinGame:
+ goto IL_235;
+ case (int)TagAlias.Gameover:
+ {
+ int num3 = reader.ReadInt32();
+ if (this.GameId == num3 && this.GameState != InnerNetClient.GameStates.Ended)
+ {
+ this.GameState = InnerNetClient.GameStates.Ended;
+ List<ClientData> obj2 = this.allClients;
+ lock (obj2)
+ {
+ this.allClients.Clear();
+ }
+ GameOverReason reason = (GameOverReason)reader.ReadByte();
+ bool showAd = reader.ReadBoolean();
+ obj = this.Dispatcher;
+ lock (obj)
+ {
+ this.Dispatcher.Add(delegate
+ {
+ this.OnGameEnd(reason, showAd);
+ });
+ return;
+ }
+ goto IL_1DD;
+ }
+ return;
+ }
+ case 9:
+ goto IL_517;
+ case 10:
+ goto IL_5BC;
+ case 11:
+ goto IL_675;
+ case 12:
+ goto IL_1DD;
+ case 13:
+ {
+ uint address = reader.ReadUInt32();
+ ushort port = reader.ReadUInt16();
+ AmongUsClient.Instance.SetEndpoint(InnerNetClient.AddressToString(address), port);
+ obj = this.Dispatcher;
+ lock (obj)
+ {
+ this.Dispatcher.Add(delegate
+ {
+ Debug.Log(string.Format("Redirected to: {0}:{1}", this.networkAddress, this.networkPort));
+ base.StopAllCoroutines();
+ this.Connect(this.mode);
+ });
+ return;
+ }
+ goto IL_70A;
+ }
+ default:
+ goto IL_70A;
+ }
+ int num4 = reader.ReadInt32();
+ if (this.GameId == num4)
+ {
+ int playerIdThatLeft = reader.ReadInt32();
+ int hostId = reader.ReadInt32();
+ DisconnectReasons reason2 = (DisconnectReasons)reader.ReadByte();
+ if (!this.AmHost)
+ {
+ this.HostId = hostId;
+ if (this.AmHost)
+ {
+ obj = this.Dispatcher;
+ lock (obj)
+ {
+ this.Dispatcher.Add(delegate
+ {
+ this.OnBecomeHost();
+ });
+ }
+ }
+ }
+ this.RemovePlayer(playerIdThatLeft, reason2);
+ return;
+ }
+ return;
+ IL_1DD:
+ int num5 = reader.ReadInt32();
+ if (this.GameId != num5)
+ {
+ return;
+ }
+ this.ClientId = reader.ReadInt32();
+ obj = this.Dispatcher;
+ lock (obj)
+ {
+ this.Dispatcher.Add(delegate
+ {
+ this.OnWaitForHost(InnerNetClient.IntToGameName(this.GameId));
+ });
+ return;
+ }
+ IL_235:
+ int num6 = reader.ReadInt32();
+ if (this.GameId != num6 || this.GameState == InnerNetClient.GameStates.Joined)
+ {
+ return;
+ }
+ this.GameState = InnerNetClient.GameStates.Joined;
+ this.ClientId = reader.ReadInt32();
+ ClientData myClient = this.GetOrCreateClient(this.ClientId);
+ this.HostId = reader.ReadInt32();
+ int num7 = reader.ReadPackedInt32();
+ for (int i = 0; i < num7; i++)
+ {
+ this.GetOrCreateClient(reader.ReadPackedInt32());
+ }
+ obj = this.Dispatcher;
+ lock (obj)
+ {
+ this.Dispatcher.Add(delegate
+ {
+ this.OnGameJoined(InnerNetClient.IntToGameName(this.GameId), myClient);
+ });
+ return;
+ }
+ IL_2F5:
+ int num8 = reader.ReadInt32();
+ DisconnectReasons disconnectReasons = (DisconnectReasons)num8;
+ if (InnerNetClient.disconnectReasons.Contains(disconnectReasons))
+ {
+ if (disconnectReasons == DisconnectReasons.Custom)
+ {
+ this.LastCustomDisconnect = reader.ReadString();
+ }
+ this.GameId = -1;
+ this.EnqueueDisconnect(disconnectReasons, null);
+ return;
+ }
+ if (this.GameId == num8)
+ {
+ int num9 = reader.ReadInt32();
+ bool amHost = this.AmHost;
+ this.HostId = reader.ReadInt32();
+ ClientData client = this.GetOrCreateClient(num9);
+ Debug.Log(string.Format("Player {0} joined", num9));
+ obj = this.Dispatcher;
+ lock (obj)
+ {
+ this.Dispatcher.Add(delegate
+ {
+ this.OnPlayerJoined(client);
+ });
+ }
+ if (!this.AmHost || amHost)
+ {
+ return;
+ }
+ obj = this.Dispatcher;
+ lock (obj)
+ {
+ this.Dispatcher.Add(delegate
+ {
+ this.OnBecomeHost();
+ });
+ return;
+ }
+ }
+ this.EnqueueDisconnect(DisconnectReasons.IncorrectGame, null);
+ return;
+ IL_517:
+ int totalGames = reader.ReadPackedInt32();
+ List<GameListing> output = new List<GameListing>();
+ while (reader.Position < reader.Length)
+ {
+ output.Add(new GameListing(reader.ReadInt32(), reader.ReadByte(), reader.ReadByte(), reader.ReadByte(), reader.ReadPackedInt32(), reader.ReadString()));
+ }
+ obj = this.Dispatcher;
+ lock (obj)
+ {
+ this.Dispatcher.Add(delegate
+ {
+ this.OnGetGameList(totalGames, output);
+ });
+ return;
+ }
+ IL_5BC:
+ int num10 = reader.ReadInt32();
+ if (this.GameId != num10)
+ {
+ return;
+ }
+ byte b = reader.ReadByte();
+ if (b == 1)
+ {
+ this.IsGamePublic = reader.ReadBoolean();
+ string str = "Alter Public = ";
+ bool flag = this.IsGamePublic;
+ Debug.Log(str + flag.ToString());
+ return;
+ }
+ Debug.Log("Alter unknown");
+ return;
+ IL_675:
+ int num11 = reader.ReadInt32();
+ if (this.GameId == num11 && reader.ReadPackedInt32() == this.ClientId)
+ {
+ this.EnqueueDisconnect(reader.ReadBoolean() ? DisconnectReasons.Banned : DisconnectReasons.Kicked, null);
+ return;
+ }
+ return;
+ IL_70A:
+ Debug.Log(string.Format("Bad tag {0} at {1}+{2}={3}: ", new object[]
+ {
+ reader.Tag,
+ reader.Offset,
+ reader.Position,
+ reader.Length
+ }) + string.Join<byte>(" ", reader.Buffer));
+ }
+
+ private static string AddressToString(uint address)
+ {
+ return string.Format("{0}.{1}.{2}.{3}", new object[]
+ {
+ (byte)address,
+ (byte)(address >> 8),
+ (byte)(address >> 16),
+ (byte)(address >> 24)
+ });
+ }
+
+ private ClientData GetOrCreateClient(int clientId)
+ {
+ List<ClientData> obj = this.allClients;
+ ClientData clientData;
+ lock (obj)
+ {
+ clientData = this.allClients.FirstOrDefault((ClientData c) => c.Id == clientId);
+ if (clientData == null)
+ {
+ clientData = new ClientData(clientId);
+ this.allClients.Add(clientData);
+ }
+ }
+ return clientData;
+ }
+
+ private void RemovePlayer(int playerIdThatLeft, DisconnectReasons reason)
+ {
+ ClientData client = null;
+ List<ClientData> obj = this.allClients;
+ lock (obj)
+ {
+ for (int i = 0; i < this.allClients.Count; i++)
+ {
+ ClientData clientData = this.allClients[i];
+ if (clientData.Id == playerIdThatLeft)
+ {
+ client = clientData;
+ this.allClients.RemoveAt(i);
+ break;
+ }
+ }
+ }
+ if (client != null)
+ {
+ List<Action> dispatcher = this.Dispatcher;
+ lock (dispatcher)
+ {
+ this.Dispatcher.Add(delegate
+ {
+ this.OnPlayerLeft(client, reason);
+ });
+ }
+ }
+ }
+
+ protected virtual void OnApplicationPause(bool pause)
+ {
+ this.appPaused = pause;
+ if (!pause)
+ {
+ Debug.Log("Resumed Game");
+ if (this.AmHost)
+ {
+ this.RemoveUnownedObjects();
+ return;
+ }
+ }
+ else if (this.GameState != InnerNetClient.GameStates.Ended && this.AmConnected)
+ {
+ Debug.Log("Lost focus during game");
+ ThreadPool.QueueUserWorkItem(new WaitCallback(this.WaitToDisconnect));
+ }
+ }
+
+ private void WaitToDisconnect(object state)
+ {
+ int num = 0;
+ while (num < 10 && this.appPaused)
+ {
+ Thread.Sleep(1000);
+ num++;
+ }
+ if (this.appPaused && this.GameState != InnerNetClient.GameStates.Ended && this.AmConnected)
+ {
+ this.DisconnectInternal(DisconnectReasons.FocusLostBackground, null);
+ this.EnqueueDisconnect(DisconnectReasons.FocusLost, null);
+ }
+ }
+
+ protected void SendInitialData(int clientId)
+ {
+ MessageWriter messageWriter = MessageWriter.Get(SendOption.Reliable);
+ messageWriter.StartMessage(6);
+ messageWriter.Write(this.GameId);
+ messageWriter.WritePacked(clientId);
+ List<InnerNetObject> obj = this.allObjects;
+ lock (obj)
+ {
+ HashSet<GameObject> hashSet = new HashSet<GameObject>();
+ for (int i = 0; i < this.allObjects.Count; i++)
+ {
+ InnerNetObject innerNetObject = this.allObjects[i];
+ if (innerNetObject && hashSet.Add(innerNetObject.gameObject))
+ {
+ this.WriteSpawnMessage(innerNetObject, innerNetObject.OwnerId, innerNetObject.SpawnFlags, messageWriter);
+ }
+ }
+ }
+ messageWriter.EndMessage();
+ this.SendOrDisconnect(messageWriter);
+ messageWriter.Recycle();
+ }
+
+ protected abstract void OnGameCreated(string gameIdString);
+
+ protected abstract void OnGameJoined(string gameIdString, ClientData client);
+
+ protected abstract void OnWaitForHost(string gameIdString);
+
+ protected abstract void OnStartGame();
+
+ protected abstract void OnGameEnd(GameOverReason reason, bool showAd);
+
+ protected abstract void OnBecomeHost();
+
+ protected abstract void OnPlayerJoined(ClientData client);
+
+ protected abstract void OnPlayerChangedScene(ClientData client, string targetScene);
+
+ protected abstract void OnPlayerLeft(ClientData client, DisconnectReasons reason);
+
+ protected abstract void OnDisconnected();
+
+ protected abstract void OnGetGameList(int totalGames, List<GameListing> availableGames);
+
+ protected abstract byte[] GetConnectionData();
+
+ protected ClientData FindClientById(int id)
+ {
+ if (id < 0)
+ {
+ return null;
+ }
+ List<ClientData> obj = this.allClients;
+ ClientData result;
+ lock (obj)
+ {
+ for (int i = 0; i < this.allClients.Count; i++)
+ {
+ ClientData clientData = this.allClients[i];
+ if (clientData.Id == id)
+ {
+ return clientData;
+ }
+ }
+ result = null;
+ }
+ return result;
+ }
+
+ public static string IntToGameName(int gameId)
+ {
+ char[] array = new char[]
+ {
+ (char)(gameId & 255),
+ (char)(gameId >> 8 & 255),
+ (char)(gameId >> 16 & 255),
+ (char)(gameId >> 24 & 255)
+ };
+ if (array.Any((char c) => c < 'A' || c > 'z'))
+ {
+ return null;
+ }
+ return new string(array);
+ }
+
+ public static int GameNameToInt(string gameId)
+ {
+ if (gameId.Length != 4)
+ {
+ return -1;
+ }
+ gameId = gameId.ToUpperInvariant();
+ return (int)(gameId[0] | (int)gameId[1] << 8 | (int)gameId[2] << 16 | (int)gameId[3] << 24);
+ }
+
+ private void FixedUpdate()
+ {
+ if (this.mode == MatchMakerModes.None || this.Streams == null)
+ {
+ this.timer = 0f;
+ return;
+ }
+ this.timer += Time.fixedDeltaTime;
+ if (this.timer < this.MinSendInterval)
+ {
+ return;
+ }
+ this.timer = 0f;
+
+ //c 写入所有场景内包含InnerNetObject派生类的数据
+ List<InnerNetObject> obj = this.allObjects;
+ lock (obj)
+ {
+ for (int i = 0; i < this.allObjects.Count; i++)
+ {
+ InnerNetObject innerNetObject = this.allObjects[i];
+ if (innerNetObject && innerNetObject.DirtyBits != 0U && (innerNetObject.AmOwner || (innerNetObject.OwnerId == -2 && this.AmHost)))
+ {
+ MessageWriter messageWriter = this.Streams[(int)innerNetObject.sendMode];
+ messageWriter.StartMessage(1);
+ messageWriter.WritePacked(innerNetObject.NetId);
+ if (innerNetObject.Serialize(messageWriter, false))
+ {
+ messageWriter.EndMessage();
+ }
+ else
+ {
+ messageWriter.CancelMessage();
+ }
+ }
+ }
+ }
+
+ for (int j = 0; j < this.Streams.Length; j++)
+ {
+ MessageWriter messageWriter2 = this.Streams[j];
+ if (messageWriter2.HasBytes(7))
+ {
+ messageWriter2.EndMessage();
+ this.SendOrDisconnect(messageWriter2);
+ messageWriter2.Clear((SendOption)j);
+ messageWriter2.StartMessage(5);
+ messageWriter2.Write(this.GameId);
+ }
+ }
+ }
+
+ public T FindObjectByNetId<T>(uint netId) where T : InnerNetObject
+ {
+ InnerNetObject innerNetObject;
+ if (this.allObjectsFast.TryGetValue(netId, out innerNetObject))
+ {
+ return (T)((object)innerNetObject);
+ }
+ return default(T);
+ }
+
+ public void SendRpcImmediately(uint targetNetId, byte callId, SendOption option)
+ {
+ MessageWriter messageWriter = MessageWriter.Get(option);
+ messageWriter.StartMessage(5);
+ messageWriter.Write(this.GameId);
+ messageWriter.StartMessage(2);
+ messageWriter.WritePacked(targetNetId);
+ messageWriter.Write(callId);
+ messageWriter.EndMessage();
+ messageWriter.EndMessage();
+ this.connection.Send(messageWriter);
+ messageWriter.Recycle();
+ }
+
+ public MessageWriter StartRpcImmediately(uint targetNetId, byte callId, SendOption option, int targetClientId = -1)
+ {
+ MessageWriter messageWriter = MessageWriter.Get(option);
+ if (targetClientId < 0)
+ {
+ messageWriter.StartMessage(5);
+ messageWriter.Write(this.GameId);
+ }
+ else
+ {
+ messageWriter.StartMessage(6);
+ messageWriter.Write(this.GameId);
+ messageWriter.WritePacked(targetClientId);
+ }
+ messageWriter.StartMessage(2);
+ messageWriter.WritePacked(targetNetId);
+ messageWriter.Write(callId);
+ return messageWriter;
+ }
+
+ public void FinishRpcImmediately(MessageWriter msg)
+ {
+ msg.EndMessage();
+ msg.EndMessage();
+ this.SendOrDisconnect(msg);
+ msg.Recycle();
+ }
+
+ public void SendRpc(uint targetNetId, byte callId, SendOption option = SendOption.Reliable)
+ {
+ this.StartRpc(targetNetId, callId, option).EndMessage();
+ }
+
+ public MessageWriter StartRpc(uint targetNetId, byte callId, SendOption option = SendOption.Reliable)
+ {
+ MessageWriter messageWriter = this.Streams[(int)option];
+ messageWriter.StartMessage(2);
+ messageWriter.WritePacked(targetNetId);
+ messageWriter.Write(callId);
+ return messageWriter;
+ }
+
+ private void SendSceneChange(string sceneName)
+ {
+ this.InOnlineScene = string.Equals(sceneName, "OnlineGame");
+ if (!this.AmConnected)
+ {
+ return;
+ }
+ Debug.Log("Changed To " + sceneName);
+ base.StartCoroutine(this.CoSendSceneChange(sceneName));
+ }
+
+ private IEnumerator CoSendSceneChange(string sceneName)
+ {
+ List<InnerNetObject> obj = this.allObjects;
+ lock (obj)
+ {
+ for (int i = this.allObjects.Count - 1; i > -1; i--)
+ {
+ if (!this.allObjects[i])
+ {
+ this.allObjects.RemoveAt(i);
+ }
+ }
+ goto IL_BF;
+ }
+ IL_A8:
+ yield return null;
+ IL_BF:
+ if (this.AmConnected && this.ClientId < 0)
+ {
+ goto IL_A8;
+ }
+ if (!this.AmConnected)
+ {
+ yield break;
+ }
+ if (!this.AmHost && this.connection.State == ConnectionState.Connected)
+ {
+ MessageWriter messageWriter = MessageWriter.Get(SendOption.Reliable);
+ messageWriter.StartMessage(5);
+ messageWriter.Write(this.GameId);
+ messageWriter.StartMessage(6);
+ messageWriter.WritePacked(this.ClientId);
+ messageWriter.Write(sceneName);
+ messageWriter.EndMessage();
+ messageWriter.EndMessage();
+ this.SendOrDisconnect(messageWriter);
+ messageWriter.Recycle();
+ }
+ ClientData client = this.FindClientById(this.ClientId);
+ if (client != null)
+ {
+ Debug.Log(string.Format("Self changed scene: {0} {1}", this.ClientId, sceneName));
+ List<Action> dispatcher = this.Dispatcher;
+ lock (dispatcher)
+ {
+ this.Dispatcher.Add(delegate
+ {
+ this.OnPlayerChangedScene(client, sceneName);
+ });
+ yield break;
+ }
+ }
+ Debug.Log(string.Format("Couldn't find self in clients: {0}: ", this.ClientId) + sceneName);
+ yield break;
+ }
+
+ public void Spawn(InnerNetObject netObjParent, int ownerId = -2, SpawnFlags flags = SpawnFlags.None)
+ {
+ if (this.AmHost)
+ {
+ ownerId = ((ownerId == -3) ? this.ClientId : ownerId);
+ MessageWriter msg = this.Streams[1];
+ this.WriteSpawnMessage(netObjParent, ownerId, flags, msg);
+ return;
+ }
+ if (!this.AmClient)
+ {
+ return;
+ }
+ Debug.LogError("Tried to spawn while not host:" + netObjParent);
+ }
+
+ private void WriteSpawnMessage(InnerNetObject netObjParent, int ownerId, SpawnFlags flags, MessageWriter msg)
+ {
+ msg.StartMessage(4);
+ msg.WritePacked(netObjParent.SpawnId);
+ msg.WritePacked(ownerId);
+ msg.Write((byte)flags);
+ InnerNetObject[] componentsInChildren = netObjParent.GetComponentsInChildren<InnerNetObject>();
+ msg.WritePacked(componentsInChildren.Length);
+ foreach (InnerNetObject innerNetObject in componentsInChildren)
+ {
+ innerNetObject.OwnerId = ownerId;
+ innerNetObject.SpawnFlags = flags;
+ if (innerNetObject.NetId == 0U)
+ {
+ InnerNetObject innerNetObject2 = innerNetObject;
+ uint netIdCnt = this.NetIdCnt;
+ this.NetIdCnt = netIdCnt + 1U;
+ innerNetObject2.NetId = netIdCnt;
+ this.allObjects.Add(innerNetObject);
+ this.allObjectsFast.Add(innerNetObject.NetId, innerNetObject);
+ }
+ msg.WritePacked(innerNetObject.NetId);
+ msg.StartMessage(1);
+ innerNetObject.Serialize(msg, true);
+ msg.EndMessage();
+ }
+ msg.EndMessage();
+ }
+
+ public void Despawn(InnerNetObject objToDespawn)
+ {
+ if (objToDespawn.NetId < 1U)
+ {
+ Debug.LogError("Tried to net destroy: " + objToDespawn);
+ return;
+ }
+ MessageWriter messageWriter = this.Streams[1];
+ messageWriter.StartMessage(5);
+ messageWriter.WritePacked(objToDespawn.NetId);
+ messageWriter.EndMessage();
+ this.RemoveNetObject(objToDespawn);
+ }
+
+ private bool AddNetObject(InnerNetObject obj)
+ {
+ uint num = obj.NetId + 1U;
+ if (num > this.NetIdCnt)
+ {
+ this.NetIdCnt = num;
+ }
+ if (!this.allObjectsFast.ContainsKey(obj.NetId))
+ {
+ this.allObjects.Add(obj);
+ this.allObjectsFast.Add(obj.NetId, obj);
+ return true;
+ }
+ return false;
+ }
+
+ public void RemoveNetObject(InnerNetObject obj)
+ {
+ int num = this.allObjects.IndexOf(obj);
+ if (num > -1)
+ {
+ this.allObjects.RemoveAt(num);
+ }
+ this.allObjectsFast.Remove(obj.NetId);
+ obj.NetId = uint.MaxValue;
+ }
+
+ public void RemoveUnownedObjects()
+ {
+ HashSet<int> hashSet = new HashSet<int>();
+ hashSet.Add(-2);
+ List<ClientData> obj = this.allClients;
+ lock (obj)
+ {
+ for (int i = 0; i < this.allClients.Count; i++)
+ {
+ ClientData clientData = this.allClients[i];
+ if (clientData.Character)
+ {
+ hashSet.Add(clientData.Id);
+ }
+ }
+ }
+ List<InnerNetObject> obj2 = this.allObjects;
+ lock (obj2)
+ {
+ for (int j = this.allObjects.Count - 1; j > -1; j--)
+ {
+ InnerNetObject innerNetObject = this.allObjects[j];
+ if (!innerNetObject)
+ {
+ this.allObjects.RemoveAt(j);
+ }
+ else if (!hashSet.Contains(innerNetObject.OwnerId))
+ {
+ innerNetObject.OwnerId = this.ClientId;
+ UnityEngine.Object.Destroy(innerNetObject.gameObject);
+ }
+ }
+ }
+ }
+
+ private void HandleGameData(MessageReader parentReader, int cnt = 0)
+ {
+ try
+ {
+ while (parentReader.Position < parentReader.Length)
+ {
+ this.HandleGameDataInner(parentReader.ReadMessage(), cnt);
+ }
+ }
+ finally
+ {
+ parentReader.Recycle();
+ }
+ }
+
+ //c 辅助tag别名
+ private enum SubTagAlias
+ {
+ Normal = 1, // 游戏内的简单数据,比如同步角色位置、动画等
+ Rpc = 2, // 远程调用Remote Procedure Call
+ SpawnCharacter = 4, // 生成角色
+ RemoveNetObjectByNetId = 5, // 删除数据
+ ChangeScene = 6, // 切换场景
+ PlayerReady = 7, // 角色准备好了
+ }
+
+ //c 处理收到的数据,用来同步
+ //c 这个方法是在主线程调用的
+ private void HandleGameDataInner(MessageReader reader, int cnt)
+ {
+ switch (reader.Tag)
+ {
+ case (int)SubTagAlias.Normal:
+ {
+ uint num = reader.ReadPackedUInt32();
+ InnerNetObject innerNetObject;
+ if (this.allObjectsFast.TryGetValue(num, out innerNetObject))
+ {
+ innerNetObject.Deserialize(reader, false);
+ return;
+ }
+ if (!this.DestroyedObjects.Contains(num))
+ {
+ this.DeferMessage(cnt, reader, "Stored data for " + num);
+ return;
+ }
+ return;
+ }
+ case (int)SubTagAlias.Rpc:
+ {
+ // 根据编号找到对应的同步数据
+ uint index = reader.ReadPackedUInt32();
+ InnerNetObject innerNetObject2;
+ if (this.allObjectsFast.TryGetValue(index, out innerNetObject2))
+ {
+ byte callId = reader.ReadByte();
+ innerNetObject2.HandleRpc(callId, reader);
+ return;
+ }
+ if (!this.DestroyedObjects.Contains(index))
+ {
+ this.DeferMessage(cnt, reader, "Stored RPC for " + index);
+ return;
+ }
+ return;
+ }
+ case (int)SubTagAlias.SpawnCharacter:
+ {
+ uint num3 = reader.ReadPackedUInt32();
+ if ((ulong)num3 >= (ulong)((long)this.SpawnableObjects.Length))
+ {
+ Debug.LogError("Couldn't find spawnable prefab: " + num3);
+ return;
+ }
+ int num4 = reader.ReadPackedInt32();
+ ClientData clientData = this.FindClientById(num4);
+ if (num4 > 0 && clientData == null)
+ {
+ this.DeferMessage(cnt, reader, "Delay spawn for unowned " + num3);
+ return;
+ }
+ // 创建角色对象并设置参数
+ InnerNetObject innerNetObject3 = UnityEngine.Object.Instantiate<InnerNetObject>(this.SpawnableObjects[(int)num3]);
+ innerNetObject3.SpawnFlags = (SpawnFlags)reader.ReadByte();
+ if ((innerNetObject3.SpawnFlags & SpawnFlags.IsClientCharacter) != SpawnFlags.None)
+ {
+ if (!clientData.Character)
+ {
+ clientData.InScene = true;
+ clientData.Character = (innerNetObject3 as PlayerControl);
+ }
+ else if (innerNetObject3)
+ {
+ Debug.LogWarning(string.Format("Double spawn character: {0} already has {1}", clientData.Id, clientData.Character.NetId));
+ UnityEngine.Object.Destroy(innerNetObject3.gameObject);
+ return;
+ }
+ }
+ int num5 = reader.ReadPackedInt32();
+ InnerNetObject[] componentsInChildren = innerNetObject3.GetComponentsInChildren<InnerNetObject>();
+ if (num5 != componentsInChildren.Length)
+ {
+ Debug.LogError("Children didn't match for spawnable " + num3);
+ UnityEngine.Object.Destroy(innerNetObject3.gameObject);
+ return;
+ }
+ for (int i = 0; i < num5; i++)
+ {
+ InnerNetObject innerNetObject4 = componentsInChildren[i];
+ innerNetObject4.NetId = reader.ReadPackedUInt32();
+ innerNetObject4.OwnerId = num4;
+ if (this.DestroyedObjects.Contains(innerNetObject4.NetId))
+ {
+ innerNetObject3.NetId = uint.MaxValue;
+ UnityEngine.Object.Destroy(innerNetObject3.gameObject);
+ return;
+ }
+ if (!this.AddNetObject(innerNetObject4))
+ {
+ innerNetObject3.NetId = uint.MaxValue;
+ UnityEngine.Object.Destroy(innerNetObject3.gameObject);
+ return;
+ }
+ MessageReader messageReader = reader.ReadMessage();
+ if (messageReader.Length > 0)
+ {
+ innerNetObject4.Deserialize(messageReader, true);
+ }
+ }
+ return;
+ }
+ case (int)SubTagAlias.RemoveNetObjectByNetId:
+ {
+ uint netId = reader.ReadPackedUInt32();
+ this.DestroyedObjects.Add(netId);
+ InnerNetObject innerNetObject5 = this.FindObjectByNetId<InnerNetObject>(netId);
+ if (innerNetObject5)
+ {
+ this.RemoveNetObject(innerNetObject5);
+ UnityEngine.Object.Destroy(innerNetObject5.gameObject);
+ return;
+ }
+ return;
+ }
+ case (int)SubTagAlias.ChangeScene:
+ {
+ int id = reader.ReadPackedInt32();
+ ClientData client = this.FindClientById(id); // 找到对应的玩家
+ string targetScene = reader.ReadString();
+ if (client != null && !string.IsNullOrWhiteSpace(targetScene))
+ {
+ Debug.Log(string.Format("Client {0} changed scene to {1}", client.Id, targetScene));
+ List<Action> dispatcher = this.Dispatcher;
+ lock (dispatcher)
+ {
+ this.Dispatcher.Add(delegate
+ {
+ // 切换场景
+ this.OnPlayerChangedScene(client, targetScene);
+ });
+ return;
+ }
+ }
+ Debug.Log(string.Format("Couldn't find client {0} to change scene to {1}", id, targetScene));
+ return;
+ }
+ case (int)SubTagAlias.PlayerReady:
+ {
+ ClientData clientData2 = this.FindClientById(reader.ReadPackedInt32());
+ if (clientData2 != null)
+ {
+ Debug.Log(string.Format("Client {0} ready", clientData2.Id));
+ clientData2.IsReady = true;
+ return;
+ }
+ return;
+ }
+ }
+
+ Debug.Log(string.Format("Bad tag {0} at {1}+{2}={3}: ", new object[]
+ {
+ reader.Tag,
+ reader.Offset,
+ reader.Position,
+ reader.Length
+ }) + string.Join<byte>(" ", reader.Buffer));
+ }
+
+ private void DeferMessage(int cnt, MessageReader reader, string logMsg)
+ {
+ if (cnt > 10)
+ {
+ Debug.Log("Giving up on: " + logMsg);
+ return;
+ }
+ int cnt2 = cnt;
+ cnt = cnt2 + 1;
+ Debug.Log(logMsg);
+ MessageReader copy = MessageReader.CopyMessageIntoParent(reader);
+ List<Action> preSpawnDispatcher = this.PreSpawnDispatcher;
+ lock (preSpawnDispatcher)
+ {
+ this.PreSpawnDispatcher.Add(delegate
+ {
+ this.HandleGameData(copy, cnt);
+ });
+ }
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/InnerNet/InnerNetObject.cs b/Client/Assembly-CSharp/InnerNet/InnerNetObject.cs
new file mode 100644
index 0000000..0fdcf2d
--- /dev/null
+++ b/Client/Assembly-CSharp/InnerNet/InnerNetObject.cs
@@ -0,0 +1,74 @@
+using System;
+using Hazel;
+using UnityEngine;
+
+namespace InnerNet
+{
+ public abstract class InnerNetObject : MonoBehaviour, IComparable<InnerNetObject>
+ {
+ public bool AmOwner
+ {
+ get
+ {
+ return this.OwnerId == AmongUsClient.Instance.ClientId;
+ }
+ }
+
+ public uint SpawnId;
+
+ public uint NetId;
+
+ public uint DirtyBits;
+
+ public SpawnFlags SpawnFlags;
+
+ public SendOption sendMode = SendOption.Reliable;
+
+ public int OwnerId;
+
+ protected bool DespawnOnDestroy = true;
+
+ public void Despawn()
+ {
+ UnityEngine.Object.Destroy(base.gameObject);
+ AmongUsClient.Instance.Despawn(this);
+ }
+
+ public virtual void OnDestroy()
+ {
+ if (AmongUsClient.Instance && this.NetId != 4294967295U)
+ {
+ if (this.DespawnOnDestroy && this.AmOwner)
+ {
+ AmongUsClient.Instance.Despawn(this);
+ return;
+ }
+ AmongUsClient.Instance.RemoveNetObject(this);
+ }
+ }
+
+ public abstract void HandleRpc(byte callId, MessageReader reader);
+
+ public abstract bool Serialize(MessageWriter writer, bool initialState);
+
+ public abstract void Deserialize(MessageReader reader, bool initialState);
+
+ public int CompareTo(InnerNetObject other)
+ {
+ if (this.NetId > other.NetId)
+ {
+ return 1;
+ }
+ if (this.NetId < other.NetId)
+ {
+ return -1;
+ }
+ return 0;
+ }
+
+ protected void SetDirtyBit(uint val)
+ {
+ this.DirtyBits |= val;
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/InnerNet/InnerNetServer.cs b/Client/Assembly-CSharp/InnerNet/InnerNetServer.cs
new file mode 100644
index 0000000..3705318
--- /dev/null
+++ b/Client/Assembly-CSharp/InnerNet/InnerNetServer.cs
@@ -0,0 +1,592 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Threading;
+using Hazel;
+using Hazel.Udp;
+using UnityEngine;
+
+namespace InnerNet
+{
+ public class InnerNetServer : DestroyableSingleton<InnerNetServer>
+ {
+ public const int MaxPlayers = 10;
+
+ public bool Running;
+
+ public const int LocalGameId = 32;
+
+ private const int InvalidHost = -1;
+
+ private int HostId = -1;
+
+ public HashSet<string> ipBans = new HashSet<string>();
+
+ public int Port = 22023;
+
+ [SerializeField]
+ private GameStates GameState;
+
+ private NetworkConnectionListener listener;
+
+ private List<InnerNetServer.Player> Clients = new List<InnerNetServer.Player>();
+
+ protected class Player
+ {
+ private static int IdCount = 1;
+
+ public int Id;
+
+ public Connection Connection;
+
+ public LimboStates LimboState;
+
+ public Player(Connection connection)
+ {
+ this.Id = Interlocked.Increment(ref InnerNetServer.Player.IdCount);
+ this.Connection = connection;
+ }
+ }
+
+ public override void OnDestroy()
+ {
+ this.StopServer();
+ base.OnDestroy();
+ }
+
+ public void StartAsServer()
+ {
+ if (this.listener != null)
+ {
+ this.StopServer();
+ }
+ this.GameState = GameStates.NotStarted;
+ this.listener = new UdpConnectionListener(new IPEndPoint(IPAddress.Any, this.Port), IPMode.IPv4, null);
+ this.listener.NewConnection += this.OnServerConnect;
+ this.listener.Start();
+ this.Running = true;
+ }
+
+ public void StopServer()
+ {
+ this.HostId = -1;
+ this.Running = false;
+ this.GameState = GameStates.Destroyed;
+ if (this.listener != null)
+ {
+ this.listener.Close();
+ this.listener.Dispose();
+ this.listener = null;
+ }
+ List<InnerNetServer.Player> clients = this.Clients;
+ lock (clients)
+ {
+ this.Clients.Clear();
+ }
+ }
+
+ public static bool IsCompatibleVersion(int version)
+ {
+ return Constants.CompatVersions.Contains(version);
+ }
+
+ private void OnServerConnect(NewConnectionEventArgs evt)
+ {
+ MessageReader handshakeData = evt.HandshakeData;
+ try
+ {
+ if (evt.HandshakeData.Length < 5)
+ {
+ InnerNetServer.SendIncorrectVersion(evt.Connection);
+ return;
+ }
+ if (!InnerNetServer.IsCompatibleVersion(handshakeData.ReadInt32()))
+ {
+ InnerNetServer.SendIncorrectVersion(evt.Connection);
+ return;
+ }
+ }
+ finally
+ {
+ handshakeData.Recycle();
+ }
+ InnerNetServer.Player client = new InnerNetServer.Player(evt.Connection);
+ Debug.Log(string.Format("Client {0} added: {1}", client.Id, evt.Connection.EndPoint));
+ UdpConnection udpConnection = (UdpConnection)evt.Connection;
+ udpConnection.KeepAliveInterval = 1500;
+ udpConnection.DisconnectTimeout = 6000;
+ udpConnection.ResendPingMultiplier = 1.5f;
+ udpConnection.DataReceived += delegate(DataReceivedEventArgs e)
+ {
+ this.OnDataReceived(client, e);
+ };
+ udpConnection.Disconnected += delegate(object o, DisconnectedEventArgs e)
+ {
+ this.ClientDisconnect(client);
+ };
+ }
+
+ private static void SendIncorrectVersion(Connection connection)
+ {
+ MessageWriter messageWriter = MessageWriter.Get(SendOption.Reliable);
+ messageWriter.StartMessage(1);
+ messageWriter.Write(5);
+ messageWriter.EndMessage();
+ connection.Send(messageWriter);
+ messageWriter.Recycle();
+ }
+
+ private void Connection_DataSentRaw(byte[] data, int length)
+ {
+ Debug.Log("Server Sent: " + string.Join(" ", (from b in data
+ select b.ToString()).ToArray<string>(), 0, length));
+ }
+
+ private void OnDataReceived(InnerNetServer.Player client, DataReceivedEventArgs evt)
+ {
+ MessageReader message = evt.Message;
+ if (message.Length <= 0)
+ {
+ Debug.Log("Server got 0 bytes");
+ message.Recycle();
+ return;
+ }
+ try
+ {
+ while (message.Position < message.Length)
+ {
+ this.HandleMessage(client, message.ReadMessage(), evt.SendOption);
+ }
+ }
+ catch (Exception arg)
+ {
+ Debug.Log(string.Format("{0}\r\n{1}", string.Join<byte>(" ", message.Buffer), arg));
+ }
+ finally
+ {
+ message.Recycle();
+ }
+ }
+
+ private void HandleMessage(InnerNetServer.Player client, MessageReader reader, SendOption sendOption)
+ {
+ switch (reader.Tag)
+ {
+ case 0:
+ {
+ Debug.Log("Server got host game");
+ MessageWriter messageWriter = MessageWriter.Get(SendOption.Reliable);
+ messageWriter.StartMessage(0);
+ messageWriter.Write(32);
+ messageWriter.EndMessage();
+ client.Connection.Send(messageWriter);
+ messageWriter.Recycle();
+ return;
+ }
+ case 1:
+ {
+ Debug.Log("Server got join game");
+ if (reader.ReadInt32() == 32)
+ {
+ this.JoinGame(client);
+ return;
+ }
+ MessageWriter messageWriter2 = MessageWriter.Get(SendOption.Reliable);
+ messageWriter2.StartMessage(1);
+ messageWriter2.Write(3);
+ messageWriter2.EndMessage();
+ client.Connection.Send(messageWriter2);
+ messageWriter2.Recycle();
+ return;
+ }
+ case 2:
+ if (reader.ReadInt32() == 32)
+ {
+ this.StartGame(reader, client);
+ return;
+ }
+ break;
+ case 3:
+ if (reader.ReadInt32() == 32)
+ {
+ this.ClientDisconnect(client);
+ return;
+ }
+ break;
+ case 4:
+ case 7:
+ case 9:
+ case 10:
+ break;
+ case 5:
+ if (this.Clients.Contains(client))
+ {
+ if (reader.ReadInt32() == 32)
+ {
+ MessageWriter messageWriter3 = MessageWriter.Get(sendOption);
+ messageWriter3.CopyFrom(reader);
+ this.Broadcast(messageWriter3, client);
+ messageWriter3.Recycle();
+ return;
+ }
+ }
+ else if (this.GameState == GameStates.Started)
+ {
+ client.Connection.Dispose();
+ return;
+ }
+ break;
+ case 6:
+ if (this.Clients.Contains(client))
+ {
+ if (reader.ReadInt32() == 32)
+ {
+ int targetId = reader.ReadPackedInt32();
+ MessageWriter messageWriter4 = MessageWriter.Get(sendOption);
+ messageWriter4.CopyFrom(reader);
+ this.SendTo(messageWriter4, targetId);
+ messageWriter4.Recycle();
+ return;
+ }
+ }
+ else if (this.GameState == GameStates.Started)
+ {
+ Debug.Log("GameDataTo: Server didn't have client");
+ client.Connection.Dispose();
+ return;
+ }
+ break;
+ case 8:
+ if (reader.ReadInt32() == 32)
+ {
+ this.EndGame(reader, client);
+ return;
+ }
+ break;
+ case 11:
+ if (reader.ReadInt32() == 32)
+ {
+ this.KickPlayer(reader.ReadPackedInt32(), reader.ReadBoolean());
+ }
+ break;
+ default:
+ return;
+ }
+ }
+
+ private void KickPlayer(int targetId, bool ban)
+ {
+ List<InnerNetServer.Player> clients = this.Clients;
+ lock (clients)
+ {
+ InnerNetServer.Player player = null;
+ for (int i = 0; i < this.Clients.Count; i++)
+ {
+ if (this.Clients[i].Id == targetId)
+ {
+ player = this.Clients[i];
+ break;
+ }
+ }
+ if (player != null)
+ {
+ if (ban)
+ {
+ HashSet<string> obj = this.ipBans;
+ lock (obj)
+ {
+ IPEndPoint endPoint = player.Connection.EndPoint;
+ this.ipBans.Add(endPoint.Address.ToString());
+ }
+ }
+ MessageWriter messageWriter = MessageWriter.Get(SendOption.Reliable);
+ messageWriter.StartMessage(11);
+ messageWriter.Write(32);
+ messageWriter.WritePacked(targetId);
+ messageWriter.Write(ban);
+ messageWriter.EndMessage();
+ this.Broadcast(messageWriter, null);
+ messageWriter.Recycle();
+ }
+ }
+ }
+
+ protected void JoinGame(InnerNetServer.Player client)
+ {
+ HashSet<string> obj = this.ipBans;
+ lock (obj)
+ {
+ IPEndPoint endPoint = client.Connection.EndPoint;
+ if (this.ipBans.Contains(endPoint.Address.ToString()))
+ {
+ MessageWriter messageWriter = MessageWriter.Get(SendOption.Reliable);
+ messageWriter.StartMessage(1);
+ messageWriter.Write(6);
+ messageWriter.EndMessage();
+ client.Connection.Send(messageWriter);
+ messageWriter.Recycle();
+ return;
+ }
+ }
+ List<InnerNetServer.Player> clients = this.Clients;
+ lock (clients)
+ {
+ switch (this.GameState)
+ {
+ case GameStates.NotStarted:
+ this.HandleNewGameJoin(client);
+ break;
+ default:
+ {
+ MessageWriter messageWriter2 = MessageWriter.Get(SendOption.Reliable);
+ messageWriter2.StartMessage(1);
+ messageWriter2.Write(2);
+ messageWriter2.EndMessage();
+ client.Connection.Send(messageWriter2);
+ messageWriter2.Recycle();
+ break;
+ }
+ case GameStates.Ended:
+ this.HandleRejoin(client);
+ break;
+ }
+ }
+ }
+
+ private void HandleRejoin(InnerNetServer.Player client)
+ {
+ if (client.Id == this.HostId)
+ {
+ this.GameState = GameStates.NotStarted;
+ this.HandleNewGameJoin(client);
+ MessageWriter messageWriter = MessageWriter.Get(SendOption.Reliable);
+ for (int i = 0; i < this.Clients.Count; i++)
+ {
+ InnerNetServer.Player player = this.Clients[i];
+ if (player != client)
+ {
+ try
+ {
+ this.WriteJoinedMessage(player, messageWriter, true);
+ player.Connection.Send(messageWriter);
+ }
+ catch
+ {
+ }
+ }
+ }
+ messageWriter.Recycle();
+ return;
+ }
+ if (this.Clients.Count >= 9)
+ {
+ MessageWriter messageWriter2 = MessageWriter.Get(SendOption.Reliable);
+ messageWriter2.StartMessage(1);
+ messageWriter2.Write(1);
+ messageWriter2.EndMessage();
+ client.Connection.Send(messageWriter2);
+ messageWriter2.Recycle();
+ return;
+ }
+ this.Clients.Add(client);
+ client.LimboState = LimboStates.WaitingForHost;
+ MessageWriter messageWriter3 = MessageWriter.Get(SendOption.Reliable);
+ try
+ {
+ messageWriter3.StartMessage(12);
+ messageWriter3.Write(32);
+ messageWriter3.Write(client.Id);
+ messageWriter3.EndMessage();
+ client.Connection.Send(messageWriter3);
+ this.BroadcastJoinMessage(client, messageWriter3);
+ }
+ catch
+ {
+ }
+ finally
+ {
+ messageWriter3.Recycle();
+ }
+ }
+
+ private void HandleNewGameJoin(InnerNetServer.Player client)
+ {
+ if (this.Clients.Count >= 10)
+ {
+ MessageWriter messageWriter = MessageWriter.Get(SendOption.Reliable);
+ try
+ {
+ messageWriter.StartMessage(1);
+ messageWriter.Write(1);
+ messageWriter.EndMessage();
+ client.Connection.Send(messageWriter);
+ }
+ catch
+ {
+ }
+ finally
+ {
+ messageWriter.Recycle();
+ }
+ return;
+ }
+ this.Clients.Add(client);
+ client.LimboState = LimboStates.PreSpawn;
+ if (this.HostId == -1)
+ {
+ this.HostId = this.Clients[0].Id;
+ }
+ if (this.HostId == client.Id)
+ {
+ client.LimboState = LimboStates.NotLimbo;
+ }
+ MessageWriter messageWriter2 = MessageWriter.Get(SendOption.Reliable);
+ try
+ {
+ this.WriteJoinedMessage(client, messageWriter2, true);
+ client.Connection.Send(messageWriter2);
+ this.BroadcastJoinMessage(client, messageWriter2);
+ }
+ catch
+ {
+ }
+ finally
+ {
+ messageWriter2.Recycle();
+ }
+ }
+
+ private void EndGame(MessageReader message, InnerNetServer.Player source)
+ {
+ if (source.Id == this.HostId)
+ {
+ this.GameState = GameStates.Ended;
+ MessageWriter messageWriter = MessageWriter.Get(SendOption.Reliable);
+ messageWriter.CopyFrom(message);
+ this.Broadcast(messageWriter, null);
+ messageWriter.Recycle();
+ List<InnerNetServer.Player> clients = this.Clients;
+ lock (clients)
+ {
+ this.Clients.Clear();
+ return;
+ }
+ }
+ Debug.LogWarning("Reset request rejected from: " + source.Id);
+ }
+
+ private void StartGame(MessageReader message, InnerNetServer.Player source)
+ {
+ this.GameState = GameStates.Started;
+ MessageWriter messageWriter = MessageWriter.Get(SendOption.Reliable);
+ messageWriter.CopyFrom(message);
+ this.Broadcast(messageWriter, null);
+ messageWriter.Recycle();
+ }
+
+ private void ClientDisconnect(InnerNetServer.Player client)
+ {
+ Debug.Log("Server DC client " + client.Id);
+ List<InnerNetServer.Player> clients = this.Clients;
+ lock (clients)
+ {
+ this.Clients.Remove(client);
+ client.Connection.Dispose();
+ if (this.Clients.Count > 0)
+ {
+ this.HostId = this.Clients[0].Id;
+ }
+ }
+ MessageWriter messageWriter = MessageWriter.Get(SendOption.Reliable);
+ messageWriter.StartMessage(4);
+ messageWriter.Write(32);
+ messageWriter.Write(client.Id);
+ messageWriter.Write(this.HostId);
+ messageWriter.Write(0);
+ messageWriter.EndMessage();
+ this.Broadcast(messageWriter, null);
+ messageWriter.Recycle();
+ }
+
+ protected void SendTo(MessageWriter msg, int targetId)
+ {
+ List<InnerNetServer.Player> clients = this.Clients;
+ lock (clients)
+ {
+ for (int i = 0; i < this.Clients.Count; i++)
+ {
+ InnerNetServer.Player player = this.Clients[i];
+ if (player.Id == targetId)
+ {
+ try
+ {
+ player.Connection.Send(msg);
+ break;
+ }
+ catch (Exception exception)
+ {
+ Debug.LogException(exception);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ protected void Broadcast(MessageWriter msg, InnerNetServer.Player source)
+ {
+ List<InnerNetServer.Player> clients = this.Clients;
+ lock (clients)
+ {
+ for (int i = 0; i < this.Clients.Count; i++)
+ {
+ InnerNetServer.Player player = this.Clients[i];
+ if (player != source)
+ {
+ try
+ {
+ player.Connection.Send(msg);
+ }
+ catch
+ {
+ }
+ }
+ }
+ }
+ }
+
+ private void BroadcastJoinMessage(InnerNetServer.Player client, MessageWriter msg)
+ {
+ msg.Clear(SendOption.Reliable);
+ msg.StartMessage(1);
+ msg.Write(32);
+ msg.Write(client.Id);
+ msg.Write(this.HostId);
+ msg.EndMessage();
+ this.Broadcast(msg, client);
+ }
+
+ private void WriteJoinedMessage(InnerNetServer.Player client, MessageWriter msg, bool clear)
+ {
+ if (clear)
+ {
+ msg.Clear(SendOption.Reliable);
+ }
+ msg.StartMessage(7);
+ msg.Write(32);
+ msg.Write(client.Id);
+ msg.Write(this.HostId);
+ msg.WritePacked(this.Clients.Count - 1);
+ for (int i = 0; i < this.Clients.Count; i++)
+ {
+ InnerNetServer.Player player = this.Clients[i];
+ if (player != client)
+ {
+ msg.WritePacked(player.Id);
+ }
+ }
+ msg.EndMessage();
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/InnerNet/JoinFailureReasons.cs b/Client/Assembly-CSharp/InnerNet/JoinFailureReasons.cs
new file mode 100644
index 0000000..6e5bf37
--- /dev/null
+++ b/Client/Assembly-CSharp/InnerNet/JoinFailureReasons.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace InnerNet
+{
+ public enum JoinFailureReasons : byte
+ {
+ TooManyPlayers = 1,
+ GameStarted,
+ GameNotFound,
+ HostNotReady,
+ IncorrectVersion,
+ Banned,
+ Kicked
+ }
+}
diff --git a/Client/Assembly-CSharp/InnerNet/LimboStates.cs b/Client/Assembly-CSharp/InnerNet/LimboStates.cs
new file mode 100644
index 0000000..80446bb
--- /dev/null
+++ b/Client/Assembly-CSharp/InnerNet/LimboStates.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace InnerNet
+{
+ public enum LimboStates
+ {
+ PreSpawn,
+ NotLimbo,
+ WaitingForHost
+ }
+}
diff --git a/Client/Assembly-CSharp/InnerNet/MatchMakerModes.cs b/Client/Assembly-CSharp/InnerNet/MatchMakerModes.cs
new file mode 100644
index 0000000..64604a4
--- /dev/null
+++ b/Client/Assembly-CSharp/InnerNet/MatchMakerModes.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace InnerNet
+{
+ public enum MatchMakerModes
+ {
+ None,
+ Client,
+ HostAndClient
+ }
+}
diff --git a/Client/Assembly-CSharp/InnerNet/MessageExtensions.cs b/Client/Assembly-CSharp/InnerNet/MessageExtensions.cs
new file mode 100644
index 0000000..9e9bd49
--- /dev/null
+++ b/Client/Assembly-CSharp/InnerNet/MessageExtensions.cs
@@ -0,0 +1,24 @@
+using System;
+using Hazel;
+
+namespace InnerNet
+{
+ public static class MessageExtensions
+ {
+ public static void WriteNetObject(this MessageWriter self, InnerNetObject obj)
+ {
+ if (!obj)
+ {
+ self.Write(0);
+ return;
+ }
+ self.WritePacked(obj.NetId);
+ }
+
+ public static T ReadNetObject<T>(this MessageReader self) where T : InnerNetObject
+ {
+ uint netId = self.ReadPackedUInt32();
+ return AmongUsClient.Instance.FindObjectByNetId<T>(netId);
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/InnerNet/SpawnFlags.cs b/Client/Assembly-CSharp/InnerNet/SpawnFlags.cs
new file mode 100644
index 0000000..644e4e9
--- /dev/null
+++ b/Client/Assembly-CSharp/InnerNet/SpawnFlags.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace InnerNet
+{
+ [Flags]
+ public enum SpawnFlags : byte
+ {
+ None = 0,
+ IsClientCharacter = 1
+ }
+}
diff --git a/Client/Assembly-CSharp/InnerNet/Tags.cs b/Client/Assembly-CSharp/InnerNet/Tags.cs
new file mode 100644
index 0000000..aa48fc3
--- /dev/null
+++ b/Client/Assembly-CSharp/InnerNet/Tags.cs
@@ -0,0 +1,35 @@
+using System;
+
+namespace InnerNet
+{
+ public static class Tags
+ {
+ public const byte HostGame = 0;
+
+ public const byte JoinGame = 1;
+
+ public const byte StartGame = 2;
+
+ public const byte RemoveGame = 3;
+
+ public const byte RemovePlayer = 4;
+
+ public const byte GameData = 5;
+
+ public const byte GameDataTo = 6;
+
+ public const byte JoinedGame = 7;
+
+ public const byte EndGame = 8;
+
+ public const byte GetGameList = 9;
+
+ public const byte AlterGame = 10;
+
+ public const byte KickPlayer = 11;
+
+ public const byte WaitForHost = 12;
+
+ public const byte Redirect = 13;
+ }
+}
diff --git a/Client/Assembly-CSharp/IntRange.cs b/Client/Assembly-CSharp/IntRange.cs
new file mode 100644
index 0000000..1989b3b
--- /dev/null
+++ b/Client/Assembly-CSharp/IntRange.cs
@@ -0,0 +1,71 @@
+using System;
+using UnityEngine;
+
+[Serializable]
+public class IntRange
+{
+ public int min;
+
+ public int max;
+
+ public IntRange()
+ {
+ }
+
+ public IntRange(int min, int max)
+ {
+ this.min = min;
+ this.max = max;
+ }
+
+ public int Next()
+ {
+ return UnityEngine.Random.Range(this.min, this.max);
+ }
+
+ public bool Contains(int value)
+ {
+ return this.min <= value && this.max >= value;
+ }
+
+ public static int Next(int max)
+ {
+ return (int)(UnityEngine.Random.value * (float)max);
+ }
+
+ internal static int Next(int min, int max)
+ {
+ return UnityEngine.Random.Range(min, max);
+ }
+
+ internal static byte NextByte(byte max)
+ {
+ return (byte)UnityEngine.Random.Range(0, (int)max);
+ }
+
+ public static void FillRandom(sbyte min, sbyte max, sbyte[] array)
+ {
+ for (int i = 0; i < array.Length; i++)
+ {
+ array[i] = (sbyte)IntRange.Next((int)min, (int)max);
+ }
+ }
+
+ public static int RandomSign()
+ {
+ if (!BoolRange.Next(0.5f))
+ {
+ return -1;
+ }
+ return 1;
+ }
+
+ public static void FillRandomRange(sbyte[] array)
+ {
+ for (int i = 0; i < array.Length; i++)
+ {
+ array[i] = (sbyte)i;
+ }
+ array.Shuffle<sbyte>();
+ }
+}
diff --git a/Client/Assembly-CSharp/IntroCutscene.cs b/Client/Assembly-CSharp/IntroCutscene.cs
new file mode 100644
index 0000000..3271c11
--- /dev/null
+++ b/Client/Assembly-CSharp/IntroCutscene.cs
@@ -0,0 +1,155 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+
+public class IntroCutscene : MonoBehaviour
+{
+ public static IntroCutscene Instance;
+
+ public TextRenderer Title;
+
+ public TextRenderer ImpostorText;
+
+ public PoolablePlayer PlayerPrefab;
+
+ public MeshRenderer BackgroundBar;
+
+ public MeshRenderer Foreground;
+
+ public FloatRange ForegroundRadius;
+
+ public SpriteRenderer FrontMost;
+
+ public AudioClip IntroStinger;
+
+ public float BaseY = -0.25f;
+
+ public IEnumerator CoBegin(List<PlayerControl> yourTeam, bool isImpostor)
+ {
+ SoundManager.Instance.PlaySound(this.IntroStinger, false, 1f);
+ if (!isImpostor)
+ {
+ this.BeginCrewmate(yourTeam);
+ }
+ else
+ {
+ this.BeginImpostor(yourTeam);
+ }
+ Color c = this.Title.Color;
+ Color fade = Color.black;
+ Color impColor = Color.white;
+ Vector3 titlePos = this.Title.transform.localPosition;
+ float timer = 0f;
+ while (timer < 3f)
+ {
+ timer += Time.deltaTime;
+ float num = Mathf.Min(1f, timer / 3f);
+ this.Foreground.material.SetFloat("_Rad", this.ForegroundRadius.ExpOutLerp(num * 2f));
+ fade.a = Mathf.Lerp(1f, 0f, num * 3f);
+ this.FrontMost.color = fade;
+ c.a = Mathf.Clamp(FloatRange.ExpOutLerp(num, 0f, 1f), 0f, 1f);
+ this.Title.Color = c;
+ impColor.a = Mathf.Lerp(0f, 1f, (num - 0.3f) * 3f);
+ this.ImpostorText.Color = impColor;
+ titlePos.y = 2.7f - num * 0.3f;
+ this.Title.transform.localPosition = titlePos;
+ yield return null;
+ }
+ timer = 0f;
+ while (timer < 1f)
+ {
+ timer += Time.deltaTime;
+ float num2 = timer / 1f;
+ fade.a = Mathf.Lerp(0f, 1f, num2 * 3f);
+ this.FrontMost.color = fade;
+ yield return null;
+ }
+ UnityEngine.Object.Destroy(base.gameObject);
+ yield break;
+ }
+
+ private void BeginCrewmate(List<PlayerControl> yourTeam)
+ {
+ Vector3 position = this.BackgroundBar.transform.position;
+ position.y -= 0.25f;
+ this.BackgroundBar.transform.position = position;
+ int adjustedNumImpostors = PlayerControl.GameOptions.GetAdjustedNumImpostors(GameData.Instance.PlayerCount);
+ if (adjustedNumImpostors == 1)
+ {
+ this.ImpostorText.Text = DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.NumImpostorsS, Array.Empty<object>());
+ }
+ else
+ {
+ this.ImpostorText.Text = DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.NumImpostorsP, new object[]
+ {
+ adjustedNumImpostors
+ });
+ }
+ this.BackgroundBar.material.SetColor("_Color", Palette.CrewmateBlue);
+ this.Title.Text = DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.Crewmate, Array.Empty<object>());
+ this.Title.Color = Palette.CrewmateBlue;
+ for (int i = 0; i < yourTeam.Count; i++)
+ {
+ PlayerControl playerControl = yourTeam[i];
+ if (playerControl)
+ {
+ GameData.PlayerInfo data = playerControl.Data;
+ if (data != null)
+ {
+ int num = (i % 2 == 0) ? -1 : 1;
+ int num2 = (i + 1) / 2;
+ float num3 = ((i == 0) ? 1.2f : 1f) - (float)num2 * 0.12f;
+ float num4 = 1f - (float)num2 * 0.08f;
+ float num5 = (float)((i == 0) ? -8 : -1);
+ PoolablePlayer poolablePlayer = UnityEngine.Object.Instantiate<PoolablePlayer>(this.PlayerPrefab, base.transform);
+ poolablePlayer.name = data.PlayerName + "Dummy";
+ poolablePlayer.SetFlipX(i % 2 == 0);
+ poolablePlayer.transform.localPosition = new Vector3(0.8f * (float)num * (float)num2 * num4, this.BaseY - 0.25f + (float)num2 * 0.1f, num5 + (float)num2 * 0.01f) * 1.5f;
+ Vector3 localScale = new Vector3(num3, num3, num3) * 1.5f;
+ poolablePlayer.transform.localScale = localScale;
+ PlayerControl.SetPlayerMaterialColors((int)data.ColorId, poolablePlayer.Body);
+ DestroyableSingleton<HatManager>.Instance.SetSkin(poolablePlayer.SkinSlot, data.SkinId);
+ PlayerControl.SetHatImage(data.HatId, poolablePlayer.HatSlot);
+ PlayerControl.SetPetImage(data.PetId, (int)data.ColorId, poolablePlayer.PetSlot);
+ poolablePlayer.NameText.gameObject.SetActive(false);
+ }
+ }
+ }
+ }
+
+ private void BeginImpostor(List<PlayerControl> yourTeam)
+ {
+ this.ImpostorText.gameObject.SetActive(false);
+ this.Title.Text = DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.Impostor, Array.Empty<object>());
+ this.Title.Color = Palette.ImpostorRed;
+ for (int i = 0; i < yourTeam.Count; i++)
+ {
+ PlayerControl playerControl = yourTeam[i];
+ if (playerControl)
+ {
+ GameData.PlayerInfo data = playerControl.Data;
+ if (data != null)
+ {
+ int num = (i % 2 == 0) ? -1 : 1;
+ int num2 = (i + 1) / 2;
+ float num3 = 1f - (float)num2 * 0.075f;
+ float num4 = 1f - (float)num2 * 0.035f;
+ float num5 = (float)((i == 0) ? -8 : -1);
+ PoolablePlayer poolablePlayer = UnityEngine.Object.Instantiate<PoolablePlayer>(this.PlayerPrefab, base.transform);
+ poolablePlayer.transform.localPosition = new Vector3((float)(num * num2) * num4, this.BaseY + (float)num2 * 0.15f, num5 + (float)num2 * 0.01f) * 1.5f;
+ Vector3 vector = new Vector3(num3, num3, num3) * 1.5f;
+ poolablePlayer.transform.localScale = vector;
+ poolablePlayer.SetFlipX(i % 2 == 1);
+ PlayerControl.SetPlayerMaterialColors((int)data.ColorId, poolablePlayer.Body);
+ DestroyableSingleton<HatManager>.Instance.SetSkin(poolablePlayer.SkinSlot, data.SkinId);
+ PlayerControl.SetHatImage(data.HatId, poolablePlayer.HatSlot);
+ PlayerControl.SetPetImage(data.PetId, (int)data.ColorId, poolablePlayer.PetSlot);
+ TextRenderer nameText = poolablePlayer.NameText;
+ nameText.Text = data.PlayerName;
+ nameText.transform.localScale = vector.Inv();
+ }
+ }
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/JoinGameButton.cs b/Client/Assembly-CSharp/JoinGameButton.cs
new file mode 100644
index 0000000..f9cea88
--- /dev/null
+++ b/Client/Assembly-CSharp/JoinGameButton.cs
@@ -0,0 +1,119 @@
+using System;
+using System.Collections;
+using InnerNet;
+using PowerTools;
+using UnityEngine;
+
+public class JoinGameButton : MonoBehaviour, IConnectButton
+{
+ public AudioClip IntroMusic;
+
+ public TextBox GameIdText;
+
+ public TextRenderer gameNameText;
+
+ public float timeRecieved;
+
+ public SpriteRenderer FillScreen;
+
+ public SpriteAnim connectIcon;
+
+ public AnimationClip connectClip;
+
+ public GameModes GameMode;
+
+ public string netAddress;
+
+ public void OnClick()
+ {
+ if (string.IsNullOrWhiteSpace(this.netAddress))
+ {
+ return;
+ }
+ if (NameTextBehaviour.Instance.ShakeIfInvalid())
+ {
+ return;
+ }
+ if (StatsManager.Instance.AmBanned)
+ {
+ AmongUsClient.Instance.LastDisconnectReason = DisconnectReasons.IntentionalLeaving;
+ DestroyableSingleton<DisconnectPopup>.Instance.Show();
+ return;
+ }
+ if (!DestroyableSingleton<MatchMaker>.Instance.Connecting(this))
+ {
+ return;
+ }
+ AmongUsClient.Instance.GameMode = this.GameMode;
+ if (this.GameMode == GameModes.OnlineGame)
+ {
+ AmongUsClient.Instance.SetEndpoint(DestroyableSingleton<ServerManager>.Instance.OnlineNetAddress, 22023);
+ AmongUsClient.Instance.MainMenuScene = "MMOnline";
+ int num = InnerNetClient.GameNameToInt(this.GameIdText.text);
+ if (num == -1)
+ {
+ base.StartCoroutine(Effects.Shake(this.GameIdText.transform, 0.75f, 0.25f));
+ DestroyableSingleton<MatchMaker>.Instance.NotConnecting();
+ return;
+ }
+ AmongUsClient.Instance.GameId = num;
+ }
+ else
+ {
+ AmongUsClient.Instance.SetEndpoint(this.netAddress, 22023);
+ AmongUsClient.Instance.GameId = 32;
+ AmongUsClient.Instance.GameMode = GameModes.LocalGame;
+ AmongUsClient.Instance.MainMenuScene = "MatchMaking";
+ }
+ base.StartCoroutine(this.JoinGame());
+ }
+
+ private IEnumerator JoinGame()
+ {
+ if (this.FillScreen)
+ {
+ SoundManager.Instance.CrossFadeSound("MainBG", null, 0.5f, 1.5f);
+ this.FillScreen.gameObject.SetActive(true);
+ for (float time = 0f; time < 0.25f; time += Time.deltaTime)
+ {
+ this.FillScreen.color = Color.Lerp(Color.clear, Color.black, time / 0.25f);
+ yield return null;
+ }
+ this.FillScreen.color = Color.black;
+ }
+ AmongUsClient.Instance.OnlineScene = "OnlineGame";
+ AmongUsClient.Instance.Connect(MatchMakerModes.Client);
+ yield return AmongUsClient.Instance.WaitForConnectionOrFail();
+ if (AmongUsClient.Instance.mode == MatchMakerModes.None)
+ {
+ if (this.FillScreen)
+ {
+ SoundManager.Instance.CrossFadeSound("MainBG", this.IntroMusic, 0.5f, 1.5f);
+ for (float time = 0f; time < 0.25f; time += Time.deltaTime)
+ {
+ this.FillScreen.color = Color.Lerp(Color.black, Color.clear, time / 0.25f);
+ yield return null;
+ }
+ this.FillScreen.color = Color.clear;
+ }
+ DestroyableSingleton<MatchMaker>.Instance.NotConnecting();
+ }
+ yield break;
+ }
+
+ public void SetGameName(string[] gameNameParts)
+ {
+ this.gameNameText.Text = gameNameParts[0] + " (" + gameNameParts[2] + "/10)";
+ }
+
+ public void StartIcon()
+ {
+ this.connectIcon.Play(this.connectClip, 1f);
+ }
+
+ public void StopIcon()
+ {
+ this.connectIcon.Stop();
+ this.connectIcon.GetComponent<SpriteRenderer>().sprite = null;
+ }
+}
diff --git a/Client/Assembly-CSharp/KerningPair.cs b/Client/Assembly-CSharp/KerningPair.cs
new file mode 100644
index 0000000..a264649
--- /dev/null
+++ b/Client/Assembly-CSharp/KerningPair.cs
@@ -0,0 +1,11 @@
+using System;
+
+[Serializable]
+public class KerningPair
+{
+ public char First;
+
+ public char Second;
+
+ public int Pixels = 4;
+}
diff --git a/Client/Assembly-CSharp/KeyboardJoystick.cs b/Client/Assembly-CSharp/KeyboardJoystick.cs
new file mode 100644
index 0000000..848eff3
--- /dev/null
+++ b/Client/Assembly-CSharp/KeyboardJoystick.cs
@@ -0,0 +1,79 @@
+using System;
+using UnityEngine;
+
+public class KeyboardJoystick : MonoBehaviour, IVirtualJoystick
+{
+ public Vector2 Delta
+ {
+ get
+ {
+ return this.del;
+ }
+ }
+
+ private Vector2 del;
+
+ private void Update()
+ {
+ if (!PlayerControl.LocalPlayer)
+ {
+ return;
+ }
+ this.del.x = (this.del.y = 0f);
+ if (Input.GetKey(KeyCode.RightArrow) || Input.GetKey(KeyCode.D))
+ {
+ this.del.x = this.del.x + 1f;
+ }
+ if (Input.GetKey(KeyCode.LeftArrow) || Input.GetKey(KeyCode.A))
+ {
+ this.del.x = this.del.x - 1f;
+ }
+ if (Input.GetKey(KeyCode.UpArrow) || Input.GetKey(KeyCode.W))
+ {
+ this.del.y = this.del.y + 1f;
+ }
+ if (Input.GetKey(KeyCode.DownArrow) || Input.GetKey(KeyCode.S))
+ {
+ this.del.y = this.del.y - 1f;
+ }
+ if (Input.GetKeyDown(KeyCode.R))
+ {
+ DestroyableSingleton<HudManager>.Instance.ReportButton.DoClick();
+ }
+ if (Input.GetKeyDown(KeyCode.Space) || Input.GetKeyDown(KeyCode.E))
+ {
+ DestroyableSingleton<HudManager>.Instance.UseButton.DoClick();
+ }
+ if (Input.GetKeyDown(KeyCode.Tab))
+ {
+ DestroyableSingleton<HudManager>.Instance.ShowMap(delegate(MapBehaviour m)
+ {
+ m.ShowNormalMap();
+ });
+ }
+ if (PlayerControl.LocalPlayer.Data.IsImpostor && Input.GetKeyDown(KeyCode.Q))
+ {
+ DestroyableSingleton<HudManager>.Instance.KillButton.PerformKill();
+ }
+ if (Input.GetKeyDown(KeyCode.Escape))
+ {
+ if (Minigame.Instance)
+ {
+ Minigame.Instance.Close();
+ }
+ else if (DestroyableSingleton<HudManager>.InstanceExists && MapBehaviour.Instance && MapBehaviour.Instance.IsOpen)
+ {
+ MapBehaviour.Instance.Close();
+ }
+ else
+ {
+ CustomPlayerMenu customPlayerMenu = UnityEngine.Object.FindObjectOfType<CustomPlayerMenu>();
+ if (customPlayerMenu)
+ {
+ customPlayerMenu.Close(true);
+ }
+ }
+ }
+ this.del.Normalize();
+ }
+}
diff --git a/Client/Assembly-CSharp/KeypadGame.cs b/Client/Assembly-CSharp/KeypadGame.cs
new file mode 100644
index 0000000..91d7a7a
--- /dev/null
+++ b/Client/Assembly-CSharp/KeypadGame.cs
@@ -0,0 +1,135 @@
+using System;
+using System.Collections;
+using UnityEngine;
+
+public class KeypadGame : Minigame
+{
+ public TextRenderer TargetText;
+
+ public TextRenderer NumberText;
+
+ public int number;
+
+ public string numString = string.Empty;
+
+ private bool animating;
+
+ public SpriteRenderer AcceptButton;
+
+ private LifeSuppSystemType system;
+
+ private NoOxyTask oxyTask;
+
+ private bool done;
+
+ public override void Begin(PlayerTask task)
+ {
+ base.Begin(task);
+ this.oxyTask = (NoOxyTask)task;
+ this.TargetText.Text = "today's code:\r\n" + this.oxyTask.targetNumber.ToString("D5");
+ this.NumberText.Text = string.Empty;
+ this.system = (LifeSuppSystemType)ShipStatus.Instance.Systems[SystemTypes.LifeSupp];
+ this.done = this.system.GetConsoleComplete(base.ConsoleId);
+ }
+
+ public void ClickNumber(int i)
+ {
+ if (this.animating)
+ {
+ return;
+ }
+ if (this.done)
+ {
+ return;
+ }
+ if (this.NumberText.Text.Length >= 5)
+ {
+ base.StartCoroutine(this.BlinkAccept());
+ return;
+ }
+ this.numString += i;
+ this.number = this.number * 10 + i;
+ this.NumberText.Text = this.numString;
+ }
+
+ private IEnumerator BlinkAccept()
+ {
+ int num;
+ for (int i = 0; i < 5; i = num)
+ {
+ this.AcceptButton.color = Color.gray;
+ yield return null;
+ yield return null;
+ this.AcceptButton.color = Color.white;
+ yield return null;
+ yield return null;
+ num = i + 1;
+ }
+ yield break;
+ }
+
+ public void ClearEntry()
+ {
+ if (this.animating)
+ {
+ return;
+ }
+ this.number = 0;
+ this.numString = string.Empty;
+ this.NumberText.Text = string.Empty;
+ }
+
+ public void Enter()
+ {
+ if (this.animating)
+ {
+ return;
+ }
+ base.StartCoroutine(this.Animate());
+ }
+
+ private IEnumerator Animate()
+ {
+ this.animating = true;
+ WaitForSeconds wait = new WaitForSeconds(0.1f);
+ yield return wait;
+ this.NumberText.Text = string.Empty;
+ yield return wait;
+ if (this.oxyTask.targetNumber == this.number)
+ {
+ this.done = true;
+ byte amount = (byte)(base.ConsoleId | 64);
+ ShipStatus.Instance.RpcRepairSystem(SystemTypes.LifeSupp, (int)amount);
+ try
+ {
+ ((SabotageTask)this.MyTask).MarkContributed();
+ }
+ catch
+ {
+ }
+ this.NumberText.Text = "OK";
+ yield return wait;
+ this.NumberText.Text = string.Empty;
+ yield return wait;
+ this.NumberText.Text = "OK";
+ yield return wait;
+ this.NumberText.Text = string.Empty;
+ yield return wait;
+ this.NumberText.Text = "OK";
+ }
+ else
+ {
+ this.NumberText.Text = "Bad";
+ yield return wait;
+ this.NumberText.Text = string.Empty;
+ yield return wait;
+ this.NumberText.Text = "Bad";
+ yield return wait;
+ this.numString = string.Empty;
+ this.number = 0;
+ this.NumberText.Text = this.numString;
+ }
+ this.animating = false;
+ yield break;
+ }
+}
diff --git a/Client/Assembly-CSharp/KillAnimType.cs b/Client/Assembly-CSharp/KillAnimType.cs
new file mode 100644
index 0000000..29b5198
--- /dev/null
+++ b/Client/Assembly-CSharp/KillAnimType.cs
@@ -0,0 +1,10 @@
+using System;
+
+public enum KillAnimType
+{
+ Stab,
+ Tongue,
+ Shoot,
+ Neck,
+ None
+}
diff --git a/Client/Assembly-CSharp/KillAnimation.cs b/Client/Assembly-CSharp/KillAnimation.cs
new file mode 100644
index 0000000..d6e980a
--- /dev/null
+++ b/Client/Assembly-CSharp/KillAnimation.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Collections;
+using PowerTools;
+using UnityEngine;
+
+public class KillAnimation : MonoBehaviour
+{
+ public AnimationClip BlurAnim;
+
+ public DeadBody bodyPrefab;
+
+ public Vector3 BodyOffset;
+
+ public IEnumerator CoPerformKill(PlayerControl source, PlayerControl target)
+ {
+ bool isParticipant = PlayerControl.LocalPlayer == source || PlayerControl.LocalPlayer == target;
+ PlayerPhysics sourcePhys = source.MyPhysics;
+ KillAnimation.SetMovement(source, false);
+ KillAnimation.SetMovement(target, false);
+ if (isParticipant)
+ {
+ Camera.main.GetComponent<FollowerCamera>().Locked = true;
+ }
+ target.Die(DeathReason.Kill);
+ SpriteAnim sourceAnim = source.GetComponent<SpriteAnim>();
+ yield return new WaitForAnimationFinish(sourceAnim, this.BlurAnim);
+ source.NetTransform.SnapTo(target.transform.position);
+ sourceAnim.Play(sourcePhys.IdleAnim, 1f);
+ KillAnimation.SetMovement(source, true);
+ DeadBody deadBody = UnityEngine.Object.Instantiate<DeadBody>(this.bodyPrefab);
+ Vector3 vector = target.transform.position + this.BodyOffset;
+ vector.z = vector.y / 1000f;
+ deadBody.transform.position = vector;
+ deadBody.ParentId = target.PlayerId;
+ target.SetPlayerMaterialColors(deadBody.GetComponent<Renderer>());
+ KillAnimation.SetMovement(target, true);
+ if (isParticipant)
+ {
+ Camera.main.GetComponent<FollowerCamera>().Locked = false;
+ }
+ yield break;
+ }
+
+ public static void SetMovement(PlayerControl source, bool canMove)
+ {
+ source.moveable = canMove;
+ source.MyPhysics.ResetAnim(false);
+ source.NetTransform.enabled = canMove;
+ source.MyPhysics.enabled = canMove;
+ source.NetTransform.Halt();
+ }
+}
diff --git a/Client/Assembly-CSharp/KillButtonManager.cs b/Client/Assembly-CSharp/KillButtonManager.cs
new file mode 100644
index 0000000..6c43b4f
--- /dev/null
+++ b/Client/Assembly-CSharp/KillButtonManager.cs
@@ -0,0 +1,69 @@
+using System;
+using UnityEngine;
+
+public class KillButtonManager : MonoBehaviour
+{
+ public PlayerControl CurrentTarget;
+
+ public SpriteRenderer renderer;
+
+ public TextRenderer TimerText;
+
+ public bool isCoolingDown = true;
+
+ public bool isActive;
+
+ private Vector2 uv;
+
+ public void Start()
+ {
+ this.renderer.SetCooldownNormalizedUvs();
+ this.SetTarget(null);
+ }
+
+ public void PerformKill()
+ {
+ if (base.isActiveAndEnabled && this.CurrentTarget && !this.isCoolingDown && !PlayerControl.LocalPlayer.Data.IsDead && PlayerControl.LocalPlayer.CanMove)
+ {
+ PlayerControl.LocalPlayer.RpcMurderPlayer(this.CurrentTarget);
+ this.SetTarget(null);
+ }
+ }
+
+ public void SetTarget(PlayerControl target)
+ {
+ if (this.CurrentTarget && this.CurrentTarget != target)
+ {
+ this.CurrentTarget.GetComponent<SpriteRenderer>().material.SetFloat("_Outline", 0f);
+ }
+ this.CurrentTarget = target;
+ if (this.CurrentTarget)
+ {
+ SpriteRenderer component = this.CurrentTarget.GetComponent<SpriteRenderer>();
+ component.material.SetFloat("_Outline", (float)(this.isActive ? 1 : 0));
+ component.material.SetColor("_OutlineColor", Color.red);
+ this.renderer.color = Palette.EnabledColor;
+ this.renderer.material.SetFloat("_Desat", 0f);
+ return;
+ }
+ this.renderer.color = Palette.DisabledColor;
+ this.renderer.material.SetFloat("_Desat", 1f);
+ }
+
+ public void SetCoolDown(float timer, float maxTimer)
+ {
+ float num = Mathf.Clamp(timer / maxTimer, 0f, 1f);
+ if (this.renderer)
+ {
+ this.renderer.material.SetFloat("_Percent", num);
+ }
+ this.isCoolingDown = (num > 0f);
+ if (this.isCoolingDown)
+ {
+ this.TimerText.Text = Mathf.CeilToInt(timer).ToString();
+ this.TimerText.gameObject.SetActive(true);
+ return;
+ }
+ this.TimerText.gameObject.SetActive(false);
+ }
+}
diff --git a/Client/Assembly-CSharp/KillOverlay.cs b/Client/Assembly-CSharp/KillOverlay.cs
new file mode 100644
index 0000000..9c0c226
--- /dev/null
+++ b/Client/Assembly-CSharp/KillOverlay.cs
@@ -0,0 +1,114 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+
+public class KillOverlay : MonoBehaviour
+{
+ public bool IsOpen
+ {
+ get
+ {
+ return this.showAll != null || this.queue.Count > 0;
+ }
+ }
+
+ public SpriteRenderer background;
+
+ public GameObject flameParent;
+
+ public OverlayKillAnimation[] KillAnims;
+
+ public float FadeTime = 0.6f;
+
+ public OverlayKillAnimation EmergencyOverlay;
+
+ public OverlayKillAnimation ReportOverlay;
+
+ private Queue<Func<IEnumerator>> queue = new Queue<Func<IEnumerator>>();
+
+ private Coroutine showAll;
+
+ private Coroutine showOne;
+
+ public IEnumerator WaitForFinish()
+ {
+ while (this.showAll != null || this.queue.Count > 0)
+ {
+ yield return null;
+ }
+ yield break;
+ }
+
+ public void ShowOne(PlayerControl killer, GameData.PlayerInfo victim)
+ {
+ this.queue.Enqueue(() => this.CoShowOne(this.KillAnims.Random<OverlayKillAnimation>(), killer, victim));
+ if (this.showAll == null)
+ {
+ this.showAll = base.StartCoroutine(this.ShowAll());
+ }
+ }
+
+ public void ShowOne(OverlayKillAnimation killAnimPrefab, PlayerControl killer, GameData.PlayerInfo victim)
+ {
+ this.queue.Enqueue(() => this.CoShowOne(killAnimPrefab, killer, victim));
+ if (this.showAll == null)
+ {
+ this.showAll = base.StartCoroutine(this.ShowAll());
+ }
+ }
+
+ private IEnumerator ShowAll()
+ {
+ while (this.queue.Count > 0 || this.showOne != null)
+ {
+ if (this.showOne == null)
+ {
+ this.showOne = base.StartCoroutine(this.queue.Dequeue()());
+ }
+ yield return null;
+ }
+ this.showAll = null;
+ yield break;
+ }
+
+ private IEnumerator CoShowOne(OverlayKillAnimation killAnimPrefab, PlayerControl killer, GameData.PlayerInfo victim)
+ {
+ OverlayKillAnimation overlayKillAnimation = UnityEngine.Object.Instantiate<OverlayKillAnimation>(killAnimPrefab, base.transform);
+ overlayKillAnimation.Begin(killer, victim);
+ overlayKillAnimation.gameObject.SetActive(false);
+ yield return this.CoShowOne(overlayKillAnimation);
+ yield break;
+ }
+
+ private IEnumerator CoShowOne(OverlayKillAnimation anim)
+ {
+ if (Constants.ShouldPlaySfx())
+ {
+ SoundManager.Instance.PlaySound(anim.Stinger, false, 1f).volume = anim.StingerVolume;
+ }
+ WaitForSeconds wait = new WaitForSeconds(0.083333336f);
+ this.background.enabled = true;
+ yield return wait;
+ this.background.enabled = false;
+ this.flameParent.SetActive(true);
+ this.flameParent.transform.localScale = new Vector3(1f, 0.3f, 1f);
+ this.flameParent.transform.localEulerAngles = new Vector3(0f, 0f, 25f);
+ yield return wait;
+ this.flameParent.transform.localScale = new Vector3(1f, 0.5f, 1f);
+ this.flameParent.transform.localEulerAngles = new Vector3(0f, 0f, -15f);
+ yield return wait;
+ this.flameParent.transform.localScale = new Vector3(1f, 1f, 1f);
+ this.flameParent.transform.localEulerAngles = new Vector3(0f, 0f, 0f);
+ anim.gameObject.SetActive(true);
+ yield return anim.WaitForFinish();
+ UnityEngine.Object.Destroy(anim.gameObject);
+ yield return new WaitForLerp(0.16666667f, delegate(float t)
+ {
+ this.flameParent.transform.localScale = new Vector3(1f, 1f - t, 1f);
+ });
+ this.flameParent.SetActive(false);
+ this.showOne = null;
+ yield break;
+ }
+}
diff --git a/Client/Assembly-CSharp/LanguageButton.cs b/Client/Assembly-CSharp/LanguageButton.cs
new file mode 100644
index 0000000..3d89dda
--- /dev/null
+++ b/Client/Assembly-CSharp/LanguageButton.cs
@@ -0,0 +1,12 @@
+using System;
+using UnityEngine;
+
+public class LanguageButton : MonoBehaviour
+{
+ public TextRenderer Title;
+
+ public PassiveButton Button;
+
+ [HideInInspector]
+ public TextAsset Language;
+}
diff --git a/Client/Assembly-CSharp/LanguageSetter.cs b/Client/Assembly-CSharp/LanguageSetter.cs
new file mode 100644
index 0000000..e5ccac7
--- /dev/null
+++ b/Client/Assembly-CSharp/LanguageSetter.cs
@@ -0,0 +1,50 @@
+using System;
+using UnityEngine;
+
+public class LanguageSetter : MonoBehaviour
+{
+ public LanguageButton ButtonPrefab;
+
+ public Scroller ButtonParent;
+
+ public float ButtonStart = 0.5f;
+
+ public float ButtonHeight = 0.5f;
+
+ private LanguageButton[] AllButtons;
+
+ public void Start()
+ {
+ TextAsset[] languages = DestroyableSingleton<TranslationController>.Instance.Languages;
+ Vector3 vector = new Vector3(0f, this.ButtonStart, -1f);
+ this.AllButtons = new LanguageButton[languages.Length];
+ for (int i = 0; i < languages.Length; i++)
+ {
+ LanguageButton button = UnityEngine.Object.Instantiate<LanguageButton>(this.ButtonPrefab, this.ButtonParent.Inner);
+ this.AllButtons[i] = button;
+ button.Language = languages[i];
+ button.Title.Text = languages[i].name;
+ if ((long)i == (long)((ulong)SaveManager.LastLanguage))
+ {
+ button.Title.Color = Color.green;
+ }
+ button.Button.OnClick.AddListener(delegate()
+ {
+ this.SetLanguage(button);
+ });
+ button.transform.localPosition = vector;
+ vector.y -= this.ButtonHeight;
+ }
+ this.ButtonParent.YBounds.max = Mathf.Max(0f, -vector.y - this.ButtonStart * 2f);
+ }
+
+ public void SetLanguage(LanguageButton selected)
+ {
+ for (int i = 0; i < this.AllButtons.Length; i++)
+ {
+ this.AllButtons[i].Title.Color = Color.white;
+ }
+ selected.Title.Color = Color.green;
+ DestroyableSingleton<TranslationController>.Instance.SetLanguage(selected.Language);
+ }
+}
diff --git a/Client/Assembly-CSharp/LanguageUnit.cs b/Client/Assembly-CSharp/LanguageUnit.cs
new file mode 100644
index 0000000..62ae51e
--- /dev/null
+++ b/Client/Assembly-CSharp/LanguageUnit.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using UnityEngine;
+
+public class LanguageUnit
+{
+ public bool IsEnglish;
+
+ private Dictionary<StringNames, string> AllStrings = new Dictionary<StringNames, string>();
+
+ private Dictionary<ImageNames, Sprite> AllImages = new Dictionary<ImageNames, Sprite>();
+
+ public LanguageUnit(TextAsset data, ImageData[] images)
+ {
+ foreach (ImageData imageData in images)
+ {
+ this.AllImages.Add(imageData.Name, imageData.Sprite);
+ }
+ using (StringReader stringReader = new StringReader(data.text))
+ {
+ for (string text = stringReader.ReadLine(); text != null; text = stringReader.ReadLine())
+ {
+ if (text.Length != 0)
+ {
+ int num = text.IndexOf(',');
+ if (num < 0)
+ {
+ Debug.LogWarning("Couldn't parse: " + text);
+ }
+ else
+ {
+ string text2 = text.Substring(0, num);
+ StringNames key;
+ if (!Enum.TryParse<StringNames>(text2, out key))
+ {
+ Debug.LogWarning("Couldn't parse: " + text2);
+ }
+ else
+ {
+ string value = LanguageUnit.UnescapeCodes(text.Substring(num + 1));
+ this.AllStrings.Add(key, value);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ public static string UnescapeCodes(string src)
+ {
+ if (src.IndexOf("\\n") < 0)
+ {
+ return src;
+ }
+ return src.Replace("\\n", "\n");
+ }
+
+ public string GetString(StringNames stringId, params object[] parts)
+ {
+ string text;
+ if (!this.AllStrings.TryGetValue(stringId, out text))
+ {
+ return "STRMISS";
+ }
+ if (parts.Length != 0)
+ {
+ return string.Format(text, parts);
+ }
+ return text;
+ }
+
+ public Sprite GetImage(ImageNames id)
+ {
+ Sprite result;
+ this.AllImages.TryGetValue(id, out result);
+ return result;
+ }
+}
diff --git a/Client/Assembly-CSharp/LeafBehaviour.cs b/Client/Assembly-CSharp/LeafBehaviour.cs
new file mode 100644
index 0000000..58e2b0a
--- /dev/null
+++ b/Client/Assembly-CSharp/LeafBehaviour.cs
@@ -0,0 +1,40 @@
+using System;
+using UnityEngine;
+
+public class LeafBehaviour : MonoBehaviour
+{
+ public Sprite[] Images;
+
+ public FloatRange SpinSpeed = new FloatRange(-45f, 45f);
+
+ public Vector2Range StartVel;
+
+ public float AccelRate = 30f;
+
+ [HideInInspector]
+ public LeafMinigame Parent;
+
+ public bool Held;
+
+ private static RandomFill<Sprite> ImageFiller = new RandomFill<Sprite>();
+
+ [HideInInspector]
+ public Rigidbody2D body;
+
+ public void Start()
+ {
+ LeafBehaviour.ImageFiller.Set(this.Images);
+ base.GetComponent<SpriteRenderer>().sprite = LeafBehaviour.ImageFiller.Get();
+ this.body = base.GetComponent<Rigidbody2D>();
+ this.body.angularVelocity = this.SpinSpeed.Next();
+ this.body.velocity = this.StartVel.Next();
+ }
+
+ public void FixedUpdate()
+ {
+ if (!this.Held && (double)base.transform.localPosition.x < -2.5)
+ {
+ this.Parent.LeafDone(this);
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/LeafMinigame.cs b/Client/Assembly-CSharp/LeafMinigame.cs
new file mode 100644
index 0000000..17f4d18
--- /dev/null
+++ b/Client/Assembly-CSharp/LeafMinigame.cs
@@ -0,0 +1,104 @@
+using System;
+using PowerTools;
+using UnityEngine;
+
+public class LeafMinigame : Minigame
+{
+ public LeafBehaviour LeafPrefab;
+
+ public Vector2Range ValidArea;
+
+ public SpriteAnim[] Arrows;
+
+ public AnimationClip[] Inactive;
+
+ public AnimationClip[] Active;
+
+ public AnimationClip[] Complete;
+
+ private Collider2D[] Leaves;
+
+ public AudioClip[] LeaveSounds;
+
+ public AudioClip[] SuckSounds;
+
+ private Controller myController = new Controller();
+
+ public override void Begin(PlayerTask task)
+ {
+ base.Begin(task);
+ this.Leaves = new Collider2D[this.MyNormTask.MaxStep - this.MyNormTask.taskStep];
+ for (int i = 0; i < this.Leaves.Length; i++)
+ {
+ LeafBehaviour leafBehaviour = UnityEngine.Object.Instantiate<LeafBehaviour>(this.LeafPrefab);
+ leafBehaviour.transform.SetParent(base.transform);
+ leafBehaviour.Parent = this;
+ Vector3 localPosition = this.ValidArea.Next();
+ localPosition.z = -1f;
+ leafBehaviour.transform.localPosition = localPosition;
+ this.Leaves[i] = leafBehaviour.GetComponent<Collider2D>();
+ }
+ }
+
+ public void FixedUpdate()
+ {
+ this.myController.Update();
+ for (int i = 0; i < this.Leaves.Length; i++)
+ {
+ Collider2D collider2D = this.Leaves[i];
+ if (collider2D)
+ {
+ LeafBehaviour component = collider2D.GetComponent<LeafBehaviour>();
+ switch (this.myController.CheckDrag(collider2D, false))
+ {
+ case DragState.TouchStart:
+ if (Constants.ShouldPlaySfx())
+ {
+ SoundManager.Instance.PlaySound(this.LeaveSounds.Random<AudioClip>(), false, 1f);
+ }
+ for (int j = 0; j < this.Arrows.Length; j++)
+ {
+ this.Arrows[j].Play(this.Active[j], 1f);
+ }
+ component.Held = true;
+ break;
+ case DragState.Dragging:
+ {
+ Vector2 vector = this.myController.DragPosition - component.body.position;
+ component.body.velocity = vector.normalized * Mathf.Min(3f, vector.magnitude * 3f);
+ break;
+ }
+ case DragState.Released:
+ component.Held = false;
+ for (int k = 0; k < this.Arrows.Length; k++)
+ {
+ this.Arrows[k].Play(this.Inactive[k], 1f);
+ this.Arrows[k].GetComponent<SpriteRenderer>().sprite = null;
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ public void LeafDone(LeafBehaviour leaf)
+ {
+ if (Constants.ShouldPlaySfx())
+ {
+ SoundManager.Instance.PlaySound(this.SuckSounds.Random<AudioClip>(), false, 1f);
+ }
+ UnityEngine.Object.Destroy(leaf.gameObject);
+ if (this.MyNormTask)
+ {
+ this.MyNormTask.NextStep();
+ if (this.MyNormTask.IsComplete)
+ {
+ for (int i = 0; i < this.Arrows.Length; i++)
+ {
+ this.Arrows[i].Play(this.Complete[i], 1f);
+ }
+ base.StartCoroutine(base.CoStartClose(0.75f));
+ }
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/LetterTree.cs b/Client/Assembly-CSharp/LetterTree.cs
new file mode 100644
index 0000000..b92f743
--- /dev/null
+++ b/Client/Assembly-CSharp/LetterTree.cs
@@ -0,0 +1,299 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+public class LetterTree
+{
+ private LetterTree.LetterNode root = new LetterTree.LetterNode('\0');
+
+ private enum NodeTypes : byte
+ {
+ NonTerm,
+ Terminal,
+ TerminalStrict,
+ TerminalExact,
+ TerminalUnbroken
+ }
+
+ private class LetterNode
+ {
+ public char Letter;
+
+ public LetterTree.NodeTypes Terminal;
+
+ public LetterTree.LetterNode[] Children = new LetterTree.LetterNode[26];
+
+ public LetterNode(char l)
+ {
+ this.Letter = l;
+ }
+
+ public LetterTree.LetterNode CreateChild(char l)
+ {
+ int num = LetterTree.LetterNode.ToIndex(l);
+ LetterTree.LetterNode letterNode = this.Children[num];
+ if (letterNode == null)
+ {
+ letterNode = (this.Children[num] = new LetterTree.LetterNode(l));
+ }
+ return letterNode;
+ }
+
+ public LetterTree.LetterNode FindChild(char l)
+ {
+ int num = LetterTree.LetterNode.ToIndex(l);
+ return this.Children[num];
+ }
+
+ public static int ToIndex(char c)
+ {
+ if (c >= 'A' && c <= 'Z')
+ {
+ return (int)(c - 'A');
+ }
+ if (c >= 'a' && c <= 'z')
+ {
+ return (int)(c - 'a');
+ }
+ if (c == 'с')
+ {
+ return 2;
+ }
+ if (c == 'к')
+ {
+ return 10;
+ }
+ if (c == '$')
+ {
+ return 18;
+ }
+ if (c == '+')
+ {
+ return 19;
+ }
+ if (c == '0')
+ {
+ return 14;
+ }
+ if (c == '1')
+ {
+ return 8;
+ }
+ if (c == '!')
+ {
+ return 8;
+ }
+ if (c == '2')
+ {
+ return 18;
+ }
+ if (c == '3')
+ {
+ return 4;
+ }
+ if (c == '4')
+ {
+ return 0;
+ }
+ if (c == '5')
+ {
+ return 18;
+ }
+ if (c == '7')
+ {
+ return 19;
+ }
+ if (c == '8')
+ {
+ return 1;
+ }
+ if (c > 'z')
+ {
+ foreach (char c2 in c.ToString().Normalize(NormalizationForm.FormD))
+ {
+ if (c2 <= 'z')
+ {
+ return LetterTree.LetterNode.ToIndex(c2);
+ }
+ }
+ }
+ return (int)c;
+ }
+ }
+
+ public void Clear()
+ {
+ this.root = new LetterTree.LetterNode('\0');
+ }
+
+ public void AddWord(string word)
+ {
+ LetterTree.LetterNode letterNode = this.root;
+ foreach (char l in word)
+ {
+ if (!this.IsFiller(l))
+ {
+ letterNode = letterNode.CreateChild(l);
+ }
+ }
+ if (letterNode.Terminal == LetterTree.NodeTypes.NonTerm)
+ {
+ letterNode.Terminal = LetterTree.NodeTypes.Terminal;
+ if (word[word.Length - 1] == '~')
+ {
+ letterNode.Terminal = LetterTree.NodeTypes.TerminalStrict;
+ }
+ if (word[word.Length - 1] == '^')
+ {
+ letterNode.Terminal = LetterTree.NodeTypes.TerminalExact;
+ }
+ if (word[word.Length - 1] == '`')
+ {
+ letterNode.Terminal = LetterTree.NodeTypes.TerminalUnbroken;
+ }
+ }
+ }
+
+ public bool IsFiller(char l)
+ {
+ return LetterTree.LetterNode.ToIndex(l) == (int)l;
+ }
+
+ public int Search(StringBuilder input, int start)
+ {
+ if (start >= input.Length || this.IsFiller(input[start]))
+ {
+ return 0;
+ }
+ bool exactStart = start == 0 || this.IsFiller(input[start - 1]);
+ return this.SubSearchRec(input, start, this.root, false, false, exactStart);
+ }
+
+ public int Search(string inputStr, int start)
+ {
+ StringBuilder stringBuilder = new StringBuilder(inputStr);
+ if (start >= stringBuilder.Length || this.IsFiller(stringBuilder[start]))
+ {
+ return 0;
+ }
+ bool exactStart = start == 0 || this.IsFiller(stringBuilder[start - 1]);
+ return this.SubSearchRec(stringBuilder, start, this.root, false, false, exactStart);
+ }
+
+ private int SubSearchRec(StringBuilder input, int start, LetterTree.LetterNode previous, bool postDupes, bool postBreak, bool exactStart)
+ {
+ if (start >= input.Length)
+ {
+ return -2;
+ }
+ char c = input[start];
+ if (this.IsFiller(c))
+ {
+ if (postDupes)
+ {
+ return -2;
+ }
+ int num = this.SubSearchRec(input, start + 1, previous, postDupes, true, exactStart);
+ if (num > 0)
+ {
+ return num + 1;
+ }
+ return -2;
+ }
+ else
+ {
+ if (c == previous.Letter && !postBreak)
+ {
+ int num2 = this.SubSearchRec(input, start + 1, previous, true, postBreak, exactStart);
+ if (num2 > 0)
+ {
+ return num2 + 1;
+ }
+ if (previous.Terminal != LetterTree.NodeTypes.NonTerm)
+ {
+ return 1;
+ }
+ }
+ LetterTree.LetterNode letterNode = previous.FindChild(c);
+ if (letterNode == null)
+ {
+ return -3;
+ }
+ int num3 = this.SubSearchRec(input, start + 1, letterNode, postDupes, postBreak, exactStart);
+ if (num3 > 0)
+ {
+ return num3 + 1;
+ }
+ if (letterNode.Terminal == LetterTree.NodeTypes.TerminalStrict && num3 == -2 && (exactStart || !postBreak))
+ {
+ return 1;
+ }
+ if (letterNode.Terminal == LetterTree.NodeTypes.TerminalUnbroken && num3 == -2 && !postBreak)
+ {
+ return 1;
+ }
+ if (letterNode.Terminal == LetterTree.NodeTypes.TerminalExact && (num3 == -2 && exactStart))
+ {
+ return 1;
+ }
+ if (letterNode.Terminal == LetterTree.NodeTypes.Terminal && num3 <= 0)
+ {
+ return 1;
+ }
+ return num3;
+ }
+ }
+
+ public IEnumerable<string> GetWords()
+ {
+ StringBuilder b = new StringBuilder();
+ foreach (LetterTree.LetterNode node in this.root.Children)
+ {
+ foreach (string text in this.GetWords(b, 0, node))
+ {
+ yield return text;
+ }
+ IEnumerator<string> enumerator = null;
+ }
+ LetterTree.LetterNode[] array = null;
+ yield break;
+ yield break;
+ }
+
+ private IEnumerable<string> GetWords(StringBuilder b, int i, LetterTree.LetterNode node)
+ {
+ if (node == null)
+ {
+ yield break;
+ }
+ int length = b.Length;
+ b.Length = length + 1;
+ b[i] = node.Letter;
+ if (node.Terminal == LetterTree.NodeTypes.Terminal)
+ {
+ yield return b.ToString();
+ }
+ else if (node.Terminal == LetterTree.NodeTypes.TerminalStrict)
+ {
+ length = b.Length;
+ b.Length = length + 1;
+ b[i + 1] = '~';
+ yield return b.ToString();
+ length = b.Length;
+ b.Length = length - 1;
+ }
+ foreach (LetterTree.LetterNode node2 in node.Children)
+ {
+ foreach (string text in this.GetWords(b, i + 1, node2))
+ {
+ yield return text;
+ }
+ IEnumerator<string> enumerator = null;
+ }
+ LetterTree.LetterNode[] array = null;
+ length = b.Length;
+ b.Length = length - 1;
+ yield break;
+ yield break;
+ }
+}
diff --git a/Client/Assembly-CSharp/LifeSuppSystemType.cs b/Client/Assembly-CSharp/LifeSuppSystemType.cs
new file mode 100644
index 0000000..2e77cfc
--- /dev/null
+++ b/Client/Assembly-CSharp/LifeSuppSystemType.cs
@@ -0,0 +1,121 @@
+using System;
+using System.Collections.Generic;
+using Hazel;
+
+public class LifeSuppSystemType : ISystemType, IActivatable
+{
+ public int UserCount
+ {
+ get
+ {
+ return this.CompletedConsoles.Count;
+ }
+ }
+
+ public bool IsActive
+ {
+ get
+ {
+ return this.Countdown < 10000f;
+ }
+ }
+
+ private const float SyncRate = 2f;
+
+ private float timer;
+
+ public const byte StartCountdown = 128;
+
+ public const byte AddUserOp = 64;
+
+ public const byte ClearCountdown = 16;
+
+ public const float CountdownStopped = 10000f;
+
+ public const float LifeSuppDuration = 45f;
+
+ public const byte ConsoleIdMask = 3;
+
+ public const byte RequiredUserCount = 2;
+
+ public float Countdown = 10000f;
+
+ private HashSet<int> CompletedConsoles = new HashSet<int>();
+
+ public bool GetConsoleComplete(int consoleId)
+ {
+ return this.CompletedConsoles.Contains(consoleId);
+ }
+
+ public void RepairDamage(PlayerControl player, byte opCode)
+ {
+ int item = (int)(opCode & 3);
+ if (opCode == 128 && !this.IsActive)
+ {
+ this.Countdown = 45f;
+ this.CompletedConsoles.Clear();
+ return;
+ }
+ if (opCode == 16)
+ {
+ this.Countdown = 10000f;
+ return;
+ }
+ if (opCode.HasAnyBit(64))
+ {
+ this.CompletedConsoles.Add(item);
+ }
+ }
+
+ public bool Detoriorate(float deltaTime)
+ {
+ if (this.IsActive)
+ {
+ if (DestroyableSingleton<HudManager>.Instance.OxyFlash == null)
+ {
+ PlayerControl.LocalPlayer.AddSystemTask(SystemTypes.LifeSupp);
+ }
+ this.Countdown -= deltaTime;
+ if (this.UserCount >= 2)
+ {
+ this.Countdown = 10000f;
+ return true;
+ }
+ this.timer += deltaTime;
+ if (this.timer > 2f)
+ {
+ this.timer = 0f;
+ return true;
+ }
+ }
+ else if (DestroyableSingleton<HudManager>.Instance.OxyFlash != null)
+ {
+ DestroyableSingleton<HudManager>.Instance.StopOxyFlash();
+ }
+ return false;
+ }
+
+ public void Serialize(MessageWriter writer, bool initialState)
+ {
+ writer.Write(this.Countdown);
+ writer.WritePacked(this.CompletedConsoles.Count);
+ foreach (int value in this.CompletedConsoles)
+ {
+ writer.WritePacked(value);
+ }
+ }
+
+ public void Deserialize(MessageReader reader, bool initialState)
+ {
+ this.Countdown = reader.ReadSingle();
+ if (reader.Position < reader.Length)
+ {
+ this.CompletedConsoles.Clear();
+ int num = reader.ReadPackedInt32();
+ for (int i = 0; i < num; i++)
+ {
+ this.CompletedConsoles.Add(reader.ReadPackedInt32());
+ }
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/LightSource.cs b/Client/Assembly-CSharp/LightSource.cs
new file mode 100644
index 0000000..891fcbf
--- /dev/null
+++ b/Client/Assembly-CSharp/LightSource.cs
@@ -0,0 +1,335 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+public class LightSource : MonoBehaviour
+{
+ public static Dictionary<GameObject, NoShadowBehaviour> NoShadows = new Dictionary<GameObject, NoShadowBehaviour>();
+
+ public static Dictionary<GameObject, OneWayShadows> OneWayShadows = new Dictionary<GameObject, OneWayShadows>();
+
+ [HideInInspector]
+ private GameObject child;
+
+ [HideInInspector]
+ private Vector2[] requiredDels;
+
+ [HideInInspector]
+ private Mesh myMesh;
+
+ public int MinRays = 24;
+
+ public float LightRadius = 3f;
+
+ public Material Material;
+
+ [HideInInspector]
+ private List<LightSource.VertInfo> verts = new List<LightSource.VertInfo>(256);
+
+ [HideInInspector]
+ private int vertCount;
+
+ private RaycastHit2D[] buffer = new RaycastHit2D[25];
+
+ private Collider2D[] hits = new Collider2D[40];
+
+ private ContactFilter2D filter;
+
+ private Vector3[] vec;
+
+ private Vector2[] uvs;
+
+ private int[] triangles = new int[1200];
+
+ public float tol = 0.05f;
+
+ private Vector2 del;
+
+ private Vector2 tan;
+
+ private Vector2 side;
+
+ private List<RaycastHit2D> lightHits = new List<RaycastHit2D>();
+
+ private class VertInfo
+ {
+ public float Angle;
+
+ public Vector3 Position;
+
+ internal void Complete(float x, float y)
+ {
+ this.Position.x = x;
+ this.Position.y = y;
+ this.Angle = LightSource.pseudoAngle(y, x);
+ }
+
+ internal void Complete(Vector2 point)
+ {
+ this.Position.x = point.x;
+ this.Position.y = point.y;
+ this.Angle = LightSource.pseudoAngle(point.y, point.x);
+ }
+ }
+
+ private class AngleComparer : IComparer<LightSource.VertInfo>
+ {
+ public static readonly LightSource.AngleComparer Instance = new LightSource.AngleComparer();
+
+ public int Compare(LightSource.VertInfo x, LightSource.VertInfo y)
+ {
+ if (x.Angle > y.Angle)
+ {
+ return 1;
+ }
+ if (x.Angle >= y.Angle)
+ {
+ return 0;
+ }
+ return -1;
+ }
+ }
+
+ private class HitDepthComparer : IComparer<RaycastHit2D>
+ {
+ public static readonly LightSource.HitDepthComparer Instance = new LightSource.HitDepthComparer();
+
+ public int Compare(RaycastHit2D x, RaycastHit2D y)
+ {
+ if (x.fraction <= y.fraction)
+ {
+ return -1;
+ }
+ return 1;
+ }
+ }
+
+ private void Start()
+ {
+ this.filter.useTriggers = true;
+ this.filter.layerMask = Constants.ShadowMask;
+ this.filter.useLayerMask = true;
+ this.requiredDels = new Vector2[this.MinRays];
+ for (int i = 0; i < this.requiredDels.Length; i++)
+ {
+ this.requiredDels[i] = Vector2.left.Rotate((float)i / (float)this.requiredDels.Length * 360f);
+ }
+ this.myMesh = new Mesh();
+ this.myMesh.MarkDynamic();
+ this.myMesh.name = "ShadowMesh";
+ GameObject gameObject = new GameObject("LightChild");
+ gameObject.layer = 10;
+ gameObject.AddComponent<MeshFilter>().mesh = this.myMesh;
+ Renderer renderer = gameObject.AddComponent<MeshRenderer>();
+ this.Material = new Material(this.Material);
+ renderer.sharedMaterial = this.Material;
+ this.child = gameObject;
+ }
+
+ private void Update()
+ {
+ this.vertCount = 0;
+ Vector3 position = base.transform.position;
+ position.z -= 7f;
+ this.child.transform.position = position;
+ Vector2 vector = position;
+ this.Material.SetFloat("_LightRadius", this.LightRadius);
+ int num = Physics2D.OverlapCircleNonAlloc(vector, this.LightRadius, this.hits, Constants.ShadowMask);
+ for (int i = 0; i < num; i++)
+ {
+ Collider2D collider2D = this.hits[i];
+ if (!collider2D.isTrigger)
+ {
+ EdgeCollider2D edgeCollider2D = collider2D as EdgeCollider2D;
+ if (edgeCollider2D)
+ {
+ Vector2[] points = edgeCollider2D.points;
+ for (int j = 0; j < points.Length; j++)
+ {
+ Vector2 vector2 = edgeCollider2D.transform.TransformPoint(points[j]);
+ this.del.x = vector2.x - vector.x;
+ this.del.y = vector2.y - vector.y;
+ this.TestBothSides(vector);
+ }
+ }
+ else
+ {
+ PolygonCollider2D polygonCollider2D = collider2D as PolygonCollider2D;
+ if (polygonCollider2D)
+ {
+ Vector2[] points2 = polygonCollider2D.points;
+ for (int k = 0; k < points2.Length; k++)
+ {
+ Vector2 vector3 = polygonCollider2D.transform.TransformPoint(points2[k]);
+ this.del.x = vector3.x - vector.x;
+ this.del.y = vector3.y - vector.y;
+ this.TestBothSides(vector);
+ }
+ }
+ else
+ {
+ BoxCollider2D boxCollider2D = collider2D as BoxCollider2D;
+ if (boxCollider2D)
+ {
+ Vector2 b = boxCollider2D.size / 2f;
+ Vector2 vector4 = boxCollider2D.transform.TransformPoint(boxCollider2D.offset - b) - vector;
+ Vector2 vector5 = boxCollider2D.transform.TransformPoint(boxCollider2D.offset + b) - vector;
+ this.del.x = vector4.x;
+ this.del.y = vector4.y;
+ this.TestBothSides(vector);
+ this.del.x = vector5.x;
+ this.TestBothSides(vector);
+ this.del.y = vector5.y;
+ this.TestBothSides(vector);
+ this.del.x = vector4.x;
+ this.TestBothSides(vector);
+ }
+ }
+ }
+ }
+ }
+ float d = this.LightRadius * 1.05f;
+ for (int l = 0; l < this.requiredDels.Length; l++)
+ {
+ Vector2 vector6 = d * this.requiredDels[l];
+ this.CreateVert(vector, ref vector6);
+ }
+ this.verts.Sort(0, this.vertCount, LightSource.AngleComparer.Instance);
+ this.myMesh.Clear();
+ if (this.vec == null || this.vec.Length < this.vertCount + 1)
+ {
+ this.vec = new Vector3[this.vertCount + 1];
+ this.uvs = new Vector2[this.vec.Length];
+ }
+ this.vec[0] = Vector3.zero;
+ this.uvs[0] = new Vector2(this.vec[0].x, this.vec[0].y);
+ for (int m = 0; m < this.vertCount; m++)
+ {
+ int num2 = m + 1;
+ this.vec[num2] = this.verts[m].Position;
+ this.uvs[num2] = new Vector2(this.vec[num2].x, this.vec[num2].y);
+ }
+ int num3 = this.vertCount * 3;
+ if (num3 > this.triangles.Length)
+ {
+ this.triangles = new int[num3];
+ Debug.LogWarning("Resized triangles to: " + num3);
+ }
+ int num4 = 0;
+ for (int n = 0; n < this.triangles.Length; n += 3)
+ {
+ if (n < num3)
+ {
+ this.triangles[n] = 0;
+ this.triangles[n + 1] = num4 + 1;
+ if (n == num3 - 3)
+ {
+ this.triangles[n + 2] = 1;
+ }
+ else
+ {
+ this.triangles[n + 2] = num4 + 2;
+ }
+ num4++;
+ }
+ else
+ {
+ this.triangles[n] = 0;
+ this.triangles[n + 1] = 0;
+ this.triangles[n + 2] = 0;
+ }
+ }
+ this.myMesh.vertices = this.vec;
+ this.myMesh.uv = this.uvs;
+ this.myMesh.SetIndices(this.triangles, MeshTopology.Triangles, 0);
+ }
+
+ private void TestBothSides(Vector2 myPos)
+ {
+ float num = LightSource.length(this.del.x, this.del.x);
+ this.tan.x = -this.del.y / num * this.tol;
+ this.tan.y = this.del.x / num * this.tol;
+ this.side.x = this.del.x + this.tan.x;
+ this.side.y = this.del.y + this.tan.y;
+ this.CreateVert(myPos, ref this.side);
+ this.side.x = this.del.x - this.tan.x;
+ this.side.y = this.del.y - this.tan.y;
+ this.CreateVert(myPos, ref this.side);
+ }
+
+ private void CreateVert(Vector2 myPos, ref Vector2 del)
+ {
+ float num = this.LightRadius * 1.5f;
+ int num2 = Physics2D.Raycast(myPos, del, this.filter, this.buffer, num);
+ if (num2 > 0)
+ {
+ this.lightHits.Clear();
+ RaycastHit2D raycastHit2D = default(RaycastHit2D);
+ Collider2D collider2D = null;
+ for (int i = 0; i < num2; i++)
+ {
+ RaycastHit2D raycastHit2D2 = this.buffer[i];
+ Collider2D collider = raycastHit2D2.collider;
+ OneWayShadows oneWayShadows;
+ if (!LightSource.OneWayShadows.TryGetValue(collider.gameObject, out oneWayShadows) || !oneWayShadows.IsIgnored(this))
+ {
+ this.lightHits.Add(raycastHit2D2);
+ if (!collider.isTrigger)
+ {
+ raycastHit2D = raycastHit2D2;
+ collider2D = collider;
+ break;
+ }
+ }
+ }
+ for (int j = 0; j < this.lightHits.Count; j++)
+ {
+ NoShadowBehaviour noShadowBehaviour;
+ if (LightSource.NoShadows.TryGetValue(this.lightHits[j].collider.gameObject, out noShadowBehaviour))
+ {
+ noShadowBehaviour.didHit = true;
+ }
+ }
+ if (collider2D && !collider2D.isTrigger)
+ {
+ Vector2 point = raycastHit2D.point;
+ this.GetEmptyVert().Complete(point.x - myPos.x, point.y - myPos.y);
+ return;
+ }
+ }
+ Vector2 normalized = del.normalized;
+ this.GetEmptyVert().Complete(normalized.x * num, normalized.y * num);
+ }
+
+ private LightSource.VertInfo GetEmptyVert()
+ {
+ if (this.vertCount < this.verts.Count)
+ {
+ List<LightSource.VertInfo> list = this.verts;
+ int num = this.vertCount;
+ this.vertCount = num + 1;
+ return list[num];
+ }
+ LightSource.VertInfo vertInfo = new LightSource.VertInfo();
+ this.verts.Add(vertInfo);
+ this.vertCount = this.verts.Count;
+ return vertInfo;
+ }
+
+ private static float length(float x, float y)
+ {
+ return Mathf.Sqrt(x * x + y * y);
+ }
+
+ public static float pseudoAngle(float dx, float dy)
+ {
+ if (dx < 0f)
+ {
+ float num = -dx;
+ float num2 = (dy > 0f) ? dy : (-dy);
+ return 2f - dy / (num + num2);
+ }
+ float num3 = (dy > 0f) ? dy : (-dy);
+ return dy / (dx + num3);
+ }
+}
diff --git a/Client/Assembly-CSharp/LobbyBehaviour.cs b/Client/Assembly-CSharp/LobbyBehaviour.cs
new file mode 100644
index 0000000..de036b7
--- /dev/null
+++ b/Client/Assembly-CSharp/LobbyBehaviour.cs
@@ -0,0 +1,82 @@
+using System;
+using Hazel;
+using InnerNet;
+using UnityEngine;
+
+public class LobbyBehaviour : InnerNetObject
+{
+ public static LobbyBehaviour Instance;
+
+ public AudioClip SpawnSound;
+
+ public AnimationClip SpawnInClip;
+
+ public Vector2[] SpawnPositions;
+
+ public AudioClip DropShipSound;
+
+ public ShipRoom[] AllRooms;
+
+ private float timer;
+
+ public void Start()
+ {
+ LobbyBehaviour.Instance = this;
+ SoundManager.Instance.StopAllSound();
+ SoundManager.Instance.PlaySound(this.DropShipSound, true, 1f).pitch = 1.2f;
+ Camera main = Camera.main;
+ if (main)
+ {
+ FollowerCamera component = main.GetComponent<FollowerCamera>();
+ if (component)
+ {
+ component.shakeAmount = 0.03f;
+ component.shakePeriod = 400f;
+ }
+ }
+ }
+
+ public void FixedUpdate()
+ {
+ this.timer += Time.deltaTime;
+ if (this.timer < 0.25f)
+ {
+ return;
+ }
+ this.timer = 0f;
+ if (PlayerControl.GameOptions != null)
+ {
+ int numPlayers = GameData.Instance ? GameData.Instance.PlayerCount : 10;
+ DestroyableSingleton<HudManager>.Instance.GameSettings.Text = PlayerControl.GameOptions.ToHudString(numPlayers);
+ DestroyableSingleton<HudManager>.Instance.GameSettings.gameObject.SetActive(true);
+ }
+ }
+
+ public override void OnDestroy()
+ {
+ Camera main = Camera.main;
+ if (main)
+ {
+ FollowerCamera component = main.GetComponent<FollowerCamera>();
+ if (component)
+ {
+ component.shakeAmount = 0.02f;
+ component.shakePeriod = 0.3f;
+ }
+ }
+ base.OnDestroy();
+ }
+
+ public override void HandleRpc(byte callId, MessageReader reader)
+ {
+ }
+
+ public override bool Serialize(MessageWriter writer, bool initialState)
+ {
+ return false;
+ }
+
+ public override void Deserialize(MessageReader reader, bool initialState)
+ {
+ }
+}
diff --git a/Client/Assembly-CSharp/MMOnlineManager.cs b/Client/Assembly-CSharp/MMOnlineManager.cs
new file mode 100644
index 0000000..f65820c
--- /dev/null
+++ b/Client/Assembly-CSharp/MMOnlineManager.cs
@@ -0,0 +1,28 @@
+using System;
+using UnityEngine;
+
+public class MMOnlineManager : DestroyableSingleton<MMOnlineManager>
+{
+ public GameObject HelpMenu;
+
+ public void Start()
+ {
+ if (this.HelpMenu)
+ {
+ if (SaveManager.ShowOnlineHelp)
+ {
+ SaveManager.ShowOnlineHelp = false;
+ return;
+ }
+ this.HelpMenu.gameObject.SetActive(false);
+ }
+ }
+
+ private void Update()
+ {
+ if (Input.GetKeyUp(KeyCode.Escape))
+ {
+ SceneChanger.ChangeScene("MainMenu");
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/MainMenuManager.cs b/Client/Assembly-CSharp/MainMenuManager.cs
new file mode 100644
index 0000000..c3a2054
--- /dev/null
+++ b/Client/Assembly-CSharp/MainMenuManager.cs
@@ -0,0 +1,66 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using UnityEngine.Analytics;
+
+public class MainMenuManager : MonoBehaviour
+{
+ public DataCollectScreen DataPolicy;
+
+ public AdDataCollectScreen AdsPolicy;
+
+ public AnnouncementPopUp Announcement;
+
+ public UnlockPopUp UnlockPop;
+
+ private static bool SentTelemetry;
+
+ public void Start()
+ {
+ if (!MainMenuManager.SentTelemetry && SaveManager.SendTelemetry)
+ {
+ MainMenuManager.SentTelemetry = true;
+ DateTime utcNow = DateTime.UtcNow;
+ if (SaveManager.LastStartDate != DateTime.MinValue && SaveManager.LastStartDate < utcNow)
+ {
+ TimeSpan timeSpan = utcNow - SaveManager.LastStartDate;
+ Analytics.CustomEvent("GameOpened", new Dictionary<string, object>
+ {
+ {
+ "TotalMinutes",
+ timeSpan.TotalMinutes
+ }
+ });
+ }
+ SaveManager.LastStartDate = utcNow;
+ }
+ base.StartCoroutine(this.Announcement.Init());
+ base.StartCoroutine(this.RunStartUp());
+ }
+
+ private IEnumerator RunStartUp()
+ {
+ yield return this.DataPolicy.Show();
+ yield return this.Announcement.Show();
+ yield return this.UnlockPop.Show();
+ DateTime utcNow = DateTime.UtcNow;
+ for (int i = 0; i < DestroyableSingleton<HatManager>.Instance.AllHats.Count; i++)
+ {
+ HatBehaviour hatBehaviour = DestroyableSingleton<HatManager>.Instance.AllHats[i];
+ if ((hatBehaviour.LimitedMonth == utcNow.Month || hatBehaviour.LimitedMonth == 0) && (hatBehaviour.LimitedYear == utcNow.Year || hatBehaviour.LimitedYear == 0) && !SaveManager.GetPurchase(hatBehaviour.ProductId))
+ {
+ SaveManager.SetPurchased(hatBehaviour.ProductId);
+ }
+ }
+ yield break;
+ }
+
+ private void Update()
+ {
+ if (Input.GetKeyUp(KeyCode.Escape))
+ {
+ Application.Quit();
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/ManualDoor.cs b/Client/Assembly-CSharp/ManualDoor.cs
new file mode 100644
index 0000000..fb2ac04
--- /dev/null
+++ b/Client/Assembly-CSharp/ManualDoor.cs
@@ -0,0 +1,90 @@
+using System;
+using System.Collections;
+using Hazel;
+using PowerTools;
+using UnityEngine;
+
+public class ManualDoor : MonoBehaviour
+{
+ private const float ClosedDuration = 10f;
+
+ public const float CooldownDuration = 30f;
+
+ public bool Open;
+
+ public BoxCollider2D myCollider;
+
+ public SpriteAnim animator;
+
+ public AnimationClip OpenDoorAnim;
+
+ public AnimationClip CloseDoorAnim;
+
+ private float size;
+
+ private void Awake()
+ {
+ Vector2 vector = this.myCollider.size;
+ this.size = ((vector.x > vector.y) ? vector.y : vector.x);
+ }
+
+ public virtual void SetDoorway(bool open)
+ {
+ if (this.Open == open)
+ {
+ return;
+ }
+ if (this.animator)
+ {
+ this.animator.Play(open ? this.OpenDoorAnim : this.CloseDoorAnim, 1f);
+ }
+ this.Open = open;
+ this.myCollider.isTrigger = open;
+ base.StopAllCoroutines();
+ if (!open)
+ {
+ Vector2 vector = this.myCollider.size;
+ base.StartCoroutine(this.CoCloseDoorway(vector.x > vector.y));
+ }
+ }
+
+ // 自动关门
+ private IEnumerator CoCloseDoorway(bool isHort)
+ {
+ Vector2 s = this.myCollider.size;
+ float i = 0f;
+ if (isHort)
+ {
+ while (i < 0.1f)
+ {
+ i += Time.deltaTime;
+ s.y = Mathf.Lerp(0.0001f, this.size, i / 0.1f);
+ this.myCollider.size = s;
+ yield return null;
+ }
+ }
+ else
+ {
+ while (i < 0.1f)
+ {
+ i += Time.deltaTime;
+ s.x = Mathf.Lerp(0.0001f, this.size, i / 0.1f);
+ this.myCollider.size = s;
+ yield return null;
+ }
+ }
+ yield break;
+ }
+
+ //c 同步门的状态
+
+ public virtual void Serialize(MessageWriter writer)
+ {
+ writer.Write(this.Open);
+ }
+
+ public virtual void Deserialize(MessageReader reader)
+ {
+ this.SetDoorway(reader.ReadBoolean());
+ }
+}
diff --git a/Client/Assembly-CSharp/MapBehaviour.cs b/Client/Assembly-CSharp/MapBehaviour.cs
new file mode 100644
index 0000000..85d6b39
--- /dev/null
+++ b/Client/Assembly-CSharp/MapBehaviour.cs
@@ -0,0 +1,113 @@
+using System;
+using UnityEngine;
+
+public class MapBehaviour : MonoBehaviour
+{
+ public bool IsOpen
+ {
+ get
+ {
+ return base.isActiveAndEnabled;
+ }
+ }
+
+ public bool IsOpenStopped
+ {
+ get
+ {
+ return this.IsOpen && this.countOverlay.isActiveAndEnabled;
+ }
+ }
+
+ public static MapBehaviour Instance;
+
+ public AlphaPulse ColorControl;
+
+ public SpriteRenderer HerePoint;
+
+ public MapCountOverlay countOverlay;
+
+ public InfectedOverlay infectedOverlay;
+
+ public MapTaskOverlay taskOverlay;
+
+ private void Awake()
+ {
+ MapBehaviour.Instance = this;
+ }
+
+ private void GenericShow()
+ {
+ base.transform.localScale = Vector3.one;
+ base.transform.localPosition = new Vector3(0f, 0f, -25f);
+ base.gameObject.SetActive(true);
+ }
+
+ public void ShowInfectedMap()
+ {
+ if (this.IsOpen)
+ {
+ this.Close();
+ return;
+ }
+ if (!PlayerControl.LocalPlayer.CanMove)
+ {
+ return;
+ }
+ PlayerControl.LocalPlayer.SetPlayerMaterialColors(this.HerePoint);
+ this.GenericShow();
+ this.infectedOverlay.gameObject.SetActive(true);
+ this.ColorControl.SetColor(Palette.ImpostorRed);
+ this.taskOverlay.Hide();
+ DestroyableSingleton<HudManager>.Instance.SetHudActive(false);
+ }
+
+ public void ShowNormalMap()
+ {
+ if (this.IsOpen)
+ {
+ this.Close();
+ return;
+ }
+ if (!PlayerControl.LocalPlayer.CanMove)
+ {
+ return;
+ }
+ PlayerControl.LocalPlayer.SetPlayerMaterialColors(this.HerePoint);
+ this.GenericShow();
+ this.taskOverlay.Show();
+ this.ColorControl.SetColor(new Color(0.05f, 0.2f, 1f, 1f));
+ DestroyableSingleton<HudManager>.Instance.SetHudActive(false);
+ }
+
+ public void ShowCountOverlay()
+ {
+ this.GenericShow();
+ this.countOverlay.gameObject.SetActive(true);
+ this.taskOverlay.Hide();
+ this.HerePoint.enabled = false;
+ DestroyableSingleton<HudManager>.Instance.SetHudActive(false);
+ }
+
+ public void FixedUpdate()
+ {
+ if (!ShipStatus.Instance)
+ {
+ return;
+ }
+ Vector3 vector = PlayerControl.LocalPlayer.transform.position;
+ vector /= ShipStatus.Instance.MapScale;
+ vector.z = -1f;
+ this.HerePoint.transform.localPosition = vector;
+ }
+
+ public void Close()
+ {
+ base.gameObject.SetActive(false);
+ this.countOverlay.gameObject.SetActive(false);
+ this.infectedOverlay.gameObject.SetActive(false);
+ this.taskOverlay.Hide();
+ this.HerePoint.enabled = true;
+ DestroyableSingleton<HudManager>.Instance.SetHudActive(true);
+ }
+}
diff --git a/Client/Assembly-CSharp/MapConsole.cs b/Client/Assembly-CSharp/MapConsole.cs
new file mode 100644
index 0000000..84a2e68
--- /dev/null
+++ b/Client/Assembly-CSharp/MapConsole.cs
@@ -0,0 +1,65 @@
+using System;
+using UnityEngine;
+
+public class MapConsole : MonoBehaviour, IUsable
+{
+ public float UsableDistance
+ {
+ get
+ {
+ return this.usableDistance;
+ }
+ }
+
+ public float PercentCool
+ {
+ get
+ {
+ return 0f;
+ }
+ }
+
+ public float usableDistance = 1f;
+
+ public SpriteRenderer Image;
+
+ public void SetOutline(bool on, bool mainTarget)
+ {
+ if (this.Image)
+ {
+ this.Image.material.SetFloat("_Outline", (float)(on ? 1 : 0));
+ this.Image.material.SetColor("_OutlineColor", Color.white);
+ this.Image.material.SetColor("_AddColor", mainTarget ? Color.white : Color.clear);
+ }
+ }
+
+ public float CanUse(GameData.PlayerInfo pc, out bool canUse, out bool couldUse)
+ {
+ float num = float.MaxValue;
+ PlayerControl @object = pc.Object;
+ couldUse = pc.Object.CanMove;
+ canUse = couldUse;
+ if (canUse)
+ {
+ num = Vector2.Distance(@object.GetTruePosition(), base.transform.position);
+ canUse &= (num <= this.UsableDistance);
+ }
+ return num;
+ }
+
+ public void Use()
+ {
+ bool flag;
+ bool flag2;
+ this.CanUse(PlayerControl.LocalPlayer.Data, out flag, out flag2);
+ if (!flag)
+ {
+ return;
+ }
+ PlayerControl.LocalPlayer.NetTransform.Halt();
+ DestroyableSingleton<HudManager>.Instance.ShowMap(delegate(MapBehaviour m)
+ {
+ m.ShowCountOverlay();
+ });
+ }
+}
diff --git a/Client/Assembly-CSharp/MapCountOverlay.cs b/Client/Assembly-CSharp/MapCountOverlay.cs
new file mode 100644
index 0000000..571b676
--- /dev/null
+++ b/Client/Assembly-CSharp/MapCountOverlay.cs
@@ -0,0 +1,90 @@
+using System;
+using System.Linq;
+using UnityEngine;
+
+public class MapCountOverlay : MonoBehaviour
+{
+ public AlphaPulse BackgroundColor;
+
+ public TextRenderer SabotageText;
+
+ public CounterArea[] CountAreas;
+
+ private Collider2D[] buffer = new Collider2D[20];
+
+ private ContactFilter2D filter;
+
+ private float timer;
+
+ private bool isSab;
+
+ public void Awake()
+ {
+ this.filter.useLayerMask = true;
+ this.filter.layerMask = Constants.PlayersOnlyMask;
+ this.filter.useTriggers = true;
+ }
+
+ public void OnEnable()
+ {
+ this.BackgroundColor.SetColor(PlayerTask.PlayerHasHudTask(PlayerControl.LocalPlayer) ? Palette.DisabledGrey : Color.green);
+ this.timer = 1f;
+ }
+
+ public void OnDisable()
+ {
+ for (int i = 0; i < this.CountAreas.Length; i++)
+ {
+ this.CountAreas[i].UpdateCount(0);
+ }
+ }
+
+ public void Update()
+ {
+ this.timer += Time.deltaTime;
+ if (this.timer < 0.1f)
+ {
+ return;
+ }
+ this.timer = 0f;
+ if (!this.isSab && PlayerTask.PlayerHasHudTask(PlayerControl.LocalPlayer))
+ {
+ this.isSab = true;
+ this.BackgroundColor.SetColor(Palette.DisabledGrey);
+ this.SabotageText.gameObject.SetActive(true);
+ return;
+ }
+ if (this.isSab && !PlayerTask.PlayerHasHudTask(PlayerControl.LocalPlayer))
+ {
+ this.isSab = false;
+ this.BackgroundColor.SetColor(Color.green);
+ this.SabotageText.gameObject.SetActive(false);
+ }
+ for (int i = 0; i < this.CountAreas.Length; i++)
+ {
+ CounterArea area = this.CountAreas[i];
+ if (!PlayerTask.PlayerHasHudTask(PlayerControl.LocalPlayer))
+ {
+ int num = ShipStatus.Instance.AllRooms.First((ShipRoom r) => r.RoomId == area.RoomType).roomArea.OverlapCollider(this.filter, this.buffer);
+ int num2 = num;
+ for (int j = 0; j < num; j++)
+ {
+ Collider2D collider2D = this.buffer[j];
+ if (!(collider2D.tag == "DeadBody"))
+ {
+ PlayerControl component = collider2D.GetComponent<PlayerControl>();
+ if (!component || component.Data == null || component.Data.Disconnected || component.Data.IsDead)
+ {
+ num2--;
+ }
+ }
+ }
+ area.UpdateCount(num2);
+ }
+ else
+ {
+ area.UpdateCount(0);
+ }
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/MapRoom.cs b/Client/Assembly-CSharp/MapRoom.cs
new file mode 100644
index 0000000..c25469c
--- /dev/null
+++ b/Client/Assembly-CSharp/MapRoom.cs
@@ -0,0 +1,92 @@
+using System;
+using UnityEngine;
+
+public class MapRoom : MonoBehaviour
+{
+ public InfectedOverlay Parent { get; set; }
+
+ public SystemTypes room;
+
+ public SpriteRenderer door;
+
+ public SpriteRenderer special;
+
+ public void Start()
+ {
+ if (this.door)
+ {
+ this.door.SetCooldownNormalizedUvs();
+ }
+ if (this.special)
+ {
+ this.special.SetCooldownNormalizedUvs();
+ }
+ }
+
+ public void OOBUpdate()
+ {
+ if (this.door && ShipStatus.Instance)
+ {
+ float timer = ((DoorsSystemType)ShipStatus.Instance.Systems[SystemTypes.Doors]).GetTimer(this.room);
+ float value = this.Parent.CanUseDoors ? (timer / 30f) : 1f;
+ this.door.material.SetFloat("_Percent", value);
+ }
+ }
+
+ internal void SetSpecialActive(float perc)
+ {
+ if (this.special)
+ {
+ this.special.material.SetFloat("_Percent", perc);
+ }
+ }
+
+ public void SabotageReactor()
+ {
+ if (!this.Parent.CanUseSpecial)
+ {
+ return;
+ }
+ ShipStatus.Instance.RpcRepairSystem(SystemTypes.Sabotage, 3);
+ }
+
+ public void SabotageComms()
+ {
+ if (!this.Parent.CanUseSpecial)
+ {
+ return;
+ }
+ ShipStatus.Instance.RpcRepairSystem(SystemTypes.Sabotage, 14);
+ }
+
+ public void SabotageOxygen()
+ {
+ if (!this.Parent.CanUseSpecial)
+ {
+ return;
+ }
+ ShipStatus.Instance.RpcRepairSystem(SystemTypes.Sabotage, 8);
+ }
+
+ public void SabotageLights()
+ {
+ if (!this.Parent.CanUseSpecial)
+ {
+ return;
+ }
+ ShipStatus.Instance.RpcRepairSystem(SystemTypes.Sabotage, 7);
+ }
+
+ public void SabotageDoors()
+ {
+ if (!this.Parent.CanUseDoors)
+ {
+ return;
+ }
+ if (((DoorsSystemType)ShipStatus.Instance.Systems[SystemTypes.Doors]).GetTimer(this.room) > 0f)
+ {
+ return;
+ }
+ ShipStatus.Instance.RpcCloseDoorsOfType(this.room);
+ }
+}
diff --git a/Client/Assembly-CSharp/MapTaskOverlay.cs b/Client/Assembly-CSharp/MapTaskOverlay.cs
new file mode 100644
index 0000000..cc70292
--- /dev/null
+++ b/Client/Assembly-CSharp/MapTaskOverlay.cs
@@ -0,0 +1,101 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+public class MapTaskOverlay : MonoBehaviour
+{
+ public ObjectPoolBehavior icons;
+
+ private Dictionary<PlayerTask, PooledMapIcon> data = new Dictionary<PlayerTask, PooledMapIcon>();
+
+ public void Show()
+ {
+ base.gameObject.SetActive(true);
+ if (PlayerTask.PlayerHasHudTask(PlayerControl.LocalPlayer))
+ {
+ return;
+ }
+ for (int i = 0; i < PlayerControl.LocalPlayer.myTasks.Count; i++)
+ {
+ PlayerTask playerTask = PlayerControl.LocalPlayer.myTasks[i];
+ if (playerTask.HasLocation && !playerTask.IsComplete)
+ {
+ PooledMapIcon pooledMapIcon = this.icons.Get<PooledMapIcon>();
+ pooledMapIcon.transform.localScale = new Vector3(pooledMapIcon.NormalSize, pooledMapIcon.NormalSize, pooledMapIcon.NormalSize);
+ if (PlayerTask.TaskIsEmergency(playerTask))
+ {
+ pooledMapIcon.rend.color = Color.red;
+ pooledMapIcon.alphaPulse.enabled = true;
+ pooledMapIcon.rend.material.SetFloat("_Outline", 1f);
+ }
+ else
+ {
+ pooledMapIcon.rend.color = Color.yellow;
+ }
+ MapTaskOverlay.SetIconLocation(playerTask, pooledMapIcon);
+ this.data.Add(playerTask, pooledMapIcon);
+ }
+ }
+ }
+
+ public void Update()
+ {
+ if (PlayerTask.PlayerHasHudTask(PlayerControl.LocalPlayer))
+ {
+ return;
+ }
+ for (int i = 0; i < PlayerControl.LocalPlayer.myTasks.Count; i++)
+ {
+ PlayerTask playerTask = PlayerControl.LocalPlayer.myTasks[i];
+ if (playerTask.HasLocation && !playerTask.IsComplete && playerTask.LocationDirty)
+ {
+ PooledMapIcon pooledMapIcon;
+ if (!this.data.TryGetValue(playerTask, out pooledMapIcon))
+ {
+ pooledMapIcon = this.icons.Get<PooledMapIcon>();
+ pooledMapIcon.transform.localScale = new Vector3(0.4f, 0.4f, 0.4f);
+ if (PlayerTask.TaskIsEmergency(playerTask))
+ {
+ pooledMapIcon.rend.color = Color.red;
+ pooledMapIcon.alphaPulse.enabled = true;
+ pooledMapIcon.rend.material.SetFloat("_Outline", 1f);
+ }
+ else
+ {
+ pooledMapIcon.rend.color = Color.yellow;
+ }
+ this.data.Add(playerTask, pooledMapIcon);
+ }
+ MapTaskOverlay.SetIconLocation(playerTask, pooledMapIcon);
+ }
+ }
+ }
+
+ private static void SetIconLocation(PlayerTask task, PooledMapIcon mapIcon)
+ {
+ if (mapIcon.lastMapTaskStep != task.TaskStep)
+ {
+ mapIcon.lastMapTaskStep = task.TaskStep;
+ Vector3 vector = task.Location;
+ vector /= ShipStatus.Instance.MapScale;
+ vector.z = -1f;
+ mapIcon.name = task.name;
+ mapIcon.transform.localPosition = vector;
+ if (task.TaskStep > 0)
+ {
+ mapIcon.alphaPulse.enabled = true;
+ mapIcon.rend.material.SetFloat("_Outline", 1f);
+ }
+ }
+ }
+
+ public void Hide()
+ {
+ foreach (KeyValuePair<PlayerTask, PooledMapIcon> keyValuePair in this.data)
+ {
+ keyValuePair.Value.OwnerPool.Reclaim(keyValuePair.Value);
+ }
+ this.data.Clear();
+ base.gameObject.SetActive(false);
+ }
+}
diff --git a/Client/Assembly-CSharp/MatchMaker.cs b/Client/Assembly-CSharp/MatchMaker.cs
new file mode 100644
index 0000000..8c36a5c
--- /dev/null
+++ b/Client/Assembly-CSharp/MatchMaker.cs
@@ -0,0 +1,41 @@
+using System;
+using InnerNet;
+using UnityEngine;
+
+public class MatchMaker : DestroyableSingleton<MatchMaker>
+{
+ public TextBox NameText;
+
+ public TextBox GameIdText;
+
+ private MonoBehaviour Connecter;
+
+ public void Start()
+ {
+ if (this.GameIdText && AmongUsClient.Instance)
+ {
+ this.GameIdText.SetText(InnerNetClient.IntToGameName(AmongUsClient.Instance.GameId) ?? "", "");
+ }
+ }
+
+ public bool Connecting(MonoBehaviour button)
+ {
+ if (!this.Connecter)
+ {
+ this.Connecter = button;
+ ((IConnectButton)this.Connecter).StartIcon();
+ return true;
+ }
+ base.StartCoroutine(Effects.Shake(this.Connecter.transform, 0.75f, 0.25f));
+ return false;
+ }
+
+ public void NotConnecting()
+ {
+ if (this.Connecter)
+ {
+ ((IConnectButton)this.Connecter).StopIcon();
+ this.Connecter = null;
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/MatchMakerGameButton.cs b/Client/Assembly-CSharp/MatchMakerGameButton.cs
new file mode 100644
index 0000000..64be6de
--- /dev/null
+++ b/Client/Assembly-CSharp/MatchMakerGameButton.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Collections;
+using InnerNet;
+using PowerTools;
+using UnityEngine;
+
+public class MatchMakerGameButton : PoolableBehavior, IConnectButton
+{
+ public TextRenderer NameText;
+
+ public TextRenderer PlayerCountText;
+
+ public TextRenderer ImpostorCountText;
+
+ public SpriteAnim connectIcon;
+
+ public AnimationClip connectClip;
+
+ public GameListing myListing;
+
+ public void OnClick()
+ {
+ if (!DestroyableSingleton<MatchMaker>.Instance.Connecting(this))
+ {
+ return;
+ }
+ AmongUsClient.Instance.GameMode = GameModes.OnlineGame;
+ AmongUsClient.Instance.OnlineScene = "OnlineGame";
+ AmongUsClient.Instance.GameId = this.myListing.GameId;
+ AmongUsClient.Instance.JoinGame();
+ base.StartCoroutine(this.ConnectForFindGame());
+ }
+
+ private IEnumerator ConnectForFindGame()
+ {
+ yield return EndGameManager.WaitWithTimeout(() => AmongUsClient.Instance.ClientId >= 0 || AmongUsClient.Instance.LastDisconnectReason > DisconnectReasons.ExitGame);
+ DestroyableSingleton<MatchMaker>.Instance.NotConnecting();
+ yield break;
+ }
+
+ public void StartIcon()
+ {
+ this.connectIcon.Play(this.connectClip, 1f);
+ }
+
+ public void StopIcon()
+ {
+ this.connectIcon.Stop();
+ this.connectIcon.GetComponent<SpriteRenderer>().sprite = null;
+ }
+
+ public void SetGame(GameListing gameListing)
+ {
+ this.myListing = gameListing;
+ this.NameText.Text = this.myListing.HostName;
+ this.ImpostorCountText.Text = this.myListing.ImpostorCount.ToString();
+ this.PlayerCountText.Text = string.Format("{0}/{1}", this.myListing.PlayerCount, this.myListing.MaxPlayers);
+ }
+}
diff --git a/Client/Assembly-CSharp/MedScanMinigame.cs b/Client/Assembly-CSharp/MedScanMinigame.cs
new file mode 100644
index 0000000..df940e2
--- /dev/null
+++ b/Client/Assembly-CSharp/MedScanMinigame.cs
@@ -0,0 +1,197 @@
+using System;
+using System.Collections;
+using System.Text;
+using UnityEngine;
+
+public class MedScanMinigame : Minigame
+{
+ private static readonly string[] ColorNames = new string[]
+ {
+ "Red",
+ "Blue",
+ "Green",
+ "Pink",
+ "Orange",
+ "Yellow",
+ "Black",
+ "White",
+ "Purple",
+ "Brown",
+ "Cyan",
+ "Lime"
+ };
+
+ private static readonly string[] BloodTypes = new string[]
+ {
+ "O-",
+ "A-",
+ "B-",
+ "AB-",
+ "O+",
+ "A+",
+ "B+",
+ "AB+"
+ };
+
+ public TextRenderer text;
+
+ public TextRenderer charStats;
+
+ public HorizontalGauge gauge;
+
+ private MedScanSystem medscan;
+
+ public float ScanDuration = 10f;
+
+ public float ScanTimer;
+
+ private string completeString;
+
+ public AudioClip ScanSound;
+
+ public AudioClip TextSound;
+
+ private Coroutine walking;
+
+ private MedScanMinigame.PositionState state;
+
+ private enum PositionState
+ {
+ None,
+ WalkingToPad,
+ WalkingToOffset
+ }
+
+ public override void Begin(PlayerTask task)
+ {
+ base.Begin(task);
+ this.medscan = (ShipStatus.Instance.Systems[SystemTypes.MedBay] as MedScanSystem);
+ this.gauge.Value = 0f;
+ base.transform.position = new Vector3(100f, 0f, 0f);
+ GameData.PlayerInfo data = PlayerControl.LocalPlayer.Data;
+ int playerId = (int)data.PlayerId;
+ int colorId = (int)data.ColorId;
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.Append("ID: ");
+ stringBuilder.Append(MedScanMinigame.ColorNames[colorId].Substring(0, 3).ToUpperInvariant());
+ stringBuilder.Append("P" + playerId);
+ stringBuilder.Append(new string(' ', 8));
+ stringBuilder.Append("HT: 3' 6\"");
+ stringBuilder.Append(new string(' ', 8));
+ stringBuilder.Append("WT: 92lb");
+ stringBuilder.AppendLine();
+ stringBuilder.Append("C: ");
+ stringBuilder.Append(MedScanMinigame.ColorNames[colorId].PadRight(17));
+ stringBuilder.Append("BT: ");
+ stringBuilder.Append(MedScanMinigame.BloodTypes[playerId * 3 % MedScanMinigame.BloodTypes.Length]);
+ this.completeString = stringBuilder.ToString();
+ this.charStats.Text = string.Empty;
+ ShipStatus.Instance.RpcRepairSystem(SystemTypes.MedBay, playerId | 128);
+ this.walking = base.StartCoroutine(this.WalkToOffset());
+ }
+
+ private IEnumerator WalkToOffset()
+ {
+ this.state = MedScanMinigame.PositionState.WalkingToOffset;
+ PlayerPhysics myPhysics = PlayerControl.LocalPlayer.MyPhysics;
+ Vector2 vector = ShipStatus.Instance.MedScanner.transform.position;
+ Vector2 a = Vector2.left.Rotate((float)(PlayerControl.LocalPlayer.PlayerId * 36));
+ vector += a / 2f;
+ Camera.main.GetComponent<FollowerCamera>().Locked = false;
+ yield return myPhysics.WalkPlayerTo(vector, 0.001f);
+ yield return new WaitForSeconds(0.1f);
+ Camera.main.GetComponent<FollowerCamera>().Locked = true;
+ this.walking = null;
+ yield break;
+ }
+
+ private IEnumerator WalkToPad()
+ {
+ this.state = MedScanMinigame.PositionState.WalkingToPad;
+ PlayerPhysics myPhysics = PlayerControl.LocalPlayer.MyPhysics;
+ Vector2 worldPos = ShipStatus.Instance.MedScanner.transform.position;
+ worldPos.x += 0.14f;
+ worldPos.y += 0.1f;
+ Camera.main.GetComponent<FollowerCamera>().Locked = false;
+ yield return myPhysics.WalkPlayerTo(worldPos, 0.001f);
+ yield return new WaitForSeconds(0.1f);
+ Camera.main.GetComponent<FollowerCamera>().Locked = true;
+ this.walking = null;
+ yield break;
+ }
+
+ private void FixedUpdate()
+ {
+ if (this.MyNormTask.IsComplete)
+ {
+ return;
+ }
+ byte playerId = PlayerControl.LocalPlayer.PlayerId;
+ if (this.medscan.CurrentUser != playerId)
+ {
+ if (this.medscan.CurrentUser == 255)
+ {
+ this.text.Text = "Scan requested";
+ return;
+ }
+ GameData.PlayerInfo playerById = GameData.Instance.GetPlayerById(this.medscan.CurrentUser);
+ this.text.Text = "Waiting for " + playerById.PlayerName;
+ return;
+ }
+ else
+ {
+ if (this.state != MedScanMinigame.PositionState.WalkingToPad)
+ {
+ if (this.walking != null)
+ {
+ base.StopCoroutine(this.walking);
+ }
+ this.walking = base.StartCoroutine(this.WalkToPad());
+ return;
+ }
+ if (this.walking != null)
+ {
+ return;
+ }
+ if (this.ScanTimer == 0f)
+ {
+ PlayerControl.LocalPlayer.RpcSetScanner(true);
+ SoundManager.Instance.PlaySound(this.ScanSound, false, 1f);
+ }
+ this.ScanTimer += Time.fixedDeltaTime;
+ this.gauge.Value = this.ScanTimer / this.ScanDuration;
+ int num = (int)(Mathf.Min(1f, this.ScanTimer / this.ScanDuration * 1.25f) * (float)this.completeString.Length);
+ if (num > this.charStats.Text.Length)
+ {
+ this.charStats.Text = this.completeString.Substring(0, num);
+ if (this.completeString[num - 1] != ' ')
+ {
+ SoundManager.Instance.PlaySoundImmediate(this.TextSound, false, 0.7f, 0.3f);
+ }
+ }
+ if (this.ScanTimer >= this.ScanDuration)
+ {
+ PlayerControl.LocalPlayer.RpcSetScanner(false);
+ this.text.Text = "Scan complete";
+ this.MyNormTask.NextStep();
+ ShipStatus.Instance.RpcRepairSystem(SystemTypes.MedBay, (int)(playerId | 64));
+ base.StartCoroutine(base.CoStartClose(0.75f));
+ return;
+ }
+ this.text.Text = "Scan complete in: " + (int)(this.ScanDuration - this.ScanTimer);
+ return;
+ }
+ }
+
+ public override void Close()
+ {
+ base.StopAllCoroutines();
+ byte playerId = PlayerControl.LocalPlayer.PlayerId;
+ SoundManager.Instance.StopSound(this.TextSound);
+ SoundManager.Instance.StopSound(this.ScanSound);
+ PlayerControl.LocalPlayer.RpcSetScanner(false);
+ ShipStatus.Instance.RpcRepairSystem(SystemTypes.MedBay, (int)(playerId | 64));
+ Camera.main.GetComponent<FollowerCamera>().Locked = false;
+ base.Close();
+ }
+}
diff --git a/Client/Assembly-CSharp/MedScanSystem.cs b/Client/Assembly-CSharp/MedScanSystem.cs
new file mode 100644
index 0000000..8a5f672
--- /dev/null
+++ b/Client/Assembly-CSharp/MedScanSystem.cs
@@ -0,0 +1,81 @@
+using System;
+using System.Collections.Generic;
+using Hazel;
+using UnityEngine;
+
+public class MedScanSystem : ISystemType
+{
+ public byte CurrentUser { get; private set; } = byte.MaxValue;
+
+ public const byte Request = 128;
+
+ public const byte Release = 64;
+
+ public const byte NumMask = 31;
+
+ public const byte NoPlayer = 255;
+
+ public List<byte> UsersList = new List<byte>();
+
+ public bool Detoriorate(float deltaTime)
+ {
+ if (this.UsersList.Count > 0)
+ {
+ if (this.CurrentUser != this.UsersList[0])
+ {
+ if (this.CurrentUser != 255)
+ {
+ Debug.Log("Released scanner from: " + this.CurrentUser);
+ }
+ this.CurrentUser = this.UsersList[0];
+ Debug.Log("Acquired scanner for: " + this.CurrentUser);
+ return true;
+ }
+ }
+ else if (this.CurrentUser != 255)
+ {
+ Debug.Log("Released scanner from: " + this.CurrentUser);
+ this.CurrentUser = byte.MaxValue;
+ return true;
+ }
+ return false;
+ }
+
+ public void RepairDamage(PlayerControl player, byte data)
+ {
+ byte playerId = data & 31;
+ if ((data & 128) != 0)
+ {
+ if (!this.UsersList.Contains(playerId))
+ {
+ Debug.Log("Added to queue: " + playerId);
+ this.UsersList.Add(playerId);
+ return;
+ }
+ }
+ else if ((data & 64) != 0)
+ {
+ Debug.Log("Removed from queue: " + playerId);
+ this.UsersList.RemoveAll((byte v) => v == playerId);
+ }
+ }
+
+ public void Serialize(MessageWriter writer, bool initialState)
+ {
+ writer.WritePacked(this.UsersList.Count);
+ for (int i = 0; i < this.UsersList.Count; i++)
+ {
+ writer.Write(this.UsersList[i]);
+ }
+ }
+
+ public void Deserialize(MessageReader reader, bool initialState)
+ {
+ this.UsersList.Clear();
+ int num = reader.ReadPackedInt32();
+ for (int i = 0; i < num; i++)
+ {
+ this.UsersList.Add(reader.ReadByte());
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/MeetingHud.cs b/Client/Assembly-CSharp/MeetingHud.cs
new file mode 100644
index 0000000..dbc22cb
--- /dev/null
+++ b/Client/Assembly-CSharp/MeetingHud.cs
@@ -0,0 +1,734 @@
+using System;
+using System.Collections;
+using System.Linq;
+using Assets.CoreScripts;
+using Hazel;
+using InnerNet;
+using UnityEngine;
+
+public class MeetingHud : InnerNetObject, IDisconnectHandler
+{
+ private const float ResultsTime = 5f;
+
+ private const float Depth = -100f;
+
+ public static MeetingHud Instance;
+
+ public Transform ButtonParent;
+
+ public TextRenderer TitleText;
+
+ public Vector3 VoteOrigin = new Vector3(-3.6f, 1.75f);
+
+ public Vector3 VoteButtonOffsets = new Vector2(3.6f, -0.91f);
+
+ private Vector3 CounterOrigin = new Vector2(0.5f, -0.13f);
+
+ private Vector3 CounterOffsets = new Vector2(0.3f, 0f);
+
+ public PlayerVoteArea SkipVoteButton;
+
+ [HideInInspector]
+ private PlayerVoteArea[] playerStates;
+
+ public PlayerVoteArea PlayerButtonPrefab;
+
+ public SpriteRenderer PlayerVotePrefab;
+
+ public Sprite CrackedGlass;
+
+ public SpriteRenderer Glass;
+
+ public PassiveButton ProceedButton;
+
+ public ExileController ExileCutscenePrefab;
+
+ public AudioClip VoteSound;
+
+ public AudioClip VoteLockinSound;
+
+ public AudioClip VoteEndingSound;
+
+ private MeetingHud.VoteStates state;
+
+ public SpriteRenderer SkippedVoting;
+
+ public SpriteRenderer HostIcon;
+
+ public Sprite KillBackground;
+
+ private GameData.PlayerInfo exiledPlayer;
+
+ private bool wasTie;
+
+ public TextRenderer TimerText;
+
+ public float discussionTimer;
+
+ private byte reporterId;
+
+ private bool amDead;
+
+ private float resultsStartedAt;
+
+ private int lastSecond = 10;
+
+ public enum VoteStates
+ {
+ Discussion,
+ NotVoted,
+ Voted,
+ Results,
+ Proceeding
+ }
+
+ private enum RpcCalls
+ {
+ Close,
+ VotingComplete,
+ CastVote,
+ ClearVote
+ }
+
+ private void Awake()
+ {
+ if (!MeetingHud.Instance)
+ {
+ MeetingHud.Instance = this;
+ return;
+ }
+ if (MeetingHud.Instance != this)
+ {
+ UnityEngine.Object.Destroy(base.gameObject);
+ }
+ }
+
+ private void Start()
+ {
+ DestroyableSingleton<HudManager>.Instance.Chat.gameObject.SetActive(true);
+ DestroyableSingleton<HudManager>.Instance.Chat.SetPosition(this);
+ DestroyableSingleton<HudManager>.Instance.StopOxyFlash();
+ DestroyableSingleton<HudManager>.Instance.StopReactorFlash();
+ this.SkipVoteButton.TargetPlayerId = -1;
+ this.SkipVoteButton.Parent = this;
+ Camera.main.GetComponent<FollowerCamera>().Locked = true;
+ if (PlayerControl.LocalPlayer.Data.IsDead)
+ {
+ this.SetForegroundForDead();
+ }
+ AmongUsClient.Instance.DisconnectHandlers.AddUnique(this);
+ }
+
+ private void SetForegroundForDead()
+ {
+ this.amDead = true;
+ this.SkipVoteButton.gameObject.SetActive(false);
+ this.Glass.sprite = this.CrackedGlass;
+ this.Glass.color = Color.white;
+ }
+
+ public void Update()
+ {
+ this.discussionTimer += Time.deltaTime;
+ this.UpdateButtons();
+ switch (this.state)
+ {
+ case MeetingHud.VoteStates.Discussion:
+ {
+ if (this.discussionTimer < (float)PlayerControl.GameOptions.DiscussionTime)
+ {
+ float f = (float)PlayerControl.GameOptions.DiscussionTime - this.discussionTimer;
+ this.TimerText.Text = string.Format("Voting Begins In: {0}s", Mathf.CeilToInt(f));
+ for (int i = 0; i < this.playerStates.Length; i++)
+ {
+ this.playerStates[i].SetDisabled();
+ }
+ this.SkipVoteButton.SetDisabled();
+ return;
+ }
+ this.state = MeetingHud.VoteStates.NotVoted;
+ bool active = PlayerControl.GameOptions.VotingTime > 0;
+ this.TimerText.gameObject.SetActive(active);
+ for (int j = 0; j < this.playerStates.Length; j++)
+ {
+ this.playerStates[j].SetEnabled();
+ }
+ this.SkipVoteButton.SetEnabled();
+ return;
+ }
+ case MeetingHud.VoteStates.NotVoted:
+ case MeetingHud.VoteStates.Voted:
+ if (PlayerControl.GameOptions.VotingTime > 0)
+ {
+ float num = this.discussionTimer - (float)PlayerControl.GameOptions.DiscussionTime;
+ float f2 = Mathf.Max(0f, (float)PlayerControl.GameOptions.VotingTime - num);
+ this.TimerText.Text = string.Format("Voting Ends In: {0}s", Mathf.CeilToInt(f2));
+ if (this.state == MeetingHud.VoteStates.NotVoted && Mathf.CeilToInt(f2) <= this.lastSecond)
+ {
+ this.lastSecond--;
+ base.StartCoroutine(Effects.PulseColor(this.TimerText, Color.red, Color.white, 0.25f));
+ SoundManager.Instance.PlaySound(this.VoteEndingSound, false, 1f).pitch = Mathf.Lerp(1.5f, 0.8f, (float)this.lastSecond / 10f);
+ }
+ if (AmongUsClient.Instance.AmHost && num >= (float)PlayerControl.GameOptions.VotingTime)
+ {
+ this.ForceSkipAll();
+ return;
+ }
+ }
+ break;
+ case MeetingHud.VoteStates.Results:
+ if (AmongUsClient.Instance.GameMode == GameModes.OnlineGame)
+ {
+ float num2 = this.discussionTimer - this.resultsStartedAt;
+ float num3 = Mathf.Max(0f, 5f - num2);
+ this.TimerText.Text = string.Format("Proceeding In: {0}s", Mathf.CeilToInt(num3));
+ if (AmongUsClient.Instance.AmHost && num3 <= 0f)
+ {
+ this.HandleProceed();
+ }
+ }
+ break;
+ default:
+ return;
+ }
+ }
+
+ public IEnumerator CoIntro(PlayerControl reporter, GameData.PlayerInfo targetPlayer)
+ {
+ if (DestroyableSingleton<HudManager>.InstanceExists)
+ {
+ DestroyableSingleton<HudManager>.Instance.Chat.ForceClosed();
+ base.transform.SetParent(DestroyableSingleton<HudManager>.Instance.transform);
+ base.transform.localPosition = new Vector3(0f, -10f, -100f);
+ DestroyableSingleton<HudManager>.Instance.SetHudActive(false);
+ }
+ OverlayKillAnimation killAnimPrefab = (targetPlayer == null) ? DestroyableSingleton<HudManager>.Instance.KillOverlay.EmergencyOverlay : DestroyableSingleton<HudManager>.Instance.KillOverlay.ReportOverlay;
+ DestroyableSingleton<HudManager>.Instance.KillOverlay.ShowOne(killAnimPrefab, reporter, targetPlayer);
+ yield return DestroyableSingleton<HudManager>.Instance.KillOverlay.WaitForFinish();
+ Vector3 temp = new Vector3(0f, 0f, -50f);
+ for (float timer = 0f; timer < 0.25f; timer += Time.deltaTime)
+ {
+ float t = timer / 0.25f;
+ temp.y = Mathf.SmoothStep(-10f, 0f, t);
+ base.transform.localPosition = temp;
+ yield return null;
+ }
+ temp.y = 0f;
+ base.transform.localPosition = temp;
+ this.TitleText.Text = "Who Is The Impostor?";
+ if (!PlayerControl.LocalPlayer.Data.IsDead)
+ {
+ yield return DestroyableSingleton<HudManager>.Instance.ShowEmblem(false);
+ }
+ for (int i = 0; i < this.playerStates.Length; i++)
+ {
+ base.StartCoroutine(this.playerStates[i].CoAnimateOverlay());
+ }
+ yield break;
+ }
+
+ private IEnumerator CoStartCutscene()
+ {
+ yield return DestroyableSingleton<HudManager>.Instance.CoFadeFullScreen(Color.clear, Color.black, 1f);
+ ExileController exileController = UnityEngine.Object.Instantiate<ExileController>(this.ExileCutscenePrefab);
+ exileController.transform.SetParent(DestroyableSingleton<HudManager>.Instance.transform, false);
+ exileController.transform.localPosition = new Vector3(0f, 0f, -60f);
+ exileController.Begin(this.exiledPlayer, this.wasTie);
+ this.DespawnOnDestroy = false;
+ UnityEngine.Object.Destroy(base.gameObject);
+ yield break;
+ }
+
+ public void ServerStart(byte reporter)
+ {
+ this.reporterId = reporter;
+ this.PopulateButtons(reporter);
+ }
+
+ public void Close()
+ {
+ GameData.PlayerInfo data = PlayerControl.LocalPlayer.Data;
+ DestroyableSingleton<HudManager>.Instance.Chat.SetPosition(null);
+ DestroyableSingleton<HudManager>.Instance.Chat.SetVisible(data.IsDead);
+ base.StartCoroutine(this.CoStartCutscene());
+ }
+
+ private void VotingComplete(byte[] states, GameData.PlayerInfo exiled, bool tie)
+ {
+ if (this.state == MeetingHud.VoteStates.Results)
+ {
+ return;
+ }
+ this.state = MeetingHud.VoteStates.Results;
+ this.resultsStartedAt = this.discussionTimer;
+ this.exiledPlayer = exiled;
+ this.wasTie = tie;
+ this.SkipVoteButton.gameObject.SetActive(false);
+ this.SkippedVoting.gameObject.SetActive(true);
+ AmongUsClient.Instance.DisconnectHandlers.Remove(this);
+ this.PopulateResults(states);
+ this.SetupProceedButton();
+ }
+
+ public bool Select(int suspectStateIdx)
+ {
+ if (this.discussionTimer < (float)PlayerControl.GameOptions.DiscussionTime)
+ {
+ return false;
+ }
+ if (PlayerControl.LocalPlayer.Data.IsDead)
+ {
+ return false;
+ }
+ SoundManager.Instance.PlaySound(this.VoteSound, false, 1f).volume = 0.8f;
+ for (int i = 0; i < this.playerStates.Length; i++)
+ {
+ PlayerVoteArea playerVoteArea = this.playerStates[i];
+ if (suspectStateIdx != (int)playerVoteArea.TargetPlayerId)
+ {
+ playerVoteArea.ClearButtons();
+ }
+ }
+ if (suspectStateIdx != -1)
+ {
+ this.SkipVoteButton.ClearButtons();
+ }
+ return true;
+ }
+
+ public void Confirm(sbyte suspectStateIdx)
+ {
+ if (PlayerControl.LocalPlayer.Data.IsDead)
+ {
+ return;
+ }
+ for (int i = 0; i < this.playerStates.Length; i++)
+ {
+ PlayerVoteArea playerVoteArea = this.playerStates[i];
+ playerVoteArea.ClearButtons();
+ playerVoteArea.voteComplete = true;
+ }
+ this.SkipVoteButton.ClearButtons();
+ this.SkipVoteButton.voteComplete = true;
+ this.SkipVoteButton.gameObject.SetActive(false);
+ MeetingHud.VoteStates voteStates = this.state;
+ if (voteStates != MeetingHud.VoteStates.NotVoted)
+ {
+ return;
+ }
+ this.state = MeetingHud.VoteStates.Voted;
+ this.CmdCastVote(PlayerControl.LocalPlayer.PlayerId, suspectStateIdx);
+ }
+
+ public void HandleDisconnect(PlayerControl pc, DisconnectReasons reason)
+ {
+ if (!AmongUsClient.Instance.AmHost)
+ {
+ return;
+ }
+ int num = this.playerStates.IndexOf((PlayerVoteArea pv) => pv.TargetPlayerId == (sbyte)pc.PlayerId);
+ PlayerVoteArea playerVoteArea = this.playerStates[num];
+ playerVoteArea.isDead = true;
+ playerVoteArea.Overlay.gameObject.SetActive(true);
+ for (int i = 0; i < this.playerStates.Length; i++)
+ {
+ PlayerVoteArea playerVoteArea2 = this.playerStates[i];
+ if (!playerVoteArea2.isDead && playerVoteArea2.didVote && playerVoteArea2.votedFor == (sbyte)pc.PlayerId)
+ {
+ playerVoteArea2.UnsetVote();
+ base.SetDirtyBit(1U << i);
+ GameData.PlayerInfo playerById = GameData.Instance.GetPlayerById((byte)playerVoteArea2.TargetPlayerId);
+ if (playerById != null)
+ {
+ int clientIdFromCharacter = AmongUsClient.Instance.GetClientIdFromCharacter(playerById.Object);
+ if (clientIdFromCharacter != -1)
+ {
+ this.RpcClearVote(clientIdFromCharacter);
+ }
+ }
+ }
+ }
+ base.SetDirtyBit(1U << num);
+ this.CheckForEndVoting();
+ if (this.state == MeetingHud.VoteStates.Results)
+ {
+ this.SetupProceedButton();
+ }
+ }
+
+ public void HandleDisconnect()
+ {
+ }
+
+ private void ForceSkipAll()
+ {
+ for (int i = 0; i < this.playerStates.Length; i++)
+ {
+ PlayerVoteArea playerVoteArea = this.playerStates[i];
+ if (!playerVoteArea.didVote)
+ {
+ playerVoteArea.didVote = true;
+ playerVoteArea.votedFor = -2;
+ base.SetDirtyBit(1U << i);
+ }
+ }
+ this.CheckForEndVoting();
+ }
+
+ public void CastVote(byte srcPlayerId, sbyte suspectPlayerId)
+ {
+ int num = this.playerStates.IndexOf((PlayerVoteArea pv) => pv.TargetPlayerId == (sbyte)srcPlayerId);
+ PlayerVoteArea playerVoteArea = this.playerStates[num];
+ if (!playerVoteArea.isDead && !playerVoteArea.didVote)
+ {
+ if (PlayerControl.LocalPlayer.PlayerId == srcPlayerId || AmongUsClient.Instance.GameMode != GameModes.LocalGame)
+ {
+ SoundManager.Instance.PlaySound(this.VoteLockinSound, false, 1f);
+ }
+ playerVoteArea.SetVote(suspectPlayerId);
+ base.SetDirtyBit(1U << num);
+ this.CheckForEndVoting();
+ PlayerControl.LocalPlayer.RpcSendChatNote(srcPlayerId, ChatNoteTypes.DidVote);
+ }
+ }
+
+ public void ClearVote()
+ {
+ for (int i = 0; i < this.playerStates.Length; i++)
+ {
+ this.playerStates[i].voteComplete = false;
+ }
+ this.SkipVoteButton.voteComplete = false;
+ this.SkipVoteButton.gameObject.SetActive(true);
+ this.state = MeetingHud.VoteStates.NotVoted;
+ }
+
+ private void CheckForEndVoting()
+ {
+ if (this.playerStates.All((PlayerVoteArea ps) => ps.isDead || ps.didVote))
+ {
+ byte[] self = this.CalculateVotes();
+ bool tie;
+ int maxIdx = self.IndexOfMax((byte p) => (int)p, out tie) - 1;
+ GameData.PlayerInfo exiled = GameData.Instance.AllPlayers.FirstOrDefault((GameData.PlayerInfo v) => (int)v.PlayerId == maxIdx);
+ byte[] states = (from ps in this.playerStates
+ select ps.GetState()).ToArray<byte>();
+ this.RpcVotingComplete(states, exiled, tie);
+ }
+ }
+
+ private byte[] CalculateVotes()
+ {
+ byte[] array = new byte[11];
+ for (int i = 0; i < this.playerStates.Length; i++)
+ {
+ PlayerVoteArea playerVoteArea = this.playerStates[i];
+ if (playerVoteArea.didVote)
+ {
+ int num = (int)(playerVoteArea.votedFor + 1);
+ if (num >= 0 && num < array.Length)
+ {
+ byte[] array2 = array;
+ int num2 = num;
+ array2[num2] += 1;
+ }
+ }
+ }
+ return array;
+ }
+
+ public override bool Serialize(MessageWriter writer, bool initialState)
+ {
+ if (this.playerStates == null)
+ {
+ return false;
+ }
+ if (initialState)
+ {
+ for (int i = 0; i < this.playerStates.Length; i++)
+ {
+ this.playerStates[i].Serialize(writer);
+ }
+ }
+ else
+ {
+ writer.WritePacked(this.DirtyBits);
+ for (int j = 0; j < this.playerStates.Length; j++)
+ {
+ if ((this.DirtyBits & 1U << j) != 0U)
+ {
+ this.playerStates[j].Serialize(writer);
+ }
+ }
+ }
+ this.DirtyBits = 0U;
+ return true;
+ }
+
+ public override void Deserialize(MessageReader reader, bool initialState)
+ {
+ if (initialState)
+ {
+ MeetingHud.Instance = this;
+ this.PopulateButtons(0);
+ for (int i = 0; i < this.playerStates.Length; i++)
+ {
+ PlayerVoteArea playerVoteArea = this.playerStates[i];
+ playerVoteArea.Deserialize(reader);
+ if (playerVoteArea.didReport)
+ {
+ this.reporterId = (byte)playerVoteArea.TargetPlayerId;
+ }
+ }
+ return;
+ }
+ uint num = reader.ReadPackedUInt32();
+ for (int j = 0; j < this.playerStates.Length; j++)
+ {
+ if ((num & 1U << j) != 0U)
+ {
+ this.playerStates[j].Deserialize(reader);
+ }
+ }
+ }
+
+ public void HandleProceed()
+ {
+ if (!AmongUsClient.Instance.AmHost)
+ {
+ base.StartCoroutine(Effects.Shake(this.HostIcon.transform, 0.75f, 0.25f));
+ return;
+ }
+ if (this.state != MeetingHud.VoteStates.Results)
+ {
+ return;
+ }
+ this.state = MeetingHud.VoteStates.Proceeding;
+ this.RpcClose();
+ }
+
+ private void SetupProceedButton()
+ {
+ if (AmongUsClient.Instance.GameMode != GameModes.OnlineGame)
+ {
+ this.TimerText.gameObject.SetActive(false);
+ this.ProceedButton.gameObject.SetActive(true);
+ this.HostIcon.gameObject.SetActive(true);
+ GameData.PlayerInfo host = GameData.Instance.GetHost();
+ if (host != null)
+ {
+ PlayerControl.SetPlayerMaterialColors((int)host.ColorId, this.HostIcon);
+ return;
+ }
+ this.HostIcon.enabled = false;
+ }
+ }
+
+ private void PopulateResults(byte[] states)
+ {
+ DestroyableSingleton<Telemetry>.Instance.WriteMeetingEnded(states, this.discussionTimer);
+ this.TitleText.Text = "Voting Results";
+ int num = 0;
+ for (int i = 0; i < this.playerStates.Length; i++)
+ {
+ PlayerVoteArea playerVoteArea = this.playerStates[i];
+ playerVoteArea.ClearForResults();
+ int num2 = 0;
+ for (int j = 0; j < this.playerStates.Length; j++)
+ {
+ if (!states[j].HasAnyBit(128))
+ {
+ GameData.PlayerInfo playerById = GameData.Instance.GetPlayerById((byte)this.playerStates[j].TargetPlayerId);
+ int num3 = (int)((states[j] & 15) - 1);
+ if (num3 == (int)playerVoteArea.TargetPlayerId)
+ {
+ SpriteRenderer spriteRenderer = UnityEngine.Object.Instantiate<SpriteRenderer>(this.PlayerVotePrefab);
+ PlayerControl.SetPlayerMaterialColors((int)playerById.ColorId, spriteRenderer);
+ spriteRenderer.transform.SetParent(playerVoteArea.transform);
+ spriteRenderer.transform.localPosition = this.CounterOrigin + new Vector3(this.CounterOffsets.x * (float)num2, 0f, 0f);
+ spriteRenderer.transform.localScale = Vector3.zero;
+ base.StartCoroutine(Effects.Bloop((float)num2 * 0.5f, spriteRenderer.transform, 0.5f));
+ num2++;
+ }
+ else if (i == 0 && num3 == -1)
+ {
+ SpriteRenderer spriteRenderer2 = UnityEngine.Object.Instantiate<SpriteRenderer>(this.PlayerVotePrefab);
+ PlayerControl.SetPlayerMaterialColors((int)playerById.ColorId, spriteRenderer2);
+ spriteRenderer2.transform.SetParent(this.SkippedVoting.transform);
+ spriteRenderer2.transform.localPosition = this.CounterOrigin + new Vector3(this.CounterOffsets.x * (float)num, 0f, 0f);
+ spriteRenderer2.transform.localScale = Vector3.zero;
+ base.StartCoroutine(Effects.Bloop((float)num * 0.5f, spriteRenderer2.transform, 0.5f));
+ num++;
+ }
+ }
+ }
+ }
+ }
+
+ private void UpdateButtons()
+ {
+ if (PlayerControl.LocalPlayer.Data.IsDead && !this.amDead)
+ {
+ this.SetForegroundForDead();
+ }
+ if (AmongUsClient.Instance.AmHost)
+ {
+ for (int i = 0; i < this.playerStates.Length; i++)
+ {
+ PlayerVoteArea playerVoteArea = this.playerStates[i];
+ GameData.PlayerInfo playerById = GameData.Instance.GetPlayerById((byte)playerVoteArea.TargetPlayerId);
+ if (playerById == null)
+ {
+ playerVoteArea.SetDisabled();
+ }
+ else
+ {
+ bool flag = playerById.Disconnected || playerById.IsDead;
+ if (flag != playerVoteArea.isDead)
+ {
+ playerVoteArea.SetDead(playerById.PlayerId == PlayerControl.LocalPlayer.PlayerId, this.reporterId == playerById.PlayerId, flag);
+ base.SetDirtyBit(1U << i);
+ }
+ }
+ }
+ }
+ }
+
+ private void PopulateButtons(byte reporter)
+ {
+ this.playerStates = new PlayerVoteArea[GameData.Instance.PlayerCount];
+ for (int i = 0; i < this.playerStates.Length; i++)
+ {
+ GameData.PlayerInfo playerInfo = GameData.Instance.AllPlayers[i];
+ PlayerVoteArea playerVoteArea = this.playerStates[i] = this.CreateButton(playerInfo);
+ playerVoteArea.Parent = this;
+ playerVoteArea.TargetPlayerId = (sbyte)playerInfo.PlayerId;
+ playerVoteArea.SetDead(playerInfo.PlayerId == PlayerControl.LocalPlayer.PlayerId, reporter == playerInfo.PlayerId, playerInfo.Disconnected || playerInfo.IsDead);
+ }
+ this.SortButtons();
+ }
+
+ private void SortButtons()
+ {
+ PlayerVoteArea[] array = this.playerStates.OrderBy(delegate(PlayerVoteArea p)
+ {
+ if (!p.isDead)
+ {
+ return 0;
+ }
+ return 50;
+ }).ThenBy((PlayerVoteArea p) => p.TargetPlayerId).ToArray<PlayerVoteArea>();
+ for (int i = 0; i < array.Length; i++)
+ {
+ int num = i % 2;
+ int num2 = i / 2;
+ array[i].transform.localPosition = this.VoteOrigin + new Vector3(this.VoteButtonOffsets.x * (float)num, this.VoteButtonOffsets.y * (float)num2, -1f);
+ }
+ }
+
+ private PlayerVoteArea CreateButton(GameData.PlayerInfo playerInfo)
+ {
+ PlayerVoteArea playerVoteArea = UnityEngine.Object.Instantiate<PlayerVoteArea>(this.PlayerButtonPrefab, this.ButtonParent.transform);
+ PlayerControl.SetPlayerMaterialColors((int)playerInfo.ColorId, playerVoteArea.PlayerIcon);
+ playerVoteArea.NameText.Text = playerInfo.PlayerName;
+ bool flag = PlayerControl.LocalPlayer.Data.IsImpostor && playerInfo.IsImpostor;
+ playerVoteArea.NameText.Color = (flag ? Palette.ImpostorRed : Color.white);
+ playerVoteArea.transform.localScale = Vector3.one;
+ return playerVoteArea;
+ }
+
+ public bool DidVote(byte playerId)
+ {
+ return this.playerStates.First((PlayerVoteArea p) => p.TargetPlayerId == (sbyte)playerId).didVote;
+ }
+
+ public int GetVotesRemaining()
+ {
+ int result;
+ try
+ {
+ result = this.playerStates.Count((PlayerVoteArea ps) => !ps.isDead && !ps.didVote);
+ }
+ catch
+ {
+ result = 0;
+ }
+ return result;
+ }
+
+ public void RpcClose()
+ {
+ if (AmongUsClient.Instance.AmClient)
+ {
+ this.Close();
+ }
+ AmongUsClient.Instance.SendRpc(this.NetId, 0, SendOption.Reliable);
+ }
+
+ public void CmdCastVote(byte playerId, sbyte suspectIdx)
+ {
+ if (AmongUsClient.Instance.AmHost)
+ {
+ this.CastVote(playerId, suspectIdx);
+ return;
+ }
+ MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(this.NetId, 2, SendOption.Reliable, AmongUsClient.Instance.HostId);
+ messageWriter.Write(playerId);
+ messageWriter.Write(suspectIdx);
+ AmongUsClient.Instance.FinishRpcImmediately(messageWriter);
+ }
+
+ private void RpcVotingComplete(byte[] states, GameData.PlayerInfo exiled, bool tie)
+ {
+ if (AmongUsClient.Instance.AmClient)
+ {
+ this.VotingComplete(states, exiled, tie);
+ }
+ MessageWriter messageWriter = AmongUsClient.Instance.StartRpc(this.NetId, 1, SendOption.Reliable);
+ messageWriter.WriteBytesAndSize(states);
+ messageWriter.Write((exiled != null) ? exiled.PlayerId : byte.MaxValue);
+ messageWriter.Write(tie);
+ messageWriter.EndMessage();
+ }
+
+ private void RpcClearVote(int clientId)
+ {
+ if (AmongUsClient.Instance.ClientId == clientId)
+ {
+ this.ClearVote();
+ return;
+ }
+ MessageWriter msg = AmongUsClient.Instance.StartRpcImmediately(this.NetId, 3, SendOption.Reliable, clientId);
+ AmongUsClient.Instance.FinishRpcImmediately(msg);
+ }
+
+ public override void HandleRpc(byte callId, MessageReader reader)
+ {
+ switch (callId)
+ {
+ case 0:
+ this.Close();
+ return;
+ case 1:
+ {
+ byte[] states = reader.ReadBytesAndSize();
+ GameData.PlayerInfo playerById = GameData.Instance.GetPlayerById(reader.ReadByte());
+ bool tie = reader.ReadBoolean();
+ this.VotingComplete(states, playerById, tie);
+ return;
+ }
+ case 2:
+ {
+ byte srcPlayerId = reader.ReadByte();
+ sbyte suspectPlayerId = reader.ReadSByte();
+ this.CastVote(srcPlayerId, suspectPlayerId);
+ return;
+ }
+ case 3:
+ this.ClearVote();
+ return;
+ default:
+ return;
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/MeetingRoomManager.cs b/Client/Assembly-CSharp/MeetingRoomManager.cs
new file mode 100644
index 0000000..dcb519f
--- /dev/null
+++ b/Client/Assembly-CSharp/MeetingRoomManager.cs
@@ -0,0 +1,36 @@
+using System;
+using InnerNet;
+
+public class MeetingRoomManager : IDisconnectHandler
+{
+ public static readonly MeetingRoomManager Instance = new MeetingRoomManager();
+
+ private PlayerControl reporter;
+
+ private GameData.PlayerInfo target;
+
+ public void AssignSelf(PlayerControl reporter, GameData.PlayerInfo target)
+ {
+ this.reporter = reporter;
+ this.target = target;
+ AmongUsClient.Instance.DisconnectHandlers.AddUnique(this);
+ }
+
+ public void RemoveSelf()
+ {
+ AmongUsClient.Instance.DisconnectHandlers.Remove(this);
+ }
+
+ public void HandleDisconnect(PlayerControl pc, DisconnectReasons reason)
+ {
+ if (AmongUsClient.Instance.AmHost)
+ {
+ this.reporter.CmdReportDeadBody(this.target);
+ }
+ }
+
+ public void HandleDisconnect()
+ {
+ this.HandleDisconnect(null, DisconnectReasons.ExitGame);
+ }
+}
diff --git a/Client/Assembly-CSharp/MemSafeStringExtensions.cs b/Client/Assembly-CSharp/MemSafeStringExtensions.cs
new file mode 100644
index 0000000..034b2bc
--- /dev/null
+++ b/Client/Assembly-CSharp/MemSafeStringExtensions.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Collections.Generic;
+
+public static class MemSafeStringExtensions
+{
+ public static void SafeSplit(this SubString subString, List<SubString> output, char delim)
+ {
+ subString.Source.SafeSplit(output, delim, subString.Start, subString.Length);
+ }
+
+ public static void SafeSplit(this string source, List<SubString> output, char delim)
+ {
+ source.SafeSplit(output, delim, 0, source.Length);
+ }
+
+ public static void SafeSplit(this string source, List<SubString> output, char delim, int start, int length)
+ {
+ output.Clear();
+ int num = start;
+ int num2 = start + length;
+ for (int i = start; i < num2; i++)
+ {
+ if (source[i] == delim)
+ {
+ if (num != i)
+ {
+ output.Add(new SubString(source, num, i - num));
+ }
+ num = i + 1;
+ }
+ }
+ if (num != num2)
+ {
+ output.Add(new SubString(source, num, num2 - num));
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/MeshRendererExtensions.cs b/Client/Assembly-CSharp/MeshRendererExtensions.cs
new file mode 100644
index 0000000..dddc50d
--- /dev/null
+++ b/Client/Assembly-CSharp/MeshRendererExtensions.cs
@@ -0,0 +1,23 @@
+using System;
+using UnityEngine;
+
+public static class MeshRendererExtensions
+{
+ public static void SetSprite(this MeshRenderer self, Texture2D spr)
+ {
+ if (spr != null)
+ {
+ self.SetCutout(spr);
+ self.material.color = Color.white;
+ return;
+ }
+ self.SetCutout(null);
+ self.material.color = Color.clear;
+ }
+
+ public static void SetCutout(this MeshRenderer self, Texture2D txt)
+ {
+ self.material.SetTexture("_MainTex", txt);
+ self.material.SetTexture("_EmissionMap", txt);
+ }
+}
diff --git a/Client/Assembly-CSharp/Minigame.cs b/Client/Assembly-CSharp/Minigame.cs
new file mode 100644
index 0000000..0897e13
--- /dev/null
+++ b/Client/Assembly-CSharp/Minigame.cs
@@ -0,0 +1,166 @@
+using System;
+using System.Collections;
+using UnityEngine;
+
+public abstract class Minigame : MonoBehaviour
+{
+ public global::Console Console { get; set; }
+
+ protected int ConsoleId
+ {
+ get
+ {
+ if (!this.Console)
+ {
+ return 0;
+ }
+ return this.Console.ConsoleId;
+ }
+ }
+
+ public static Minigame Instance;
+
+ public const float Depth = -50f;
+
+ public TransitionType TransType;
+
+ protected PlayerTask MyTask;
+
+ protected NormalPlayerTask MyNormTask;
+
+ protected Minigame.CloseState amClosing;
+
+ public AudioClip OpenSound;
+
+ public AudioClip CloseSound;
+
+ protected enum CloseState
+ {
+ None,
+ Waiting,
+ Closing
+ }
+
+ public virtual void Begin(PlayerTask task)
+ {
+ Minigame.Instance = this;
+ this.MyTask = task;
+ this.MyNormTask = (task as NormalPlayerTask);
+ if (PlayerControl.LocalPlayer)
+ {
+ if (MapBehaviour.Instance)
+ {
+ MapBehaviour.Instance.Close();
+ }
+ PlayerControl.LocalPlayer.NetTransform.Halt();
+ }
+ base.StartCoroutine(this.CoAnimateOpen());
+ }
+
+ protected IEnumerator CoStartClose(float duration = 0.75f)
+ {
+ if (this.amClosing != Minigame.CloseState.None)
+ {
+ yield break;
+ }
+ this.amClosing = Minigame.CloseState.Waiting;
+ yield return Effects.Wait(duration);
+ this.Close();
+ yield break;
+ }
+
+ [Obsolete("Don't use, I just don't want to reselect the close button event handlers", true)]
+ public void Close(bool allowMovement)
+ {
+ this.Close();
+ }
+
+ public virtual void Close()
+ {
+ if (this.amClosing != Minigame.CloseState.Closing)
+ {
+ if (this.CloseSound && Constants.ShouldPlaySfx())
+ {
+ SoundManager.Instance.PlaySound(this.CloseSound, false, 1f);
+ }
+ this.amClosing = Minigame.CloseState.Closing;
+ base.StartCoroutine(this.CoDestroySelf());
+ return;
+ }
+ UnityEngine.Object.Destroy(base.gameObject);
+ }
+
+ protected virtual IEnumerator CoAnimateOpen()
+ {
+ if (this.OpenSound && Constants.ShouldPlaySfx())
+ {
+ SoundManager.Instance.PlaySound(this.OpenSound, false, 1f);
+ }
+ TransitionType transType = this.TransType;
+ if (transType != TransitionType.SlideBottom)
+ {
+ if (transType == TransitionType.Alpha)
+ {
+ SpriteRenderer[] rends = base.GetComponentsInChildren<SpriteRenderer>();
+ for (float timer = 0f; timer < 0.25f; timer += Time.deltaTime)
+ {
+ float t = timer / 0.25f;
+ for (int i = 0; i < rends.Length; i++)
+ {
+ rends[i].color = Color.Lerp(Palette.ClearWhite, Color.white, t);
+ }
+ yield return null;
+ }
+ for (int j = 0; j < rends.Length; j++)
+ {
+ rends[j].color = Color.white;
+ }
+ rends = null;
+ }
+ }
+ else
+ {
+ for (float timer = 0f; timer < 0.25f; timer += Time.deltaTime)
+ {
+ float t2 = timer / 0.25f;
+ base.transform.localPosition = new Vector3(0f, Mathf.SmoothStep(-8f, 0f, t2), -50f);
+ yield return null;
+ }
+ base.transform.localPosition = new Vector3(0f, 0f, -50f);
+ }
+ yield break;
+ }
+
+ protected virtual IEnumerator CoDestroySelf()
+ {
+ TransitionType transType = this.TransType;
+ if (transType != TransitionType.SlideBottom)
+ {
+ if (transType == TransitionType.Alpha)
+ {
+ SpriteRenderer[] rends = base.GetComponentsInChildren<SpriteRenderer>();
+ for (float timer = 0f; timer < 0.25f; timer += Time.deltaTime)
+ {
+ float t = timer / 0.25f;
+ for (int i = 0; i < rends.Length; i++)
+ {
+ rends[i].color = Color.Lerp(Color.white, Palette.ClearWhite, t);
+ }
+ yield return null;
+ }
+ rends = null;
+ }
+ }
+ else
+ {
+ for (float timer = 0f; timer < 0.25f; timer += Time.deltaTime)
+ {
+ float t2 = timer / 0.25f;
+ base.transform.localPosition = new Vector3(0f, Mathf.SmoothStep(0f, -8f, t2), -50f);
+ yield return null;
+ }
+ }
+ UnityEngine.Object.Destroy(base.gameObject);
+ yield break;
+ }
+}
diff --git a/Client/Assembly-CSharp/MonoPInvokeCallbackAttribute.cs b/Client/Assembly-CSharp/MonoPInvokeCallbackAttribute.cs
new file mode 100644
index 0000000..96960fe
--- /dev/null
+++ b/Client/Assembly-CSharp/MonoPInvokeCallbackAttribute.cs
@@ -0,0 +1,8 @@
+using System;
+
+public sealed class MonoPInvokeCallbackAttribute : Attribute
+{
+ public MonoPInvokeCallbackAttribute(Type type)
+ {
+ }
+}
diff --git a/Client/Assembly-CSharp/NameTextBehaviour.cs b/Client/Assembly-CSharp/NameTextBehaviour.cs
new file mode 100644
index 0000000..83a3ad7
--- /dev/null
+++ b/Client/Assembly-CSharp/NameTextBehaviour.cs
@@ -0,0 +1,61 @@
+using System;
+using UnityEngine;
+using UnityEngine.Events;
+
+public class NameTextBehaviour : MonoBehaviour
+{
+ public static NameTextBehaviour Instance;
+
+ public TextBox nameSource;
+
+ public void Start()
+ {
+ NameTextBehaviour.Instance = this;
+ this.nameSource.SetText(SaveManager.PlayerName, "");
+ this.nameSource.OnFocusLost.AddListener(new UnityAction(this.UpdateName));
+ }
+
+ public void UpdateName()
+ {
+ if (this.ShakeIfInvalid())
+ {
+ return;
+ }
+ SaveManager.PlayerName = this.nameSource.text;
+ }
+
+ public static bool IsValidName(string text)
+ {
+ if (text == null || text.Length == 0)
+ {
+ return false;
+ }
+ if (text.Equals("Enter Name", StringComparison.OrdinalIgnoreCase))
+ {
+ return false;
+ }
+ if (BlockedWords.ContainsWord(text))
+ {
+ return false;
+ }
+ bool result = false;
+ for (int i = 0; i < text.Length; i++)
+ {
+ if (text[i] != ' ')
+ {
+ result = true;
+ }
+ }
+ return result;
+ }
+
+ public bool ShakeIfInvalid()
+ {
+ if (!NameTextBehaviour.IsValidName(this.nameSource.text))
+ {
+ base.StartCoroutine(Effects.Shake(this.nameSource.transform, 0.75f, 0.25f));
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/Client/Assembly-CSharp/NavigationMinigame.cs b/Client/Assembly-CSharp/NavigationMinigame.cs
new file mode 100644
index 0000000..6e789e2
--- /dev/null
+++ b/Client/Assembly-CSharp/NavigationMinigame.cs
@@ -0,0 +1,88 @@
+using System;
+using System.Collections;
+using UnityEngine;
+
+public class NavigationMinigame : Minigame
+{
+ public MeshRenderer TwoAxisImage;
+
+ public SpriteRenderer CrossHairImage;
+
+ public Collider2D hitbox;
+
+ private Controller myController = new Controller();
+
+ private Vector2 crossHair;
+
+ private Vector2 half = new Vector2(0.5f, 0.5f);
+
+ public override void Begin(PlayerTask task)
+ {
+ base.Begin(task);
+ this.crossHair = UnityEngine.Random.insideUnitCircle.normalized / 2f * 0.6f;
+ Vector3 localPosition = new Vector3(this.crossHair.x * this.TwoAxisImage.bounds.size.x, this.crossHair.y * this.TwoAxisImage.bounds.size.y, -2f);
+ this.CrossHairImage.transform.localPosition = localPosition;
+ this.TwoAxisImage.material.SetVector("_CrossHair", this.crossHair + this.half);
+ }
+
+ public void FixedUpdate()
+ {
+ if (this.MyNormTask && this.MyNormTask.IsComplete)
+ {
+ return;
+ }
+ this.myController.Update();
+ DragState dragState = this.myController.CheckDrag(this.hitbox, false);
+ if (dragState != DragState.Dragging)
+ {
+ if (dragState != DragState.Released)
+ {
+ return;
+ }
+ if ((this.crossHair - this.half).magnitude < 0.05f)
+ {
+ base.StartCoroutine(this.CompleteGame());
+ this.MyNormTask.NextStep();
+ }
+ }
+ else
+ {
+ Vector2 dragPosition = this.myController.DragPosition;
+ Vector2 a = dragPosition - (this.TwoAxisImage.transform.position - this.TwoAxisImage.bounds.size / 2f);
+ this.crossHair = a.Div(this.TwoAxisImage.bounds.size);
+ if ((this.crossHair - this.half).magnitude < 0.45f)
+ {
+ Vector3 localPosition = dragPosition - base.transform.position;
+ localPosition.z = -2f;
+ this.CrossHairImage.transform.localPosition = localPosition;
+ this.TwoAxisImage.material.SetVector("_CrossHair", this.crossHair);
+ return;
+ }
+ }
+ }
+
+ private IEnumerator CompleteGame()
+ {
+ WaitForSeconds wait = new WaitForSeconds(0.1f);
+ Color green = new Color(0f, 0.8f, 0f, 1f);
+ Color32 yellow = new Color32(byte.MaxValue, 202, 0, byte.MaxValue);
+ this.CrossHairImage.transform.localPosition = new Vector3(0f, 0f, -2f);
+ this.TwoAxisImage.material.SetVector("_CrossHair", this.half);
+ this.CrossHairImage.color = yellow;
+ this.TwoAxisImage.material.SetColor("_CrossColor", yellow);
+ yield return wait;
+ this.CrossHairImage.color = Color.white;
+ this.TwoAxisImage.material.SetColor("_CrossColor", Color.white);
+ yield return wait;
+ this.CrossHairImage.color = yellow;
+ this.TwoAxisImage.material.SetColor("_CrossColor", yellow);
+ yield return wait;
+ this.CrossHairImage.color = Color.white;
+ this.TwoAxisImage.material.SetColor("_CrossColor", Color.white);
+ yield return wait;
+ this.CrossHairImage.color = green;
+ this.TwoAxisImage.material.SetColor("_CrossColor", green);
+ yield return base.CoStartClose(0.75f);
+ yield break;
+ }
+}
diff --git a/Client/Assembly-CSharp/NoOxyTask.cs b/Client/Assembly-CSharp/NoOxyTask.cs
new file mode 100644
index 0000000..47f9e88
--- /dev/null
+++ b/Client/Assembly-CSharp/NoOxyTask.cs
@@ -0,0 +1,106 @@
+using System;
+using System.Linq;
+using System.Text;
+using UnityEngine;
+
+public class NoOxyTask : SabotageTask
+{
+ public override int TaskStep
+ {
+ get
+ {
+ return this.reactor.UserCount;
+ }
+ }
+
+ public override bool IsComplete
+ {
+ get
+ {
+ return this.isComplete;
+ }
+ }
+
+ private bool isComplete;
+
+ private LifeSuppSystemType reactor;
+
+ private bool even;
+
+ public int targetNumber;
+
+ public override void Initialize()
+ {
+ this.targetNumber = IntRange.Next(0, 99999);
+ ShipStatus instance = ShipStatus.Instance;
+ this.reactor = (LifeSuppSystemType)instance.Systems[SystemTypes.LifeSupp];
+ DestroyableSingleton<HudManager>.Instance.StartOxyFlash();
+ base.SetupArrows();
+ }
+
+ private void FixedUpdate()
+ {
+ if (this.isComplete)
+ {
+ return;
+ }
+ if (!this.reactor.IsActive)
+ {
+ this.Complete();
+ return;
+ }
+ for (int i = 0; i < this.Arrows.Length; i++)
+ {
+ this.Arrows[i].gameObject.SetActive(!this.reactor.GetConsoleComplete(i));
+ }
+ }
+
+ public override bool ValidConsole(global::Console console)
+ {
+ return !this.reactor.GetConsoleComplete(console.ConsoleId) && console.TaskTypes.Contains(TaskTypes.RestoreOxy);
+ }
+
+ public override void OnRemove()
+ {
+ }
+
+ public override void Complete()
+ {
+ this.isComplete = true;
+ PlayerControl.LocalPlayer.RemoveTask(this);
+ if (this.didContribute)
+ {
+ StatsManager instance = StatsManager.Instance;
+ uint sabsFixed = instance.SabsFixed;
+ instance.SabsFixed = sabsFixed + 1U;
+ }
+ }
+
+ public override void AppendTaskText(StringBuilder sb)
+ {
+ this.even = !this.even;
+ Color color = this.even ? Color.yellow : Color.red;
+ if (this.reactor != null)
+ {
+ sb.Append(color.ToTextColor());
+ sb.Append(DestroyableSingleton<TranslationController>.Instance.GetString(TaskTypes.RestoreOxy));
+ sb.Append(" ");
+ sb.Append(Mathf.CeilToInt(this.reactor.Countdown));
+ sb.AppendLine(string.Format(" ({0}/{1})[]", this.reactor.UserCount, 2));
+ }
+ else
+ {
+ sb.AppendLine(color.ToTextColor() + "Oxygen depleting[]");
+ }
+ for (int i = 0; i < this.Arrows.Length; i++)
+ {
+ try
+ {
+ this.Arrows[i].image.color = color;
+ }
+ catch
+ {
+ }
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/NoShadowBehaviour.cs b/Client/Assembly-CSharp/NoShadowBehaviour.cs
new file mode 100644
index 0000000..67023ce
--- /dev/null
+++ b/Client/Assembly-CSharp/NoShadowBehaviour.cs
@@ -0,0 +1,55 @@
+using System;
+using UnityEngine;
+
+public class NoShadowBehaviour : MonoBehaviour
+{
+ public Renderer rend;
+
+ public bool didHit;
+
+ public Renderer shadowChild;
+
+ public void Start()
+ {
+ LightSource.NoShadows.Add(base.gameObject, this);
+ }
+
+ public void OnDestroy()
+ {
+ LightSource.NoShadows.Remove(base.gameObject);
+ }
+
+ private void LateUpdate()
+ {
+ if (!PlayerControl.LocalPlayer)
+ {
+ return;
+ }
+ GameData.PlayerInfo data = PlayerControl.LocalPlayer.Data;
+ if (data != null && !data.IsDead)
+ {
+ if (this.didHit)
+ {
+ this.didHit = false;
+ ShipStatus instance = ShipStatus.Instance;
+ if (instance && instance.CalculateLightRadius(data) > instance.MaxLightRadius / 3f)
+ {
+ this.SetMaskFunction(8);
+ return;
+ }
+ }
+ this.SetMaskFunction(1);
+ return;
+ }
+ this.SetMaskFunction(8);
+ }
+
+ private void SetMaskFunction(int func)
+ {
+ this.rend.material.SetInt("_Mask", func);
+ if (this.shadowChild)
+ {
+ this.shadowChild.material.SetInt("_Mask", func);
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/NormalPlayerTask.cs b/Client/Assembly-CSharp/NormalPlayerTask.cs
new file mode 100644
index 0000000..bd5133a
--- /dev/null
+++ b/Client/Assembly-CSharp/NormalPlayerTask.cs
@@ -0,0 +1,284 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using UnityEngine;
+
+public class NormalPlayerTask : PlayerTask
+{
+ public override int TaskStep
+ {
+ get
+ {
+ return this.taskStep;
+ }
+ }
+
+ public override bool IsComplete
+ {
+ get
+ {
+ return this.taskStep >= this.MaxStep;
+ }
+ }
+
+ public int taskStep;
+
+ public int MaxStep;
+
+ public bool ShowTaskStep = true;
+
+ public bool ShowTaskTimer;
+
+ public NormalPlayerTask.TimerState TimerStarted;
+
+ public float TaskTimer;
+
+ public byte[] Data;
+
+ public ArrowBehaviour Arrow;
+
+ public enum TimerState
+ {
+ NotStarted,
+ Started,
+ Finished
+ }
+
+ public override void Initialize()
+ {
+ if (this.Arrow && !base.Owner.AmOwner)
+ {
+ this.Arrow.gameObject.SetActive(false);
+ }
+ this.HasLocation = true;
+ this.LocationDirty = true;
+ TaskTypes taskType = this.TaskType;
+ switch (taskType)
+ {
+ case TaskTypes.PrimeShields:
+ {
+ this.Data = new byte[1];
+ int num = 0;
+ for (int i = 0; i < 7; i++)
+ {
+ byte b = (byte)(1 << i);
+ if (BoolRange.Next(0.7f))
+ {
+ byte[] data = this.Data;
+ int num2 = 0;
+ data[num2] |= b;
+ num++;
+ }
+ }
+ byte[] data2 = this.Data;
+ int num3 = 0;
+ data2[num3] &= 118;
+ return;
+ }
+ case TaskTypes.FuelEngines:
+ this.Data = new byte[2];
+ return;
+ case TaskTypes.ChartCourse:
+ this.Data = new byte[4];
+ return;
+ case TaskTypes.StartReactor:
+ this.Data = new byte[6];
+ return;
+ case TaskTypes.SwipeCard:
+ case TaskTypes.ClearAsteroids:
+ case TaskTypes.UploadData:
+ case TaskTypes.EmptyChute:
+ case TaskTypes.EmptyGarbage:
+ break;
+ case TaskTypes.InspectSample:
+ this.Data = new byte[2];
+ return;
+ case TaskTypes.AlignEngineOutput:
+ this.Data = new byte[2];
+ this.Data[0] = AlignGame.ToByte((float)IntRange.RandomSign() * FloatRange.Next(1f, 3f));
+ this.Data[1] = (byte)(IntRange.RandomSign() * IntRange.Next(25, 255));
+ return;
+ case TaskTypes.FixWiring:
+ {
+ this.Data = new byte[this.MaxStep];
+ List<global::Console> list = (from t in ShipStatus.Instance.AllConsoles
+ where t.TaskTypes.Contains(TaskTypes.FixWiring)
+ select t).ToList<global::Console>();
+ List<global::Console> list2 = new List<global::Console>(list);
+ for (int j = 0; j < this.Data.Length; j++)
+ {
+ int index = list2.RandomIdx<global::Console>();
+ this.Data[j] = (byte)list2[index].ConsoleId;
+ list2.RemoveAt(index);
+ }
+ Array.Sort<byte>(this.Data);
+ global::Console console = list.First((global::Console v) => v.ConsoleId == (int)this.Data[0]);
+ this.StartAt = console.Room;
+ break;
+ }
+ default:
+ if (taskType == TaskTypes.EnterIdCode)
+ {
+ this.Data = BitConverter.GetBytes(IntRange.Next(1, 99999));
+ return;
+ }
+ break;
+ }
+ }
+
+ public void NextStep()
+ {
+ this.taskStep++;
+ this.UpdateArrow();
+ if (this.taskStep >= this.MaxStep)
+ {
+ this.taskStep = this.MaxStep;
+ if (PlayerControl.LocalPlayer)
+ {
+ if (DestroyableSingleton<HudManager>.InstanceExists)
+ {
+ DestroyableSingleton<HudManager>.Instance.ShowTaskComplete();
+ StatsManager instance = StatsManager.Instance;
+ uint num = instance.TasksCompleted;
+ instance.TasksCompleted = num + 1U;
+ if (PlayerTask.AllTasksCompleted(PlayerControl.LocalPlayer))
+ {
+ StatsManager instance2 = StatsManager.Instance;
+ num = instance2.CompletedAllTasks;
+ instance2.CompletedAllTasks = num + 1U;
+ }
+ }
+ PlayerControl.LocalPlayer.RpcCompleteTask(base.Id);
+ return;
+ }
+ }
+ else if (this.ShowTaskStep && Constants.ShouldPlaySfx())
+ {
+ SoundManager.Instance.PlaySound(DestroyableSingleton<HudManager>.Instance.TaskUpdateSound, false, 1f);
+ }
+ }
+
+ public void UpdateArrow()
+ {
+ if (!this.Arrow)
+ {
+ return;
+ }
+ if (!base.Owner.AmOwner)
+ {
+ this.Arrow.gameObject.SetActive(false);
+ return;
+ }
+ if (!this.IsComplete)
+ {
+ this.Arrow.gameObject.SetActive(true);
+ if (this.TaskType == TaskTypes.FixWiring)
+ {
+ global::Console console3 = base.FindSpecialConsole((global::Console c) => c.TaskTypes.Contains(TaskTypes.FixWiring) && c.ConsoleId == (int)this.Data[this.taskStep]);
+ this.Arrow.target = console3.transform.position;
+ this.StartAt = console3.Room;
+ }
+ else if (this.TaskType == TaskTypes.AlignEngineOutput)
+ {
+ if (AlignGame.IsSuccess(this.Data[0]))
+ {
+ this.Arrow.target = base.FindSpecialConsole((global::Console c) => c.TaskTypes.Contains(TaskTypes.AlignEngineOutput) && c.ConsoleId == 1).transform.position;
+ this.StartAt = SystemTypes.UpperEngine;
+ }
+ else
+ {
+ this.Arrow.target = base.FindSpecialConsole((global::Console console) => console.TaskTypes.Contains(TaskTypes.AlignEngineOutput) && console.ConsoleId == 0).transform.position;
+ this.StartAt = SystemTypes.LowerEngine;
+ }
+ }
+ else
+ {
+ global::Console console2 = base.FindObjectPos();
+ this.Arrow.target = console2.transform.position;
+ this.StartAt = console2.Room;
+ }
+ this.LocationDirty = true;
+ return;
+ }
+ this.Arrow.gameObject.SetActive(false);
+ }
+
+ private void FixedUpdate()
+ {
+ if (this.TimerStarted == NormalPlayerTask.TimerState.Started)
+ {
+ this.TaskTimer -= Time.fixedDeltaTime;
+ if (this.TaskTimer <= 0f)
+ {
+ this.TaskTimer = 0f;
+ this.TimerStarted = NormalPlayerTask.TimerState.Finished;
+ }
+ }
+ }
+
+ public override bool ValidConsole(global::Console console)
+ {
+ if (this.TaskType == TaskTypes.FixWiring)
+ {
+ return console.TaskTypes.Contains(this.TaskType) && console.ConsoleId == (int)this.Data[this.taskStep];
+ }
+ if (this.TaskType == TaskTypes.AlignEngineOutput)
+ {
+ return console.TaskTypes.Contains(this.TaskType) && !AlignGame.IsSuccess(this.Data[console.ConsoleId]);
+ }
+ if (this.TaskType == TaskTypes.FuelEngines)
+ {
+ return (console.TaskTypes.Contains(this.TaskType) && console.ConsoleId == (int)this.Data[1]) || console.ValidTasks.Any((TaskSet set) => this.TaskType == set.taskType && set.taskStep.Contains((int)this.Data[1]));
+ }
+ return console.TaskTypes.Any((TaskTypes tt) => tt == this.TaskType) || console.ValidTasks.Any((TaskSet set) => this.TaskType == set.taskType && set.taskStep.Contains(this.taskStep));
+ }
+
+ public override void Complete()
+ {
+ this.taskStep = this.MaxStep;
+ }
+
+ public override void AppendTaskText(StringBuilder sb)
+ {
+ bool flag = this.ShouldYellowText();
+ if (flag)
+ {
+ if (this.IsComplete)
+ {
+ sb.Append("[00DD00FF]");
+ }
+ else
+ {
+ sb.Append("[FFFF00FF]");
+ }
+ }
+ sb.Append(DestroyableSingleton<TranslationController>.Instance.GetString(this.StartAt));
+ sb.Append(": ");
+ sb.Append(DestroyableSingleton<TranslationController>.Instance.GetString(this.TaskType));
+ if (this.ShowTaskTimer && this.TimerStarted == NormalPlayerTask.TimerState.Started)
+ {
+ sb.Append(" (");
+ sb.Append((int)this.TaskTimer);
+ sb.Append("s)");
+ }
+ else if (this.ShowTaskStep)
+ {
+ sb.Append(" (");
+ sb.Append(this.taskStep);
+ sb.Append("/");
+ sb.Append(this.MaxStep);
+ sb.Append(")");
+ }
+ if (flag)
+ {
+ sb.Append("[]");
+ }
+ sb.AppendLine();
+ }
+
+ private bool ShouldYellowText()
+ {
+ return (this.TaskType == TaskTypes.FuelEngines && this.Data[1] > 0) || this.taskStep > 0 || this.TimerStarted > NormalPlayerTask.TimerState.NotStarted;
+ }
+}
diff --git a/Client/Assembly-CSharp/NotificationPopper.cs b/Client/Assembly-CSharp/NotificationPopper.cs
new file mode 100644
index 0000000..8a98115
--- /dev/null
+++ b/Client/Assembly-CSharp/NotificationPopper.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Text;
+using UnityEngine;
+
+public class NotificationPopper : MonoBehaviour
+{
+ public TextRenderer TextArea;
+
+ public float zPos = -350f;
+
+ private float alphaTimer;
+
+ public float ShowDuration = 5f;
+
+ public float FadeDuration = 1f;
+
+ public Color textColor = Color.white;
+
+ private StringBuilder builder = new StringBuilder();
+
+ public AudioClip NotificationSound;
+
+ public void Update()
+ {
+ if (this.alphaTimer > 0f)
+ {
+ float num = Camera.main.orthographicSize * Camera.main.aspect;
+ if (!DestroyableSingleton<HudManager>.Instance.TaskText.isActiveAndEnabled)
+ {
+ float height = DestroyableSingleton<HudManager>.Instance.GameSettings.Height;
+ Transform transform = DestroyableSingleton<HudManager>.Instance.GameSettings.transform;
+ base.transform.localPosition = new Vector3(-num + 0.1f, transform.localPosition.y - height, this.zPos);
+ }
+ else
+ {
+ float height2 = DestroyableSingleton<HudManager>.Instance.TaskText.Height;
+ Transform parent = DestroyableSingleton<HudManager>.Instance.TaskText.transform.parent;
+ base.transform.localPosition = new Vector3(-num + 0.1f, parent.localPosition.y - height2 - 0.2f, this.zPos);
+ }
+ this.alphaTimer -= Time.deltaTime;
+ this.textColor.a = Mathf.Clamp(this.alphaTimer / this.FadeDuration, 0f, 1f);
+ this.TextArea.Color = this.textColor;
+ if (this.alphaTimer <= 0f)
+ {
+ this.builder.Clear();
+ this.TextArea.Text = string.Empty;
+ }
+ }
+ }
+
+ public void AddItem(string item)
+ {
+ this.builder.AppendLine(item);
+ this.TextArea.Text = this.builder.ToString();
+ this.alphaTimer = this.ShowDuration;
+ SoundManager.Instance.PlaySound(this.NotificationSound, false, 1f);
+ }
+}
diff --git a/Client/Assembly-CSharp/NumberOption.cs b/Client/Assembly-CSharp/NumberOption.cs
new file mode 100644
index 0000000..ebe2f94
--- /dev/null
+++ b/Client/Assembly-CSharp/NumberOption.cs
@@ -0,0 +1,111 @@
+using System;
+using UnityEngine;
+
+public class NumberOption : OptionBehaviour
+{
+ public TextRenderer TitleText;
+
+ public TextRenderer ValueText;
+
+ public float Value = 1f;
+
+ private float oldValue = float.MaxValue;
+
+ public float Increment;
+
+ public FloatRange ValidRange = new FloatRange(0f, 2f);
+
+ public string FormatString = "{0:0.0}x";
+
+ public bool ZeroIsInfinity;
+
+ public void OnEnable()
+ {
+ this.TitleText.Text = DestroyableSingleton<TranslationController>.Instance.GetString(this.Title, Array.Empty<object>());
+ this.ValueText.Text = string.Format(this.FormatString, this.Value);
+ GameOptionsData gameOptions = PlayerControl.GameOptions;
+ StringNames title = this.Title;
+ switch (title)
+ {
+ case StringNames.GameNumImpostors:
+ this.Value = (float)gameOptions.NumImpostors;
+ return;
+ case StringNames.GameNumMeetings:
+ this.Value = (float)gameOptions.NumEmergencyMeetings;
+ return;
+ case StringNames.GameDiscussTime:
+ this.Value = (float)gameOptions.DiscussionTime;
+ return;
+ case StringNames.GameVotingTime:
+ this.Value = (float)gameOptions.VotingTime;
+ return;
+ case StringNames.GamePlayerSpeed:
+ this.Value = gameOptions.PlayerSpeedMod;
+ return;
+ case StringNames.GameCrewLight:
+ this.Value = gameOptions.CrewLightMod;
+ return;
+ case StringNames.GameImpostorLight:
+ this.Value = gameOptions.ImpostorLightMod;
+ return;
+ case StringNames.GameKillCooldown:
+ this.Value = gameOptions.KillCooldown;
+ return;
+ case StringNames.GameKillDistance:
+ break;
+ case StringNames.GameCommonTasks:
+ this.Value = (float)gameOptions.NumCommonTasks;
+ return;
+ case StringNames.GameLongTasks:
+ this.Value = (float)gameOptions.NumLongTasks;
+ return;
+ case StringNames.GameShortTasks:
+ this.Value = (float)gameOptions.NumShortTasks;
+ return;
+ default:
+ if (title == StringNames.GameEmergencyCooldown)
+ {
+ this.Value = (float)gameOptions.EmergencyCooldown;
+ return;
+ }
+ break;
+ }
+ Debug.Log("Ono, unrecognized setting: " + this.Title);
+ }
+
+ private void FixedUpdate()
+ {
+ if (this.oldValue != this.Value)
+ {
+ this.oldValue = this.Value;
+ if (this.ZeroIsInfinity && Mathf.Abs(this.Value) < 0.0001f)
+ {
+ this.ValueText.Text = string.Format(this.FormatString, "∞");
+ return;
+ }
+ this.ValueText.Text = string.Format(this.FormatString, this.Value);
+ }
+ }
+
+ public void Increase()
+ {
+ this.Value = this.ValidRange.Clamp(this.Value + this.Increment);
+ this.OnValueChanged(this);
+ }
+
+ public void Decrease()
+ {
+ this.Value = this.ValidRange.Clamp(this.Value - this.Increment);
+ this.OnValueChanged(this);
+ }
+
+ public override float GetFloat()
+ {
+ return this.Value;
+ }
+
+ public override int GetInt()
+ {
+ return (int)this.Value;
+ }
+}
diff --git a/Client/Assembly-CSharp/ObjectPoolBehavior.cs b/Client/Assembly-CSharp/ObjectPoolBehavior.cs
new file mode 100644
index 0000000..657837b
--- /dev/null
+++ b/Client/Assembly-CSharp/ObjectPoolBehavior.cs
@@ -0,0 +1,138 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+public class ObjectPoolBehavior : IObjectPool
+{
+ public override int InUse
+ {
+ get
+ {
+ return this.activeChildren.Count;
+ }
+ }
+
+ public override int NotInUse
+ {
+ get
+ {
+ return this.inactiveChildren.Count;
+ }
+ }
+
+ public int poolSize = 20;
+
+ [SerializeField]
+ private List<PoolableBehavior> inactiveChildren = new List<PoolableBehavior>();
+
+ [SerializeField]
+ public List<PoolableBehavior> activeChildren = new List<PoolableBehavior>();
+
+ public PoolableBehavior Prefab;
+
+ public bool AutoInit;
+
+ public bool DetachOnGet;
+
+ public virtual void Awake()
+ {
+ if (this.AutoInit)
+ {
+ this.InitPool(this.Prefab);
+ }
+ }
+
+ public void InitPool(PoolableBehavior prefab)
+ {
+ this.AutoInit = false;
+ for (int i = 0; i < this.poolSize; i++)
+ {
+ this.CreateOneInactive(prefab);
+ }
+ }
+
+ private void CreateOneInactive(PoolableBehavior prefab)
+ {
+ PoolableBehavior poolableBehavior = UnityEngine.Object.Instantiate<PoolableBehavior>(prefab);
+ poolableBehavior.transform.SetParent(base.transform);
+ poolableBehavior.gameObject.SetActive(false);
+ poolableBehavior.OwnerPool = this;
+ this.inactiveChildren.Add(poolableBehavior);
+ }
+
+ public void ReclaimOldest()
+ {
+ if (this.activeChildren.Count > 0)
+ {
+ this.Reclaim(this.activeChildren[0]);
+ return;
+ }
+ this.InitPool(this.Prefab);
+ }
+
+ public void ReclaimAll()
+ {
+ foreach (PoolableBehavior obj in this.activeChildren.ToArray())
+ {
+ this.Reclaim(obj);
+ }
+ }
+
+ public override T Get<T>()
+ {
+ List<PoolableBehavior> obj = this.inactiveChildren;
+ PoolableBehavior poolableBehavior;
+ lock (obj)
+ {
+ if (this.inactiveChildren.Count == 0)
+ {
+ if (this.activeChildren.Count == 0)
+ {
+ this.InitPool(this.Prefab);
+ }
+ else
+ {
+ this.CreateOneInactive(this.Prefab);
+ }
+ }
+ poolableBehavior = this.inactiveChildren[this.inactiveChildren.Count - 1];
+ this.inactiveChildren.RemoveAt(this.inactiveChildren.Count - 1);
+ this.activeChildren.Add(poolableBehavior);
+ }
+ if (this.DetachOnGet)
+ {
+ poolableBehavior.transform.SetParent(null, false);
+ }
+ poolableBehavior.gameObject.SetActive(true);
+ poolableBehavior.Reset();
+ return poolableBehavior as T;
+ }
+
+ public override void Reclaim(PoolableBehavior obj)
+ {
+ if (!this)
+ {
+ DefaultPool.Instance.Reclaim(obj);
+ return;
+ }
+ obj.gameObject.SetActive(false);
+ obj.transform.SetParent(base.transform);
+ List<PoolableBehavior> obj2 = this.inactiveChildren;
+ lock (obj2)
+ {
+ if (this.activeChildren.Remove(obj))
+ {
+ this.inactiveChildren.Add(obj);
+ }
+ else if (this.inactiveChildren.Contains(obj))
+ {
+ Debug.Log("ObjectPoolBehavior: :| Something was reclaimed without being gotten");
+ }
+ else
+ {
+ Debug.Log("ObjectPoolBehavior: Destroying this thing I don't own");
+ UnityEngine.Object.Destroy(obj.gameObject);
+ }
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/OffsetAdjustment.cs b/Client/Assembly-CSharp/OffsetAdjustment.cs
new file mode 100644
index 0000000..83e6b1c
--- /dev/null
+++ b/Client/Assembly-CSharp/OffsetAdjustment.cs
@@ -0,0 +1,11 @@
+using System;
+
+[Serializable]
+public class OffsetAdjustment
+{
+ public char Char;
+
+ public int OffsetX;
+
+ public int OffsetY;
+}
diff --git a/Client/Assembly-CSharp/OneWayShadows.cs b/Client/Assembly-CSharp/OneWayShadows.cs
new file mode 100644
index 0000000..e5aff45
--- /dev/null
+++ b/Client/Assembly-CSharp/OneWayShadows.cs
@@ -0,0 +1,22 @@
+using System;
+using UnityEngine;
+
+public class OneWayShadows : MonoBehaviour
+{
+ public Collider2D RoomCollider;
+
+ public void Start()
+ {
+ LightSource.OneWayShadows.Add(base.gameObject, this);
+ }
+
+ public void OnDestroy()
+ {
+ LightSource.OneWayShadows.Remove(base.gameObject);
+ }
+
+ public bool IsIgnored(LightSource lightSource)
+ {
+ return this.RoomCollider.OverlapPoint(lightSource.transform.position);
+ }
+}
diff --git a/Client/Assembly-CSharp/OptionBehaviour.cs b/Client/Assembly-CSharp/OptionBehaviour.cs
new file mode 100644
index 0000000..f17b80f
--- /dev/null
+++ b/Client/Assembly-CSharp/OptionBehaviour.cs
@@ -0,0 +1,33 @@
+using System;
+using UnityEngine;
+
+public abstract class OptionBehaviour : MonoBehaviour
+{
+ public StringNames Title;
+
+ public Action<OptionBehaviour> OnValueChanged;
+
+ public virtual float GetFloat()
+ {
+ throw new NotImplementedException();
+ }
+
+ public virtual int GetInt()
+ {
+ throw new NotImplementedException();
+ }
+
+ public virtual bool GetBool()
+ {
+ throw new NotImplementedException();
+ }
+
+ public void SetAsPlayer()
+ {
+ PassiveButton[] componentsInChildren = base.GetComponentsInChildren<PassiveButton>();
+ for (int i = 0; i < componentsInChildren.Length; i++)
+ {
+ componentsInChildren[i].gameObject.SetActive(false);
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/OptionsConsole.cs b/Client/Assembly-CSharp/OptionsConsole.cs
new file mode 100644
index 0000000..4d80de9
--- /dev/null
+++ b/Client/Assembly-CSharp/OptionsConsole.cs
@@ -0,0 +1,64 @@
+using System;
+using UnityEngine;
+
+public class OptionsConsole : MonoBehaviour, IUsable
+{
+ public float UsableDistance
+ {
+ get
+ {
+ return 1f;
+ }
+ }
+
+ public float PercentCool
+ {
+ get
+ {
+ return 0f;
+ }
+ }
+
+ public CustomPlayerMenu MenuPrefab;
+
+ public SpriteRenderer Outline;
+
+ public float CanUse(GameData.PlayerInfo pc, out bool canUse, out bool couldUse)
+ {
+ float num = float.MaxValue;
+ PlayerControl @object = pc.Object;
+ couldUse = @object.CanMove;
+ canUse = couldUse;
+ if (canUse)
+ {
+ num = Vector2.Distance(@object.GetTruePosition(), base.transform.position);
+ canUse &= (num <= this.UsableDistance);
+ }
+ return num;
+ }
+
+ public void SetOutline(bool on, bool mainTarget)
+ {
+ if (this.Outline)
+ {
+ this.Outline.material.SetFloat("_Outline", (float)(on ? 1 : 0));
+ this.Outline.material.SetColor("_OutlineColor", Color.white);
+ this.Outline.material.SetColor("_AddColor", mainTarget ? Color.white : Color.clear);
+ }
+ }
+
+ public void Use()
+ {
+ bool flag;
+ bool flag2;
+ this.CanUse(PlayerControl.LocalPlayer.Data, out flag, out flag2);
+ if (!flag)
+ {
+ return;
+ }
+ PlayerControl.LocalPlayer.NetTransform.Halt();
+ CustomPlayerMenu customPlayerMenu = UnityEngine.Object.Instantiate<CustomPlayerMenu>(this.MenuPrefab);
+ customPlayerMenu.transform.SetParent(Camera.main.transform, false);
+ customPlayerMenu.transform.localPosition = new Vector3(0f, 0f, -20f);
+ }
+}
diff --git a/Client/Assembly-CSharp/OptionsMenuBehaviour.cs b/Client/Assembly-CSharp/OptionsMenuBehaviour.cs
new file mode 100644
index 0000000..fffe2b4
--- /dev/null
+++ b/Client/Assembly-CSharp/OptionsMenuBehaviour.cs
@@ -0,0 +1,200 @@
+using System;
+using UnityEngine;
+
+public class OptionsMenuBehaviour : MonoBehaviour, ITranslatedText
+{
+ public bool IsOpen
+ {
+ get
+ {
+ return base.isActiveAndEnabled;
+ }
+ }
+
+ public SpriteRenderer Background;
+
+ public SpriteRenderer JoystickButton;
+
+ public SpriteRenderer TouchButton;
+
+ public SlideBar JoystickSizeSlider;
+
+ public FloatRange JoystickSizes = new FloatRange(0.5f, 1.5f);
+
+ public SlideBar SoundSlider;
+
+ public SlideBar MusicSlider;
+
+ public ToggleButtonBehaviour SendTelemButton;
+
+ public ToggleButtonBehaviour PersonalizedAdsButton;
+
+ public ToggleButtonBehaviour CensorChatButton;
+
+ public bool Toggle = true;
+
+ public TabGroup[] Tabs;
+
+ public void OpenTabGroup(TabGroup selected)
+ {
+ selected.Open();
+ for (int i = 0; i < this.Tabs.Length; i++)
+ {
+ TabGroup tabGroup = this.Tabs[i];
+ if (!(tabGroup == selected))
+ {
+ tabGroup.Close();
+ }
+ }
+ }
+
+ private void Update()
+ {
+ if (Input.GetKeyUp(KeyCode.Escape))
+ {
+ this.Close();
+ }
+ }
+
+ public void Start()
+ {
+ DestroyableSingleton<TranslationController>.Instance.ActiveTexts.Add(this);
+ }
+
+ public void OnDestroy()
+ {
+ DestroyableSingleton<TranslationController>.Instance.ActiveTexts.Remove(this);
+ }
+
+ public void ResetText()
+ {
+ this.JoystickButton.transform.parent.GetComponentInChildren<TextRenderer>().Text = DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.SettingsMouseMode, Array.Empty<object>());
+ this.TouchButton.transform.parent.GetComponentInChildren<TextRenderer>().Text = DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.SettingsKeyboardMode, Array.Empty<object>());
+ this.JoystickSizeSlider.gameObject.SetActive(false);
+ }
+
+ public void Open()
+ {
+ this.ResetText();
+ if (base.gameObject.activeSelf)
+ {
+ if (this.Toggle)
+ {
+ base.GetComponent<TransitionOpen>().Close();
+ }
+ return;
+ }
+ this.OpenTabGroup(this.Tabs[0]);
+ this.UpdateButtons();
+ base.gameObject.SetActive(true);
+ }
+
+ public void SetControlType(int i)
+ {
+ SaveManager.TouchConfig = i;
+ this.UpdateButtons();
+ if (DestroyableSingleton<HudManager>.InstanceExists)
+ {
+ DestroyableSingleton<HudManager>.Instance.SetTouchType(i);
+ }
+ }
+
+ public void UpdateJoystickSize(SlideBar slider)
+ {
+ SaveManager.JoystickSize = this.JoystickSizes.Lerp(slider.Value);
+ if (DestroyableSingleton<HudManager>.InstanceExists)
+ {
+ DestroyableSingleton<HudManager>.Instance.SetJoystickSize(SaveManager.JoystickSize);
+ }
+ }
+
+ public void ToggleSendTelemetry()
+ {
+ SaveManager.SendTelemetry = !SaveManager.SendTelemetry;
+ this.UpdateButtons();
+ }
+
+ public void ToggleSendName()
+ {
+ SaveManager.SendName = !SaveManager.SendName;
+ this.UpdateButtons();
+ }
+
+ public void UpdateSfxVolume(SlideBar button)
+ {
+ SaveManager.SfxVolume = button.Value;
+ SoundManager.Instance.ChangeSfxVolume(button.Value);
+ }
+
+ public void UpdateMusicVolume(SlideBar button)
+ {
+ SaveManager.MusicVolume = button.Value;
+ SoundManager.Instance.ChangeMusicVolume(button.Value);
+ }
+
+ public void TogglePersonalizedAd()
+ {
+ ShowAdsState showAdsState = SaveManager.ShowAdsScreen & (ShowAdsState)127;
+ if (showAdsState != ShowAdsState.Personalized)
+ {
+ if (showAdsState == ShowAdsState.NonPersonalized)
+ {
+ SaveManager.ShowAdsScreen = ShowAdsState.Accepted;
+ goto IL_30;
+ }
+ if (showAdsState == ShowAdsState.Purchased)
+ {
+ goto IL_30;
+ }
+ }
+ SaveManager.ShowAdsScreen = (ShowAdsState)129;
+ IL_30:
+ this.UpdateButtons();
+ }
+
+ public void ToggleCensorChat()
+ {
+ SaveManager.CensorChat = !SaveManager.CensorChat;
+ this.UpdateButtons();
+ }
+
+ public void UpdateButtons()
+ {
+ if (SaveManager.TouchConfig == 0)
+ {
+ this.JoystickButton.color = new Color32(0, byte.MaxValue, 42, byte.MaxValue);
+ this.TouchButton.color = Color.white;
+ this.JoystickSizeSlider.enabled = true;
+ this.JoystickSizeSlider.OnEnable();
+ }
+ else
+ {
+ this.JoystickButton.color = Color.white;
+ this.TouchButton.color = new Color32(0, byte.MaxValue, 42, byte.MaxValue);
+ this.JoystickSizeSlider.enabled = false;
+ this.JoystickSizeSlider.OnDisable();
+ }
+ this.JoystickSizeSlider.Value = this.JoystickSizes.ReverseLerp(SaveManager.JoystickSize);
+ this.SoundSlider.Value = SaveManager.SfxVolume;
+ this.MusicSlider.Value = SaveManager.MusicVolume;
+ if (this.SendTelemButton)
+ {
+ this.SendTelemButton.UpdateText(SaveManager.SendTelemetry);
+ }
+ this.CensorChatButton.UpdateText(SaveManager.CensorChat);
+ if (this.PersonalizedAdsButton)
+ {
+ if (SaveManager.ShowAdsScreen.HasFlag(ShowAdsState.Purchased) || SaveManager.BoughtNoAds)
+ {
+ this.PersonalizedAdsButton.transform.parent.gameObject.SetActive(false);
+ return;
+ }
+ this.PersonalizedAdsButton.UpdateText(!SaveManager.ShowAdsScreen.HasFlag(ShowAdsState.NonPersonalized));
+ }
+ }
+
+ public void Close()
+ {
+ base.gameObject.SetActive(false);
+ }
+}
diff --git a/Client/Assembly-CSharp/OverlayKillAnimation.cs b/Client/Assembly-CSharp/OverlayKillAnimation.cs
new file mode 100644
index 0000000..d6f711e
--- /dev/null
+++ b/Client/Assembly-CSharp/OverlayKillAnimation.cs
@@ -0,0 +1,150 @@
+using System;
+using System.Collections;
+using PowerTools;
+using UnityEngine;
+
+public class OverlayKillAnimation : MonoBehaviour
+{
+ public KillAnimType KillType;
+
+ public PoolablePlayer killerParts;
+
+ public PoolablePlayer victimParts;
+
+ private uint victimHat;
+
+ public AudioClip Stinger;
+
+ public AudioClip Sfx;
+
+ public float StingerVolume = 0.6f;
+
+ public void Begin(PlayerControl killer, GameData.PlayerInfo vInfo)
+ {
+ if (this.killerParts)
+ {
+ GameData.PlayerInfo kInfo = killer.Data;
+ PlayerControl.SetPlayerMaterialColors((int)kInfo.ColorId, this.killerParts.Body);
+ this.killerParts.Hands.ForEach(delegate(SpriteRenderer b)
+ {
+ PlayerControl.SetPlayerMaterialColors((int)kInfo.ColorId, b);
+ });
+ PlayerControl.SetHatImage(kInfo.HatId, this.killerParts.HatSlot);
+ switch (this.KillType)
+ {
+ case KillAnimType.Stab:
+ case KillAnimType.Neck:
+ PlayerControl.SetSkinImage(kInfo.SkinId, this.killerParts.SkinSlot);
+ break;
+ case KillAnimType.Tongue:
+ {
+ SkinData skinById = DestroyableSingleton<HatManager>.Instance.GetSkinById(kInfo.SkinId);
+ this.killerParts.SkinSlot.GetComponent<SpriteAnim>().Play(skinById.KillTongueImpostor, 1f);
+ break;
+ }
+ case KillAnimType.Shoot:
+ {
+ SkinData skinById2 = DestroyableSingleton<HatManager>.Instance.GetSkinById(kInfo.SkinId);
+ this.killerParts.SkinSlot.GetComponent<SpriteAnim>().Play(skinById2.KillShootImpostor, 1f);
+ break;
+ }
+ }
+ if (this.killerParts.PetSlot)
+ {
+ PetBehaviour petById = DestroyableSingleton<HatManager>.Instance.GetPetById(kInfo.PetId);
+ if (petById && petById.scaredClip)
+ {
+ this.killerParts.PetSlot.GetComponent<SpriteAnim>().Play(petById.idleClip, 1f);
+ this.killerParts.PetSlot.sharedMaterial = petById.rend.sharedMaterial;
+ PlayerControl.SetPlayerMaterialColors((int)kInfo.ColorId, this.killerParts.PetSlot);
+ }
+ else
+ {
+ this.killerParts.PetSlot.enabled = false;
+ }
+ }
+ }
+ if (vInfo != null && this.victimParts)
+ {
+ this.victimHat = vInfo.HatId;
+ PlayerControl.SetPlayerMaterialColors((int)vInfo.ColorId, this.victimParts.Body);
+ PlayerControl.SetHatImage(vInfo.HatId, this.victimParts.HatSlot);
+ SkinData skinById3 = DestroyableSingleton<HatManager>.Instance.GetSkinById(vInfo.SkinId);
+ switch (this.KillType)
+ {
+ case KillAnimType.Stab:
+ this.victimParts.SkinSlot.GetComponent<SpriteAnim>().Play(skinById3.KillStabVictim, 1f);
+ break;
+ case KillAnimType.Tongue:
+ this.victimParts.SkinSlot.GetComponent<SpriteAnim>().Play(skinById3.KillTongueVictim, 1f);
+ break;
+ case KillAnimType.Shoot:
+ this.victimParts.SkinSlot.GetComponent<SpriteAnim>().Play(skinById3.KillShootVictim, 1f);
+ break;
+ case KillAnimType.Neck:
+ this.victimParts.SkinSlot.GetComponent<SpriteAnim>().Play(skinById3.KillNeckVictim, 1f);
+ break;
+ }
+ if (this.victimParts.PetSlot)
+ {
+ PetBehaviour petById2 = DestroyableSingleton<HatManager>.Instance.GetPetById(vInfo.PetId);
+ if (petById2 && petById2.scaredClip)
+ {
+ this.victimParts.PetSlot.GetComponent<SpriteAnim>().Play(petById2.scaredClip, 1f);
+ this.victimParts.PetSlot.sharedMaterial = petById2.rend.sharedMaterial;
+ PlayerControl.SetPlayerMaterialColors((int)vInfo.ColorId, this.victimParts.PetSlot);
+ return;
+ }
+ this.victimParts.PetSlot.enabled = false;
+ }
+ }
+ }
+
+ public void SetHatFloor()
+ {
+ HatBehaviour hatById = DestroyableSingleton<HatManager>.Instance.GetHatById(this.victimHat);
+ if (!hatById)
+ {
+ return;
+ }
+ this.victimParts.HatSlot.sprite = hatById.FloorImage;
+ }
+
+ public void PlayKillSound()
+ {
+ if (Constants.ShouldPlaySfx())
+ {
+ SoundManager.Instance.PlaySound(this.Sfx, false, 1f).volume = 0.8f;
+ }
+ }
+
+ public IEnumerator WaitForFinish()
+ {
+ SpriteAnim[] anims = base.GetComponentsInChildren<SpriteAnim>();
+ if (anims.Length == 0)
+ {
+ yield return new WaitForSeconds(1f);
+ }
+ else
+ {
+ for (;;)
+ {
+ bool flag = false;
+ for (int i = 0; i < anims.Length; i++)
+ {
+ if (anims[i].IsPlaying(null))
+ {
+ flag = true;
+ break;
+ }
+ }
+ if (!flag)
+ {
+ break;
+ }
+ yield return null;
+ }
+ }
+ yield break;
+ }
+}
diff --git a/Client/Assembly-CSharp/Palette.cs b/Client/Assembly-CSharp/Palette.cs
new file mode 100644
index 0000000..044fa1a
--- /dev/null
+++ b/Client/Assembly-CSharp/Palette.cs
@@ -0,0 +1,67 @@
+using System;
+using UnityEngine;
+
+public static class Palette
+{
+ public static readonly Color DisabledGrey = new Color(0.3f, 0.3f, 0.3f, 1f);
+
+ public static readonly Color DisabledColor = new Color(1f, 1f, 1f, 0.3f);
+
+ public static readonly Color EnabledColor = new Color(1f, 1f, 1f, 1f);
+
+ public static readonly Color Black = new Color(0f, 0f, 0f, 1f);
+
+ public static readonly Color ClearWhite = new Color(1f, 1f, 1f, 0f);
+
+ public static readonly Color HalfWhite = new Color(1f, 1f, 1f, 0.5f);
+
+ public static readonly Color White = new Color(1f, 1f, 1f, 1f);
+
+ public static readonly Color LightBlue = new Color(0.5f, 0.5f, 1f);
+
+ public static readonly Color Blue = new Color(0.2f, 0.2f, 1f);
+
+ public static readonly Color Orange = new Color(1f, 0.6f, 0.005f);
+
+ public static readonly Color Purple = new Color(0.6f, 0.1f, 0.6f);
+
+ public static readonly Color Brown = new Color(0.72f, 0.43f, 0.11f);
+
+ public static readonly Color CrewmateBlue = new Color32(140, byte.MaxValue, byte.MaxValue, byte.MaxValue);
+
+ public static readonly Color ImpostorRed = new Color32(byte.MaxValue, 25, 25, byte.MaxValue);
+
+ public static readonly Color32[] PlayerColors = new Color32[]
+ {
+ new Color32(198, 17, 17, byte.MaxValue),
+ new Color32(19, 46, 210, byte.MaxValue),
+ new Color32(17, 128, 45, byte.MaxValue),
+ new Color32(238, 84, 187, byte.MaxValue),
+ new Color32(240, 125, 13, byte.MaxValue),
+ new Color32(246, 246, 87, byte.MaxValue),
+ new Color32(63, 71, 78, byte.MaxValue),
+ new Color32(215, 225, 241, byte.MaxValue),
+ new Color32(107, 47, 188, byte.MaxValue),
+ new Color32(113, 73, 30, byte.MaxValue),
+ new Color32(56, byte.MaxValue, 221, byte.MaxValue),
+ new Color32(80, 240, 57, byte.MaxValue)
+ };
+
+ public static readonly Color32[] ShadowColors = new Color32[]
+ {
+ new Color32(122, 8, 56, byte.MaxValue),
+ new Color32(9, 21, 142, byte.MaxValue),
+ new Color32(10, 77, 46, byte.MaxValue),
+ new Color32(172, 43, 174, byte.MaxValue),
+ new Color32(180, 62, 21, byte.MaxValue),
+ new Color32(195, 136, 34, byte.MaxValue),
+ new Color32(30, 31, 38, byte.MaxValue),
+ new Color32(132, 149, 192, byte.MaxValue),
+ new Color32(59, 23, 124, byte.MaxValue),
+ new Color32(94, 38, 21, byte.MaxValue),
+ new Color32(36, 169, 191, byte.MaxValue),
+ new Color32(21, 168, 66, byte.MaxValue)
+ };
+
+ public static readonly Color32 VisorColor = new Color32(149, 202, 220, byte.MaxValue);
+}
diff --git a/Client/Assembly-CSharp/ParallaxController.cs b/Client/Assembly-CSharp/ParallaxController.cs
new file mode 100644
index 0000000..892453d
--- /dev/null
+++ b/Client/Assembly-CSharp/ParallaxController.cs
@@ -0,0 +1,22 @@
+using System;
+using UnityEngine;
+
+public class ParallaxController : MonoBehaviour
+{
+ public float Rate;
+
+ private Camera cam;
+
+ public void Start()
+ {
+ this.cam = Camera.main;
+ }
+
+ private void Update()
+ {
+ Vector3 vector = base.transform.parent.position - this.cam.transform.position;
+ vector *= this.Rate;
+ vector.z = -this.Rate;
+ base.transform.localPosition = vector;
+ }
+}
diff --git a/Client/Assembly-CSharp/ParticleInfo.cs b/Client/Assembly-CSharp/ParticleInfo.cs
new file mode 100644
index 0000000..2c5da66
--- /dev/null
+++ b/Client/Assembly-CSharp/ParticleInfo.cs
@@ -0,0 +1,12 @@
+using System;
+using UnityEngine;
+
+[Serializable]
+public struct ParticleInfo
+{
+ public Vector3 Position;
+
+ public float Scale;
+
+ public float Timer;
+}
diff --git a/Client/Assembly-CSharp/PassiveButton.cs b/Client/Assembly-CSharp/PassiveButton.cs
new file mode 100644
index 0000000..f7e9337
--- /dev/null
+++ b/Client/Assembly-CSharp/PassiveButton.cs
@@ -0,0 +1,47 @@
+using System;
+using UnityEngine;
+using UnityEngine.Events;
+using UnityEngine.UI;
+
+public class PassiveButton : MonoBehaviour
+{
+ public bool OnUp = true;
+
+ public bool OnDown;
+
+ public Button.ButtonClickedEvent OnClick = new Button.ButtonClickedEvent();
+
+ public AudioClip ClickSound;
+
+ public UnityEvent OnMouseOver;
+
+ public UnityEvent OnMouseOut;
+
+ public Collider2D[] Colliders;
+
+ public void Start()
+ {
+ DestroyableSingleton<PassiveButtonManager>.Instance.RegisterOne(this);
+ if (this.Colliders == null || this.Colliders.Length == 0)
+ {
+ this.Colliders = base.GetComponents<Collider2D>();
+ }
+ }
+
+ public void DoClick()
+ {
+ if (this.ClickSound)
+ {
+ SoundManager.Instance.PlaySound(this.ClickSound, false, 1f);
+ }
+ this.OnClick.Invoke();
+ }
+
+ public void OnDestroy()
+ {
+ if (DestroyableSingleton<PassiveButtonManager>.InstanceExists)
+ {
+ DestroyableSingleton<PassiveButtonManager>.Instance.RemoveOne(this);
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/PassiveButtonManager.cs b/Client/Assembly-CSharp/PassiveButtonManager.cs
new file mode 100644
index 0000000..b45379b
--- /dev/null
+++ b/Client/Assembly-CSharp/PassiveButtonManager.cs
@@ -0,0 +1,256 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+public class PassiveButtonManager : DestroyableSingleton<PassiveButtonManager>
+{
+ public List<PassiveButton> Buttons = new List<PassiveButton>();
+
+ private List<IFocusHolder> FocusHolders = new List<IFocusHolder>();
+
+ private PassiveButton currentOver;
+
+ public Controller Controller = new Controller();
+
+ private PassiveButton currentDown;
+
+ private Collider2D[] results = new Collider2D[40];
+
+ private class DepthComparer : IComparer<PassiveButton>
+ {
+ public static readonly PassiveButtonManager.DepthComparer Instance = new PassiveButtonManager.DepthComparer();
+
+ public int Compare(PassiveButton x, PassiveButton y)
+ {
+ if (x == null)
+ {
+ return 1;
+ }
+ if (y == null)
+ {
+ return -1;
+ }
+ return x.transform.position.z.CompareTo(y.transform.position.z);
+ }
+ }
+
+ public void RegisterOne(PassiveButton button)
+ {
+ this.Buttons.Add(button);
+ }
+
+ public void RemoveOne(PassiveButton passiveButton)
+ {
+ this.Buttons.Remove(passiveButton);
+ }
+
+ public void RegisterOne(IFocusHolder focusHolder)
+ {
+ this.FocusHolders.Add(focusHolder);
+ }
+
+ public void RemoveOne(IFocusHolder focusHolder)
+ {
+ this.FocusHolders.Remove(focusHolder);
+ }
+
+ public void Update()
+ {
+ this.Controller.Update();
+ for (int i = 1; i < this.Buttons.Count; i++)
+ {
+ if (PassiveButtonManager.DepthComparer.Instance.Compare(this.Buttons[i - 1], this.Buttons[i]) > 0)
+ {
+ this.Buttons.Sort(PassiveButtonManager.DepthComparer.Instance);
+ break;
+ }
+ }
+ Vector2 position = this.Controller.Touches[0].Position;
+ int num = Physics2D.OverlapPointNonAlloc(position, this.results);
+ bool flag = false;
+ for (int j = 0; j < this.Buttons.Count; j++)
+ {
+ PassiveButton passiveButton = this.Buttons[j];
+ if (!passiveButton)
+ {
+ this.Buttons.RemoveAt(j);
+ j--;
+ }
+ else if (passiveButton.isActiveAndEnabled)
+ {
+ bool flag2 = false;
+ for (int k = 0; k < num; k++)
+ {
+ if (this.results[k].gameObject == passiveButton.gameObject)
+ {
+ flag2 = true;
+ break;
+ }
+ }
+ if (flag2)
+ {
+ flag = true;
+ if (passiveButton != this.currentOver)
+ {
+ if (this.currentOver)
+ {
+ this.currentOver.OnMouseOut.Invoke();
+ }
+ this.currentOver = passiveButton;
+ this.currentDown = null;
+ this.currentOver.OnMouseOver.Invoke();
+ break;
+ }
+ break;
+ }
+ }
+ }
+ if (!flag && this.currentOver)
+ {
+ this.currentOver.OnMouseOut.Invoke();
+ this.currentOver = null;
+ this.currentDown = null;
+ }
+ if (this.Controller.AnyTouchDown)
+ {
+ if (this.currentOver)
+ {
+ this.currentDown = this.currentOver;
+ if (this.currentOver.OnDown)
+ {
+ this.currentOver.DoClick();
+ }
+ }
+ this.HandleFocus(position);
+ return;
+ }
+ if (this.Controller.AnyTouchUp && this.currentDown)
+ {
+ if (this.currentDown.OnUp)
+ {
+ this.currentDown.DoClick();
+ }
+ this.currentDown = null;
+ }
+ }
+
+ private void CheckForDown()
+ {
+ Vector2 touch = this.GetTouch(true);
+ for (int i = 0; i < this.Buttons.Count; i++)
+ {
+ PassiveButton passiveButton = this.Buttons[i];
+ if (!passiveButton)
+ {
+ this.Buttons.RemoveAt(i);
+ i--;
+ }
+ else if (passiveButton.isActiveAndEnabled)
+ {
+ for (int j = 0; j < passiveButton.Colliders.Length; j++)
+ {
+ Collider2D collider2D = passiveButton.Colliders[j];
+ if (collider2D && collider2D.OverlapPoint(touch))
+ {
+ this.currentDown = passiveButton;
+ if (passiveButton.OnDown)
+ {
+ passiveButton.DoClick();
+ }
+ return;
+ }
+ }
+ }
+ }
+ this.HandleFocus(touch);
+ }
+
+ private void HandleFocus(Vector2 pt)
+ {
+ bool flag = false;
+ for (int i = 0; i < this.FocusHolders.Count; i++)
+ {
+ IFocusHolder focusHolder = this.FocusHolders[i];
+ if (!(focusHolder as MonoBehaviour))
+ {
+ this.FocusHolders.RemoveAt(i);
+ i--;
+ }
+ else if (focusHolder.CheckCollision(pt))
+ {
+ flag = true;
+ focusHolder.GiveFocus();
+ for (int j = 0; j < this.FocusHolders.Count; j++)
+ {
+ if (j != i)
+ {
+ this.FocusHolders[j].LoseFocus();
+ }
+ }
+ break;
+ }
+ }
+ if (!flag)
+ {
+ for (int k = 0; k < this.FocusHolders.Count; k++)
+ {
+ this.FocusHolders[k].LoseFocus();
+ }
+ }
+ }
+
+ private void HandleMouseOut(PassiveButton button)
+ {
+ if (this.currentOver == button)
+ {
+ button.OnMouseOut.Invoke();
+ this.currentOver = null;
+ }
+ }
+
+ private void CheckForUp()
+ {
+ if (!this.currentDown)
+ {
+ return;
+ }
+ PassiveButton passiveButton = this.currentDown;
+ this.currentDown = null;
+ if (!passiveButton.OnUp)
+ {
+ return;
+ }
+ Vector2 touch = this.GetTouch(false);
+ for (int i = 0; i < passiveButton.Colliders.Length; i++)
+ {
+ if (passiveButton.Colliders[i].OverlapPoint(touch))
+ {
+ if (passiveButton.OnUp)
+ {
+ passiveButton.DoClick();
+ }
+ return;
+ }
+ }
+ }
+
+ private Vector2 GetTouch(bool downOrUp)
+ {
+ if (downOrUp)
+ {
+ if (this.Controller.Touches[0].TouchStart)
+ {
+ return this.Controller.Touches[0].Position;
+ }
+ return this.Controller.Touches[1].Position;
+ }
+ else
+ {
+ if (this.Controller.Touches[0].TouchEnd)
+ {
+ return this.Controller.Touches[0].Position;
+ }
+ return this.Controller.Touches[1].Position;
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/PetBehaviour.cs b/Client/Assembly-CSharp/PetBehaviour.cs
new file mode 100644
index 0000000..bf94b13
--- /dev/null
+++ b/Client/Assembly-CSharp/PetBehaviour.cs
@@ -0,0 +1,140 @@
+using System;
+using PowerTools;
+using UnityEngine;
+
+public class PetBehaviour : MonoBehaviour, IBuyable
+{
+ public string ProdId
+ {
+ get
+ {
+ return this.ProductId;
+ }
+ }
+
+ public bool Visible
+ {
+ set
+ {
+ this.rend.enabled = value;
+ this.shadowRend.enabled = value;
+ }
+ }
+
+ private const float SnapDistance = 2f;
+
+ public bool Free;
+
+ public string ProductId;
+
+ public string StoreName;
+
+ public uint SteamId;
+
+ public int ItchId;
+
+ public string ItchUrl;
+
+ public PlayerControl Source;
+
+ public const float MinDistance = 0.2f;
+
+ public const float damping = 0.7f;
+
+ public const float Easing = 0.2f;
+
+ public const float Speed = 5f;
+
+ public float YOffset = -0.25f;
+
+ public SpriteAnim animator;
+
+ public SpriteRenderer rend;
+
+ public SpriteRenderer shadowRend;
+
+ public Rigidbody2D body;
+
+ public Collider2D Collider;
+
+ public AnimationClip idleClip;
+
+ public AnimationClip sadClip;
+
+ public AnimationClip scaredClip;
+
+ public AnimationClip walkClip;
+
+ private Vector2 GetTruePosition()
+ {
+ return base.transform.position + this.Collider.offset * 0.7f;
+ }
+
+ public void FixedUpdate()
+ {
+ if (!this.Source)
+ {
+ this.body.velocity = Vector2.zero;
+ return;
+ }
+ this.Visible = this.Source.Visible;
+ Vector2 truePosition = this.Source.GetTruePosition();
+ Vector2 truePosition2 = this.GetTruePosition();
+ Vector2 vector = this.body.velocity;
+ Vector2 a = truePosition - truePosition2;
+ float num = 0f;
+ if (this.Source.CanMove)
+ {
+ num = 0.2f;
+ }
+ if (a.sqrMagnitude > num)
+ {
+ if (a.sqrMagnitude > 2f)
+ {
+ base.transform.position = truePosition;
+ return;
+ }
+ a *= 5f * PlayerControl.GameOptions.PlayerSpeedMod;
+ vector = vector * 0.8f + a * 0.2f;
+ }
+ else
+ {
+ vector *= 0.7f;
+ }
+ AnimationClip currentAnimation = this.animator.GetCurrentAnimation();
+ if (vector.sqrMagnitude > 0.01f)
+ {
+ if (currentAnimation != this.walkClip)
+ {
+ this.animator.Play(this.walkClip, 1f);
+ }
+ if (vector.x < -0.01f)
+ {
+ this.rend.flipX = true;
+ }
+ else if (vector.x > 0.01f)
+ {
+ this.rend.flipX = false;
+ }
+ }
+ else if (currentAnimation == this.walkClip)
+ {
+ this.animator.Play(this.idleClip, 1f);
+ }
+ this.body.velocity = vector;
+ }
+
+ private void LateUpdate()
+ {
+ Vector3 localPosition = base.transform.localPosition;
+ localPosition.z = (localPosition.y + this.YOffset) / 1000f + 0.0002f;
+ base.transform.localPosition = localPosition;
+ }
+
+ public void SetMourning()
+ {
+ this.Source = null;
+ this.body.velocity = Vector2.zero;
+ this.animator.Play(this.sadClip, 1f);
+ }
+}
diff --git a/Client/Assembly-CSharp/PetsTab.cs b/Client/Assembly-CSharp/PetsTab.cs
new file mode 100644
index 0000000..2c362c3
--- /dev/null
+++ b/Client/Assembly-CSharp/PetsTab.cs
@@ -0,0 +1,78 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+public class PetsTab : MonoBehaviour
+{
+ public ColorChip ColorTabPrefab;
+
+ public SpriteRenderer DemoImage;
+
+ public SpriteRenderer HatImage;
+
+ public SpriteRenderer SkinImage;
+
+ public SpriteRenderer PetImage;
+
+ public FloatRange XRange = new FloatRange(1.5f, 3f);
+
+ public float YStart = 0.8f;
+
+ public float YOffset = 0.8f;
+
+ public int NumPerRow = 4;
+
+ public Scroller scroller;
+
+ private List<ColorChip> ColorChips = new List<ColorChip>();
+
+ public void OnEnable()
+ {
+ PlayerControl.SetPlayerMaterialColors((int)PlayerControl.LocalPlayer.Data.ColorId, this.DemoImage);
+ PlayerControl.SetHatImage(SaveManager.LastHat, this.HatImage);
+ PlayerControl.SetSkinImage(SaveManager.LastSkin, this.SkinImage);
+ PlayerControl.SetPetImage(SaveManager.LastPet, (int)PlayerControl.LocalPlayer.Data.ColorId, this.PetImage);
+ PetBehaviour[] unlockedPets = DestroyableSingleton<HatManager>.Instance.GetUnlockedPets();
+ for (int i = 0; i < unlockedPets.Length; i++)
+ {
+ PetBehaviour pet = unlockedPets[i];
+ float x = this.XRange.Lerp((float)(i % this.NumPerRow) / ((float)this.NumPerRow - 1f));
+ float y = this.YStart - (float)(i / this.NumPerRow) * this.YOffset;
+ ColorChip chip = UnityEngine.Object.Instantiate<ColorChip>(this.ColorTabPrefab, this.scroller.Inner);
+ chip.transform.localPosition = new Vector3(x, y, -1f);
+ chip.InUseForeground.SetActive(DestroyableSingleton<HatManager>.Instance.GetIdFromPet(pet) == SaveManager.LastPet);
+ chip.Button.OnClick.AddListener(delegate()
+ {
+ this.SelectPet(chip, pet);
+ });
+ PlayerControl.SetPetImage(pet, (int)PlayerControl.LocalPlayer.Data.ColorId, chip.Inner);
+ this.ColorChips.Add(chip);
+ }
+ this.scroller.YBounds.max = -(this.YStart - (float)(unlockedPets.Length / this.NumPerRow) * this.YOffset) - 3f;
+ }
+
+ public void OnDisable()
+ {
+ for (int i = 0; i < this.ColorChips.Count; i++)
+ {
+ UnityEngine.Object.Destroy(this.ColorChips[i].gameObject);
+ }
+ this.ColorChips.Clear();
+ }
+
+ private void SelectPet(ColorChip sender, PetBehaviour pet)
+ {
+ uint idFromPet = DestroyableSingleton<HatManager>.Instance.GetIdFromPet(pet);
+ SaveManager.LastPet = idFromPet;
+ PlayerControl.SetPetImage(pet, (int)PlayerControl.LocalPlayer.Data.ColorId, this.PetImage);
+ if (PlayerControl.LocalPlayer)
+ {
+ PlayerControl.LocalPlayer.RpcSetPet(idFromPet);
+ }
+ for (int i = 0; i < this.ColorChips.Count; i++)
+ {
+ ColorChip colorChip = this.ColorChips[i];
+ colorChip.InUseForeground.SetActive(colorChip == sender);
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/PhysicsHelpers.cs b/Client/Assembly-CSharp/PhysicsHelpers.cs
new file mode 100644
index 0000000..706073f
--- /dev/null
+++ b/Client/Assembly-CSharp/PhysicsHelpers.cs
@@ -0,0 +1,38 @@
+using System;
+using UnityEngine;
+
+public static class PhysicsHelpers
+{
+ private static RaycastHit2D[] castHits = new RaycastHit2D[20];
+
+ private static Vector2 temp = default(Vector2);
+
+ private static ContactFilter2D filter = new ContactFilter2D
+ {
+ useLayerMask = true
+ };
+
+ public static bool AnythingBetween(Vector2 source, Vector2 target, int layerMask, bool useTriggers)
+ {
+ PhysicsHelpers.filter.layerMask = layerMask;
+ PhysicsHelpers.filter.useTriggers = useTriggers;
+ PhysicsHelpers.temp.x = target.x - source.x;
+ PhysicsHelpers.temp.y = target.y - source.y;
+ return Physics2D.Raycast(source, PhysicsHelpers.temp, PhysicsHelpers.filter, PhysicsHelpers.castHits, PhysicsHelpers.temp.magnitude) > 0;
+ }
+
+ public static bool AnyNonTriggersBetween(Vector2 source, Vector2 dirNorm, float mag, int layerMask)
+ {
+ int num = Physics2D.RaycastNonAlloc(source, dirNorm, PhysicsHelpers.castHits, mag, layerMask);
+ bool result = false;
+ for (int i = 0; i < num; i++)
+ {
+ if (!PhysicsHelpers.castHits[i].collider.isTrigger)
+ {
+ result = true;
+ break;
+ }
+ }
+ return result;
+ }
+}
diff --git a/Client/Assembly-CSharp/PingTracker.cs b/Client/Assembly-CSharp/PingTracker.cs
new file mode 100644
index 0000000..727d71c
--- /dev/null
+++ b/Client/Assembly-CSharp/PingTracker.cs
@@ -0,0 +1,19 @@
+using System;
+using UnityEngine;
+
+public class PingTracker : MonoBehaviour
+{
+ public TextRenderer text;
+
+ private void Update()
+ {
+ if (AmongUsClient.Instance)
+ {
+ if (AmongUsClient.Instance.GameMode == GameModes.FreePlay)
+ {
+ base.gameObject.SetActive(false);
+ }
+ this.text.Text = string.Format("Ping: {0} ms", AmongUsClient.Instance.Ping);
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/PlayerAnimator.cs b/Client/Assembly-CSharp/PlayerAnimator.cs
new file mode 100644
index 0000000..dd48ace
--- /dev/null
+++ b/Client/Assembly-CSharp/PlayerAnimator.cs
@@ -0,0 +1,90 @@
+using System;
+using System.Collections;
+using PowerTools;
+using UnityEngine;
+
+public class PlayerAnimator : MonoBehaviour
+{
+ public float Speed = 2.5f;
+
+ public VirtualJoystick joystick;
+
+ public SpriteRenderer UseButton;
+
+ public FingerBehaviour finger;
+
+ public AnimationClip RunAnim;
+
+ public AnimationClip IdleAnim;
+
+ private Vector2 velocity;
+
+ [HideInInspector]
+ private SpriteAnim Animator;
+
+ [HideInInspector]
+ private SpriteRenderer rend;
+
+ public int NearbyConsoles;
+
+ private void Start()
+ {
+ this.Animator = base.GetComponent<SpriteAnim>();
+ this.rend = base.GetComponent<SpriteRenderer>();
+ this.rend.material.SetColor("_BackColor", Palette.ShadowColors[0]);
+ this.rend.material.SetColor("_BodyColor", Palette.PlayerColors[0]);
+ this.rend.material.SetColor("_VisorColor", Palette.VisorColor);
+ }
+
+ public void FixedUpdate()
+ {
+ base.transform.Translate(this.velocity * Time.fixedDeltaTime);
+ this.UseButton.enabled = (this.NearbyConsoles > 0);
+ }
+
+ public void LateUpdate()
+ {
+ if (this.velocity.sqrMagnitude >= 0.1f)
+ {
+ if (this.Animator.GetCurrentAnimation() != this.RunAnim)
+ {
+ this.Animator.Play(this.RunAnim, 1f);
+ }
+ this.rend.flipX = (this.velocity.x < 0f);
+ return;
+ }
+ if (this.Animator.GetCurrentAnimation() == this.RunAnim)
+ {
+ this.Animator.Play(this.IdleAnim, 1f);
+ }
+ }
+
+ public IEnumerator WalkPlayerTo(Vector2 worldPos, bool relax, float tolerance = 0.01f)
+ {
+ worldPos.y += 0.3636f;
+ if (!(this.joystick is DemoKeyboardStick))
+ {
+ this.finger.ClickOn();
+ }
+ for (;;)
+ {
+ Vector2 vector2;
+ Vector2 vector = vector2 = worldPos - base.transform.position;
+ if (vector2.sqrMagnitude <= tolerance)
+ {
+ break;
+ }
+ float d = Mathf.Clamp(vector.magnitude * 2f, 0.01f, 1f);
+ this.velocity = vector.normalized * this.Speed * d;
+ this.joystick.UpdateJoystick(this.finger, this.velocity, true);
+ yield return null;
+ }
+ if (relax)
+ {
+ this.finger.ClickOff();
+ this.velocity = Vector2.zero;
+ this.joystick.UpdateJoystick(this.finger, this.velocity, false);
+ }
+ yield break;
+ }
+}
diff --git a/Client/Assembly-CSharp/PlayerControl.cs b/Client/Assembly-CSharp/PlayerControl.cs
new file mode 100644
index 0000000..f0e5fbc
--- /dev/null
+++ b/Client/Assembly-CSharp/PlayerControl.cs
@@ -0,0 +1,1470 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using Assets.CoreScripts;
+using Hazel;
+using InnerNet;
+using PowerTools;
+using UnityEngine;
+
+public class PlayerControl : InnerNetObject
+{
+ public bool CanMove
+ {
+ get
+ {
+ return this.moveable && !Minigame.Instance && (!DestroyableSingleton<HudManager>.InstanceExists || (!DestroyableSingleton<HudManager>.Instance.Chat.IsOpen && !DestroyableSingleton<HudManager>.Instance.KillOverlay.IsOpen && !DestroyableSingleton<HudManager>.Instance.GameMenu.IsOpen)) && (!MapBehaviour.Instance || !MapBehaviour.Instance.IsOpenStopped) && !MeetingHud.Instance && !CustomPlayerMenu.Instance && !ExileController.Instance && !IntroCutscene.Instance;
+ }
+ }
+
+ public GameData.PlayerInfo Data
+ {
+ get
+ {
+ if (this._cachedData == null)
+ {
+ if (!GameData.Instance)
+ {
+ return null;
+ }
+ this._cachedData = GameData.Instance.GetPlayerById(this.PlayerId);
+ }
+ return this._cachedData;
+ }
+ }
+
+ public bool Visible
+ {
+ get
+ {
+ return this.myRend.enabled;
+ }
+ set
+ {
+ this.myRend.enabled = value;
+ this.MyPhysics.Skin.Visible = value;
+ this.HatRenderer.enabled = value;
+ this.nameText.gameObject.SetActive(value);
+ }
+ }
+
+ public byte PlayerId = byte.MaxValue;
+
+ public float MaxReportDistance = 5f;
+
+ public bool moveable = true;
+
+ public bool inVent;
+
+ public static PlayerControl LocalPlayer;
+
+ private GameData.PlayerInfo _cachedData;
+
+ public AudioSource FootSteps;
+
+ public AudioClip KillSfx;
+
+ public KillAnimation[] KillAnimations;
+
+ [SerializeField]
+ private float killTimer;
+
+ public int RemainingEmergencies;
+
+ public TextRenderer nameText;
+
+ public LightSource LightPrefab;
+
+ private LightSource myLight;
+
+ [HideInInspector]
+ public Collider2D Collider;
+
+ [HideInInspector]
+ public PlayerPhysics MyPhysics;
+
+ [HideInInspector]
+ public CustomNetworkTransform NetTransform;
+
+ public PetBehaviour CurrentPet;
+
+ public SpriteRenderer HatRenderer;
+
+ private SpriteRenderer myRend;
+
+ private Collider2D[] hitBuffer = new Collider2D[20];
+
+ public static GameOptionsData GameOptions = new GameOptionsData();
+
+ public List<PlayerTask> myTasks = new List<PlayerTask>();
+
+ [NonSerialized]
+ public uint TaskIdCount;
+
+ public SpriteAnim[] ScannerAnims;
+
+ public SpriteRenderer[] ScannersImages;
+
+ public AudioClip[] VentMoveSounds;
+
+ public AudioClip VentEnterSound;
+
+ private IUsable closest;
+
+ private bool isNew = true;
+
+ public float crewStreak;
+
+ public static List<PlayerControl> AllPlayerControls = new List<PlayerControl>();
+
+ private Dictionary<Collider2D, IUsable> cache = new Dictionary<Collider2D, IUsable>(PlayerControl.ColliderComparer.Instance);
+
+ private List<IUsable> itemsInRange = new List<IUsable>();
+
+ private List<IUsable> newItemsInRange = new List<IUsable>();
+
+ private byte scannerCount;
+
+ private int LastStartCounter;
+
+ public class ColliderComparer : IEqualityComparer<Collider2D>
+ {
+ public static readonly PlayerControl.ColliderComparer Instance = new PlayerControl.ColliderComparer();
+
+ public bool Equals(Collider2D x, Collider2D y)
+ {
+ return x == y;
+ }
+
+ public int GetHashCode(Collider2D obj)
+ {
+ return obj.GetInstanceID();
+ }
+ }
+
+ public class UsableComparer : IEqualityComparer<IUsable>
+ {
+ public static readonly PlayerControl.UsableComparer Instance = new PlayerControl.UsableComparer();
+
+ public bool Equals(IUsable x, IUsable y)
+ {
+ return x == y;
+ }
+
+ public int GetHashCode(IUsable obj)
+ {
+ return obj.GetHashCode();
+ }
+ }
+
+ public enum RpcCalls : byte
+ {
+ PlayAnimation,
+ CompleteTask,
+ SyncSettings,
+ SetInfected,
+ Exiled,
+ CheckName,
+ SetName,
+ CheckColor,
+ SetColor,
+ SetHat,
+ SetSkin,
+ ReportDeadBody,
+ MurderPlayer,
+ SendChat,
+ TimesImpostor,
+ StartMeeting,
+ SetScanner,
+ SendChatNote,
+ SetPet,
+ SetStartCounter
+ }
+
+ public void SetKillTimer(float time)
+ {
+ this.killTimer = time;
+ if (PlayerControl.GameOptions.KillCooldown > 0f)
+ {
+ DestroyableSingleton<HudManager>.Instance.KillButton.SetCoolDown(this.killTimer, PlayerControl.GameOptions.KillCooldown);
+ return;
+ }
+ DestroyableSingleton<HudManager>.Instance.KillButton.SetCoolDown(0f, PlayerControl.GameOptions.KillCooldown);
+ }
+
+ private void Awake()
+ {
+ this.myRend = base.GetComponent<SpriteRenderer>();
+ this.MyPhysics = base.GetComponent<PlayerPhysics>();
+ this.NetTransform = base.GetComponent<CustomNetworkTransform>();
+ this.Collider = base.GetComponent<Collider2D>();
+ PlayerControl.AllPlayerControls.Add(this);
+ }
+
+ private void Start()
+ {
+ this.RemainingEmergencies = PlayerControl.GameOptions.NumEmergencyMeetings;
+ if (base.AmOwner)
+ {
+ this.myLight = UnityEngine.Object.Instantiate<LightSource>(this.LightPrefab);
+ this.myLight.transform.SetParent(base.transform);
+ this.myLight.transform.localPosition = this.Collider.offset;
+ PlayerControl.LocalPlayer = this;
+ Camera.main.GetComponent<FollowerCamera>().SetTarget(this);
+ this.SetName(SaveManager.PlayerName);
+ this.SetColor(SaveManager.BodyColor);
+ this.CmdCheckName(SaveManager.PlayerName);
+ this.CmdCheckColor(SaveManager.BodyColor);
+ this.RpcSetPet(SaveManager.LastPet);
+ this.RpcSetHat(SaveManager.LastHat);
+ this.RpcSetSkin(SaveManager.LastSkin);
+ this.RpcSetTimesImpostor(StatsManager.Instance.CrewmateStreak);
+ }
+ else
+ {
+ base.StartCoroutine(this.ClientInitialize());
+ }
+ if (this.isNew)
+ {
+ this.isNew = false;
+ base.StartCoroutine(this.MyPhysics.CoSpawnPlayer(LobbyBehaviour.Instance));
+ }
+ }
+
+ private IEnumerator ClientInitialize()
+ {
+ this.Visible = false;
+ while (!GameData.Instance)
+ {
+ yield return null;
+ }
+ while (this.Data == null)
+ {
+ yield return null;
+ }
+ while (string.IsNullOrEmpty(this.Data.PlayerName))
+ {
+ yield return null;
+ }
+ this.SetName(this.Data.PlayerName);
+ this.SetColor(this.Data.ColorId);
+ this.SetHat(this.Data.HatId);
+ this.SetSkin(this.Data.SkinId);
+ this.SetPet(this.Data.PetId);
+ this.Visible = true;
+ yield break;
+ }
+
+ public override void OnDestroy()
+ {
+ if (this.CurrentPet)
+ {
+ UnityEngine.Object.Destroy(this.CurrentPet.gameObject);
+ }
+ PlayerControl.AllPlayerControls.Remove(this);
+ base.OnDestroy();
+ }
+
+ private void FixedUpdate()
+ {
+ if (!GameData.Instance)
+ {
+ return;
+ }
+ GameData.PlayerInfo data = this.Data;
+ if (data == null)
+ {
+ return;
+ }
+ if (data.IsDead && PlayerControl.LocalPlayer)
+ {
+ this.Visible = PlayerControl.LocalPlayer.Data.IsDead;
+ }
+ if (base.AmOwner)
+ {
+ if (ShipStatus.Instance)
+ {
+ this.myLight.LightRadius = ShipStatus.Instance.CalculateLightRadius(data);
+ }
+ if (data.IsImpostor && this.CanMove && !data.IsDead)
+ {
+ this.SetKillTimer(Mathf.Max(0f, this.killTimer - Time.fixedDeltaTime));
+ PlayerControl target = this.FindClosestTarget();
+ DestroyableSingleton<HudManager>.Instance.KillButton.SetTarget(target);
+ }
+ else
+ {
+ DestroyableSingleton<HudManager>.Instance.KillButton.SetTarget(null);
+ }
+ if (this.CanMove || this.inVent)
+ {
+ this.newItemsInRange.Clear();
+ bool flag = (PlayerControl.GameOptions.GhostsDoTasks || !data.IsDead) && (!AmongUsClient.Instance || !AmongUsClient.Instance.IsGameOver) && this.CanMove;
+ Vector2 truePosition = this.GetTruePosition();
+ int num = Physics2D.OverlapCircleNonAlloc(truePosition, this.MaxReportDistance, this.hitBuffer, Constants.Usables);
+ IUsable usable = null;
+ float num2 = float.MaxValue;
+ bool flag2 = false;
+ for (int i = 0; i < num; i++)
+ {
+ Collider2D collider2D = this.hitBuffer[i];
+ IUsable usable2;
+ if (!this.cache.TryGetValue(collider2D, out usable2))
+ {
+ usable2 = (this.cache[collider2D] = collider2D.GetComponent<IUsable>());
+ }
+ if (usable2 != null && (flag || this.inVent))
+ {
+ bool flag3;
+ bool flag4;
+ float num3 = usable2.CanUse(data, out flag3, out flag4);
+ if (flag3 || flag4)
+ {
+ this.newItemsInRange.Add(usable2);
+ }
+ if (flag3 && num3 < num2)
+ {
+ num2 = num3;
+ usable = usable2;
+ }
+ }
+ if (flag && !data.IsDead && !flag2 && collider2D.tag == "DeadBody")
+ {
+ DeadBody component = collider2D.GetComponent<DeadBody>();
+ if (!PhysicsHelpers.AnythingBetween(truePosition, component.TruePosition, Constants.ShipAndObjectsMask, false))
+ {
+ flag2 = true;
+ }
+ }
+ }
+ for (int l = this.itemsInRange.Count - 1; l > -1; l--)
+ {
+ IUsable item = this.itemsInRange[l];
+ int num4 = this.newItemsInRange.FindIndex((IUsable j) => j == item);
+ if (num4 == -1)
+ {
+ item.SetOutline(false, false);
+ this.itemsInRange.RemoveAt(l);
+ }
+ else
+ {
+ this.newItemsInRange.RemoveAt(num4);
+ item.SetOutline(true, usable == item);
+ }
+ }
+ for (int k = 0; k < this.newItemsInRange.Count; k++)
+ {
+ IUsable usable3 = this.newItemsInRange[k];
+ usable3.SetOutline(true, usable == usable3);
+ this.itemsInRange.Add(usable3);
+ }
+ this.closest = usable;
+ DestroyableSingleton<HudManager>.Instance.UseButton.SetTarget(usable);
+ DestroyableSingleton<HudManager>.Instance.ReportButton.SetActive(flag2);
+ return;
+ }
+ this.closest = null;
+ DestroyableSingleton<HudManager>.Instance.UseButton.SetTarget(null);
+ DestroyableSingleton<HudManager>.Instance.ReportButton.SetActive(false);
+ }
+ }
+
+ public void UseClosest()
+ {
+ if (this.closest != null)
+ {
+ this.closest.Use();
+ }
+ this.closest = null;
+ DestroyableSingleton<HudManager>.Instance.UseButton.SetTarget(null);
+ }
+
+ public void ReportClosest()
+ {
+ if (AmongUsClient.Instance.IsGameOver)
+ {
+ return;
+ }
+ if (PlayerControl.LocalPlayer.Data.IsDead)
+ {
+ return;
+ }
+ foreach (Collider2D collider2D in Physics2D.OverlapCircleAll(base.transform.position, this.MaxReportDistance, Constants.NotShipMask))
+ {
+ if (!(collider2D.tag != "DeadBody"))
+ {
+ DeadBody component = collider2D.GetComponent<DeadBody>();
+ if (component && !component.Reported)
+ {
+ component.OnClick();
+ if (component.Reported)
+ {
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ public void PlayStepSound()
+ {
+ if (!Constants.ShouldPlaySfx())
+ {
+ return;
+ }
+ if (DestroyableSingleton<HudManager>.InstanceExists && PlayerControl.LocalPlayer == this)
+ {
+ ShipRoom lastRoom = DestroyableSingleton<HudManager>.Instance.roomTracker.LastRoom;
+ if (lastRoom && lastRoom.FootStepSounds)
+ {
+ AudioClip clip = lastRoom.FootStepSounds.Random();
+ this.FootSteps.clip = clip;
+ this.FootSteps.Play();
+ }
+ }
+ }
+
+ private void SetScanner(bool on, byte cnt)
+ {
+ if (cnt < this.scannerCount)
+ {
+ return;
+ }
+ this.scannerCount = cnt;
+ for (int i = 0; i < this.ScannerAnims.Length; i++)
+ {
+ SpriteAnim spriteAnim = this.ScannerAnims[i];
+ if (on && !this.Data.IsDead)
+ {
+ spriteAnim.gameObject.SetActive(true);
+ spriteAnim.Play(null, 1f);
+ this.ScannersImages[i].flipX = !this.myRend.flipX;
+ }
+ else
+ {
+ if (spriteAnim.isActiveAndEnabled)
+ {
+ spriteAnim.Stop();
+ }
+ spriteAnim.gameObject.SetActive(false);
+ }
+ }
+ }
+
+ public Vector2 GetTruePosition()
+ {
+ return base.transform.position + this.Collider.offset;
+ }
+
+ private PlayerControl FindClosestTarget()
+ {
+ PlayerControl result = null;
+ float num = GameOptionsData.KillDistances[Mathf.Clamp(PlayerControl.GameOptions.KillDistance, 0, 2)];
+ if (!ShipStatus.Instance)
+ {
+ return null;
+ }
+ Vector2 truePosition = this.GetTruePosition();
+ List<GameData.PlayerInfo> allPlayers = GameData.Instance.AllPlayers;
+ for (int i = 0; i < allPlayers.Count; i++)
+ {
+ GameData.PlayerInfo playerInfo = allPlayers[i];
+ if (!playerInfo.Disconnected && playerInfo.PlayerId != this.PlayerId && !playerInfo.IsDead && !playerInfo.IsImpostor)
+ {
+ PlayerControl @object = playerInfo.Object;
+ if (@object)
+ {
+ Vector2 vector = @object.GetTruePosition() - truePosition;
+ float magnitude = vector.magnitude;
+ if (magnitude <= num && !PhysicsHelpers.AnyNonTriggersBetween(truePosition, vector.normalized, magnitude, Constants.ShipAndObjectsMask))
+ {
+ result = @object;
+ num = magnitude;
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ public void SetTasks(byte[] tasks)
+ {
+ base.StartCoroutine(this.CoSetTasks(tasks));
+ }
+
+ private IEnumerator CoSetTasks(byte[] tasks)
+ {
+ while (!ShipStatus.Instance)
+ {
+ yield return null;
+ }
+ if (base.AmOwner)
+ {
+ DestroyableSingleton<HudManager>.Instance.TaskStuff.SetActive(true);
+ StatsManager instance = StatsManager.Instance;
+ uint num = instance.GamesStarted;
+ instance.GamesStarted = num + 1U;
+ if (this.Data.IsImpostor)
+ {
+ StatsManager instance2 = StatsManager.Instance;
+ num = instance2.TimesImpostor;
+ instance2.TimesImpostor = num + 1U;
+ StatsManager.Instance.CrewmateStreak = 0U;
+ }
+ else
+ {
+ StatsManager instance3 = StatsManager.Instance;
+ num = instance3.TimesCrewmate;
+ instance3.TimesCrewmate = num + 1U;
+ StatsManager instance4 = StatsManager.Instance;
+ num = instance4.CrewmateStreak;
+ instance4.CrewmateStreak = num + 1U;
+ DestroyableSingleton<HudManager>.Instance.KillButton.gameObject.SetActive(false);
+ }
+ DestroyableSingleton<Telemetry>.Instance.StartGame(SaveManager.SendName, AmongUsClient.Instance.AmHost, GameData.Instance.PlayerCount, PlayerControl.GameOptions.NumImpostors, AmongUsClient.Instance.GameMode, StatsManager.Instance.TimesImpostor, StatsManager.Instance.GamesStarted, StatsManager.Instance.CrewmateStreak);
+ }
+ foreach (byte idx in tasks)
+ {
+ NormalPlayerTask normalPlayerTask = UnityEngine.Object.Instantiate<NormalPlayerTask>(ShipStatus.Instance.GetTaskById(idx), base.transform);
+ PlayerTask playerTask = normalPlayerTask;
+ uint num = this.TaskIdCount;
+ this.TaskIdCount = num + 1U;
+ playerTask.Id = num;
+ normalPlayerTask.Owner = this;
+ normalPlayerTask.Initialize();
+ this.myTasks.Add(normalPlayerTask);
+ }
+ yield break;
+ }
+
+ public void AddSystemTask(SystemTypes system)
+ {
+ PlayerTask original;
+ if (system <= SystemTypes.Electrical)
+ {
+ if (system != SystemTypes.Reactor)
+ {
+ if (system != SystemTypes.Electrical)
+ {
+ return;
+ }
+ original = ShipStatus.Instance.SpecialTasks[1];
+ }
+ else
+ {
+ original = ShipStatus.Instance.SpecialTasks[0];
+ }
+ }
+ else if (system != SystemTypes.LifeSupp)
+ {
+ if (system != SystemTypes.Comms)
+ {
+ return;
+ }
+ original = ShipStatus.Instance.SpecialTasks[2];
+ }
+ else
+ {
+ original = ShipStatus.Instance.SpecialTasks[3];
+ }
+ PlayerControl localPlayer = PlayerControl.LocalPlayer;
+ PlayerTask playerTask = UnityEngine.Object.Instantiate<PlayerTask>(original, localPlayer.transform);
+ PlayerTask playerTask2 = playerTask;
+ PlayerControl playerControl = localPlayer;
+ uint taskIdCount = playerControl.TaskIdCount;
+ playerControl.TaskIdCount = taskIdCount + 1U;
+ playerTask2.Id = (uint)((byte)taskIdCount);
+ playerTask.Owner = localPlayer;
+ playerTask.Initialize();
+ localPlayer.myTasks.Add(playerTask);
+ }
+
+ public void RemoveTask(PlayerTask task)
+ {
+ task.OnRemove();
+ this.myTasks.Remove(task);
+ GameData.Instance.TutOnlyRemoveTask(this.PlayerId, task.Id);
+ DestroyableSingleton<HudManager>.Instance.UseButton.SetTarget(null);
+ UnityEngine.Object.Destroy(task.gameObject);
+ }
+
+ private void ClearTasks()
+ {
+ for (int i = 0; i < this.myTasks.Count; i++)
+ {
+ PlayerTask playerTask = this.myTasks[i];
+ playerTask.OnRemove();
+ UnityEngine.Object.Destroy(playerTask.gameObject);
+ }
+ this.myTasks.Clear();
+ }
+
+ public void RemoveInfected()
+ {
+ GameData.PlayerInfo playerById = GameData.Instance.GetPlayerById(this.PlayerId);
+ if (playerById.IsImpostor)
+ {
+ playerById.Object.nameText.Color = Color.white;
+ playerById.IsImpostor = false;
+ this.myTasks.RemoveAt(0);
+ DestroyableSingleton<HudManager>.Instance.KillButton.gameObject.SetActive(false);
+ }
+ }
+
+ public void Die(DeathReason reason)
+ {
+ if (!DestroyableSingleton<TutorialManager>.InstanceExists)
+ {
+ StatsManager.Instance.LastGameStarted = DateTime.MinValue;
+ StatsManager instance = StatsManager.Instance;
+ float banPoints = instance.BanPoints;
+ instance.BanPoints = banPoints - 1f;
+ }
+ TempData.LastDeathReason = reason;
+ if (this.CurrentPet)
+ {
+ this.CurrentPet.SetMourning();
+ }
+ this.Data.IsDead = true;
+ base.gameObject.layer = LayerMask.NameToLayer("Ghost");
+ this.nameText.GetComponent<MeshRenderer>().material.SetInt("_Mask", 0);
+ if (base.AmOwner)
+ {
+ DestroyableSingleton<HudManager>.Instance.Chat.SetVisible(true);
+ }
+ }
+
+ public void Revive()
+ {
+ this.Data.IsDead = false;
+ base.gameObject.layer = LayerMask.NameToLayer("Players");
+ this.MyPhysics.ResetAnim(true);
+ if (this.CurrentPet)
+ {
+ this.CurrentPet.Source = this;
+ }
+ this.nameText.GetComponent<MeshRenderer>().material.SetInt("_Mask", 4);
+ if (base.AmOwner)
+ {
+ DestroyableSingleton<HudManager>.Instance.KillButton.gameObject.SetActive(this.Data.IsImpostor);
+ DestroyableSingleton<HudManager>.Instance.Chat.ForceClosed();
+ DestroyableSingleton<HudManager>.Instance.Chat.SetVisible(false);
+ }
+ }
+
+ public void PlayAnimation(byte animType)
+ {
+ if (animType == 1)
+ {
+ ShipStatus.Instance.StartShields();
+ return;
+ }
+ if (animType == 6)
+ {
+ ShipStatus.Instance.FireWeapon();
+ return;
+ }
+ if (animType - 9 > 1)
+ {
+ return;
+ }
+ ShipStatus.Instance.OpenHatch();
+ }
+
+ public void CompleteTask(uint idx)
+ {
+ PlayerTask playerTask = this.myTasks.Find((PlayerTask p) => p.Id == idx);
+ if (playerTask)
+ {
+ GameData.Instance.CompleteTask(this, idx);
+ playerTask.Complete();
+ DestroyableSingleton<Telemetry>.Instance.WriteCompleteTask(this.PlayerId, playerTask.TaskType);
+ return;
+ }
+ Debug.LogWarning(this.PlayerId + ": Server didn't have task: " + idx);
+ }
+
+ public void SetInfected(byte[] infected)
+ {
+ if (!GameData.Instance)
+ {
+ Debug.Log("No game data instance.");
+ }
+ StatsManager instance = StatsManager.Instance;
+ float banPoints = instance.BanPoints;
+ instance.BanPoints = banPoints + 1f;
+ StatsManager.Instance.LastGameStarted = DateTime.UtcNow;
+ for (int i = 0; i < infected.Length; i++)
+ {
+ GameData.PlayerInfo playerById = GameData.Instance.GetPlayerById(infected[i]);
+ if (playerById != null)
+ {
+ playerById.IsImpostor = true;
+ }
+ else
+ {
+ Debug.LogError("Couldn't set impostor: " + infected[i]);
+ }
+ }
+ DestroyableSingleton<HudManager>.Instance.MapButton.gameObject.SetActive(true);
+ DestroyableSingleton<HudManager>.Instance.ReportButton.gameObject.SetActive(true);
+ PlayerControl.LocalPlayer.RemainingEmergencies = PlayerControl.GameOptions.NumEmergencyMeetings;
+ GameData.PlayerInfo data = PlayerControl.LocalPlayer.Data;
+ if (data.IsImpostor)
+ {
+ ImportantTextTask importantTextTask = new GameObject("_Player").AddComponent<ImportantTextTask>();
+ importantTextTask.transform.SetParent(PlayerControl.LocalPlayer.transform, false);
+ importantTextTask.Text = DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.ImpostorTask, Array.Empty<object>()) + "\r\n[FFFFFFFF]" + DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.FakeTasks, Array.Empty<object>());
+ this.myTasks.Insert(0, importantTextTask);
+ DestroyableSingleton<HudManager>.Instance.KillButton.gameObject.SetActive(true);
+ PlayerControl.LocalPlayer.SetKillTimer(10f);
+ for (int j = 0; j < infected.Length; j++)
+ {
+ GameData.PlayerInfo playerById2 = GameData.Instance.GetPlayerById(infected[j]);
+ if (playerById2 != null)
+ {
+ playerById2.Object.nameText.Color = Palette.ImpostorRed;
+ }
+ }
+ }
+ if (!DestroyableSingleton<TutorialManager>.InstanceExists)
+ {
+ List<PlayerControl> yourTeam;
+ if (data.IsImpostor)
+ {
+ yourTeam = (from pcd in GameData.Instance.AllPlayers
+ where !pcd.Disconnected
+ where pcd.IsImpostor
+ select pcd.Object).OrderBy(delegate(PlayerControl pc)
+ {
+ if (!(pc == PlayerControl.LocalPlayer))
+ {
+ return 1;
+ }
+ return 0;
+ }).ToList<PlayerControl>();
+ }
+ else
+ {
+ yourTeam = (from pcd in GameData.Instance.AllPlayers
+ where !pcd.Disconnected
+ select pcd.Object).OrderBy(delegate(PlayerControl pc)
+ {
+ if (!(pc == PlayerControl.LocalPlayer))
+ {
+ return 1;
+ }
+ return 0;
+ }).ToList<PlayerControl>();
+ }
+ base.StopAllCoroutines();
+ DestroyableSingleton<HudManager>.Instance.StartCoroutine(DestroyableSingleton<HudManager>.Instance.CoShowIntro(yourTeam));
+ }
+ }
+
+ public void Exiled()
+ {
+ this.Die(DeathReason.Exile);
+ if (base.AmOwner)
+ {
+ StatsManager instance = StatsManager.Instance;
+ uint timesEjected = instance.TimesEjected;
+ instance.TimesEjected = timesEjected + 1U;
+ DestroyableSingleton<HudManager>.Instance.ShadowQuad.gameObject.SetActive(false);
+ ImportantTextTask importantTextTask = new GameObject("_Player").AddComponent<ImportantTextTask>();
+ importantTextTask.transform.SetParent(base.transform, false);
+ if (this.Data.IsImpostor)
+ {
+ this.ClearTasks();
+ importantTextTask.Text = DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.GhostImpostor, Array.Empty<object>());
+ }
+ else if (!PlayerControl.GameOptions.GhostsDoTasks)
+ {
+ this.ClearTasks();
+ importantTextTask.Text = DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.GhostIgnoreTasks, Array.Empty<object>());
+ }
+ else
+ {
+ importantTextTask.Text = DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.GhostDoTasks, Array.Empty<object>());
+ }
+ this.myTasks.Insert(0, importantTextTask);
+ }
+ }
+
+ public void CheckName(string name)
+ {
+ List<GameData.PlayerInfo> allPlayers = GameData.Instance.AllPlayers;
+ bool flag = allPlayers.Any((GameData.PlayerInfo i) => i.PlayerId != this.PlayerId && i.PlayerName.Equals(name, StringComparison.OrdinalIgnoreCase));
+ if (flag)
+ {
+ for (int k = 1; k < 100; k++)
+ {
+ string text = name + " " + k;
+ flag = false;
+ for (int j = 0; j < allPlayers.Count; j++)
+ {
+ if (allPlayers[j].PlayerId != this.PlayerId && allPlayers[j].PlayerName.Equals(text, StringComparison.OrdinalIgnoreCase))
+ {
+ flag = true;
+ break;
+ }
+ }
+ if (!flag)
+ {
+ name = text;
+ break;
+ }
+ }
+ }
+ this.RpcSetName(name);
+ GameData.Instance.UpdateName(this.PlayerId, name);
+ }
+
+ public void SetName(string name)
+ {
+ if (GameData.Instance)
+ {
+ GameData.Instance.UpdateName(this.PlayerId, name);
+ }
+ base.gameObject.name = name;
+ this.nameText.Text = name;
+ this.nameText.GetComponent<MeshRenderer>().material.SetInt("_Mask", 4);
+ }
+
+ public void CheckColor(byte bodyColor)
+ {
+ List<GameData.PlayerInfo> allPlayers = GameData.Instance.AllPlayers;
+ int num = 0;
+ while (num++ < 100 && allPlayers.Any((GameData.PlayerInfo p) => !p.Disconnected && p.PlayerId != this.PlayerId && p.ColorId == bodyColor))
+ {
+ bodyColor = (byte)((int)(bodyColor + 1) % Palette.PlayerColors.Length);
+ }
+ this.RpcSetColor(bodyColor);
+ }
+
+ public void SetHatAlpha(float a)
+ {
+ Color white = Color.white;
+ white.a = a;
+ this.HatRenderer.color = white;
+ }
+
+ public void SetColor(byte bodyColor)
+ {
+ if (GameData.Instance)
+ {
+ GameData.Instance.UpdateColor(this.PlayerId, bodyColor);
+ }
+ if (this.myRend == null)
+ {
+ base.GetComponent<SpriteRenderer>();
+ }
+ PlayerControl.SetPlayerMaterialColors((int)bodyColor, this.myRend);
+ if (this.CurrentPet)
+ {
+ PlayerControl.SetPlayerMaterialColors((int)bodyColor, this.CurrentPet.rend);
+ }
+ }
+
+ public void SetSkin(uint skinId)
+ {
+ if (GameData.Instance)
+ {
+ GameData.Instance.UpdateSkin(this.PlayerId, skinId);
+ }
+ this.MyPhysics.SetSkin(skinId);
+ }
+
+ public void SetHat(uint hatId)
+ {
+ if (GameData.Instance)
+ {
+ GameData.Instance.UpdateHat(this.PlayerId, hatId);
+ }
+ PlayerControl.SetHatImage(hatId, this.HatRenderer);
+ this.nameText.transform.localPosition = new Vector3(0f, (hatId == 0U) ? 0.7f : 1.05f, -0.5f);
+ }
+
+ public void SetPet(uint petId)
+ {
+ if (this.CurrentPet)
+ {
+ UnityEngine.Object.Destroy(this.CurrentPet.gameObject);
+ }
+ this.CurrentPet = UnityEngine.Object.Instantiate<PetBehaviour>(DestroyableSingleton<HatManager>.Instance.GetPetById(petId));
+ this.CurrentPet.transform.position = base.transform.position;
+ this.CurrentPet.Source = this;
+ GameData.PlayerInfo data = this.Data;
+ if (this.Data != null)
+ {
+ GameData.Instance.UpdatePet(this.PlayerId, petId);
+ this.Data.PetId = petId;
+ PlayerControl.SetPlayerMaterialColors((int)this.Data.ColorId, this.CurrentPet.rend);
+ }
+ }
+
+ public static void SetPetImage(uint petId, int colorId, SpriteRenderer target)
+ {
+ if (!DestroyableSingleton<HatManager>.InstanceExists)
+ {
+ return;
+ }
+ PlayerControl.SetPetImage(DestroyableSingleton<HatManager>.Instance.GetPetById(petId), colorId, target);
+ }
+
+ public static void SetPetImage(PetBehaviour pet, int colorId, SpriteRenderer target)
+ {
+ target.sprite = pet.rend.sprite;
+ if (target != pet.rend)
+ {
+ target.material = new Material(pet.rend.sharedMaterial);
+ PlayerControl.SetPlayerMaterialColors(colorId, target);
+ }
+ }
+
+ public static void SetSkinImage(uint skinId, SpriteRenderer target)
+ {
+ if (!DestroyableSingleton<HatManager>.InstanceExists)
+ {
+ return;
+ }
+ PlayerControl.SetSkinImage(DestroyableSingleton<HatManager>.Instance.GetSkinById(skinId), target);
+ }
+
+ public static void SetSkinImage(SkinData skin, SpriteRenderer target)
+ {
+ target.sprite = skin.IdleFrame;
+ }
+
+ public static void SetHatImage(uint hatId, SpriteRenderer target)
+ {
+ if (!DestroyableSingleton<HatManager>.InstanceExists)
+ {
+ return;
+ }
+ PlayerControl.SetHatImage(DestroyableSingleton<HatManager>.Instance.GetHatById(hatId), target);
+ }
+
+ public static void SetHatImage(HatBehaviour hat, SpriteRenderer target)
+ {
+ if (!target)
+ {
+ return;
+ }
+ if (hat)
+ {
+ target.sprite = hat.MainImage;
+ Vector3 localPosition = target.transform.localPosition;
+ localPosition.z = (hat.InFront ? -0.0001f : 0.0001f);
+ target.transform.localPosition = localPosition;
+ return;
+ }
+ string str = (!hat) ? "null" : hat.name;
+ Debug.LogError("Player: " + target.name + "\tHat: " + str);
+ }
+
+ private void ReportDeadBody(GameData.PlayerInfo target)
+ {
+ if (AmongUsClient.Instance.IsGameOver)
+ {
+ return;
+ }
+ if (MeetingHud.Instance)
+ {
+ return;
+ }
+ if (target == null && PlayerControl.LocalPlayer.myTasks.Any(new Func<PlayerTask, bool>(PlayerTask.TaskIsEmergency)))
+ {
+ return;
+ }
+ if (this.Data.IsDead)
+ {
+ return;
+ }
+ MeetingRoomManager.Instance.AssignSelf(this, target);
+ if (!AmongUsClient.Instance.AmHost)
+ {
+ return;
+ }
+ if (ShipStatus.Instance.CheckTaskCompletion())
+ {
+ return;
+ }
+ DestroyableSingleton<HudManager>.Instance.OpenMeetingRoom(this);
+ this.RpcStartMeeting(target);
+ }
+
+ public IEnumerator CoStartMeeting(GameData.PlayerInfo target)
+ {
+ DestroyableSingleton<Telemetry>.Instance.WriteMeetingStarted(target == null);
+ while (!MeetingHud.Instance)
+ {
+ yield return null;
+ }
+ MeetingRoomManager.Instance.RemoveSelf();
+ DeadBody[] array = UnityEngine.Object.FindObjectsOfType<DeadBody>();
+ for (int i = 0; i < array.Length; i++)
+ {
+ UnityEngine.Object.Destroy(array[i].gameObject);
+ }
+ for (int j = 0; j < PlayerControl.AllPlayerControls.Count; j++)
+ {
+ PlayerControl playerControl = PlayerControl.AllPlayerControls[j];
+ if (!playerControl.GetComponent<DummyBehaviour>().enabled)
+ {
+ playerControl.MyPhysics.ExitAllVents();
+ playerControl.NetTransform.SnapTo(ShipStatus.Instance.GetSpawnLocation((int)playerControl.PlayerId, GameData.Instance.PlayerCount));
+ }
+ }
+ if (base.AmOwner)
+ {
+ if (target != null)
+ {
+ StatsManager instance = StatsManager.Instance;
+ uint num = instance.BodiesReported;
+ instance.BodiesReported = num + 1U;
+ }
+ else
+ {
+ this.RemainingEmergencies--;
+ StatsManager instance2 = StatsManager.Instance;
+ uint num = instance2.EmergenciesCalled;
+ instance2.EmergenciesCalled = num + 1U;
+ }
+ }
+ if (MapBehaviour.Instance)
+ {
+ MapBehaviour.Instance.Close();
+ }
+ if (Minigame.Instance)
+ {
+ Minigame.Instance.Close();
+ }
+ KillAnimation.SetMovement(this, true);
+ MeetingHud.Instance.StartCoroutine(MeetingHud.Instance.CoIntro(this, target));
+ yield break;
+ }
+
+ public void MurderPlayer(PlayerControl target)
+ {
+ if (AmongUsClient.Instance.IsGameOver)
+ {
+ return;
+ }
+ if (!target || this.Data.IsDead || !this.Data.IsImpostor || this.Data.Disconnected)
+ {
+ Debug.LogWarning(string.Format("Bad kill from {0} to {1}", this.PlayerId, ((int)((target != null) ? new byte?(target.PlayerId) : null)) ?? -1));
+ return;
+ }
+ GameData.PlayerInfo data = target.Data;
+ if (data != null && !data.IsDead)
+ {
+ if (base.AmOwner)
+ {
+ StatsManager instance = StatsManager.Instance;
+ uint num = instance.ImpostorKills;
+ instance.ImpostorKills = num + 1U;
+ if (Constants.ShouldPlaySfx())
+ {
+ SoundManager.Instance.PlaySound(PlayerControl.LocalPlayer.KillSfx, false, 0.8f);
+ }
+ }
+ this.SetKillTimer(PlayerControl.GameOptions.KillCooldown);
+ DestroyableSingleton<Telemetry>.Instance.WriteMurder(this.PlayerId, target.PlayerId, target.transform.position);
+ target.gameObject.layer = LayerMask.NameToLayer("Ghost");
+ if (target.AmOwner)
+ {
+ StatsManager instance2 = StatsManager.Instance;
+ uint num = instance2.TimesMurdered;
+ instance2.TimesMurdered = num + 1U;
+ if (Minigame.Instance)
+ {
+ Minigame.Instance.Close();
+ Minigame.Instance.Close();
+ }
+ DestroyableSingleton<HudManager>.Instance.ShadowQuad.gameObject.SetActive(false);
+ target.nameText.GetComponent<MeshRenderer>().material.SetInt("_Mask", 0);
+ DestroyableSingleton<HudManager>.Instance.KillOverlay.ShowOne(this, data);
+ target.RpcSetScanner(false);
+ ImportantTextTask importantTextTask = new GameObject("_Player").AddComponent<ImportantTextTask>();
+ importantTextTask.transform.SetParent(base.transform, false);
+ if (!PlayerControl.GameOptions.GhostsDoTasks)
+ {
+ target.ClearTasks();
+ importantTextTask.Text = DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.GhostIgnoreTasks, Array.Empty<object>());
+ }
+ else
+ {
+ importantTextTask.Text = DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.GhostDoTasks, Array.Empty<object>());
+ }
+ target.myTasks.Insert(0, importantTextTask);
+ }
+ this.MyPhysics.StartCoroutine(this.KillAnimations.Random<KillAnimation>().CoPerformKill(this, target));
+ }
+ }
+
+ public override bool Serialize(MessageWriter writer, bool initialState)
+ {
+ if (initialState)
+ {
+ writer.Write(this.isNew);
+ }
+ writer.Write(this.PlayerId);
+ return true;
+ }
+
+ public override void Deserialize(MessageReader reader, bool initialState)
+ {
+ if (initialState)
+ {
+ this.isNew = reader.ReadBoolean();
+ }
+ this.PlayerId = reader.ReadByte();
+ }
+
+ public void SetPlayerMaterialColors(Renderer rend)
+ {
+ GameData.PlayerInfo playerById = GameData.Instance.GetPlayerById(this.PlayerId);
+ PlayerControl.SetPlayerMaterialColors((int)((playerById != null) ? playerById.ColorId : 0), rend);
+ }
+
+ public static void SetPlayerMaterialColors(int colorId, Renderer rend)
+ {
+ if (!rend)
+ {
+ return;
+ }
+ rend.material.SetColor("_BackColor", Palette.ShadowColors[colorId]);
+ rend.material.SetColor("_BodyColor", Palette.PlayerColors[colorId]);
+ rend.material.SetColor("_VisorColor", Palette.VisorColor);
+ }
+
+ public void RpcSetScanner(bool value)
+ {
+ byte b = this.scannerCount + 1;
+ this.scannerCount = b;
+ byte b2 = b;
+ if (AmongUsClient.Instance.AmClient)
+ {
+ this.SetScanner(value, b2);
+ }
+ MessageWriter messageWriter = AmongUsClient.Instance.StartRpc(this.NetId, 16, SendOption.Reliable);
+ messageWriter.Write(value);
+ messageWriter.Write(b2);
+ messageWriter.EndMessage();
+ }
+
+ public void RpcPlayAnimation(byte animType)
+ {
+ if (AmongUsClient.Instance.AmClient)
+ {
+ this.PlayAnimation(animType);
+ }
+ MessageWriter messageWriter = AmongUsClient.Instance.StartRpc(this.NetId, 0, SendOption.None);
+ messageWriter.Write(animType);
+ messageWriter.EndMessage();
+ }
+
+ public void RpcSetStartCounter(int secondsLeft)
+ {
+ int lastStartCounter = this.LastStartCounter;
+ this.LastStartCounter = lastStartCounter + 1;
+ int value = lastStartCounter;
+ MessageWriter messageWriter = AmongUsClient.Instance.StartRpc(this.NetId, 19, SendOption.Reliable);
+ messageWriter.WritePacked(value);
+ messageWriter.Write((sbyte)secondsLeft);
+ messageWriter.EndMessage();
+ }
+
+ public void RpcCompleteTask(uint idx)
+ {
+ if (AmongUsClient.Instance.AmClient)
+ {
+ this.CompleteTask(idx);
+ }
+ MessageWriter messageWriter = AmongUsClient.Instance.StartRpc(this.NetId, 1, SendOption.Reliable);
+ messageWriter.WritePacked(idx);
+ messageWriter.EndMessage();
+ }
+
+ public void RpcSyncSettings(GameOptionsData gameOptions)
+ {
+ if (!AmongUsClient.Instance.AmHost || DestroyableSingleton<TutorialManager>.InstanceExists)
+ {
+ return;
+ }
+ PlayerControl.GameOptions = gameOptions;
+ SaveManager.GameHostOptions = gameOptions;
+ MessageWriter messageWriter = AmongUsClient.Instance.StartRpc(this.NetId, 2, SendOption.Reliable);
+ messageWriter.WriteBytesAndSize(gameOptions.ToBytes());
+ messageWriter.EndMessage();
+ }
+
+ public void RpcSetInfected(GameData.PlayerInfo[] infected)
+ {
+ byte[] array = (from p in infected
+ select p.PlayerId).ToArray<byte>();
+ if (AmongUsClient.Instance.AmClient)
+ {
+ this.SetInfected(array);
+ }
+ MessageWriter messageWriter = AmongUsClient.Instance.StartRpc(this.NetId, 3, SendOption.Reliable);
+ messageWriter.WriteBytesAndSize(array);
+ messageWriter.EndMessage();
+ }
+
+ public void CmdCheckName(string name)
+ {
+ if (AmongUsClient.Instance.AmHost)
+ {
+ this.CheckName(name);
+ return;
+ }
+ MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(this.NetId, 5, SendOption.Reliable, AmongUsClient.Instance.HostId);
+ messageWriter.Write(name);
+ AmongUsClient.Instance.FinishRpcImmediately(messageWriter);
+ }
+
+ public void RpcSetSkin(uint skinId)
+ {
+ if (AmongUsClient.Instance.AmClient)
+ {
+ this.SetSkin(skinId);
+ }
+ MessageWriter messageWriter = AmongUsClient.Instance.StartRpc(this.NetId, 10, SendOption.Reliable);
+ messageWriter.WritePacked(skinId);
+ messageWriter.EndMessage();
+ }
+
+ public void RpcSetHat(uint hatId)
+ {
+ if (AmongUsClient.Instance.AmClient)
+ {
+ this.SetHat(hatId);
+ }
+ MessageWriter messageWriter = AmongUsClient.Instance.StartRpc(this.NetId, 9, SendOption.Reliable);
+ messageWriter.WritePacked(hatId);
+ messageWriter.EndMessage();
+ }
+
+ public void RpcSetPet(uint petId)
+ {
+ if (AmongUsClient.Instance.AmClient)
+ {
+ this.SetPet(petId);
+ }
+ MessageWriter messageWriter = AmongUsClient.Instance.StartRpc(this.NetId, 18, SendOption.Reliable);
+ messageWriter.WritePacked(petId);
+ messageWriter.EndMessage();
+ }
+
+ public void RpcSetName(string name)
+ {
+ if (AmongUsClient.Instance.AmClient)
+ {
+ this.SetName(name);
+ }
+ MessageWriter messageWriter = AmongUsClient.Instance.StartRpc(this.NetId, 6, SendOption.Reliable);
+ messageWriter.Write(name);
+ messageWriter.EndMessage();
+ }
+
+ public void CmdCheckColor(byte bodyColor)
+ {
+ if (AmongUsClient.Instance.AmHost)
+ {
+ this.CheckColor(bodyColor);
+ return;
+ }
+ MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(this.NetId, 7, SendOption.Reliable, AmongUsClient.Instance.HostId);
+ messageWriter.Write(bodyColor);
+ AmongUsClient.Instance.FinishRpcImmediately(messageWriter);
+ }
+
+ public void RpcSetColor(byte bodyColor)
+ {
+ if (AmongUsClient.Instance.AmClient)
+ {
+ this.SetColor(bodyColor);
+ }
+ MessageWriter messageWriter = AmongUsClient.Instance.StartRpc(this.NetId, 8, SendOption.Reliable);
+ messageWriter.Write(bodyColor);
+ messageWriter.EndMessage();
+ }
+
+ public void RpcSetTimesImpostor(float percImpostor)
+ {
+ if (AmongUsClient.Instance.AmClient)
+ {
+ this.crewStreak = percImpostor;
+ }
+ MessageWriter messageWriter = AmongUsClient.Instance.StartRpc(this.NetId, 14, SendOption.None);
+ messageWriter.Write(percImpostor);
+ messageWriter.EndMessage();
+ }
+
+ public bool RpcSendChat(string chatText)
+ {
+ if (string.IsNullOrWhiteSpace(chatText))
+ {
+ return false;
+ }
+ if (AmongUsClient.Instance.AmClient && DestroyableSingleton<HudManager>.Instance)
+ {
+ DestroyableSingleton<HudManager>.Instance.Chat.AddChat(this, chatText);
+ }
+ if (chatText.IndexOf("who", StringComparison.OrdinalIgnoreCase) >= 0)
+ {
+ DestroyableSingleton<Telemetry>.Instance.SendWho();
+ }
+ MessageWriter messageWriter = AmongUsClient.Instance.StartRpc(this.NetId, 13, SendOption.Reliable);
+ messageWriter.Write(chatText);
+ messageWriter.EndMessage();
+ return true;
+ }
+
+ public void RpcSendChatNote(byte srcPlayerId, ChatNoteTypes noteType)
+ {
+ if (AmongUsClient.Instance.AmClient)
+ {
+ GameData.PlayerInfo playerById = GameData.Instance.GetPlayerById(srcPlayerId);
+ DestroyableSingleton<HudManager>.Instance.Chat.AddChatNote(playerById, noteType);
+ }
+ MessageWriter messageWriter = AmongUsClient.Instance.StartRpc(this.NetId, 17, SendOption.Reliable);
+ messageWriter.Write(srcPlayerId);
+ messageWriter.Write((byte)noteType);
+ messageWriter.EndMessage();
+ }
+
+ public void CmdReportDeadBody(GameData.PlayerInfo target)
+ {
+ if (AmongUsClient.Instance.AmHost)
+ {
+ this.ReportDeadBody(target);
+ return;
+ }
+ MessageWriter messageWriter = AmongUsClient.Instance.StartRpc(this.NetId, 11, SendOption.Reliable);
+ messageWriter.Write((target != null) ? target.PlayerId : byte.MaxValue);
+ messageWriter.EndMessage();
+ }
+
+ public void RpcStartMeeting(GameData.PlayerInfo info)
+ {
+ if (AmongUsClient.Instance.AmClient)
+ {
+ base.StartCoroutine(this.CoStartMeeting(info));
+ }
+ MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(this.NetId, 15, SendOption.Reliable, -1);
+ messageWriter.Write((info != null) ? info.PlayerId : byte.MaxValue);
+ AmongUsClient.Instance.FinishRpcImmediately(messageWriter);
+ }
+
+ public void RpcMurderPlayer(PlayerControl target)
+ {
+ if (AmongUsClient.Instance.AmClient)
+ {
+ this.MurderPlayer(target);
+ }
+ MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(this.NetId, 12, SendOption.Reliable, -1);
+ messageWriter.WriteNetObject(target);
+ AmongUsClient.Instance.FinishRpcImmediately(messageWriter);
+ }
+
+ public override void HandleRpc(byte callId, MessageReader reader)
+ {
+ switch (callId)
+ {
+ case 0:
+ this.PlayAnimation(reader.ReadByte());
+ return;
+ case 1:
+ this.CompleteTask(reader.ReadPackedUInt32());
+ return;
+ case 2:
+ PlayerControl.GameOptions = GameOptionsData.FromBytes(reader.ReadBytesAndSize());
+ return;
+ case 3:
+ this.SetInfected(reader.ReadBytesAndSize());
+ return;
+ case 4:
+ this.Exiled();
+ return;
+ case 5:
+ this.CheckName(reader.ReadString());
+ return;
+ case 6:
+ this.SetName(reader.ReadString());
+ return;
+ case 7:
+ this.CheckColor(reader.ReadByte());
+ return;
+ case 8:
+ this.SetColor(reader.ReadByte());
+ return;
+ case 9:
+ this.SetHat(reader.ReadPackedUInt32());
+ return;
+ case 10:
+ this.SetSkin(reader.ReadPackedUInt32());
+ return;
+ case 11:
+ {
+ GameData.PlayerInfo playerById = GameData.Instance.GetPlayerById(reader.ReadByte());
+ this.ReportDeadBody(playerById);
+ return;
+ }
+ case 12:
+ {
+ PlayerControl target = reader.ReadNetObject<PlayerControl>();
+ this.MurderPlayer(target);
+ return;
+ }
+ case 13:
+ {
+ string chatText = reader.ReadString();
+ if (DestroyableSingleton<HudManager>.Instance)
+ {
+ DestroyableSingleton<HudManager>.Instance.Chat.AddChat(this, chatText);
+ return;
+ }
+ break;
+ }
+ case 14:
+ this.crewStreak = reader.ReadSingle();
+ return;
+ case 15:
+ {
+ GameData.PlayerInfo playerById2 = GameData.Instance.GetPlayerById(reader.ReadByte());
+ base.StartCoroutine(this.CoStartMeeting(playerById2));
+ return;
+ }
+ case 16:
+ this.SetScanner(reader.ReadBoolean(), reader.ReadByte());
+ break;
+ case 17:
+ {
+ GameData.PlayerInfo playerById3 = GameData.Instance.GetPlayerById(reader.ReadByte());
+ DestroyableSingleton<HudManager>.Instance.Chat.AddChatNote(playerById3, (ChatNoteTypes)reader.ReadByte());
+ return;
+ }
+ case 18:
+ this.SetPet(reader.ReadPackedUInt32());
+ return;
+ case 19:
+ {
+ int num = reader.ReadPackedInt32();
+ sbyte startCounter = reader.ReadSByte();
+ if (DestroyableSingleton<GameStartManager>.InstanceExists && this.LastStartCounter < num)
+ {
+ this.LastStartCounter = num;
+ DestroyableSingleton<GameStartManager>.Instance.SetStartCounter(startCounter);
+ return;
+ }
+ break;
+ }
+ default:
+ return;
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/PlayerParticle.cs b/Client/Assembly-CSharp/PlayerParticle.cs
new file mode 100644
index 0000000..41fa495
--- /dev/null
+++ b/Client/Assembly-CSharp/PlayerParticle.cs
@@ -0,0 +1,39 @@
+using System;
+using UnityEngine;
+
+public class PlayerParticle : PoolableBehavior
+{
+ public Camera FollowCamera { get; set; }
+
+ public SpriteRenderer myRend;
+
+ public float maxDistance = 6f;
+
+ public Vector2 velocity;
+
+ public float angularVelocity;
+
+ private Vector3 lastCamera;
+
+ public void Update()
+ {
+ Vector3 vector = base.transform.localPosition;
+ float sqrMagnitude = vector.sqrMagnitude;
+ if (this.FollowCamera)
+ {
+ Vector3 position = this.FollowCamera.transform.position;
+ position.z = 0f;
+ vector += (position - this.lastCamera) * (1f - base.transform.localScale.x);
+ this.lastCamera = position;
+ sqrMagnitude = (vector - position).sqrMagnitude;
+ }
+ if (sqrMagnitude > this.maxDistance * this.maxDistance)
+ {
+ this.OwnerPool.Reclaim(this);
+ return;
+ }
+ vector += this.velocity * Time.deltaTime;
+ base.transform.localPosition = vector;
+ base.transform.Rotate(0f, 0f, Time.deltaTime * this.angularVelocity);
+ }
+}
diff --git a/Client/Assembly-CSharp/PlayerParticleInfo.cs b/Client/Assembly-CSharp/PlayerParticleInfo.cs
new file mode 100644
index 0000000..620b19f
--- /dev/null
+++ b/Client/Assembly-CSharp/PlayerParticleInfo.cs
@@ -0,0 +1,12 @@
+using System;
+using UnityEngine;
+
+[Serializable]
+public class PlayerParticleInfo
+{
+ public Sprite image;
+
+ public FloatRange angularVel;
+
+ public bool alignToVel;
+}
diff --git a/Client/Assembly-CSharp/PlayerParticles.cs b/Client/Assembly-CSharp/PlayerParticles.cs
new file mode 100644
index 0000000..4f7cf4a
--- /dev/null
+++ b/Client/Assembly-CSharp/PlayerParticles.cs
@@ -0,0 +1,73 @@
+using System;
+using UnityEngine;
+
+public class PlayerParticles : MonoBehaviour
+{
+ public PlayerParticleInfo[] Sprites;
+
+ public FloatRange velocity;
+
+ public FloatRange scale;
+
+ public ObjectPoolBehavior pool;
+
+ public float StartRadius;
+
+ public Camera FollowCamera;
+
+ private RandomFill<PlayerParticleInfo> fill;
+
+ public void Start()
+ {
+ this.fill = new RandomFill<PlayerParticleInfo>();
+ this.fill.Set(this.Sprites);
+ int num = 0;
+ while (this.pool.NotInUse > 0)
+ {
+ PlayerParticle playerParticle = this.pool.Get<PlayerParticle>();
+ PlayerControl.SetPlayerMaterialColors(num++, playerParticle.myRend);
+ this.PlacePlayer(playerParticle, true);
+ }
+ }
+
+ public void Update()
+ {
+ while (this.pool.NotInUse > 0)
+ {
+ PlayerParticle part = this.pool.Get<PlayerParticle>();
+ this.PlacePlayer(part, false);
+ }
+ }
+
+ private void PlacePlayer(PlayerParticle part, bool initial)
+ {
+ Vector3 vector = UnityEngine.Random.insideUnitCircle;
+ if (!initial)
+ {
+ vector.Normalize();
+ }
+ vector *= this.StartRadius;
+ float num = this.scale.Next();
+ part.transform.localScale = new Vector3(num, num, num);
+ vector.z = -num * 0.001f;
+ if (this.FollowCamera)
+ {
+ Vector3 position = this.FollowCamera.transform.position;
+ position.z = 0f;
+ vector += position;
+ part.FollowCamera = this.FollowCamera;
+ }
+ part.transform.localPosition = vector;
+ PlayerParticleInfo playerParticleInfo = this.fill.Get();
+ part.myRend.sprite = playerParticleInfo.image;
+ part.myRend.flipX = BoolRange.Next(0.5f);
+ Vector2 vector2 = -vector.normalized;
+ vector2 = vector2.Rotate(FloatRange.Next(-45f, 45f));
+ part.velocity = vector2 * this.velocity.Next();
+ part.angularVelocity = playerParticleInfo.angularVel.Next();
+ if (playerParticleInfo.alignToVel)
+ {
+ part.transform.localEulerAngles = new Vector3(0f, 0f, Vector2.up.AngleSigned(vector2));
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/PlayerPhysics.cs b/Client/Assembly-CSharp/PlayerPhysics.cs
new file mode 100644
index 0000000..c35e21d
--- /dev/null
+++ b/Client/Assembly-CSharp/PlayerPhysics.cs
@@ -0,0 +1,330 @@
+using System;
+using System.Collections;
+using System.Linq;
+using Assets.CoreScripts;
+using Hazel;
+using InnerNet;
+using PowerTools;
+using UnityEngine;
+
+public class PlayerPhysics : InnerNetObject
+{
+ public float TrueSpeed
+ {
+ get
+ {
+ return this.Speed * PlayerControl.GameOptions.PlayerSpeedMod;
+ }
+ }
+
+ public float TrueGhostSpeed
+ {
+ get
+ {
+ return this.GhostSpeed * PlayerControl.GameOptions.PlayerSpeedMod;
+ }
+ }
+
+ public float Speed = 4.5f;
+
+ public float GhostSpeed = 3f;
+
+ [HideInInspector]
+ private Rigidbody2D body;
+
+ [HideInInspector]
+ private SpriteAnim Animator;
+
+ [HideInInspector]
+ private SpriteRenderer rend;
+
+ [HideInInspector]
+ private PlayerControl myPlayer;
+
+ public AnimationClip RunAnim;
+
+ public AnimationClip IdleAnim;
+
+ public AnimationClip GhostIdleAnim;
+
+ public AnimationClip EnterVentAnim;
+
+ public AnimationClip ExitVentAnim;
+
+ public AnimationClip SpawnAnim;
+
+ public SkinLayer Skin;
+
+ private enum RpcCalls
+ {
+ EnterVent,
+ ExitVent
+ }
+
+ public void Awake()
+ {
+ this.body = base.GetComponent<Rigidbody2D>();
+ this.Animator = base.GetComponent<SpriteAnim>();
+ this.rend = base.GetComponent<SpriteRenderer>();
+ this.myPlayer = base.GetComponent<PlayerControl>();
+ }
+
+ private void FixedUpdate()
+ {
+ this.HandleAnimation();
+ if (base.AmOwner && this.myPlayer.CanMove && GameData.Instance)
+ {
+ DestroyableSingleton<Telemetry>.Instance.WritePosition(this.myPlayer.PlayerId, base.transform.position);
+ GameData.PlayerInfo data = this.myPlayer.Data;
+ if (data == null)
+ {
+ return;
+ }
+ bool isDead = data.IsDead;
+ this.body.velocity = DestroyableSingleton<HudManager>.Instance.joystick.Delta * (isDead ? this.TrueGhostSpeed : this.TrueSpeed);
+ }
+ }
+
+ private void LateUpdate()
+ {
+ Vector3 position = base.transform.position;
+ position.z = position.y / 1000f;
+ base.transform.position = position;
+ }
+
+ public Vector3 Vec2ToPosition(Vector2 pos)
+ {
+ return new Vector3(pos.x, pos.y, pos.y / 1000f);
+ }
+
+ public void SetSkin(uint skinId)
+ {
+ this.Skin.SetSkin(skinId);
+ if (this.Animator.IsPlaying(this.SpawnAnim))
+ {
+ this.Skin.SetSpawn(this.Animator.Time);
+ }
+ }
+
+ public void ResetAnim(bool stopCoroutines = true)
+ {
+ if (stopCoroutines)
+ {
+ this.myPlayer.StopAllCoroutines();
+ base.StopAllCoroutines();
+ }
+ base.enabled = true;
+ this.myPlayer.inVent = false;
+ this.myPlayer.Visible = true;
+ GameData.PlayerInfo data = this.myPlayer.Data;
+ if (data == null || !data.IsDead)
+ {
+ this.Skin.SetIdle();
+ this.Animator.Play(this.IdleAnim, 1f);
+ this.myPlayer.Visible = true;
+ this.myPlayer.SetHatAlpha(1f);
+ return;
+ }
+ this.Skin.SetGhost();
+ this.Animator.Play(this.GhostIdleAnim, 1f);
+ this.myPlayer.SetHatAlpha(0.5f);
+ }
+
+ private void HandleAnimation()
+ {
+ if (this.Animator.IsPlaying(this.SpawnAnim))
+ {
+ return;
+ }
+ if (!GameData.Instance)
+ {
+ return;
+ }
+ Vector2 velocity = this.body.velocity;
+ AnimationClip currentAnimation = this.Animator.GetCurrentAnimation();
+ GameData.PlayerInfo data = this.myPlayer.Data;
+ if (data == null)
+ {
+ return;
+ }
+ if (!data.IsDead)
+ {
+ if (velocity.sqrMagnitude >= 0.05f)
+ {
+ if (currentAnimation != this.RunAnim)
+ {
+ this.Animator.Play(this.RunAnim, 1f);
+ this.Skin.SetRun();
+ }
+ if (velocity.x < -0.01f)
+ {
+ this.rend.flipX = true;
+ }
+ else if (velocity.x > 0.01f)
+ {
+ this.rend.flipX = false;
+ }
+ }
+ else if (currentAnimation == this.RunAnim || currentAnimation == this.SpawnAnim || !currentAnimation)
+ {
+ this.Skin.SetIdle();
+ this.Animator.Play(this.IdleAnim, 1f);
+ this.myPlayer.SetHatAlpha(1f);
+ }
+ }
+ else
+ {
+ this.Skin.SetGhost();
+ if (currentAnimation != this.GhostIdleAnim)
+ {
+ this.Animator.Play(this.GhostIdleAnim, 1f);
+ this.myPlayer.SetHatAlpha(0.5f);
+ }
+ if (velocity.x < -0.01f)
+ {
+ this.rend.flipX = true;
+ }
+ else if (velocity.x > 0.01f)
+ {
+ this.rend.flipX = false;
+ }
+ }
+ this.Skin.Flipped = this.rend.flipX;
+ }
+
+ public IEnumerator CoSpawnPlayer(LobbyBehaviour lobby)
+ {
+ if (!lobby)
+ {
+ yield break;
+ }
+ Vector3 spawnPos = this.Vec2ToPosition(lobby.SpawnPositions[(int)this.myPlayer.PlayerId % lobby.SpawnPositions.Length]);
+ this.myPlayer.nameText.gameObject.SetActive(false);
+ this.myPlayer.Collider.enabled = false;
+ KillAnimation.SetMovement(this.myPlayer, false);
+ bool amFlipped = this.myPlayer.PlayerId > 4;
+ this.myPlayer.GetComponent<SpriteRenderer>().flipX = amFlipped;
+ this.myPlayer.transform.position = spawnPos;
+ SoundManager.Instance.PlaySound(lobby.SpawnSound, false, 1f).volume = 0.75f;
+ this.Skin.SetSpawn(0f);
+ this.Skin.Flipped = this.rend.flipX;
+ yield return new WaitForAnimationFinish(this.Animator, this.SpawnAnim);
+ base.transform.position = spawnPos + new Vector3(amFlipped ? -0.3f : 0.3f, -0.24f);
+ this.ResetAnim(false);
+ Vector2 b = (-spawnPos).normalized;
+ yield return this.WalkPlayerTo(spawnPos + b, 0.01f);
+ this.myPlayer.Collider.enabled = true;
+ KillAnimation.SetMovement(this.myPlayer, true);
+ this.myPlayer.nameText.gameObject.SetActive(true);
+ yield break;
+ }
+
+ public void ExitAllVents()
+ {
+ this.ResetAnim(true);
+ this.myPlayer.moveable = true;
+ Vent[] allVents = ShipStatus.Instance.AllVents;
+ for (int i = 0; i < allVents.Length; i++)
+ {
+ allVents[i].SetButtons(false);
+ }
+ }
+
+ private IEnumerator CoEnterVent(int id)
+ {
+ Vent vent = ShipStatus.Instance.AllVents.First((Vent v) => v.Id == id);
+ this.myPlayer.moveable = false;
+ yield return this.WalkPlayerTo(vent.transform.position, 0.01f);
+ vent.EnterVent();
+ this.Skin.SetEnterVent();
+ yield return new WaitForAnimationFinish(this.Animator, this.EnterVentAnim);
+ this.Skin.SetIdle();
+ this.Animator.Play(this.IdleAnim, 1f);
+ this.myPlayer.Visible = false;
+ this.myPlayer.inVent = true;
+ yield break;
+ }
+
+ private IEnumerator CoExitVent(int id)
+ {
+ Vent vent = ShipStatus.Instance.AllVents.First((Vent v) => v.Id == id);
+ this.myPlayer.Visible = true;
+ this.myPlayer.inVent = false;
+ vent.ExitVent();
+ this.Skin.SetExitVent();
+ yield return new WaitForAnimationFinish(this.Animator, this.ExitVentAnim);
+ this.Skin.SetIdle();
+ this.Animator.Play(this.IdleAnim, 1f);
+ this.myPlayer.moveable = true;
+ yield break;
+ }
+
+ // 移动位置
+ public IEnumerator WalkPlayerTo(Vector2 worldPos, float tolerance = 0.01f)
+ {
+ worldPos -= base.GetComponent<CircleCollider2D>().offset;
+ Rigidbody2D body = this.body;
+ do
+ {
+ Vector2 vector2;
+ Vector2 vector = vector2 = worldPos - base.transform.position;
+ if (vector2.sqrMagnitude <= tolerance)
+ {
+ break;
+ }
+ float d = Mathf.Clamp(vector.magnitude * 2f, 0.01f, 1f);
+ body.velocity = vector.normalized * this.Speed * d;
+ yield return null;
+ }
+ while (body.velocity.magnitude >= 0.0001f);
+ body.velocity = Vector2.zero;
+ yield break;
+ }
+
+ public override bool Serialize(MessageWriter writer, bool initialState)
+ {
+ return false;
+ }
+
+ public override void Deserialize(MessageReader reader, bool initialState)
+ {
+ }
+
+ public void RpcEnterVent(int id)
+ {
+ if (AmongUsClient.Instance.AmClient)
+ {
+ base.StartCoroutine(this.CoEnterVent(id));
+ }
+ MessageWriter messageWriter = AmongUsClient.Instance.StartRpc(this.NetId, 0, SendOption.Reliable);
+ messageWriter.WritePacked(id);
+ messageWriter.EndMessage();
+ }
+
+ public void RpcExitVent(int id)
+ {
+ if (AmongUsClient.Instance.AmClient)
+ {
+ base.StartCoroutine(this.CoExitVent(id));
+ }
+ MessageWriter messageWriter = AmongUsClient.Instance.StartRpc(this.NetId, 1, SendOption.Reliable);
+ messageWriter.WritePacked(id);
+ messageWriter.EndMessage();
+ }
+
+ public override void HandleRpc(byte callId, MessageReader reader)
+ {
+ if (callId == 0)
+ {
+ int id = reader.ReadPackedInt32();
+ base.StartCoroutine(this.CoEnterVent(id));
+ return;
+ }
+ if (callId != 1)
+ {
+ return;
+ }
+ int id2 = reader.ReadPackedInt32();
+ base.StartCoroutine(this.CoExitVent(id2));
+ }
+}
diff --git a/Client/Assembly-CSharp/PlayerTab.cs b/Client/Assembly-CSharp/PlayerTab.cs
new file mode 100644
index 0000000..af57c2b
--- /dev/null
+++ b/Client/Assembly-CSharp/PlayerTab.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+public class PlayerTab : MonoBehaviour
+{
+ public ColorChip ColorTabPrefab;
+
+ public SpriteRenderer DemoImage;
+
+ public SpriteRenderer HatImage;
+
+ public SpriteRenderer SkinImage;
+
+ public SpriteRenderer PetImage;
+
+ public FloatRange XRange = new FloatRange(1.5f, 3f);
+
+ public FloatRange YRange = new FloatRange(-1f, -3f);
+
+ private HashSet<int> AvailableColors = new HashSet<int>();
+
+ private List<ColorChip> ColorChips = new List<ColorChip>();
+
+ private const int Columns = 3;
+
+ public void OnEnable()
+ {
+ PlayerControl.SetPlayerMaterialColors((int)PlayerControl.LocalPlayer.Data.ColorId, this.DemoImage);
+ PlayerControl.SetHatImage(SaveManager.LastHat, this.HatImage);
+ PlayerControl.SetSkinImage(SaveManager.LastSkin, this.SkinImage);
+ PlayerControl.SetPetImage(SaveManager.LastPet, (int)PlayerControl.LocalPlayer.Data.ColorId, this.PetImage);
+ float num = (float)Palette.PlayerColors.Length / 3f;
+ for (int i = 0; i < Palette.PlayerColors.Length; i++)
+ {
+ float x = this.XRange.Lerp((float)(i % 3) / 2f);
+ float y = this.YRange.Lerp(1f - (float)(i / 3) / num);
+ ColorChip colorChip = UnityEngine.Object.Instantiate<ColorChip>(this.ColorTabPrefab);
+ colorChip.transform.SetParent(base.transform);
+ colorChip.transform.localPosition = new Vector3(x, y, -1f);
+ int j = i;
+ colorChip.Button.OnClick.AddListener(delegate()
+ {
+ this.SelectColor(j);
+ });
+ colorChip.Inner.color = Palette.PlayerColors[i];
+ this.ColorChips.Add(colorChip);
+ }
+ }
+
+ public void OnDisable()
+ {
+ for (int i = 0; i < this.ColorChips.Count; i++)
+ {
+ UnityEngine.Object.Destroy(this.ColorChips[i].gameObject);
+ }
+ this.ColorChips.Clear();
+ }
+
+ public void Update()
+ {
+ this.UpdateAvailableColors();
+ for (int i = 0; i < this.ColorChips.Count; i++)
+ {
+ this.ColorChips[i].InUseForeground.SetActive(!this.AvailableColors.Contains(i));
+ }
+ }
+
+ private void SelectColor(int colorId)
+ {
+ this.UpdateAvailableColors();
+ if (this.AvailableColors.Remove(colorId))
+ {
+ SaveManager.BodyColor = (byte)colorId;
+ if (PlayerControl.LocalPlayer)
+ {
+ PlayerControl.LocalPlayer.CmdCheckColor((byte)colorId);
+ }
+ }
+ }
+
+ public void UpdateAvailableColors()
+ {
+ PlayerControl.SetPlayerMaterialColors((int)PlayerControl.LocalPlayer.Data.ColorId, this.DemoImage);
+ PlayerControl.SetPetImage(SaveManager.LastPet, (int)PlayerControl.LocalPlayer.Data.ColorId, this.PetImage);
+ for (int i = 0; i < Palette.PlayerColors.Length; i++)
+ {
+ this.AvailableColors.Add(i);
+ }
+ if (GameData.Instance)
+ {
+ List<GameData.PlayerInfo> allPlayers = GameData.Instance.AllPlayers;
+ for (int j = 0; j < allPlayers.Count; j++)
+ {
+ GameData.PlayerInfo playerInfo = allPlayers[j];
+ this.AvailableColors.Remove((int)playerInfo.ColorId);
+ }
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/PlayerTask.cs b/Client/Assembly-CSharp/PlayerTask.cs
new file mode 100644
index 0000000..ddccafe
--- /dev/null
+++ b/Client/Assembly-CSharp/PlayerTask.cs
@@ -0,0 +1,137 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using UnityEngine;
+
+public abstract class PlayerTask : MonoBehaviour
+{
+ public int Index { get; internal set; }
+
+ public uint Id { get; internal set; }
+
+ public PlayerControl Owner { get; internal set; }
+
+ public abstract int TaskStep { get; }
+
+ public abstract bool IsComplete { get; }
+
+ public Vector2 Location
+ {
+ get
+ {
+ this.LocationDirty = false;
+ return this.FindObjectPos().transform.position;
+ }
+ }
+
+ public SystemTypes StartAt;
+
+ public TaskTypes TaskType;
+
+ public Minigame MinigamePrefab;
+
+ public bool HasLocation;
+
+ public bool LocationDirty = true;
+
+ public abstract void Initialize();
+
+ public virtual void OnRemove()
+ {
+ }
+
+ public abstract bool ValidConsole(global::Console console);
+
+ public abstract void Complete();
+
+ public abstract void AppendTaskText(StringBuilder sb);
+
+ internal static bool TaskIsEmergency(PlayerTask arg)
+ {
+ return arg is NoOxyTask || arg is HudOverrideTask || arg is ReactorTask || arg is ElectricTask;
+ }
+
+ protected List<global::Console> FindConsoles()
+ {
+ List<global::Console> list = new List<global::Console>();
+ global::Console[] allConsoles = ShipStatus.Instance.AllConsoles;
+ for (int i = 0; i < allConsoles.Length; i++)
+ {
+ if (this.ValidConsole(allConsoles[i]))
+ {
+ list.Add(allConsoles[i]);
+ }
+ }
+ return list;
+ }
+
+ public static bool PlayerHasHudTask(PlayerControl localPlayer)
+ {
+ if (!localPlayer)
+ {
+ return true;
+ }
+ for (int i = 0; i < localPlayer.myTasks.Count; i++)
+ {
+ if (localPlayer.myTasks[i] is HudOverrideTask)
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ protected List<Vector2> FindObjectsPos()
+ {
+ List<Vector2> list = new List<Vector2>();
+ global::Console[] allConsoles = ShipStatus.Instance.AllConsoles;
+ for (int i = 0; i < allConsoles.Length; i++)
+ {
+ if (this.ValidConsole(allConsoles[i]))
+ {
+ list.Add(allConsoles[i].transform.position);
+ }
+ }
+ return list;
+ }
+
+ protected global::Console FindSpecialConsole(Func<global::Console, bool> func)
+ {
+ global::Console[] allConsoles = ShipStatus.Instance.AllConsoles;
+ for (int i = 0; i < allConsoles.Length; i++)
+ {
+ if (func(allConsoles[i]))
+ {
+ return allConsoles[i];
+ }
+ }
+ return null;
+ }
+
+ protected global::Console FindObjectPos()
+ {
+ global::Console[] allConsoles = ShipStatus.Instance.AllConsoles;
+ for (int i = 0; i < allConsoles.Length; i++)
+ {
+ if (this.ValidConsole(allConsoles[i]))
+ {
+ return allConsoles[i];
+ }
+ }
+ Debug.LogError("Couldn't find location for task: " + base.name);
+ return null;
+ }
+
+ protected static bool AllTasksCompleted(PlayerControl player)
+ {
+ for (int i = 0; i < player.myTasks.Count; i++)
+ {
+ PlayerTask playerTask = player.myTasks[i];
+ if (playerTask is NormalPlayerTask && !playerTask.IsComplete)
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/Client/Assembly-CSharp/PlayerVoteArea.cs b/Client/Assembly-CSharp/PlayerVoteArea.cs
new file mode 100644
index 0000000..d23a2a0
--- /dev/null
+++ b/Client/Assembly-CSharp/PlayerVoteArea.cs
@@ -0,0 +1,211 @@
+using System;
+using System.Collections;
+using Hazel;
+using UnityEngine;
+
+public class PlayerVoteArea : MonoBehaviour
+{
+ public MeetingHud Parent { get; set; }
+
+ public sbyte TargetPlayerId;
+
+ public const byte DeadBit = 128;
+
+ public const byte VotedBit = 64;
+
+ public const byte ReportedBit = 32;
+
+ public const byte VoteMask = 15;
+
+ public GameObject Buttons;
+
+ public SpriteRenderer PlayerIcon;
+
+ public SpriteRenderer Flag;
+
+ public SpriteRenderer Megaphone;
+
+ public SpriteRenderer Overlay;
+
+ public TextRenderer NameText;
+
+ public bool isDead;
+
+ public bool didVote;
+
+ public bool didReport;
+
+ public sbyte votedFor;
+
+ public bool voteComplete;
+
+ public bool resultsShowing;
+
+ public void SetDead(bool isMe, bool didReport, bool isDead)
+ {
+ this.isDead = isDead;
+ this.didReport = didReport;
+ this.Megaphone.enabled = didReport;
+ this.Overlay.gameObject.SetActive(false);
+ this.Overlay.transform.GetChild(0).gameObject.SetActive(isDead);
+ }
+
+ public void SetDisabled()
+ {
+ if (this.isDead)
+ {
+ return;
+ }
+ if (this.Overlay)
+ {
+ this.Overlay.gameObject.SetActive(true);
+ this.Overlay.transform.GetChild(0).gameObject.SetActive(false);
+ return;
+ }
+ base.GetComponent<SpriteRenderer>().enabled = false;
+ }
+
+ public void SetEnabled()
+ {
+ if (this.isDead)
+ {
+ return;
+ }
+ if (this.Overlay)
+ {
+ this.Overlay.gameObject.SetActive(false);
+ return;
+ }
+ base.GetComponent<SpriteRenderer>().enabled = true;
+ }
+
+ public IEnumerator CoAnimateOverlay()
+ {
+ this.Overlay.gameObject.SetActive(this.isDead);
+ if (this.isDead)
+ {
+ Transform xMark = this.Overlay.transform.GetChild(0);
+ this.Overlay.color = Palette.ClearWhite;
+ xMark.localScale = Vector3.zero;
+ float fadeDuration = 0.5f;
+ for (float t = 0f; t < fadeDuration; t += Time.deltaTime)
+ {
+ this.Overlay.color = Color.Lerp(Palette.ClearWhite, Color.white, t / fadeDuration);
+ yield return null;
+ }
+ this.Overlay.color = Color.white;
+ float scaleDuration = 0.15f;
+ for (float t = 0f; t < scaleDuration; t += Time.deltaTime)
+ {
+ float num = Mathf.Lerp(3f, 1f, t / scaleDuration);
+ xMark.transform.localScale = new Vector3(num, num, num);
+ yield return null;
+ }
+ xMark.transform.localScale = Vector3.one;
+ xMark = null;
+ }
+ else if (this.didReport)
+ {
+ float scaleDuration = 1f;
+ for (float fadeDuration = 0f; fadeDuration < scaleDuration; fadeDuration += Time.deltaTime)
+ {
+ float num2 = fadeDuration / scaleDuration;
+ float num3 = PlayerVoteArea.TriangleWave(num2 * 3f) * 2f - 1f;
+ this.Megaphone.transform.localEulerAngles = new Vector3(0f, 0f, num3 * 30f);
+ num3 = Mathf.Lerp(0.7f, 1.2f, PlayerVoteArea.TriangleWave(num2 * 2f));
+ this.Megaphone.transform.localScale = new Vector3(num3, num3, num3);
+ yield return null;
+ }
+ this.Megaphone.transform.localEulerAngles = Vector3.zero;
+ this.Megaphone.transform.localScale = Vector3.one;
+ }
+ yield break;
+ }
+
+ private static float TriangleWave(float t)
+ {
+ t -= (float)((int)t);
+ if (t < 0.5f)
+ {
+ return t * 2f;
+ }
+ return 1f - (t - 0.5f) * 2f;
+ }
+
+ internal void SetVote(sbyte suspectIdx)
+ {
+ this.didVote = true;
+ this.votedFor = suspectIdx;
+ this.Flag.enabled = true;
+ }
+
+ public void UnsetVote()
+ {
+ this.Flag.enabled = false;
+ this.votedFor = 0;
+ this.didVote = false;
+ }
+
+ public void ClearButtons()
+ {
+ this.Buttons.SetActive(false);
+ }
+
+ public void ClearForResults()
+ {
+ this.resultsShowing = true;
+ this.Flag.enabled = false;
+ }
+
+ public void VoteForMe()
+ {
+ if (!this.voteComplete)
+ {
+ this.Parent.Confirm(this.TargetPlayerId);
+ }
+ }
+
+ public void Select()
+ {
+ if (PlayerControl.LocalPlayer.Data.IsDead)
+ {
+ return;
+ }
+ if (this.isDead)
+ {
+ return;
+ }
+ if (!this.voteComplete && this.Parent.Select((int)this.TargetPlayerId))
+ {
+ this.Buttons.SetActive(true);
+ }
+ }
+
+ public void Cancel()
+ {
+ this.Buttons.SetActive(false);
+ }
+
+ public void Serialize(MessageWriter writer)
+ {
+ byte state = this.GetState();
+ writer.Write(state);
+ }
+
+ public void Deserialize(MessageReader reader)
+ {
+ byte b = reader.ReadByte();
+ this.votedFor = (sbyte)((b & 15) - 1);
+ this.isDead = ((b & 128) > 0);
+ this.didVote = ((b & 64) > 0);
+ this.didReport = ((b & 32) > 0);
+ this.Flag.enabled = (this.didVote && !this.resultsShowing);
+ this.Overlay.gameObject.SetActive(this.isDead);
+ this.Megaphone.enabled = this.didReport;
+ }
+
+ public byte GetState()
+ {
+ return (byte)((int)(this.votedFor + 1 & 15) | (this.isDead ? 128 : 0) | (this.didVote ? 64 : 0) | (this.didReport ? 32 : 0));
+ }
+}
diff --git a/Client/Assembly-CSharp/PoolableBehavior.cs b/Client/Assembly-CSharp/PoolableBehavior.cs
new file mode 100644
index 0000000..9f3cc11
--- /dev/null
+++ b/Client/Assembly-CSharp/PoolableBehavior.cs
@@ -0,0 +1,17 @@
+using System;
+using UnityEngine;
+
+public class PoolableBehavior : MonoBehaviour
+{
+ [HideInInspector]
+ public IObjectPool OwnerPool;
+
+ public virtual void Reset()
+ {
+ }
+
+ public void Awake()
+ {
+ this.OwnerPool = DefaultPool.Instance;
+ }
+}
diff --git a/Client/Assembly-CSharp/PoolablePlayer.cs b/Client/Assembly-CSharp/PoolablePlayer.cs
new file mode 100644
index 0000000..5b948fb
--- /dev/null
+++ b/Client/Assembly-CSharp/PoolablePlayer.cs
@@ -0,0 +1,45 @@
+using System;
+using UnityEngine;
+
+public class PoolablePlayer : MonoBehaviour
+{
+ public SpriteRenderer Body;
+
+ public SpriteRenderer[] Hands;
+
+ public SpriteRenderer HatSlot;
+
+ public SpriteRenderer SkinSlot;
+
+ public SpriteRenderer PetSlot;
+
+ public TextRenderer NameText;
+
+ public void SetFlipX(bool flipped)
+ {
+ this.Body.flipX = flipped;
+ this.SkinSlot.flipX = !flipped;
+ this.PetSlot.flipX = flipped;
+ this.HatSlot.flipX = !flipped;
+ if (flipped)
+ {
+ Vector3 localPosition = this.HatSlot.transform.localPosition;
+ localPosition.x = -localPosition.x;
+ this.HatSlot.transform.localPosition = localPosition;
+ }
+ }
+
+ public void SetDeadFlipX(bool flipped)
+ {
+ this.Body.flipX = flipped;
+ this.PetSlot.flipX = flipped;
+ this.HatSlot.flipX = flipped;
+ if (flipped)
+ {
+ Vector3 localPosition = this.HatSlot.transform.localPosition;
+ localPosition.x = -localPosition.x;
+ localPosition.y = 0.725f;
+ this.HatSlot.transform.localPosition = localPosition;
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/PooledMapIcon.cs b/Client/Assembly-CSharp/PooledMapIcon.cs
new file mode 100644
index 0000000..8e75ff3
--- /dev/null
+++ b/Client/Assembly-CSharp/PooledMapIcon.cs
@@ -0,0 +1,35 @@
+using System;
+using UnityEngine;
+
+public class PooledMapIcon : PoolableBehavior
+{
+ public float NormalSize = 0.3f;
+
+ public int lastMapTaskStep = -1;
+
+ public SpriteRenderer rend;
+
+ public AlphaPulse alphaPulse;
+
+ public void Update()
+ {
+ if (this.alphaPulse.enabled)
+ {
+ float num = Mathf.Abs(Mathf.Cos((this.alphaPulse.Offset + Time.time) * 3.1415927f / this.alphaPulse.Duration));
+ if ((double)num > 0.9)
+ {
+ num -= 0.9f;
+ num = this.NormalSize + num;
+ base.transform.localScale = new Vector3(num, num, num);
+ }
+ }
+ }
+
+ public override void Reset()
+ {
+ this.lastMapTaskStep = -1;
+ this.alphaPulse.enabled = false;
+ this.rend.material.SetFloat("_Outline", 0f);
+ base.Reset();
+ }
+}
diff --git a/Client/Assembly-CSharp/PowerTools/SpriteAnim.cs b/Client/Assembly-CSharp/PowerTools/SpriteAnim.cs
new file mode 100644
index 0000000..863854e
--- /dev/null
+++ b/Client/Assembly-CSharp/PowerTools/SpriteAnim.cs
@@ -0,0 +1,255 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+using UnityEngine.UI;
+
+namespace PowerTools
+{
+ [RequireComponent(typeof(Animator))]
+ [DisallowMultipleComponent]
+ public class SpriteAnim : SpriteAnimEventHandler
+ {
+ public bool Playing
+ {
+ get
+ {
+ return this.IsPlaying(null);
+ }
+ }
+
+ public bool Paused
+ {
+ get
+ {
+ return this.IsPaused();
+ }
+ set
+ {
+ if (value)
+ {
+ this.Pause();
+ return;
+ }
+ this.Resume();
+ }
+ }
+
+ public float Speed
+ {
+ get
+ {
+ return this.m_speed;
+ }
+ set
+ {
+ this.SetSpeed(value);
+ }
+ }
+
+ public float Time
+ {
+ get
+ {
+ return this.GetTime();
+ }
+ set
+ {
+ this.SetTime(value);
+ }
+ }
+
+ public float NormalizedTime
+ {
+ get
+ {
+ return this.GetNormalisedTime();
+ }
+ set
+ {
+ this.SetNormalizedTime(value);
+ }
+ }
+
+ public AnimationClip Clip
+ {
+ get
+ {
+ return this.m_currAnim;
+ }
+ }
+
+ public string ClipName
+ {
+ get
+ {
+ if (!(this.m_currAnim != null))
+ {
+ return string.Empty;
+ }
+ return this.m_currAnim.name;
+ }
+ }
+
+ private static readonly string STATE_NAME = "a";
+
+ private static readonly string CONTROLLER_PATH = "SpriteAnimController";
+
+ [SerializeField]
+ private AnimationClip m_defaultAnim;
+
+ private static RuntimeAnimatorController m_sharedAnimatorController = null;
+
+ private Animator m_animator;
+
+ private AnimatorOverrideController m_controller;
+
+ private SpriteAnimNodes m_nodes;
+
+ private List<KeyValuePair<AnimationClip, AnimationClip>> m_clipPairList = new List<KeyValuePair<AnimationClip, AnimationClip>>(1);
+
+ private AnimationClip m_currAnim;
+
+ private float m_speed = 1f;
+
+ public void Play(AnimationClip anim = null, float speed = 1f)
+ {
+ if (anim == null)
+ {
+ anim = (this.m_currAnim ? this.m_currAnim : this.m_defaultAnim);
+ if (anim == null)
+ {
+ return;
+ }
+ }
+ if (!this.m_animator.enabled)
+ {
+ this.m_animator.enabled = true;
+ }
+ if (this.m_nodes != null)
+ {
+ this.m_nodes.Reset();
+ }
+ this.m_clipPairList[0] = new KeyValuePair<AnimationClip, AnimationClip>(this.m_clipPairList[0].Key, anim);
+ this.m_controller.ApplyOverrides(this.m_clipPairList);
+ this.m_animator.Update(0f);
+ this.m_animator.Play(SpriteAnim.STATE_NAME, 0, 0f);
+ this.m_speed = Mathf.Max(0f, speed);
+ this.m_animator.speed = this.m_speed;
+ this.m_currAnim = anim;
+ this.m_animator.Update(0f);
+ }
+
+ public void Stop()
+ {
+ this.m_animator.enabled = false;
+ }
+
+ public void Pause()
+ {
+ this.m_animator.speed = 0f;
+ }
+
+ public void Resume()
+ {
+ this.m_animator.speed = this.m_speed;
+ }
+
+ public AnimationClip GetCurrentAnimation()
+ {
+ return this.m_currAnim;
+ }
+
+ public bool IsPlaying(AnimationClip clip = null)
+ {
+ return (clip == null || this.m_currAnim == clip) && this.m_animator.GetCurrentAnimatorStateInfo(0).normalizedTime < 1f;
+ }
+
+ public bool IsPlaying(string animName)
+ {
+ return !(this.m_currAnim == null) && this.m_currAnim.name == animName && this.m_animator.GetCurrentAnimatorStateInfo(0).normalizedTime < 1f;
+ }
+
+ public bool IsPaused()
+ {
+ return !(this.m_currAnim == null) && this.m_animator.speed == 0f;
+ }
+
+ public void SetSpeed(float speed)
+ {
+ this.m_speed = Mathf.Max(0f, speed);
+ this.m_animator.speed = this.m_speed;
+ }
+
+ public float GetSpeed()
+ {
+ return this.m_speed;
+ }
+
+ public float GetTime()
+ {
+ if (this.m_currAnim != null)
+ {
+ return this.m_animator.GetCurrentAnimatorStateInfo(0).normalizedTime * this.m_currAnim.length;
+ }
+ return 0f;
+ }
+
+ public void SetTime(float time)
+ {
+ if (this.m_currAnim == null || this.m_currAnim.length <= 0f)
+ {
+ return;
+ }
+ this.SetNormalizedTime(time / this.m_currAnim.length);
+ }
+
+ public float GetNormalisedTime()
+ {
+ if (this.m_currAnim != null)
+ {
+ return this.m_animator.GetCurrentAnimatorStateInfo(0).normalizedTime;
+ }
+ return 0f;
+ }
+
+ public void SetNormalizedTime(float ratio)
+ {
+ if (this.m_currAnim == null)
+ {
+ return;
+ }
+ this.m_animator.Play(SpriteAnim.STATE_NAME, 0, this.m_currAnim.isLooping ? Mathf.Repeat(ratio, 1f) : Mathf.Clamp01(ratio));
+ }
+
+ private void Awake()
+ {
+ this.m_controller = new AnimatorOverrideController();
+ if (SpriteAnim.m_sharedAnimatorController == null)
+ {
+ SpriteAnim.m_sharedAnimatorController = Resources.Load<RuntimeAnimatorController>(SpriteAnim.CONTROLLER_PATH);
+ }
+ this.m_controller.runtimeAnimatorController = SpriteAnim.m_sharedAnimatorController;
+ this.m_animator = base.GetComponent<Animator>();
+ this.m_animator.runtimeAnimatorController = this.m_controller;
+ this.m_controller.GetOverrides(this.m_clipPairList);
+ this.Play(this.m_defaultAnim, 1f);
+ this.m_nodes = base.GetComponent<SpriteAnimNodes>();
+ }
+
+ private void Reset()
+ {
+ if (base.GetComponent<RectTransform>() == null)
+ {
+ if (base.GetComponent<Sprite>() == null)
+ {
+ base.gameObject.AddComponent<SpriteRenderer>();
+ return;
+ }
+ }
+ else if (base.GetComponent<Image>() == null)
+ {
+ base.gameObject.AddComponent<Image>();
+ }
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/PowerTools/SpriteAnimEventHandler.cs b/Client/Assembly-CSharp/PowerTools/SpriteAnimEventHandler.cs
new file mode 100644
index 0000000..b97b8c2
--- /dev/null
+++ b/Client/Assembly-CSharp/PowerTools/SpriteAnimEventHandler.cs
@@ -0,0 +1,111 @@
+using System;
+using UnityEngine;
+
+namespace PowerTools
+{
+ [DisallowMultipleComponent]
+ public class SpriteAnimEventHandler : MonoBehaviour
+ {
+ private string m_eventWithObjectMessage;
+
+ private object m_eventWithObjectData;
+
+ public static class EventParser
+ {
+ public static readonly char MESSAGE_DELIMITER = '\t';
+
+ public static readonly string MESSAGE_NOPARAM = "_Anim";
+
+ public static readonly string MESSAGE_INT = "_AnimInt";
+
+ public static readonly string MESSAGE_FLOAT = "_AnimFloat";
+
+ public static readonly string MESSAGE_STRING = "_AnimString";
+
+ public static readonly string MESSAGE_OBJECT_FUNCNAME = "_AnimObjectFunc";
+
+ public static readonly string MESSAGE_OBJECT_DATA = "_AnimObjectData";
+
+ public static int ParseInt(ref string messageString)
+ {
+ int num = messageString.IndexOf(SpriteAnimEventHandler.EventParser.MESSAGE_DELIMITER);
+ int result = 0;
+ int.TryParse(messageString.Substring(num + 1), out result);
+ messageString = messageString.Substring(0, num);
+ return result;
+ }
+
+ public static float ParseFloat(ref string messageString)
+ {
+ int num = messageString.IndexOf(SpriteAnimEventHandler.EventParser.MESSAGE_DELIMITER);
+ float result = 0f;
+ float.TryParse(messageString.Substring(num + 1), out result);
+ messageString = messageString.Substring(0, num);
+ return result;
+ }
+
+ public static string ParseString(ref string messageString)
+ {
+ int num = messageString.IndexOf(SpriteAnimEventHandler.EventParser.MESSAGE_DELIMITER);
+ string result = messageString.Substring(num + 1);
+ messageString = messageString.Substring(0, num);
+ return result;
+ }
+ }
+
+ private void _Anim(string function)
+ {
+ base.SendMessageUpwards(function, SendMessageOptions.DontRequireReceiver);
+ }
+
+ private void _AnimInt(string messageString)
+ {
+ int num = SpriteAnimEventHandler.EventParser.ParseInt(ref messageString);
+ base.SendMessageUpwards(messageString, num, SendMessageOptions.DontRequireReceiver);
+ }
+
+ private void _AnimFloat(string messageString)
+ {
+ float num = SpriteAnimEventHandler.EventParser.ParseFloat(ref messageString);
+ base.SendMessageUpwards(messageString, num, SendMessageOptions.DontRequireReceiver);
+ }
+
+ private void _AnimString(string messageString)
+ {
+ string value = SpriteAnimEventHandler.EventParser.ParseString(ref messageString);
+ base.SendMessageUpwards(messageString, value, SendMessageOptions.DontRequireReceiver);
+ }
+
+ private void _AnimObjectFunc(string funcName)
+ {
+ if (this.m_eventWithObjectData != null)
+ {
+ base.SendMessageUpwards(funcName, this.m_eventWithObjectData, SendMessageOptions.DontRequireReceiver);
+ this.m_eventWithObjectMessage = null;
+ this.m_eventWithObjectData = null;
+ return;
+ }
+ if (!string.IsNullOrEmpty(this.m_eventWithObjectMessage))
+ {
+ Debug.LogError("Animation event with object parameter had no object");
+ }
+ this.m_eventWithObjectMessage = funcName;
+ }
+
+ private void _AnimObjectData(UnityEngine.Object data)
+ {
+ if (!string.IsNullOrEmpty(this.m_eventWithObjectMessage))
+ {
+ base.SendMessageUpwards(this.m_eventWithObjectMessage, data, SendMessageOptions.DontRequireReceiver);
+ this.m_eventWithObjectMessage = null;
+ this.m_eventWithObjectData = null;
+ return;
+ }
+ if (this.m_eventWithObjectData != null)
+ {
+ Debug.LogError("Animation event with object parameter had no object");
+ }
+ this.m_eventWithObjectData = data;
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/PowerTools/SpriteAnimNodeSync.cs b/Client/Assembly-CSharp/PowerTools/SpriteAnimNodeSync.cs
new file mode 100644
index 0000000..e5fad22
--- /dev/null
+++ b/Client/Assembly-CSharp/PowerTools/SpriteAnimNodeSync.cs
@@ -0,0 +1,36 @@
+using System;
+using UnityEngine;
+
+namespace PowerTools
+{
+ public class SpriteAnimNodeSync : MonoBehaviour
+ {
+ public int NodeId;
+
+ public SpriteAnimNodes Parent;
+
+ public SpriteRenderer ParentRenderer;
+
+ public SpriteRenderer Renderer;
+
+ public void LateUpdate()
+ {
+ if (this.Renderer && this.ParentRenderer)
+ {
+ this.Renderer.flipX = this.ParentRenderer.flipX;
+ }
+ Vector3 localPosition = base.transform.localPosition;
+ Vector3 localPosition2 = this.Parent.GetLocalPosition(this.NodeId, false);
+ localPosition.x = localPosition2.x;
+ localPosition.y = localPosition2.y;
+ base.transform.localPosition = localPosition;
+ float angle = this.Parent.GetAngle(this.NodeId);
+ if (!this.Renderer || !this.Renderer.flipX)
+ {
+ base.transform.eulerAngles = new Vector3(0f, 0f, angle);
+ return;
+ }
+ base.transform.eulerAngles = new Vector3(0f, 0f, -angle);
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/PowerTools/SpriteAnimNodes.cs b/Client/Assembly-CSharp/PowerTools/SpriteAnimNodes.cs
new file mode 100644
index 0000000..2669ac1
--- /dev/null
+++ b/Client/Assembly-CSharp/PowerTools/SpriteAnimNodes.cs
@@ -0,0 +1,232 @@
+using System;
+using UnityEngine;
+
+namespace PowerTools
+{
+ public class SpriteAnimNodes : MonoBehaviour
+ {
+ public static readonly int NUM_NODES = 10;
+
+ [SerializeField]
+ [HideInInspector]
+ private Vector2 m_node0 = Vector2.zero;
+
+ [SerializeField]
+ [HideInInspector]
+ private Vector2 m_node1 = Vector2.zero;
+
+ [SerializeField]
+ [HideInInspector]
+ private Vector2 m_node2 = Vector2.zero;
+
+ [SerializeField]
+ [HideInInspector]
+ private Vector2 m_node3 = Vector2.zero;
+
+ [SerializeField]
+ [HideInInspector]
+ private Vector2 m_node4 = Vector2.zero;
+
+ [SerializeField]
+ [HideInInspector]
+ private Vector2 m_node5 = Vector2.zero;
+
+ [SerializeField]
+ [HideInInspector]
+ private Vector2 m_node6 = Vector2.zero;
+
+ [SerializeField]
+ [HideInInspector]
+ private Vector2 m_node7 = Vector2.zero;
+
+ [SerializeField]
+ [HideInInspector]
+ private Vector2 m_node8 = Vector2.zero;
+
+ [SerializeField]
+ [HideInInspector]
+ private Vector2 m_node9 = Vector2.zero;
+
+ [SerializeField]
+ [HideInInspector]
+ private float m_ang0;
+
+ [SerializeField]
+ [HideInInspector]
+ private float m_ang1;
+
+ [SerializeField]
+ [HideInInspector]
+ private float m_ang2;
+
+ [SerializeField]
+ [HideInInspector]
+ private float m_ang3;
+
+ [SerializeField]
+ [HideInInspector]
+ private float m_ang4;
+
+ [SerializeField]
+ [HideInInspector]
+ private float m_ang5;
+
+ [SerializeField]
+ [HideInInspector]
+ private float m_ang6;
+
+ [SerializeField]
+ [HideInInspector]
+ private float m_ang7;
+
+ [SerializeField]
+ [HideInInspector]
+ private float m_ang8;
+
+ [SerializeField]
+ [HideInInspector]
+ private float m_ang9;
+
+ private SpriteRenderer m_spriteRenderer;
+
+ public Vector3 GetPosition(int nodeId, bool ignoredPivot = false)
+ {
+ if (this.m_spriteRenderer == null)
+ {
+ this.m_spriteRenderer = base.GetComponent<SpriteRenderer>();
+ }
+ if (this.m_spriteRenderer == null || this.m_spriteRenderer.sprite == null)
+ {
+ return Vector2.zero;
+ }
+ Vector3 vector = this.GetLocalPosition(nodeId, ignoredPivot);
+ vector = base.transform.rotation * vector;
+ vector.Scale(base.transform.lossyScale);
+ return vector + base.transform.position;
+ }
+
+ public Vector3 GetLocalPosition(int nodeId, bool ignoredPivot = false)
+ {
+ if (this.m_spriteRenderer == null)
+ {
+ this.m_spriteRenderer = base.GetComponent<SpriteRenderer>();
+ }
+ if (this.m_spriteRenderer == null || this.m_spriteRenderer.sprite == null)
+ {
+ return Vector2.zero;
+ }
+ Vector3 vector = this.GetPositionRaw(nodeId);
+ vector.y = -vector.y;
+ if (ignoredPivot)
+ {
+ vector += this.m_spriteRenderer.sprite.rect.size * 0.5f - this.m_spriteRenderer.sprite.pivot;
+ }
+ vector *= 1f / this.m_spriteRenderer.sprite.pixelsPerUnit;
+ if (this.m_spriteRenderer.flipX)
+ {
+ vector.x = -vector.x;
+ }
+ if (this.m_spriteRenderer.flipY)
+ {
+ vector.y = -vector.y;
+ }
+ return vector;
+ }
+
+ public float GetAngle(int nodeId)
+ {
+ float angleRaw = this.GetAngleRaw(nodeId);
+ if (this.m_spriteRenderer == null)
+ {
+ this.m_spriteRenderer = base.GetComponent<SpriteRenderer>();
+ }
+ if (this.m_spriteRenderer == null || this.m_spriteRenderer.sprite == null)
+ {
+ return 0f;
+ }
+ return angleRaw + base.transform.eulerAngles.z;
+ }
+
+ public Vector2 GetPositionRaw(int nodeId)
+ {
+ switch (nodeId)
+ {
+ case 0:
+ return this.m_node0;
+ case 1:
+ return this.m_node1;
+ case 2:
+ return this.m_node2;
+ case 3:
+ return this.m_node3;
+ case 4:
+ return this.m_node4;
+ case 5:
+ return this.m_node5;
+ case 6:
+ return this.m_node6;
+ case 7:
+ return this.m_node7;
+ case 8:
+ return this.m_node8;
+ case 9:
+ return this.m_node9;
+ default:
+ return Vector2.zero;
+ }
+ }
+
+ public float GetAngleRaw(int nodeId)
+ {
+ switch (nodeId)
+ {
+ case 0:
+ return this.m_ang0;
+ case 1:
+ return this.m_ang1;
+ case 2:
+ return this.m_ang2;
+ case 3:
+ return this.m_ang3;
+ case 4:
+ return this.m_ang4;
+ case 5:
+ return this.m_ang5;
+ case 6:
+ return this.m_ang6;
+ case 7:
+ return this.m_ang7;
+ case 8:
+ return this.m_ang8;
+ case 9:
+ return this.m_ang9;
+ default:
+ return 0f;
+ }
+ }
+
+ public void Reset()
+ {
+ this.m_node0 = Vector2.zero;
+ this.m_node1 = Vector2.zero;
+ this.m_node2 = Vector2.zero;
+ this.m_node3 = Vector2.zero;
+ this.m_node4 = Vector2.zero;
+ this.m_node5 = Vector2.zero;
+ this.m_node6 = Vector2.zero;
+ this.m_node7 = Vector2.zero;
+ this.m_node8 = Vector2.zero;
+ this.m_node9 = Vector2.zero;
+ this.m_ang0 = 0f;
+ this.m_ang1 = 0f;
+ this.m_ang2 = 0f;
+ this.m_ang3 = 0f;
+ this.m_ang4 = 0f;
+ this.m_ang5 = 0f;
+ this.m_ang6 = 0f;
+ this.m_ang7 = 0f;
+ this.m_ang8 = 0f;
+ this.m_ang9 = 0f;
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/PowerTools/WaitForAnimationFinish.cs b/Client/Assembly-CSharp/PowerTools/WaitForAnimationFinish.cs
new file mode 100644
index 0000000..f4f083c
--- /dev/null
+++ b/Client/Assembly-CSharp/PowerTools/WaitForAnimationFinish.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Collections;
+using UnityEngine;
+
+namespace PowerTools
+{
+ public class WaitForAnimationFinish : IEnumerator
+ {
+ public object Current
+ {
+ get
+ {
+ return null;
+ }
+ }
+
+ private SpriteAnim animator;
+
+ private AnimationClip clip;
+
+ private bool first = true;
+
+ public WaitForAnimationFinish(SpriteAnim animator, AnimationClip clip)
+ {
+ this.animator = animator;
+ this.clip = clip;
+ this.animator.Play(this.clip, 1f);
+ }
+
+ public bool MoveNext()
+ {
+ if (this.first)
+ {
+ this.first = false;
+ return true;
+ }
+ bool result;
+ try
+ {
+ result = this.animator.IsPlaying(this.clip);
+ }
+ catch
+ {
+ result = false;
+ }
+ return result;
+ }
+
+ public void Reset()
+ {
+ this.first = true;
+ this.animator.Play(this.clip, 1f);
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/ProgressTracker.cs b/Client/Assembly-CSharp/ProgressTracker.cs
new file mode 100644
index 0000000..60cae92
--- /dev/null
+++ b/Client/Assembly-CSharp/ProgressTracker.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Linq;
+using UnityEngine;
+
+public class ProgressTracker : MonoBehaviour
+{
+ public MeshRenderer TileParent;
+
+ private float curValue;
+
+ public void Start()
+ {
+ this.TileParent.material.SetFloat("_Buckets", 1f);
+ this.TileParent.material.SetFloat("_FullBuckets", 0f);
+ }
+
+ public void FixedUpdate()
+ {
+ if (PlayerTask.PlayerHasHudTask(PlayerControl.LocalPlayer))
+ {
+ this.TileParent.enabled = false;
+ return;
+ }
+ if (!this.TileParent.enabled)
+ {
+ this.TileParent.enabled = true;
+ }
+ GameData instance = GameData.Instance;
+ if (instance && instance.TotalTasks > 0)
+ {
+ int num = DestroyableSingleton<TutorialManager>.InstanceExists ? 1 : (instance.AllPlayers.Count - PlayerControl.GameOptions.NumImpostors);
+ num -= instance.AllPlayers.Count((GameData.PlayerInfo p) => p.Disconnected);
+ float b = (float)instance.CompletedTasks / (float)instance.TotalTasks * (float)num;
+ this.curValue = Mathf.Lerp(this.curValue, b, Time.fixedDeltaTime * 2f);
+ this.TileParent.material.SetFloat("_Buckets", (float)num);
+ this.TileParent.material.SetFloat("_FullBuckets", this.curValue);
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/Properties/AssemblyInfo.cs b/Client/Assembly-CSharp/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..4480267
--- /dev/null
+++ b/Client/Assembly-CSharp/Properties/AssemblyInfo.cs
@@ -0,0 +1,6 @@
+using System;
+using System.Diagnostics;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+
+[assembly: AssemblyVersion("0.0.0.0")]
diff --git a/Client/Assembly-CSharp/PurchaseButton.cs b/Client/Assembly-CSharp/PurchaseButton.cs
new file mode 100644
index 0000000..56c3171
--- /dev/null
+++ b/Client/Assembly-CSharp/PurchaseButton.cs
@@ -0,0 +1,90 @@
+using System;
+using UnityEngine;
+
+public class PurchaseButton : MonoBehaviour
+{
+ public StoreMenu Parent { get; set; }
+
+ public SpriteRenderer PurchasedIcon;
+
+ public TextRenderer NameText;
+
+ public SpriteRenderer HatImage;
+
+ public Sprite MannequinFrame;
+
+ public SpriteRenderer Background;
+
+ public IBuyable Product;
+
+ public bool Purchased;
+
+ public string Name;
+
+ public string Price;
+
+ public string ProductId;
+
+ public void SetItem(IBuyable product, string productId, string name, string price, bool purchased)
+ {
+ this.Product = product;
+ this.Purchased = purchased;
+ this.Name = name;
+ this.Price = price;
+ this.ProductId = productId;
+ this.PurchasedIcon.enabled = this.Purchased;
+ if (this.Product is HatBehaviour)
+ {
+ HatBehaviour hat = (HatBehaviour)this.Product;
+ this.NameText.gameObject.SetActive(false);
+ this.HatImage.transform.parent.gameObject.SetActive(true);
+ PlayerControl.SetHatImage(hat, this.HatImage);
+ this.SetSquare();
+ return;
+ }
+ if (this.Product is SkinData)
+ {
+ SkinData skin = (SkinData)this.Product;
+ this.NameText.gameObject.SetActive(false);
+ this.HatImage.transform.parent.gameObject.SetActive(true);
+ this.HatImage.transform.parent.GetComponent<SpriteRenderer>().sprite = this.MannequinFrame;
+ this.HatImage.transform.parent.localPosition = new Vector3(0f, 0f, -0.01f);
+ this.HatImage.transform.parent.localScale = Vector3.one * 0.3f;
+ this.HatImage.transform.localPosition = new Vector3(0f, 0f, -0.01f);
+ this.HatImage.transform.localScale = Vector3.one * 2f;
+ PlayerControl.SetSkinImage(skin, this.HatImage);
+ this.SetSquare();
+ return;
+ }
+ if (this.Product is PetBehaviour)
+ {
+ PetBehaviour petBehaviour = (PetBehaviour)this.Product;
+ this.NameText.gameObject.SetActive(false);
+ this.HatImage.transform.parent.gameObject.SetActive(true);
+ this.HatImage.transform.parent.GetComponent<SpriteRenderer>().enabled = false;
+ this.HatImage.material = new Material(petBehaviour.rend.sharedMaterial);
+ PlayerControl.SetPetImage(petBehaviour, (int)SaveManager.BodyColor, this.HatImage);
+ this.SetSquare();
+ return;
+ }
+ this.NameText.Text = this.Name;
+ }
+
+ private void SetSquare()
+ {
+ this.Background.size = new Vector2(0.7f, 0.7f);
+ this.Background.GetComponent<BoxCollider2D>().size = new Vector2(0.7f, 0.7f);
+ this.PurchasedIcon.transform.localPosition = new Vector3(0f, 0f, -1f);
+ }
+
+ internal void SetPurchased()
+ {
+ this.Purchased = true;
+ this.PurchasedIcon.enabled = true;
+ }
+
+ public void DoPurchase()
+ {
+ this.Parent.SetProduct(this);
+ }
+}
diff --git a/Client/Assembly-CSharp/PurchaseStates.cs b/Client/Assembly-CSharp/PurchaseStates.cs
new file mode 100644
index 0000000..e2ceb83
--- /dev/null
+++ b/Client/Assembly-CSharp/PurchaseStates.cs
@@ -0,0 +1,9 @@
+using System;
+
+public enum PurchaseStates
+{
+ NotStarted,
+ Started,
+ Success,
+ Fail
+}
diff --git a/Client/Assembly-CSharp/RadioWaveBehaviour.cs b/Client/Assembly-CSharp/RadioWaveBehaviour.cs
new file mode 100644
index 0000000..c46234f
--- /dev/null
+++ b/Client/Assembly-CSharp/RadioWaveBehaviour.cs
@@ -0,0 +1,81 @@
+using System;
+using UnityEngine;
+
+[RequireComponent(typeof(MeshFilter))]
+[RequireComponent(typeof(MeshRenderer))]
+public class RadioWaveBehaviour : MonoBehaviour
+{
+ public int NumPoints = 128;
+
+ public FloatRange Width;
+
+ public FloatRange Height;
+
+ private Mesh mesh;
+
+ private Vector3[] vecs;
+
+ public float TickRate = 0.1f;
+
+ private float timer;
+
+ public int Skip = 2;
+
+ public float Frequency = 5f;
+
+ private int Tick;
+
+ public bool Random;
+
+ [Range(0f, 1f)]
+ public float NoiseLevel;
+
+ public void Start()
+ {
+ this.mesh = new Mesh();
+ base.GetComponent<MeshFilter>().mesh = this.mesh;
+ this.mesh.MarkDynamic();
+ this.vecs = new Vector3[this.NumPoints];
+ int[] array = new int[this.NumPoints];
+ for (int i = 0; i < this.vecs.Length; i++)
+ {
+ Vector3 vector = this.vecs[i];
+ vector.x = this.Width.Lerp((float)i / (float)this.vecs.Length);
+ vector.y = this.Height.Next();
+ this.vecs[i] = vector;
+ array[i] = i;
+ }
+ this.mesh.vertices = this.vecs;
+ this.mesh.SetIndices(array, MeshTopology.LineStrip, 0);
+ }
+
+ public void Update()
+ {
+ this.timer += Time.deltaTime;
+ if (this.timer > this.TickRate)
+ {
+ this.timer = 0f;
+ this.Tick++;
+ for (int i = 0; i < this.vecs.Length - this.Skip; i++)
+ {
+ this.vecs[i].y = this.vecs[i + this.Skip].y;
+ }
+ if (this.Random)
+ {
+ for (int j = 1; j <= this.Skip; j++)
+ {
+ this.vecs[this.vecs.Length - j].y = this.Height.Next();
+ }
+ }
+ else
+ {
+ float num = 1f - this.NoiseLevel;
+ for (int k = 0; k < this.Skip; k++)
+ {
+ this.vecs[this.vecs.Length - 1 - this.Skip + k].y = Mathf.Sin(((float)this.Tick + (float)k / (float)this.Skip) * this.Frequency) * this.Height.max * num + this.Height.Next() * this.NoiseLevel;
+ }
+ }
+ this.mesh.vertices = this.vecs;
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/RandomFill.cs b/Client/Assembly-CSharp/RandomFill.cs
new file mode 100644
index 0000000..352eafd
--- /dev/null
+++ b/Client/Assembly-CSharp/RandomFill.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+public class RandomFill<T>
+{
+ private T[] values;
+
+ private int idx;
+
+ public void Set(IEnumerable<T> values)
+ {
+ if (this.values == null)
+ {
+ this.values = values.ToArray<T>();
+ this.values.Shuffle<T>();
+ this.idx = this.values.Length - 1;
+ }
+ }
+
+ public T Get()
+ {
+ if (this.idx < 0)
+ {
+ this.values.Shuffle<T>();
+ this.idx = this.values.Length - 1;
+ }
+ T[] array = this.values;
+ int num = this.idx;
+ this.idx = num - 1;
+ return array[num];
+ }
+}
diff --git a/Client/Assembly-CSharp/ReactorMinigame.cs b/Client/Assembly-CSharp/ReactorMinigame.cs
new file mode 100644
index 0000000..21e1551
--- /dev/null
+++ b/Client/Assembly-CSharp/ReactorMinigame.cs
@@ -0,0 +1,110 @@
+using System;
+using UnityEngine;
+
+public class ReactorMinigame : Minigame
+{
+ private Color bad = new Color(1f, 0.16078432f, 0f);
+
+ private Color good = new Color(0.3019608f, 0.8862745f, 0.8352941f);
+
+ private ReactorSystemType reactor;
+
+ public TextRenderer statusText;
+
+ public SpriteRenderer hand;
+
+ private FloatRange YSweep = new FloatRange(-2.15f, 1.56f);
+
+ public SpriteRenderer sweeper;
+
+ public AudioClip HandSound;
+
+ private bool isButtonDown;
+
+ public override void Begin(PlayerTask task)
+ {
+ ShipStatus instance = ShipStatus.Instance;
+ if (!instance)
+ {
+ this.reactor = new ReactorSystemType();
+ }
+ else
+ {
+ this.reactor = (instance.Systems[SystemTypes.Reactor] as ReactorSystemType);
+ }
+ this.hand.color = this.bad;
+ }
+
+ public void ButtonDown()
+ {
+ if (PlayerControl.LocalPlayer.Data.IsImpostor)
+ {
+ return;
+ }
+ if (!this.reactor.IsActive)
+ {
+ return;
+ }
+ this.isButtonDown = !this.isButtonDown;
+ if (this.isButtonDown)
+ {
+ if (Constants.ShouldPlaySfx())
+ {
+ SoundManager.Instance.PlaySound(this.HandSound, true, 1f);
+ }
+ ShipStatus.Instance.RpcRepairSystem(SystemTypes.Reactor, (int)((byte)(64 | base.ConsoleId)));
+ }
+ else
+ {
+ SoundManager.Instance.StopSound(this.HandSound);
+ ShipStatus.Instance.RpcRepairSystem(SystemTypes.Reactor, (int)((byte)(32 | base.ConsoleId)));
+ }
+ try
+ {
+ ((SabotageTask)this.MyTask).MarkContributed();
+ }
+ catch
+ {
+ }
+ }
+
+ public void FixedUpdate()
+ {
+ if (!this.reactor.IsActive)
+ {
+ if (this.amClosing == Minigame.CloseState.None)
+ {
+ this.hand.color = this.good;
+ this.statusText.Text = "Reactor Nominal";
+ this.sweeper.enabled = false;
+ SoundManager.Instance.StopSound(this.HandSound);
+ base.StartCoroutine(base.CoStartClose(0.75f));
+ return;
+ }
+ }
+ else
+ {
+ if (!this.isButtonDown)
+ {
+ this.statusText.Text = "Hold to stop meltdown";
+ this.sweeper.enabled = false;
+ return;
+ }
+ this.statusText.Text = "Waiting for second user";
+ Vector3 localPosition = this.sweeper.transform.localPosition;
+ localPosition.y = this.YSweep.Lerp(Mathf.Sin(Time.time) * 0.5f + 0.5f);
+ this.sweeper.transform.localPosition = localPosition;
+ this.sweeper.enabled = true;
+ }
+ }
+
+ public override void Close()
+ {
+ SoundManager.Instance.StopSound(this.HandSound);
+ if (ShipStatus.Instance)
+ {
+ ShipStatus.Instance.RpcRepairSystem(SystemTypes.Reactor, (int)((byte)(32 | base.ConsoleId)));
+ }
+ base.Close();
+ }
+}
diff --git a/Client/Assembly-CSharp/ReactorRoomWire.cs b/Client/Assembly-CSharp/ReactorRoomWire.cs
new file mode 100644
index 0000000..6f65fb9
--- /dev/null
+++ b/Client/Assembly-CSharp/ReactorRoomWire.cs
@@ -0,0 +1,51 @@
+using System;
+using PowerTools;
+using UnityEngine;
+
+public class ReactorRoomWire : MonoBehaviour
+{
+ public global::Console myConsole;
+
+ public SpriteAnim Image;
+
+ public AnimationClip Normal;
+
+ public AnimationClip MeltdownNeed;
+
+ public AnimationClip MeltdownReady;
+
+ private ReactorSystemType reactor;
+
+ public void Update()
+ {
+ if (this.reactor == null)
+ {
+ ISystemType systemType;
+ if (!ShipStatus.Instance || !ShipStatus.Instance.Systems.TryGetValue(SystemTypes.Reactor, out systemType))
+ {
+ return;
+ }
+ this.reactor = (ReactorSystemType)systemType;
+ }
+ if (this.reactor.IsActive)
+ {
+ if (this.reactor.GetConsoleComplete(this.myConsole.ConsoleId))
+ {
+ if (!this.Image.IsPlaying(this.MeltdownReady))
+ {
+ this.Image.Play(this.MeltdownReady, 1f);
+ return;
+ }
+ }
+ else if (!this.Image.IsPlaying(this.MeltdownNeed))
+ {
+ this.Image.Play(this.MeltdownNeed, 1f);
+ return;
+ }
+ }
+ else if (!this.Image.IsPlaying(this.Normal))
+ {
+ this.Image.Play(this.Normal, 1f);
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/ReactorShipRoom.cs b/Client/Assembly-CSharp/ReactorShipRoom.cs
new file mode 100644
index 0000000..c1975c7
--- /dev/null
+++ b/Client/Assembly-CSharp/ReactorShipRoom.cs
@@ -0,0 +1,74 @@
+using System;
+using PowerTools;
+using UnityEngine;
+
+public class ReactorShipRoom : ShipRoom
+{
+ public Sprite normalManifolds;
+
+ public Sprite meltdownManifolds;
+
+ public SpriteRenderer Manifolds;
+
+ public AnimationClip normalReactor;
+
+ public AnimationClip meltdownReactor;
+
+ public SpriteAnim Reactor;
+
+ public AnimationClip normalHighFloor;
+
+ public AnimationClip meltdownHighFloor;
+
+ public SpriteAnim HighFloor;
+
+ public AnimationClip normalMidFloor;
+
+ public AnimationClip meltdownMidFloor;
+
+ public SpriteAnim MidFloor1;
+
+ public SpriteAnim MidFloor2;
+
+ public AnimationClip normalLowFloor;
+
+ public AnimationClip meltdownLowFloor;
+
+ public SpriteAnim LowFloor;
+
+ public AnimationClip[] normalPipes;
+
+ public AnimationClip[] meltdownPipes;
+
+ public SpriteAnim[] Pipes;
+
+ public void StartMeltdown()
+ {
+ DestroyableSingleton<HudManager>.Instance.StartReactorFlash();
+ this.Manifolds.sprite = this.meltdownManifolds;
+ this.Reactor.Play(this.meltdownReactor, 1f);
+ this.HighFloor.Play(this.meltdownHighFloor, 1f);
+ this.MidFloor1.Play(this.meltdownMidFloor, 1f);
+ this.MidFloor2.Play(this.meltdownMidFloor, 1f);
+ this.LowFloor.Play(this.meltdownLowFloor, 1f);
+ for (int i = 0; i < this.Pipes.Length; i++)
+ {
+ this.Pipes[i].Play(this.meltdownPipes[i], 1f);
+ }
+ }
+
+ public void StopMeltdown()
+ {
+ DestroyableSingleton<HudManager>.Instance.StopReactorFlash();
+ this.Manifolds.sprite = this.normalManifolds;
+ this.Reactor.Play(this.normalReactor, 1f);
+ this.HighFloor.Play(this.normalHighFloor, 1f);
+ this.MidFloor1.Play(this.normalMidFloor, 1f);
+ this.MidFloor2.Play(this.normalMidFloor, 1f);
+ this.LowFloor.Play(this.normalLowFloor, 1f);
+ for (int i = 0; i < this.Pipes.Length; i++)
+ {
+ this.Pipes[i].Play(this.normalPipes[i], 1f);
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/ReactorSystemType.cs b/Client/Assembly-CSharp/ReactorSystemType.cs
new file mode 100644
index 0000000..a1e1fa0
--- /dev/null
+++ b/Client/Assembly-CSharp/ReactorSystemType.cs
@@ -0,0 +1,137 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Hazel;
+
+public class ReactorSystemType : ISystemType, IActivatable
+{
+ public int UserCount
+ {
+ get
+ {
+ int num = 0;
+ int num2 = 0;
+ foreach (Tuple<byte, byte> tuple in this.UserConsolePairs)
+ {
+ int num3 = 1 << (int)tuple.Item2;
+ if ((num3 & num2) == 0)
+ {
+ num++;
+ num2 |= num3;
+ }
+ }
+ return num;
+ }
+ }
+
+ public bool IsActive
+ {
+ get
+ {
+ return this.Countdown < 10000f;
+ }
+ }
+
+ private const float SyncRate = 2f;
+
+ private float timer;
+
+ public const byte StartCountdown = 128;
+
+ public const byte AddUserOp = 64;
+
+ public const byte RemoveUserOp = 32;
+
+ public const byte ClearCountdown = 16;
+
+ public const float CountdownStopped = 10000f;
+
+ public const float ReactorDuration = 30f;
+
+ public const byte ConsoleIdMask = 3;
+
+ public const byte RequiredUserCount = 2;
+
+ public float Countdown = 10000f;
+
+ private HashSet<Tuple<byte, byte>> UserConsolePairs = new HashSet<Tuple<byte, byte>>();
+
+ public bool GetConsoleComplete(int consoleId)
+ {
+ return this.UserConsolePairs.Any((Tuple<byte, byte> kvp) => (int)kvp.Item2 == consoleId);
+ }
+
+ public void RepairDamage(PlayerControl player, byte opCode)
+ {
+ int num = (int)(opCode & 3);
+ if (opCode == 128 && !this.IsActive)
+ {
+ this.Countdown = 30f;
+ this.UserConsolePairs.Clear();
+ return;
+ }
+ if (opCode == 16)
+ {
+ this.Countdown = 10000f;
+ return;
+ }
+ if (opCode.HasAnyBit(64))
+ {
+ this.UserConsolePairs.Add(new Tuple<byte, byte>(player.PlayerId, (byte)num));
+ if (this.UserCount >= 2)
+ {
+ this.Countdown = 10000f;
+ return;
+ }
+ }
+ else if (opCode.HasAnyBit(32))
+ {
+ this.UserConsolePairs.Remove(new Tuple<byte, byte>(player.PlayerId, (byte)num));
+ }
+ }
+
+ public bool Detoriorate(float deltaTime)
+ {
+ if (this.IsActive)
+ {
+ if (DestroyableSingleton<HudManager>.Instance.ReactorFlash == null)
+ {
+ PlayerControl.LocalPlayer.AddSystemTask(SystemTypes.Reactor);
+ }
+ this.Countdown -= deltaTime;
+ this.timer += deltaTime;
+ if (this.timer > 2f)
+ {
+ this.timer = 0f;
+ return true;
+ }
+ }
+ else if (DestroyableSingleton<HudManager>.Instance.ReactorFlash != null)
+ {
+ ((ReactorShipRoom)ShipStatus.Instance.AllRooms.First((ShipRoom r) => r.RoomId == SystemTypes.Reactor)).StopMeltdown();
+ }
+ return false;
+ }
+
+ public void Serialize(MessageWriter writer, bool initialState)
+ {
+ writer.Write(this.Countdown);
+ writer.WritePacked(this.UserConsolePairs.Count);
+ foreach (Tuple<byte, byte> tuple in this.UserConsolePairs)
+ {
+ writer.Write(tuple.Item1);
+ writer.Write(tuple.Item2);
+ }
+ }
+
+ public void Deserialize(MessageReader reader, bool initialState)
+ {
+ this.Countdown = reader.ReadSingle();
+ this.UserConsolePairs.Clear();
+ int num = reader.ReadPackedInt32();
+ for (int i = 0; i < num; i++)
+ {
+ this.UserConsolePairs.Add(new Tuple<byte, byte>(reader.ReadByte(), reader.ReadByte()));
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/ReactorTask.cs b/Client/Assembly-CSharp/ReactorTask.cs
new file mode 100644
index 0000000..43f44ba
--- /dev/null
+++ b/Client/Assembly-CSharp/ReactorTask.cs
@@ -0,0 +1,85 @@
+using System;
+using System.Linq;
+using System.Text;
+using UnityEngine;
+
+public class ReactorTask : SabotageTask
+{
+ public override int TaskStep
+ {
+ get
+ {
+ return this.reactor.UserCount;
+ }
+ }
+
+ public override bool IsComplete
+ {
+ get
+ {
+ return this.isComplete;
+ }
+ }
+
+ private bool isComplete;
+
+ private ReactorSystemType reactor;
+
+ private bool even;
+
+ public override void Initialize()
+ {
+ ShipStatus instance = ShipStatus.Instance;
+ this.reactor = (ReactorSystemType)instance.Systems[SystemTypes.Reactor];
+ ((ReactorShipRoom)instance.AllRooms.First((ShipRoom r) => r.RoomId == SystemTypes.Reactor)).StartMeltdown();
+ base.SetupArrows();
+ }
+
+ private void FixedUpdate()
+ {
+ if (this.isComplete)
+ {
+ return;
+ }
+ if (!this.reactor.IsActive)
+ {
+ this.Complete();
+ }
+ }
+
+ public override bool ValidConsole(global::Console console)
+ {
+ return console.TaskTypes.Contains(TaskTypes.ResetReactor);
+ }
+
+ public override void OnRemove()
+ {
+ }
+
+ public override void Complete()
+ {
+ this.isComplete = true;
+ PlayerControl.LocalPlayer.RemoveTask(this);
+ if (this.didContribute)
+ {
+ StatsManager instance = StatsManager.Instance;
+ uint sabsFixed = instance.SabsFixed;
+ instance.SabsFixed = sabsFixed + 1U;
+ }
+ }
+
+ public override void AppendTaskText(StringBuilder sb)
+ {
+ this.even = !this.even;
+ Color color = this.even ? Color.yellow : Color.red;
+ sb.Append(color.ToTextColor());
+ sb.Append(DestroyableSingleton<TranslationController>.Instance.GetString(TaskTypes.ResetReactor));
+ sb.Append(" ");
+ sb.Append((int)this.reactor.Countdown);
+ sb.AppendLine(string.Format(" ({0}/{1})[]", this.reactor.UserCount, 2));
+ for (int i = 0; i < this.Arrows.Length; i++)
+ {
+ this.Arrows[i].image.color = color;
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/RefuelMinigame.cs b/Client/Assembly-CSharp/RefuelMinigame.cs
new file mode 100644
index 0000000..c54401f
--- /dev/null
+++ b/Client/Assembly-CSharp/RefuelMinigame.cs
@@ -0,0 +1,23 @@
+using System;
+
+public class RefuelMinigame : Minigame
+{
+ public RefuelStage[] Stages;
+
+ private RefuelStage stage;
+
+ public override void Begin(PlayerTask task)
+ {
+ base.Begin(task);
+ this.stage = this.Stages[(int)this.MyNormTask.Data[1]];
+ this.stage.MyNormTask = this.MyNormTask;
+ this.stage.gameObject.SetActive(true);
+ this.stage.Begin();
+ }
+
+ public override void Close()
+ {
+ SoundManager.Instance.StopSound(this.stage.RefuelSound);
+ base.Close();
+ }
+}
diff --git a/Client/Assembly-CSharp/RefuelStage.cs b/Client/Assembly-CSharp/RefuelStage.cs
new file mode 100644
index 0000000..41b64dc
--- /dev/null
+++ b/Client/Assembly-CSharp/RefuelStage.cs
@@ -0,0 +1,98 @@
+using System;
+using UnityEngine;
+
+public class RefuelStage : MonoBehaviour
+{
+ public NormalPlayerTask MyNormTask { get; set; }
+
+ public float RefuelDuration = 5f;
+
+ private Color darkRed = new Color32(90, 0, 0, byte.MaxValue);
+
+ private Color red = new Color32(byte.MaxValue, 58, 0, byte.MaxValue);
+
+ private Color green = Color.green;
+
+ public SpriteRenderer redLight;
+
+ public SpriteRenderer greenLight;
+
+ public VerticalGauge srcGauge;
+
+ public VerticalGauge destGauge;
+
+ public AudioClip RefuelSound;
+
+ private float timer;
+
+ private bool isDown;
+
+ private bool complete;
+
+ public void Begin()
+ {
+ this.timer = (float)this.MyNormTask.Data[0] / 255f;
+ }
+
+ public void FixedUpdate()
+ {
+ if (this.complete)
+ {
+ return;
+ }
+ if (this.isDown && this.timer < 1f)
+ {
+ this.timer += Time.fixedDeltaTime / this.RefuelDuration;
+ this.MyNormTask.Data[0] = (byte)Mathf.Min(255f, this.timer * 255f);
+ if (this.timer >= 1f)
+ {
+ this.complete = true;
+ this.greenLight.color = this.green;
+ this.redLight.color = this.darkRed;
+ this.MyNormTask.Data[0] = 0;
+ byte[] data = this.MyNormTask.Data;
+ int num = 1;
+ data[num] += 1;
+ if (this.MyNormTask.Data[1] % 2 == 0)
+ {
+ this.MyNormTask.NextStep();
+ }
+ this.MyNormTask.UpdateArrow();
+ }
+ }
+ this.destGauge.value = this.timer;
+ if (this.srcGauge)
+ {
+ this.srcGauge.value = 1f - this.timer;
+ }
+ }
+
+ public void Refuel()
+ {
+ if (this.complete)
+ {
+ base.transform.parent.GetComponent<Minigame>().Close();
+ return;
+ }
+ this.isDown = !this.isDown;
+ this.redLight.color = (this.isDown ? this.red : this.darkRed);
+ if (this.isDown)
+ {
+ if (Constants.ShouldPlaySfx())
+ {
+ SoundManager.Instance.PlayDynamicSound("Refuel", this.RefuelSound, true, new DynamicSound.GetDynamicsFunction(this.GetRefuelDynamics), true);
+ return;
+ }
+ }
+ else
+ {
+ SoundManager.Instance.StopSound(this.RefuelSound);
+ }
+ }
+
+ private void GetRefuelDynamics(AudioSource player, float dt)
+ {
+ player.volume = 1f;
+ player.pitch = Mathf.Lerp(0.75f, 1.25f, this.timer);
+ }
+}
diff --git a/Client/Assembly-CSharp/ReportButtonManager.cs b/Client/Assembly-CSharp/ReportButtonManager.cs
new file mode 100644
index 0000000..151b75b
--- /dev/null
+++ b/Client/Assembly-CSharp/ReportButtonManager.cs
@@ -0,0 +1,28 @@
+using System;
+using UnityEngine;
+
+public class ReportButtonManager : MonoBehaviour
+{
+ public SpriteRenderer renderer;
+
+ public void SetActive(bool isActive)
+ {
+ if (isActive)
+ {
+ this.renderer.color = Palette.EnabledColor;
+ this.renderer.material.SetFloat("_Desat", 0f);
+ return;
+ }
+ this.renderer.color = Palette.DisabledColor;
+ this.renderer.material.SetFloat("_Desat", 1f);
+ }
+
+ public void DoClick()
+ {
+ if (!base.isActiveAndEnabled)
+ {
+ return;
+ }
+ PlayerControl.LocalPlayer.ReportClosest();
+ }
+}
diff --git a/Client/Assembly-CSharp/ResSetter.cs b/Client/Assembly-CSharp/ResSetter.cs
new file mode 100644
index 0000000..d1632cf
--- /dev/null
+++ b/Client/Assembly-CSharp/ResSetter.cs
@@ -0,0 +1,29 @@
+using System;
+using System.IO;
+using UnityEngine;
+
+public class ResSetter : MonoBehaviour
+{
+ public int Width = 1438;
+
+ public int Height = 810;
+
+ private int cnt;
+
+ public void Start()
+ {
+ Screen.SetResolution(this.Width, this.Height, false);
+ }
+
+ public void Update()
+ {
+ if (Input.GetKeyDown(KeyCode.S))
+ {
+ Directory.CreateDirectory("C:\\AmongUsSS");
+ string format = "C:\\AmongUsSS\\Screenshot-{0}.png";
+ int num = this.cnt;
+ this.cnt = num + 1;
+ ScreenCapture.CaptureScreenshot(string.Format(format, num));
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/ResolutionManager.cs b/Client/Assembly-CSharp/ResolutionManager.cs
new file mode 100644
index 0000000..15a8ce7
--- /dev/null
+++ b/Client/Assembly-CSharp/ResolutionManager.cs
@@ -0,0 +1,44 @@
+using System;
+using UnityEngine;
+
+public static class ResolutionManager
+{
+ public static event Action<float> ResolutionChanged;
+
+ public static void SetResolution(int width, int height, bool fullscreen)
+ {
+ Action<float> resolutionChanged = ResolutionManager.ResolutionChanged;
+ if (resolutionChanged != null)
+ {
+ resolutionChanged((float)width / (float)height);
+ }
+ Screen.SetResolution(width, height, fullscreen);
+ }
+
+ public static void ToggleFullscreen()
+ {
+ bool flag = !Screen.fullScreen;
+ int width;
+ int height;
+ if (flag)
+ {
+ Resolution[] resolutions = Screen.resolutions;
+ Resolution resolution = resolutions[0];
+ for (int i = 0; i < resolutions.Length; i++)
+ {
+ if (resolution.height < resolutions[i].height)
+ {
+ resolution = resolutions[i];
+ }
+ }
+ width = resolution.width;
+ height = resolution.height;
+ }
+ else
+ {
+ width = 711;
+ height = 400;
+ }
+ ResolutionManager.SetResolution(width, height, flag);
+ }
+}
diff --git a/Client/Assembly-CSharp/ResolutionSlider.cs b/Client/Assembly-CSharp/ResolutionSlider.cs
new file mode 100644
index 0000000..088854a
--- /dev/null
+++ b/Client/Assembly-CSharp/ResolutionSlider.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Linq;
+using UnityEngine;
+
+public class ResolutionSlider : MonoBehaviour
+{
+ private int targetIdx;
+
+ private Resolution targetResolution;
+
+ private bool targetFullscreen;
+
+ private Resolution[] allResolutions;
+
+ public SlideBar slider;
+
+ public ToggleButtonBehaviour Fullscreen;
+
+ public ToggleButtonBehaviour VSync;
+
+ public TextRenderer Display;
+
+ public void OnEnable()
+ {
+ this.allResolutions = (from r in Screen.resolutions
+ where r.height > 480
+ select r).ToArray<Resolution>();
+ this.targetResolution = Screen.currentResolution;
+ this.targetFullscreen = Screen.fullScreen;
+ this.targetIdx = this.allResolutions.IndexOf((Resolution e) => e.width == this.targetResolution.width && e.height == this.targetResolution.height);
+ this.slider.Value = (float)this.targetIdx / ((float)this.allResolutions.Length - 1f);
+ this.Display.Text = string.Format("{0}x{1}", this.targetResolution.width, this.targetResolution.height);
+ this.Fullscreen.UpdateText(this.targetFullscreen);
+ this.VSync.UpdateText(QualitySettings.vSyncCount != 0);
+ }
+
+ public void ToggleVSync()
+ {
+ bool flag = QualitySettings.vSyncCount != 0;
+ if (flag)
+ {
+ QualitySettings.vSyncCount = 0;
+ }
+ else
+ {
+ QualitySettings.vSyncCount = 1;
+ }
+ this.VSync.UpdateText(!flag);
+ }
+
+ public void ToggleFullscreen()
+ {
+ this.targetFullscreen = !this.targetFullscreen;
+ this.Fullscreen.UpdateText(this.targetFullscreen);
+ }
+
+ public void OnResChange(SlideBar slider)
+ {
+ int num = Mathf.RoundToInt((float)(this.allResolutions.Length - 1) * slider.Value);
+ if (num != this.targetIdx)
+ {
+ this.targetIdx = num;
+ this.targetResolution = this.allResolutions[num];
+ this.Display.Text = string.Format("{0}x{1}", this.targetResolution.width, this.targetResolution.height);
+ }
+ slider.Value = (float)this.targetIdx / ((float)this.allResolutions.Length - 1f);
+ }
+
+ public void SaveChange()
+ {
+ ResolutionManager.SetResolution(this.targetResolution.width, this.targetResolution.height, this.targetFullscreen);
+ }
+}
diff --git a/Client/Assembly-CSharp/RingBuffer.cs b/Client/Assembly-CSharp/RingBuffer.cs
new file mode 100644
index 0000000..e12313e
--- /dev/null
+++ b/Client/Assembly-CSharp/RingBuffer.cs
@@ -0,0 +1,75 @@
+using System;
+
+public class RingBuffer<T>
+{
+ public int Count { get; private set; }
+
+ public int Capacity
+ {
+ get
+ {
+ return this.Data.Length;
+ }
+ }
+
+ public T this[int i]
+ {
+ get
+ {
+ int num = (this.startIdx + i) % this.Data.Length;
+ return this.Data[num];
+ }
+ }
+
+ private readonly T[] Data;
+
+ private int startIdx;
+
+ public RingBuffer(int size)
+ {
+ this.Data = new T[size];
+ }
+
+ public T First()
+ {
+ return this.Data[this.startIdx];
+ }
+
+ public T Last()
+ {
+ int num = (this.startIdx + this.Count - 1) % this.Data.Length;
+ return this.Data[num];
+ }
+
+ public void Add(T item)
+ {
+ int num = (this.startIdx + this.Count) % this.Data.Length;
+ this.Data[num] = item;
+ if (this.Count < this.Data.Length)
+ {
+ int count = this.Count;
+ this.Count = count + 1;
+ return;
+ }
+ this.startIdx = (this.startIdx + 1) % this.Data.Length;
+ }
+
+ public T RemoveFirst()
+ {
+ if (this.Count == 0)
+ {
+ throw new InvalidOperationException("Data is empty");
+ }
+ T result = this.Data[this.startIdx];
+ this.startIdx = (this.startIdx + 1) % this.Data.Length;
+ int count = this.Count;
+ this.Count = count - 1;
+ return result;
+ }
+
+ public void Clear()
+ {
+ this.startIdx = 0;
+ this.Count = 0;
+ }
+}
diff --git a/Client/Assembly-CSharp/RoomTracker.cs b/Client/Assembly-CSharp/RoomTracker.cs
new file mode 100644
index 0000000..3c24ed6
--- /dev/null
+++ b/Client/Assembly-CSharp/RoomTracker.cs
@@ -0,0 +1,159 @@
+using System;
+using System.Collections;
+using UnityEngine;
+
+public class RoomTracker : MonoBehaviour
+{
+ public TextRenderer text;
+
+ public float SourceY = -2.5f;
+
+ public float TargetY = -3.25f;
+
+ private Collider2D playerCollider;
+
+ private ContactFilter2D filter;
+
+ private Collider2D[] buffer = new Collider2D[10];
+
+ public ShipRoom LastRoom;
+
+ private Coroutine slideInRoutine;
+
+ public void Awake()
+ {
+ this.filter = default(ContactFilter2D);
+ this.filter.layerMask = Constants.PlayersOnlyMask;
+ this.filter.useLayerMask = true;
+ this.filter.useTriggers = false;
+ }
+
+ public void OnDisable()
+ {
+ this.LastRoom = null;
+ Vector3 localPosition = this.text.transform.localPosition;
+ localPosition.y = this.TargetY;
+ this.text.transform.localPosition = localPosition;
+ }
+
+ public void FixedUpdate()
+ {
+ ShipRoom[] array = null;
+ if (LobbyBehaviour.Instance)
+ {
+ array = LobbyBehaviour.Instance.AllRooms;
+ }
+ if (ShipStatus.Instance)
+ {
+ array = ShipStatus.Instance.AllRooms;
+ }
+ if (array == null)
+ {
+ return;
+ }
+ ShipRoom shipRoom = null;
+ if (this.LastRoom)
+ {
+ int hitCount = this.LastRoom.roomArea.OverlapCollider(this.filter, this.buffer);
+ if (RoomTracker.CheckHitsForPlayer(this.buffer, hitCount))
+ {
+ shipRoom = this.LastRoom;
+ }
+ }
+ if (!shipRoom)
+ {
+ foreach (ShipRoom shipRoom2 in array)
+ {
+ if (shipRoom2.roomArea)
+ {
+ int hitCount2 = shipRoom2.roomArea.OverlapCollider(this.filter, this.buffer);
+ if (RoomTracker.CheckHitsForPlayer(this.buffer, hitCount2))
+ {
+ shipRoom = shipRoom2;
+ }
+ }
+ }
+ }
+ if (shipRoom)
+ {
+ if (this.LastRoom != shipRoom)
+ {
+ this.LastRoom = shipRoom;
+ if (this.slideInRoutine != null)
+ {
+ base.StopCoroutine(this.slideInRoutine);
+ }
+ if (shipRoom.RoomId != SystemTypes.Hallway)
+ {
+ this.slideInRoutine = base.StartCoroutine(this.CoSlideIn(shipRoom.RoomId));
+ return;
+ }
+ this.slideInRoutine = base.StartCoroutine(this.SlideOut());
+ return;
+ }
+ }
+ else if (this.LastRoom)
+ {
+ this.LastRoom = null;
+ if (this.slideInRoutine != null)
+ {
+ base.StopCoroutine(this.slideInRoutine);
+ }
+ this.slideInRoutine = base.StartCoroutine(this.SlideOut());
+ }
+ }
+
+ private IEnumerator CoSlideIn(SystemTypes newRoom)
+ {
+ yield return this.SlideOut();
+ Vector3 tempPos = this.text.transform.localPosition;
+ Color tempColor = Color.white;
+ this.text.Text = DestroyableSingleton<TranslationController>.Instance.GetString(newRoom);
+ float timer = 0f;
+ while (timer < 0.25f)
+ {
+ timer = Mathf.Min(0.25f, timer + Time.deltaTime);
+ float t = timer / 0.25f;
+ tempPos.y = Mathf.SmoothStep(this.TargetY, this.SourceY, t);
+ tempColor.a = Mathf.Lerp(0f, 1f, t);
+ this.text.transform.localPosition = tempPos;
+ this.text.Color = tempColor;
+ yield return null;
+ }
+ yield break;
+ }
+
+ private IEnumerator SlideOut()
+ {
+ Vector3 tempPos = this.text.transform.localPosition;
+ Color tempColor = Color.white;
+ float timer = FloatRange.ReverseLerp(tempPos.y, this.SourceY, this.TargetY) * 0.1f;
+ while (timer < 0.1f)
+ {
+ timer = Mathf.Min(0.1f, timer + Time.deltaTime);
+ float t = timer / 0.1f;
+ tempPos.y = Mathf.SmoothStep(this.SourceY, this.TargetY, t);
+ tempColor.a = Mathf.Lerp(1f, 0f, t);
+ this.text.transform.localPosition = tempPos;
+ this.text.Color = tempColor;
+ yield return null;
+ }
+ yield break;
+ }
+
+ private static bool CheckHitsForPlayer(Collider2D[] buffer, int hitCount)
+ {
+ if (!PlayerControl.LocalPlayer)
+ {
+ return false;
+ }
+ for (int i = 0; i < hitCount; i++)
+ {
+ if (buffer[i].gameObject == PlayerControl.LocalPlayer.gameObject)
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/Client/Assembly-CSharp/SabotageSystemType.cs b/Client/Assembly-CSharp/SabotageSystemType.cs
new file mode 100644
index 0000000..59dd234
--- /dev/null
+++ b/Client/Assembly-CSharp/SabotageSystemType.cs
@@ -0,0 +1,135 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Hazel;
+
+public class SabotageSystemType : ISystemType
+{
+ public float Timer { get; set; }
+
+ public float PercentCool
+ {
+ get
+ {
+ return this.Timer / 30f;
+ }
+ }
+
+ public bool AnyActive
+ {
+ get
+ {
+ return this.specials.Any((IActivatable s) => s.IsActive);
+ }
+ }
+
+ public const float SpecialSabDelay = 30f;
+
+ private List<IActivatable> specials;
+
+ private bool dirty;
+
+ private SabotageSystemType.DummySab dummy = new SabotageSystemType.DummySab();
+
+ public class DummySab : IActivatable
+ {
+ public bool IsActive
+ {
+ get
+ {
+ return this.timer > 0f;
+ }
+ }
+
+ public float timer;
+ }
+
+ public SabotageSystemType(IActivatable[] specials)
+ {
+ this.specials = new List<IActivatable>(specials);
+ this.specials.Add(this.dummy);
+ }
+
+ public bool Detoriorate(float deltaTime)
+ {
+ this.dummy.timer -= deltaTime;
+ if (this.Timer > 0f && !this.AnyActive)
+ {
+ this.Timer -= deltaTime;
+ if (this.Timer <= 0f)
+ {
+ return true;
+ }
+ }
+ return this.dirty;
+ }
+
+ public void ForceSabTime(float t)
+ {
+ this.dummy.timer = t;
+ }
+
+ public void RepairDamage(PlayerControl player, byte amount)
+ {
+ this.dirty = true;
+ if (this.Timer > 0f)
+ {
+ return;
+ }
+ if (MeetingHud.Instance)
+ {
+ return;
+ }
+ if (AmongUsClient.Instance.AmHost)
+ {
+ if (amount <= 7)
+ {
+ if (amount != 3)
+ {
+ if (amount == 7)
+ {
+ byte b = 4;
+ for (int i = 0; i < 5; i++)
+ {
+ if (BoolRange.Next(0.5f))
+ {
+ b |= (byte)(1 << i);
+ }
+ }
+ ShipStatus.Instance.RpcRepairSystem(SystemTypes.Electrical, (int)(b | 128));
+ }
+ }
+ else
+ {
+ ShipStatus.Instance.RepairSystem(SystemTypes.Reactor, player, 128);
+ }
+ }
+ else if (amount != 8)
+ {
+ if (amount == 14)
+ {
+ ShipStatus.Instance.RepairSystem(SystemTypes.Comms, player, 128);
+ }
+ }
+ else
+ {
+ ShipStatus.Instance.RepairSystem(SystemTypes.LifeSupp, player, 128);
+ }
+ }
+ this.Timer = 30f;
+ }
+
+ public void Serialize(MessageWriter writer, bool initialState)
+ {
+ writer.Write(this.Timer);
+ if (!initialState)
+ {
+ this.dirty = false;
+ }
+ }
+
+ public void Deserialize(MessageReader reader, bool initialState)
+ {
+ this.Timer = reader.ReadSingle();
+ }
+}
diff --git a/Client/Assembly-CSharp/SabotageTask.cs b/Client/Assembly-CSharp/SabotageTask.cs
new file mode 100644
index 0000000..2f3e21a
--- /dev/null
+++ b/Client/Assembly-CSharp/SabotageTask.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+
+public abstract class SabotageTask : PlayerTask
+{
+ protected bool didContribute;
+
+ public ArrowBehaviour[] Arrows;
+
+ public void MarkContributed()
+ {
+ this.didContribute = true;
+ }
+
+ protected void SetupArrows()
+ {
+ if (base.Owner.AmOwner)
+ {
+ List<global::Console> list = base.FindConsoles();
+ for (int i = 0; i < list.Count; i++)
+ {
+ int consoleId = list[i].ConsoleId;
+ this.Arrows[consoleId].target = list[i].transform.position;
+ this.Arrows[consoleId].gameObject.SetActive(true);
+ }
+ return;
+ }
+ for (int j = 0; j < this.Arrows.Length; j++)
+ {
+ this.Arrows[j].gameObject.SetActive(false);
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/SampleMinigame.cs b/Client/Assembly-CSharp/SampleMinigame.cs
new file mode 100644
index 0000000..fd60a67
--- /dev/null
+++ b/Client/Assembly-CSharp/SampleMinigame.cs
@@ -0,0 +1,382 @@
+using System;
+using System.Collections;
+using UnityEngine;
+
+public class SampleMinigame : Minigame
+{
+ private SampleMinigame.States State
+ {
+ get
+ {
+ return (SampleMinigame.States)this.MyNormTask.Data[0];
+ }
+ set
+ {
+ this.MyNormTask.Data[0] = (byte)value;
+ }
+ }
+
+ private int AnomalyId
+ {
+ get
+ {
+ return (int)this.MyNormTask.Data[1];
+ }
+ set
+ {
+ this.MyNormTask.Data[1] = (byte)value;
+ }
+ }
+
+ private static string[] ProcessingStrings = new string[]
+ {
+ "Take a break",
+ "Go grab a coffee",
+ "You dont need to wait",
+ "go do something else"
+ };
+
+ private const float PanelMoveDuration = 0.75f;
+
+ private const byte TubeMask = 15;
+
+ public TextRenderer UpperText;
+
+ public TextRenderer LowerText;
+
+ public float TimePerStep = 15f;
+
+ public FloatRange platformY = new FloatRange(-3.5f, -0.75f);
+
+ public FloatRange dropperX = new FloatRange(-1.25f, 1.25f);
+
+ public SpriteRenderer CenterPanel;
+
+ public SpriteRenderer Dropper;
+
+ public SpriteRenderer[] Tubes;
+
+ public SpriteRenderer[] Buttons;
+
+ public SpriteRenderer LowerButton;
+
+ public AudioClip ButtonSound;
+
+ public AudioClip PanelMoveSound;
+
+ public AudioClip FailSound;
+
+ public AudioClip[] DropSounds;
+
+ private RandomFill<AudioClip> dropSounds;
+
+ public enum States : byte
+ {
+ PrepareSample,
+ Complete = 16,
+ AwaitingStart = 32,
+ Selection = 64,
+ Processing = 128
+ }
+
+ public void Awake()
+ {
+ this.dropSounds = new RandomFill<AudioClip>();
+ this.dropSounds.Set(this.DropSounds);
+ }
+
+ public override void Begin(PlayerTask task)
+ {
+ base.Begin(task);
+ SampleMinigame.States state = this.State;
+ if (state <= SampleMinigame.States.AwaitingStart)
+ {
+ if (state != SampleMinigame.States.PrepareSample)
+ {
+ if (state != SampleMinigame.States.AwaitingStart)
+ {
+ return;
+ }
+ this.LowerText.Text = "Press To Begin -->";
+ this.SetPlatformTop();
+ return;
+ }
+ else
+ {
+ base.StartCoroutine(this.BringPanelUp(true));
+ }
+ }
+ else
+ {
+ if (state == SampleMinigame.States.Selection)
+ {
+ for (int i = 0; i < this.Tubes.Length; i++)
+ {
+ this.Tubes[i].color = Color.blue;
+ }
+ this.Tubes[this.AnomalyId].color = Color.red;
+ this.LowerText.Text = "Select Anomaly";
+ this.SetPlatformTop();
+ return;
+ }
+ if (state == SampleMinigame.States.Processing)
+ {
+ for (int j = 0; j < this.Tubes.Length; j++)
+ {
+ this.Tubes[j].color = Color.blue;
+ }
+ this.LowerText.Text = SampleMinigame.ProcessingStrings.Random<string>();
+ this.SetPlatformBottom();
+ return;
+ }
+ }
+ }
+
+ private void SetPlatformBottom()
+ {
+ Vector3 localPosition = this.CenterPanel.transform.localPosition;
+ localPosition.y = this.platformY.min;
+ this.CenterPanel.transform.localPosition = localPosition;
+ }
+
+ private void SetPlatformTop()
+ {
+ Vector3 localPosition = this.CenterPanel.transform.localPosition;
+ localPosition.y = this.platformY.max;
+ this.CenterPanel.transform.localPosition = localPosition;
+ }
+
+ public void FixedUpdate()
+ {
+ if (this.State == SampleMinigame.States.Processing)
+ {
+ if (this.MyNormTask.TaskTimer <= 0f)
+ {
+ this.State = SampleMinigame.States.Selection;
+ this.LowerText.Text = "Select Anomaly";
+ this.UpperText.Text = "";
+ this.AnomalyId = this.Tubes.RandomIdx<SpriteRenderer>();
+ this.Tubes[this.AnomalyId].color = Color.red;
+ this.LowerButton.color = Color.white;
+ base.StartCoroutine(this.BringPanelUp(false));
+ return;
+ }
+ this.UpperText.Text = "ETA: " + (int)this.MyNormTask.TaskTimer;
+ return;
+ }
+ else
+ {
+ if (this.State == SampleMinigame.States.Selection)
+ {
+ float num = Mathf.Cos(Time.time * 1.5f) - 0.2f;
+ Color color = new Color(num, 1f, num, 1f);
+ for (int i = 0; i < this.Buttons.Length; i++)
+ {
+ this.Buttons[i].color = color;
+ }
+ return;
+ }
+ if (this.State == SampleMinigame.States.AwaitingStart)
+ {
+ float num2 = Mathf.Cos(Time.time * 1.5f) - 0.2f;
+ Color color2 = new Color(num2, 1f, num2, 1f);
+ this.LowerButton.color = color2;
+ }
+ return;
+ }
+ }
+
+ public IEnumerator BringPanelUp(bool isBeginning)
+ {
+ if (Constants.ShouldPlaySfx())
+ {
+ SoundManager.Instance.PlaySound(this.PanelMoveSound, false, 1f);
+ }
+ WaitForFixedUpdate wait = new WaitForFixedUpdate();
+ Vector3 pos = this.CenterPanel.transform.localPosition;
+ for (float i = 0f; i < 0.75f; i += Time.deltaTime)
+ {
+ pos.y = this.platformY.Lerp(i / 0.75f);
+ this.CenterPanel.transform.localPosition = pos;
+ yield return wait;
+ }
+ if (isBeginning)
+ {
+ this.State = SampleMinigame.States.AwaitingStart;
+ this.LowerText.Text = "Press To Begin -->";
+ }
+ pos.y = this.platformY.max;
+ this.CenterPanel.transform.localPosition = pos;
+ yield break;
+ }
+
+ public IEnumerator BringPanelDown()
+ {
+ if (Constants.ShouldPlaySfx())
+ {
+ SoundManager.Instance.PlaySound(this.PanelMoveSound, false, 1f);
+ }
+ WaitForFixedUpdate wait = new WaitForFixedUpdate();
+ Vector3 pos = this.CenterPanel.transform.localPosition;
+ for (float i = 0f; i < 0.75f; i += Time.deltaTime)
+ {
+ pos.y = this.platformY.Lerp(1f - i / 0.75f);
+ this.CenterPanel.transform.localPosition = pos;
+ yield return wait;
+ }
+ pos.y = this.platformY.min;
+ this.CenterPanel.transform.localPosition = pos;
+ yield break;
+ }
+
+ private IEnumerator DropTube(int id)
+ {
+ if (Constants.ShouldPlaySfx())
+ {
+ SoundManager.Instance.PlaySound(this.dropSounds.Get(), false, 1f);
+ }
+ this.Tubes[id].color = Color.blue;
+ yield break;
+ }
+
+ public void SelectTube(int tubeId)
+ {
+ if (this.State != SampleMinigame.States.Selection)
+ {
+ return;
+ }
+ this.State = SampleMinigame.States.PrepareSample;
+ for (int i = 0; i < this.Buttons.Length; i++)
+ {
+ this.Buttons[i].color = Color.white;
+ }
+ base.StartCoroutine(this.CoSelectTube(this.AnomalyId, tubeId));
+ }
+
+ private IEnumerator CoSelectTube(int correctTube, int selectedTube)
+ {
+ if (selectedTube != correctTube)
+ {
+ if (Constants.ShouldPlaySfx())
+ {
+ SoundManager.Instance.PlaySound(this.FailSound, false, 1f);
+ }
+ this.UpperText.Text = "Bad Result";
+ this.LowerText.Text = "Bad Result";
+ for (int i = 0; i < this.Buttons.Length; i++)
+ {
+ this.Buttons[i].color = Color.red;
+ }
+ yield return new WaitForSeconds(0.25f);
+ for (int j = 0; j < this.Buttons.Length; j++)
+ {
+ this.Buttons[j].color = Color.white;
+ }
+ yield return new WaitForSeconds(0.25f);
+ for (int k = 0; k < this.Buttons.Length; k++)
+ {
+ this.Buttons[k].color = Color.red;
+ }
+ yield return new WaitForSeconds(0.25f);
+ for (int l = 0; l < this.Buttons.Length; l++)
+ {
+ this.Buttons[l].color = Color.white;
+ }
+ this.UpperText.Text = "";
+ }
+ else
+ {
+ if (Constants.ShouldPlaySfx())
+ {
+ SoundManager.Instance.PlaySound(this.ButtonSound, false, 0.6f);
+ }
+ this.UpperText.Text = "Thank you";
+ this.MyNormTask.NextStep();
+ if (this.MyNormTask.IsComplete)
+ {
+ this.State = SampleMinigame.States.Complete;
+ }
+ }
+ int num = this.MyNormTask.MaxStep - this.MyNormTask.taskStep;
+ if (num == 0)
+ {
+ this.LowerText.Text = "Tests Complete";
+ }
+ else
+ {
+ this.LowerText.Text = num + " more";
+ }
+ yield return this.BringPanelDown();
+ for (int m = 0; m < this.Tubes.Length; m++)
+ {
+ this.Tubes[m].color = Color.white;
+ }
+ if (!this.MyNormTask.IsComplete)
+ {
+ yield return this.BringPanelUp(true);
+ }
+ yield break;
+ }
+
+ public void NextStep()
+ {
+ if (this.State != SampleMinigame.States.AwaitingStart)
+ {
+ return;
+ }
+ this.State = SampleMinigame.States.Processing;
+ this.LowerButton.color = Color.white;
+ if (Constants.ShouldPlaySfx())
+ {
+ SoundManager.Instance.PlaySound(this.ButtonSound, false, 1f).volume = 0.6f;
+ }
+ base.StartCoroutine(this.CoStartProcessing());
+ }
+
+ private IEnumerator CoStartProcessing()
+ {
+ this.MyNormTask.TaskTimer = this.TimePerStep;
+ this.MyNormTask.TimerStarted = NormalPlayerTask.TimerState.Started;
+ yield return this.DropLiquid();
+ this.LowerText.Text = SampleMinigame.ProcessingStrings.Random<string>();
+ yield return this.BringPanelDown();
+ yield break;
+ }
+
+ private IEnumerator DropLiquid()
+ {
+ this.LowerText.Text = "Adding Reagent";
+ WaitForSeconds dropWait = new WaitForSeconds(0.25f);
+ WaitForFixedUpdate wait = new WaitForFixedUpdate();
+ Vector3 pos = this.Dropper.transform.localPosition;
+ yield return dropWait;
+ yield return this.DropTube(0);
+ int num;
+ for (int step = -2; step < 2; step = num)
+ {
+ float start = (float)step / 2f * 1.25f;
+ float xTarg = (float)(step + 1) / 2f * 1.25f;
+ for (float i = 0f; i < 0.15f; i += Time.deltaTime)
+ {
+ pos.x = Mathf.Lerp(start, xTarg, i / 0.15f);
+ this.Dropper.transform.localPosition = pos;
+ yield return wait;
+ }
+ pos.x = xTarg;
+ this.Dropper.transform.localPosition = pos;
+ yield return dropWait;
+ int id = step + 3;
+ yield return this.DropTube(id);
+ num = step + 1;
+ }
+ for (float xTarg = 0f; xTarg < 0.15f; xTarg += Time.deltaTime)
+ {
+ pos.x = this.dropperX.Lerp(1f - xTarg / 0.15f);
+ this.Dropper.transform.localPosition = pos;
+ yield return wait;
+ }
+ pos.x = this.dropperX.min;
+ this.Dropper.transform.localPosition = pos;
+ yield break;
+ }
+}
diff --git a/Client/Assembly-CSharp/SaveManager.cs b/Client/Assembly-CSharp/SaveManager.cs
new file mode 100644
index 0000000..8687d45
--- /dev/null
+++ b/Client/Assembly-CSharp/SaveManager.cs
@@ -0,0 +1,750 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using UnityEngine;
+
+public static class SaveManager
+{
+ public static Announcement LastAnnouncement
+ {
+ get
+ {
+ SaveManager.LoadAnnouncement();
+ return SaveManager.lastAnnounce;
+ }
+ set
+ {
+ SaveManager.lastAnnounce = value;
+ SaveManager.SaveAnnouncement();
+ }
+ }
+
+ public static DateTime LastStartDate
+ {
+ get
+ {
+ SaveManager.LoadSecureData2();
+ return SaveManager.lastStartDate;
+ }
+ set
+ {
+ SaveManager.LoadSecureData2();
+ if (SaveManager.lastStartDate < value)
+ {
+ SaveManager.lastStartDate = value;
+ SaveManager.SaveSecureData2();
+ }
+ }
+ }
+
+ public static int Month
+ {
+ get
+ {
+ return SaveManager.LastStartDate.Month;
+ }
+ }
+
+ public static bool BoughtNoAds
+ {
+ get
+ {
+ SaveManager.LoadSecureData();
+ return SaveManager.purchases.Contains("bought_ads");
+ }
+ }
+
+ public static bool CensorChat
+ {
+ get
+ {
+ SaveManager.LoadPlayerPrefs();
+ return SaveManager.censorChat;
+ }
+ set
+ {
+ SaveManager.LoadPlayerPrefs();
+ SaveManager.censorChat = value;
+ SaveManager.SavePlayerPrefs();
+ }
+ }
+
+ public static ShowAdsState ShowAdsScreen
+ {
+ get
+ {
+ SaveManager.LoadPlayerPrefs();
+ return (ShowAdsState)SaveManager.showAdsScreen;
+ }
+ set
+ {
+ SaveManager.LoadPlayerPrefs();
+ SaveManager.showAdsScreen = (byte)value;
+ SaveManager.SavePlayerPrefs();
+ }
+ }
+
+ public static bool SendDataScreen
+ {
+ get
+ {
+ SaveManager.LoadPlayerPrefs();
+ return SaveManager.sendDataScreen;
+ }
+ set
+ {
+ SaveManager.LoadPlayerPrefs();
+ SaveManager.sendDataScreen = value;
+ SaveManager.SavePlayerPrefs();
+ }
+ }
+
+ public static bool SendName
+ {
+ get
+ {
+ SaveManager.LoadPlayerPrefs();
+ return SaveManager.sendName && SaveManager.SendTelemetry;
+ }
+ set
+ {
+ SaveManager.LoadPlayerPrefs();
+ SaveManager.sendName = value;
+ SaveManager.SavePlayerPrefs();
+ }
+ }
+
+ public static bool SendTelemetry
+ {
+ get
+ {
+ SaveManager.LoadPlayerPrefs();
+ return SaveManager.sendTelemetry;
+ }
+ set
+ {
+ SaveManager.LoadPlayerPrefs();
+ SaveManager.sendTelemetry = value;
+ SaveManager.SavePlayerPrefs();
+ }
+ }
+
+ public static bool ShowMinPlayerWarning
+ {
+ get
+ {
+ SaveManager.LoadPlayerPrefs();
+ return SaveManager.showMinPlayerWarning;
+ }
+ set
+ {
+ SaveManager.LoadPlayerPrefs();
+ SaveManager.showMinPlayerWarning = value;
+ SaveManager.SavePlayerPrefs();
+ }
+ }
+
+ public static bool ShowOnlineHelp
+ {
+ get
+ {
+ SaveManager.LoadPlayerPrefs();
+ return SaveManager.showOnlineHelp;
+ }
+ set
+ {
+ SaveManager.LoadPlayerPrefs();
+ SaveManager.showOnlineHelp = value;
+ SaveManager.SavePlayerPrefs();
+ }
+ }
+
+ public static float SfxVolume
+ {
+ get
+ {
+ SaveManager.LoadPlayerPrefs();
+ return (float)SaveManager.sfxVolume / 255f;
+ }
+ set
+ {
+ SaveManager.LoadPlayerPrefs();
+ SaveManager.sfxVolume = (byte)(value * 255f);
+ SaveManager.SavePlayerPrefs();
+ }
+ }
+
+ public static float MusicVolume
+ {
+ get
+ {
+ SaveManager.LoadPlayerPrefs();
+ return (float)SaveManager.musicVolume / 255f;
+ }
+ set
+ {
+ SaveManager.LoadPlayerPrefs();
+ SaveManager.musicVolume = (byte)(value * 255f);
+ SaveManager.SavePlayerPrefs();
+ }
+ }
+
+ public static int TouchConfig
+ {
+ get
+ {
+ SaveManager.LoadPlayerPrefs();
+ return SaveManager.touchConfig;
+ }
+ set
+ {
+ SaveManager.LoadPlayerPrefs();
+ SaveManager.touchConfig = value;
+ SaveManager.SavePlayerPrefs();
+ }
+ }
+
+ public static float JoystickSize
+ {
+ get
+ {
+ SaveManager.LoadPlayerPrefs();
+ return SaveManager.joyStickSize;
+ }
+ set
+ {
+ SaveManager.LoadPlayerPrefs();
+ SaveManager.joyStickSize = value;
+ SaveManager.SavePlayerPrefs();
+ }
+ }
+
+ public static string PlayerName
+ {
+ get
+ {
+ SaveManager.LoadPlayerPrefs();
+ return SaveManager.lastPlayerName ?? "Enter Name";
+ }
+ set
+ {
+ SaveManager.LoadPlayerPrefs();
+ SaveManager.lastPlayerName = value;
+ SaveManager.SavePlayerPrefs();
+ }
+ }
+
+ public static uint LastPet
+ {
+ get
+ {
+ SaveManager.LoadPlayerPrefs();
+ return SaveManager.lastPet;
+ }
+ set
+ {
+ SaveManager.LoadPlayerPrefs();
+ SaveManager.lastPet = value;
+ SaveManager.SavePlayerPrefs();
+ }
+ }
+
+ public static uint LastHat
+ {
+ get
+ {
+ SaveManager.LoadPlayerPrefs();
+ return SaveManager.lastHat;
+ }
+ set
+ {
+ SaveManager.LoadPlayerPrefs();
+ SaveManager.lastHat = value;
+ SaveManager.SavePlayerPrefs();
+ }
+ }
+
+ public static uint LastSkin
+ {
+ get
+ {
+ SaveManager.LoadPlayerPrefs();
+ return SaveManager.lastSkin;
+ }
+ set
+ {
+ SaveManager.LoadPlayerPrefs();
+ SaveManager.lastSkin = value;
+ SaveManager.SavePlayerPrefs();
+ }
+ }
+
+ public static uint LastLanguage
+ {
+ get
+ {
+ SaveManager.LoadPlayerPrefs();
+ return SaveManager.lastLanguage;
+ }
+ set
+ {
+ SaveManager.LoadPlayerPrefs();
+ SaveManager.lastLanguage = value;
+ SaveManager.SavePlayerPrefs();
+ }
+ }
+
+ public static byte BodyColor
+ {
+ get
+ {
+ SaveManager.LoadPlayerPrefs();
+ return (byte)(SaveManager.colorConfig & 255U);
+ }
+ set
+ {
+ SaveManager.LoadPlayerPrefs();
+ SaveManager.colorConfig = ((SaveManager.colorConfig & 16776960U) | (uint)(value & byte.MaxValue));
+ SaveManager.SavePlayerPrefs();
+ }
+ }
+
+ public static GameOptionsData GameHostOptions
+ {
+ get
+ {
+ if (SaveManager.hostOptionsData == null)
+ {
+ SaveManager.hostOptionsData = SaveManager.LoadGameOptions("gameHostOptions");
+ }
+ SaveManager.hostOptionsData.NumImpostors = Mathf.Clamp(SaveManager.hostOptionsData.NumImpostors, 1, 3);
+ SaveManager.hostOptionsData.KillDistance = Mathf.Clamp(SaveManager.hostOptionsData.KillDistance, 0, 2);
+ return SaveManager.hostOptionsData;
+ }
+ set
+ {
+ SaveManager.hostOptionsData = value;
+ SaveManager.SaveGameOptions(SaveManager.hostOptionsData, "gameHostOptions");
+ }
+ }
+
+ public static GameOptionsData GameSearchOptions
+ {
+ get
+ {
+ GameOptionsData result;
+ if ((result = SaveManager.searchOptionsData) == null)
+ {
+ result = (SaveManager.searchOptionsData = SaveManager.LoadGameOptions("gameSearchOptions"));
+ }
+ return result;
+ }
+ set
+ {
+ SaveManager.searchOptionsData = value;
+ SaveManager.SaveGameOptions(SaveManager.searchOptionsData, "gameSearchOptions");
+ }
+ }
+
+ private static bool loaded;
+
+ private static bool loadedStats;
+
+ private static bool loadedAnnounce;
+
+ private static string lastPlayerName;
+
+ private static byte sfxVolume = byte.MaxValue;
+
+ private static byte musicVolume = byte.MaxValue;
+
+ private static bool showMinPlayerWarning = true;
+
+ private static bool showOnlineHelp = true;
+
+ private static bool sendDataScreen = false;
+
+ private static byte showAdsScreen = 0;
+
+ private static bool sendName = true;
+
+ private static bool sendTelemetry = true;
+
+ private static bool censorChat = true;
+
+ private static int touchConfig;
+
+ private static float joyStickSize = 1f;
+
+ private static uint colorConfig;
+
+ private static uint lastPet;
+
+ private static uint lastHat;
+
+ private static uint lastSkin;
+
+ private static uint lastLanguage;
+
+ private static GameOptionsData hostOptionsData;
+
+ private static GameOptionsData searchOptionsData;
+
+ private static Announcement lastAnnounce;
+
+ private static SaveManager.SecureDataFile secure2 = new SaveManager.SecureDataFile(Path.Combine(Application.persistentDataPath, "secure2"));
+
+ private static DateTime lastStartDate = DateTime.MinValue;
+
+ private static SaveManager.SecureDataFile purchaseFile = new SaveManager.SecureDataFile(Path.Combine(Application.persistentDataPath, "secureNew"));
+
+ private static HashSet<string> purchases = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
+
+ private class SecureDataFile
+ {
+ public bool Loaded { get; private set; }
+
+ private string filePath;
+
+ public SecureDataFile(string filePath)
+ {
+ this.filePath = filePath;
+ }
+
+ public BinaryReader LoadData()
+ {
+ this.Loaded = true;
+ Debug.Log("Loading secure: " + this.filePath);
+ if (File.Exists(this.filePath))
+ {
+ byte[] array = File.ReadAllBytes(this.filePath);
+ for (int i = 0; i < array.Length; i++)
+ {
+ byte[] array2 = array;
+ int num = i;
+ array2[num] ^= (byte)(i % 212);
+ }
+ try
+ {
+ BinaryReader binaryReader = new BinaryReader(new MemoryStream(array));
+ binaryReader.ReadString();
+ return binaryReader;
+ }
+ catch
+ {
+ Debug.Log("Deleted corrupt secure file inner");
+ this.Delete();
+ }
+ }
+ return null;
+ }
+
+ public void SaveData(params object[] items)
+ {
+ byte[] array;
+ using (MemoryStream memoryStream = new MemoryStream())
+ {
+ using (BinaryWriter binaryWriter = new BinaryWriter(memoryStream))
+ {
+ binaryWriter.Write(SystemInfo.deviceUniqueIdentifier);
+ foreach (object obj in items)
+ {
+ if (obj is long)
+ {
+ binaryWriter.Write((long)obj);
+ }
+ else if (obj is HashSet<string>)
+ {
+ foreach (string value in ((HashSet<string>)obj))
+ {
+ binaryWriter.Write(value);
+ }
+ }
+ }
+ binaryWriter.Flush();
+ memoryStream.Position = 0L;
+ array = memoryStream.ToArray();
+ }
+ }
+ for (int j = 0; j < array.Length; j++)
+ {
+ byte[] array2 = array;
+ int num = j;
+ array2[num] ^= (byte)(j % 212);
+ }
+ File.WriteAllBytes(this.filePath, array);
+ }
+
+ public void Delete()
+ {
+ try
+ {
+ File.Delete(this.filePath);
+ }
+ catch
+ {
+ }
+ }
+ }
+
+ private static void SaveSecureData2()
+ {
+ SaveManager.secure2.SaveData(new object[]
+ {
+ SaveManager.lastStartDate.Ticks
+ });
+ }
+
+ private static void LoadSecureData2()
+ {
+ if (SaveManager.secure2.Loaded)
+ {
+ return;
+ }
+ try
+ {
+ using (BinaryReader binaryReader = SaveManager.secure2.LoadData())
+ {
+ SaveManager.lastStartDate = new DateTime(binaryReader.ReadInt64());
+ }
+ }
+ catch
+ {
+ }
+ }
+
+ public static bool GetPurchase(string key)
+ {
+ SaveManager.LoadSecureData();
+ return SaveManager.purchases.Contains(key);
+ }
+
+ public static void SetPurchased(string key)
+ {
+ SaveManager.LoadSecureData();
+ SaveManager.purchases.Add(key ?? "null");
+ if (key == "bought_ads")
+ {
+ SaveManager.ShowAdsScreen = ShowAdsState.Purchased;
+ }
+ SaveManager.SaveSecureData();
+ }
+
+ private static void LoadSecureData()
+ {
+ if (SaveManager.purchaseFile.Loaded)
+ {
+ return;
+ }
+ try
+ {
+ using (BinaryReader binaryReader = SaveManager.purchaseFile.LoadData())
+ {
+ while (binaryReader.BaseStream.Position < binaryReader.BaseStream.Length)
+ {
+ SaveManager.purchases.Add(binaryReader.ReadString());
+ }
+ }
+ }
+ catch (NullReferenceException)
+ {
+ }
+ catch (Exception arg)
+ {
+ Debug.Log("Deleted corrupt secure file outer: " + arg);
+ SaveManager.purchaseFile.Delete();
+ }
+ }
+
+ private static void SaveSecureData()
+ {
+ SaveManager.purchaseFile.SaveData(new object[]
+ {
+ SaveManager.purchases
+ });
+ }
+
+ private static GameOptionsData LoadGameOptions(string filename)
+ {
+ string path = Path.Combine(Application.persistentDataPath, filename);
+ if (File.Exists(path))
+ {
+ using (FileStream fileStream = File.OpenRead(path))
+ {
+ using (BinaryReader binaryReader = new BinaryReader(fileStream))
+ {
+ return GameOptionsData.Deserialize(binaryReader) ?? new GameOptionsData();
+ }
+ }
+ }
+ return new GameOptionsData();
+ }
+
+ private static void SaveGameOptions(GameOptionsData data, string filename)
+ {
+ using (FileStream fileStream = new FileStream(Path.Combine(Application.persistentDataPath, filename), FileMode.Create, FileAccess.Write))
+ {
+ using (BinaryWriter binaryWriter = new BinaryWriter(fileStream))
+ {
+ data.Serialize(binaryWriter);
+ }
+ }
+ }
+
+ private static void LoadAnnouncement()
+ {
+ if (SaveManager.loadedAnnounce)
+ {
+ return;
+ }
+ SaveManager.loadedAnnounce = true;
+ string path = Path.Combine(Application.persistentDataPath, "announcement");
+ if (File.Exists(path))
+ {
+ string[] array = File.ReadAllText(path).Split(new char[1]);
+ if (array.Length == 3)
+ {
+ Announcement announcement = default(Announcement);
+ SaveManager.TryGetUint(array, 0, out announcement.Id);
+ announcement.AnnounceText = array[1];
+ SaveManager.TryGetInt(array, 2, out announcement.DateFetched);
+ SaveManager.lastAnnounce = announcement;
+ return;
+ }
+ SaveManager.lastAnnounce = default(Announcement);
+ }
+ }
+
+ public static void SaveAnnouncement()
+ {
+ File.WriteAllText(Path.Combine(Application.persistentDataPath, "announcement"), string.Join("\0", new object[]
+ {
+ SaveManager.lastAnnounce.Id,
+ SaveManager.lastAnnounce.AnnounceText,
+ SaveManager.lastAnnounce.DateFetched
+ }));
+ }
+
+ private static void LoadPlayerPrefs()
+ {
+ if (SaveManager.loaded)
+ {
+ return;
+ }
+ SaveManager.loaded = true;
+ string path = Path.Combine(Application.persistentDataPath, "playerPrefs");
+ if (File.Exists(path))
+ {
+ string[] array = File.ReadAllText(path).Split(new char[]
+ {
+ ','
+ });
+ SaveManager.lastPlayerName = array[0];
+ if (array.Length > 1)
+ {
+ int.TryParse(array[1], out SaveManager.touchConfig);
+ }
+ if (array.Length <= 2 || !uint.TryParse(array[2], out SaveManager.colorConfig))
+ {
+ SaveManager.colorConfig = (uint)((byte)(Palette.PlayerColors.RandomIdx<Color32>() << 16) | (byte)(Palette.PlayerColors.RandomIdx<Color32>() << 8) | (byte)Palette.PlayerColors.RandomIdx<Color32>());
+ }
+ SaveManager.TryGetBool(array, 4, out SaveManager.sendName, false);
+ SaveManager.TryGetBool(array, 5, out SaveManager.sendTelemetry, false);
+ SaveManager.TryGetBool(array, 6, out SaveManager.sendDataScreen, false);
+ SaveManager.TryGetByte(array, 7, out SaveManager.showAdsScreen);
+ SaveManager.TryGetBool(array, 8, out SaveManager.showMinPlayerWarning, false);
+ SaveManager.TryGetBool(array, 9, out SaveManager.showOnlineHelp, false);
+ SaveManager.TryGetUint(array, 10, out SaveManager.lastHat);
+ SaveManager.TryGetByte(array, 11, out SaveManager.sfxVolume);
+ SaveManager.TryGetByte(array, 12, out SaveManager.musicVolume);
+ SaveManager.TryGetFloat(array, 13, out SaveManager.joyStickSize, 1f);
+ SaveManager.TryGetUint(array, 15, out SaveManager.lastSkin);
+ SaveManager.TryGetUint(array, 16, out SaveManager.lastPet);
+ SaveManager.TryGetBool(array, 17, out SaveManager.censorChat, true);
+ SaveManager.TryGetUint(array, 18, out SaveManager.lastLanguage);
+ }
+ }
+
+ private static void SavePlayerPrefs()
+ {
+ SaveManager.LoadPlayerPrefs();
+ File.WriteAllText(Path.Combine(Application.persistentDataPath, "playerPrefs"), string.Join(",", new object[]
+ {
+ SaveManager.lastPlayerName,
+ SaveManager.touchConfig,
+ SaveManager.colorConfig,
+ 1,
+ SaveManager.sendName,
+ SaveManager.sendTelemetry,
+ SaveManager.sendDataScreen,
+ SaveManager.showAdsScreen,
+ SaveManager.showMinPlayerWarning,
+ SaveManager.showOnlineHelp,
+ SaveManager.lastHat,
+ SaveManager.sfxVolume,
+ SaveManager.musicVolume,
+ SaveManager.joyStickSize.ToString(CultureInfo.InvariantCulture),
+ 0L,
+ SaveManager.lastSkin,
+ SaveManager.lastPet,
+ SaveManager.censorChat,
+ SaveManager.lastLanguage
+ }));
+ }
+
+ private static void TryGetBool(string[] parts, int index, out bool value, bool @default = false)
+ {
+ value = @default;
+ if (parts.Length > index)
+ {
+ bool.TryParse(parts[index], out value);
+ }
+ }
+
+ private static void TryGetByte(string[] parts, int index, out byte value)
+ {
+ value = 0;
+ if (parts.Length > index)
+ {
+ byte.TryParse(parts[index], out value);
+ }
+ }
+
+ private static void TryGetFloat(string[] parts, int index, out float value, float @default = 0f)
+ {
+ value = @default;
+ if (parts.Length > index)
+ {
+ float.TryParse(parts[index], NumberStyles.Number, CultureInfo.InvariantCulture, out value);
+ }
+ }
+
+ private static void TryGetInt(string[] parts, int index, out int value)
+ {
+ value = 0;
+ if (parts.Length > index)
+ {
+ int.TryParse(parts[index], out value);
+ }
+ }
+
+ private static void TryGetUint(string[] parts, int index, out uint value)
+ {
+ value = 0U;
+ if (parts.Length > index)
+ {
+ uint.TryParse(parts[index], out value);
+ }
+ }
+
+ private static void TryGetDateTicks(string[] parts, int index, out DateTime value)
+ {
+ value = DateTime.MinValue;
+ long ticks;
+ if (parts.Length > index && long.TryParse(parts[index], out ticks))
+ {
+ value = new DateTime(ticks, DateTimeKind.Utc);
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/Scene0Controller.cs b/Client/Assembly-CSharp/Scene0Controller.cs
new file mode 100644
index 0000000..abada07
--- /dev/null
+++ b/Client/Assembly-CSharp/Scene0Controller.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Collections;
+using UnityEngine;
+
+public class Scene0Controller : SceneController
+{
+ public float Duration = 3f;
+
+ public SpriteRenderer[] ExtraBoys;
+
+ public AnimationCurve PopInCurve;
+
+ public AnimationCurve PopOutCurve;
+
+ public float OutDuration = 0.2f;
+
+ public void OnEnable()
+ {
+ base.StartCoroutine(this.Run());
+ }
+
+ public void OnDisable()
+ {
+ for (int i = 0; i < this.ExtraBoys.Length; i++)
+ {
+ this.ExtraBoys[i].enabled = false;
+ }
+ }
+
+ private IEnumerator Run()
+ {
+ int lastBoy = 0;
+ float start = Time.time;
+ for (;;)
+ {
+ float num = (Time.time - start) / this.Duration;
+ int num2 = Mathf.RoundToInt((Mathf.Cos(3.1415927f * num + 3.1415927f) + 1f) / 2f * (float)this.ExtraBoys.Length);
+ if (lastBoy < num2)
+ {
+ base.StartCoroutine(this.PopIn(this.ExtraBoys[lastBoy]));
+ lastBoy = num2;
+ }
+ else if (lastBoy > num2)
+ {
+ lastBoy = num2;
+ base.StartCoroutine(this.PopOut(this.ExtraBoys[lastBoy]));
+ }
+ yield return null;
+ }
+ yield break;
+ }
+
+ private IEnumerator PopIn(SpriteRenderer boy)
+ {
+ boy.enabled = true;
+ for (float timer = 0f; timer < 0.2f; timer += Time.deltaTime)
+ {
+ float num = this.PopInCurve.Evaluate(timer / 0.2f);
+ boy.transform.localScale = new Vector3(num, num, num);
+ yield return null;
+ }
+ boy.transform.localScale = Vector3.one;
+ yield break;
+ }
+
+ private IEnumerator PopOut(SpriteRenderer boy)
+ {
+ boy.enabled = true;
+ for (float timer = 0f; timer < this.OutDuration; timer += Time.deltaTime)
+ {
+ float num = this.PopOutCurve.Evaluate(timer / this.OutDuration);
+ boy.transform.localScale = new Vector3(num, num, num);
+ yield return null;
+ }
+ boy.transform.localScale = Vector3.one;
+ boy.enabled = false;
+ yield break;
+ }
+}
diff --git a/Client/Assembly-CSharp/Scene1Controller.cs b/Client/Assembly-CSharp/Scene1Controller.cs
new file mode 100644
index 0000000..d1896ee
--- /dev/null
+++ b/Client/Assembly-CSharp/Scene1Controller.cs
@@ -0,0 +1,94 @@
+using System;
+using System.Collections;
+using UnityEngine;
+
+public class Scene1Controller : SceneController
+{
+ public PlayerAnimator[] players;
+
+ public DummyConsole[] Consoles;
+
+ public Vector2[] WayPoints;
+
+ public Camera backupCam;
+
+ public void OnDrawGizmos()
+ {
+ for (int i = 0; i < this.WayPoints.Length; i++)
+ {
+ Vector2 v = this.WayPoints[i];
+ Vector2 v2 = this.WayPoints[(i + 1) % this.WayPoints.Length];
+ Gizmos.DrawLine(v, v2);
+ }
+ }
+
+ public void OnEnable()
+ {
+ this.backupCam.cullingMask = 0;
+ base.StartCoroutine(this.RunPlayer(0));
+ base.StartCoroutine(this.RunPlayer(1));
+ }
+
+ public void OnDisable()
+ {
+ this.backupCam.cullingMask = (int.MaxValue ^ LayerMask.GetMask(new string[]
+ {
+ "UI"
+ }));
+ }
+
+ private IEnumerator RunPlayer(int idx)
+ {
+ PlayerAnimator myPlayer = this.players[idx];
+ for (;;)
+ {
+ int num;
+ for (int i = 0; i < this.WayPoints.Length; i = num)
+ {
+ bool willInterrupt = i == 2 || i == 5;
+ yield return myPlayer.WalkPlayerTo(this.WayPoints[i], willInterrupt, 0.1f);
+ if (willInterrupt)
+ {
+ yield return this.DoUse(idx, (i == 2) ? 0 : 1);
+ }
+ num = i + 1;
+ }
+ }
+ yield break;
+ }
+
+ private IEnumerator DoUse(int idx, int consoleid)
+ {
+ PlayerAnimator myPlayer = this.players[idx];
+ yield return Scene1Controller.WaitForSeconds(0.2f);
+ if (idx == 0)
+ {
+ yield return myPlayer.finger.MoveTo(myPlayer.UseButton.transform.position, 0.75f);
+ }
+ else
+ {
+ yield return myPlayer.finger.MoveTo(this.Consoles[consoleid].transform.position, 0.75f);
+ }
+ yield return Scene1Controller.WaitForSeconds(0.2f);
+ yield return myPlayer.finger.DoClick(0.4f);
+ yield return Scene1Controller.WaitForSeconds(0.2f);
+ if (!(myPlayer.joystick is DemoKeyboardStick))
+ {
+ yield return myPlayer.finger.MoveTo(myPlayer.joystick.transform.position, 0.75f);
+ }
+ else
+ {
+ yield return Scene1Controller.WaitForSeconds(0.75f);
+ }
+ yield break;
+ }
+
+ public static IEnumerator WaitForSeconds(float duration)
+ {
+ for (float time = 0f; time < duration; time += Time.deltaTime)
+ {
+ yield return null;
+ }
+ yield break;
+ }
+}
diff --git a/Client/Assembly-CSharp/SceneChanger.cs b/Client/Assembly-CSharp/SceneChanger.cs
new file mode 100644
index 0000000..0a58fa1
--- /dev/null
+++ b/Client/Assembly-CSharp/SceneChanger.cs
@@ -0,0 +1,27 @@
+using System;
+using UnityEngine;
+using UnityEngine.SceneManagement;
+using UnityEngine.UI;
+
+public class SceneChanger : MonoBehaviour
+{
+ public string TargetScene;
+
+ public Button.ButtonClickedEvent BeforeSceneChange;
+
+ public void Click()
+ {
+ this.BeforeSceneChange.Invoke();
+ SceneChanger.ChangeScene(this.TargetScene);
+ }
+
+ public static void ChangeScene(string target)
+ {
+ SceneManager.LoadScene(target);
+ }
+
+ public void ExitGame()
+ {
+ Application.Quit();
+ }
+}
diff --git a/Client/Assembly-CSharp/SceneController.cs b/Client/Assembly-CSharp/SceneController.cs
new file mode 100644
index 0000000..6129737
--- /dev/null
+++ b/Client/Assembly-CSharp/SceneController.cs
@@ -0,0 +1,6 @@
+using System;
+using UnityEngine;
+
+public class SceneController : MonoBehaviour
+{
+}
diff --git a/Client/Assembly-CSharp/ScreenJoystick.cs b/Client/Assembly-CSharp/ScreenJoystick.cs
new file mode 100644
index 0000000..f787ea3
--- /dev/null
+++ b/Client/Assembly-CSharp/ScreenJoystick.cs
@@ -0,0 +1,54 @@
+using System;
+using UnityEngine;
+
+public class ScreenJoystick : MonoBehaviour, IVirtualJoystick
+{
+ public Vector2 Delta { get; private set; }
+
+ private Collider2D[] hitBuffer = new Collider2D[20];
+
+ private Controller myController = new Controller();
+
+ private int touchId = -1;
+
+ private void FixedUpdate()
+ {
+ this.myController.Update();
+ if (this.touchId <= -1)
+ {
+ for (int i = 0; i < this.myController.Touches.Length; i++)
+ {
+ Controller.TouchState touchState = this.myController.Touches[i];
+ if (touchState.TouchStart)
+ {
+ bool flag = false;
+ int num = Physics2D.OverlapPointNonAlloc(touchState.Position, this.hitBuffer, Constants.NotShipMask);
+ for (int j = 0; j < num; j++)
+ {
+ Collider2D collider2D = this.hitBuffer[j];
+ if (collider2D.GetComponent<ButtonBehavior>() || collider2D.GetComponent<PassiveButton>())
+ {
+ flag = true;
+ break;
+ }
+ }
+ if (!flag)
+ {
+ this.touchId = i;
+ return;
+ }
+ }
+ }
+ return;
+ }
+ Controller.TouchState touchState2 = this.myController.Touches[this.touchId];
+ if (touchState2.IsDown)
+ {
+ Vector2 b = Camera.main.ViewportToWorldPoint(new Vector2(0.5f, 0.5f));
+ this.Delta = (touchState2.Position - b).normalized;
+ return;
+ }
+ this.touchId = -1;
+ this.Delta = Vector2.zero;
+ }
+}
diff --git a/Client/Assembly-CSharp/Scroller.cs b/Client/Assembly-CSharp/Scroller.cs
new file mode 100644
index 0000000..e38a9cb
--- /dev/null
+++ b/Client/Assembly-CSharp/Scroller.cs
@@ -0,0 +1,85 @@
+using System;
+using UnityEngine;
+
+public class Scroller : MonoBehaviour
+{
+ public bool AtTop
+ {
+ get
+ {
+ return this.Inner.localPosition.y <= this.YBounds.min + 0.25f;
+ }
+ }
+
+ public bool AtBottom
+ {
+ get
+ {
+ return this.Inner.localPosition.y >= this.YBounds.max - 0.25f;
+ }
+ }
+
+ public Transform Inner;
+
+ public Collider2D HitBox;
+
+ private Controller myController = new Controller();
+
+ private Vector3 origin;
+
+ public bool allowX;
+
+ public FloatRange XBounds = new FloatRange(-10f, 10f);
+
+ public bool allowY;
+
+ public FloatRange YBounds = new FloatRange(-10f, 10f);
+
+ public float YBoundPerItem;
+
+ public void FixedUpdate()
+ {
+ if (!this.Inner)
+ {
+ return;
+ }
+ Vector2 mouseScrollDelta = Input.mouseScrollDelta;
+ mouseScrollDelta.y = -mouseScrollDelta.y;
+ this.DoScroll(this.Inner.transform.localPosition, mouseScrollDelta);
+ this.myController.Update();
+ DragState dragState = this.myController.CheckDrag(this.HitBox, false);
+ if (dragState == DragState.TouchStart)
+ {
+ this.origin = this.Inner.transform.localPosition;
+ return;
+ }
+ if (dragState != DragState.Dragging)
+ {
+ return;
+ }
+ Vector2 del = this.myController.DragPosition - this.myController.DragStartPosition;
+ this.DoScroll(this.origin, del);
+ }
+
+ private void DoScroll(Vector3 origin, Vector2 del)
+ {
+ if (del.magnitude < 0.05f)
+ {
+ return;
+ }
+ if (!this.allowX)
+ {
+ del.x = 0f;
+ }
+ if (!this.allowY)
+ {
+ del.y = 0f;
+ }
+ Vector3 vector = origin + del;
+ vector.x = this.XBounds.Clamp(vector.x);
+ int childCount = this.Inner.transform.childCount;
+ float max = Mathf.Max(this.YBounds.min, this.YBounds.max + this.YBoundPerItem * (float)childCount);
+ vector.y = Mathf.Clamp(vector.y, this.YBounds.min, max);
+ this.Inner.transform.localPosition = vector;
+ }
+}
diff --git a/Client/Assembly-CSharp/SecurityCameraSystemType.cs b/Client/Assembly-CSharp/SecurityCameraSystemType.cs
new file mode 100644
index 0000000..f7a90ce
--- /dev/null
+++ b/Client/Assembly-CSharp/SecurityCameraSystemType.cs
@@ -0,0 +1,78 @@
+using System;
+using System.Collections.Generic;
+using Hazel;
+
+//监控器
+public class SecurityCameraSystemType : ISystemType
+{
+ public bool InUse
+ {
+ get
+ {
+ return this.PlayersUsing.Count > 0;
+ }
+ }
+
+ public const byte IncrementOp = 1;
+
+ public const byte DecrementOp = 2;
+
+ private HashSet<byte> PlayersUsing = new HashSet<byte>();
+
+ public bool Detoriorate(float deltaTime)
+ {
+ return false;
+ }
+
+ public void RepairDamage(PlayerControl player, byte amount)
+ {
+ if (amount == 1)
+ {
+ this.PlayersUsing.Add(player.PlayerId);
+ }
+ else
+ {
+ this.PlayersUsing.Remove(player.PlayerId);
+ }
+ this.UpdateCameras();
+ }
+
+ private void UpdateCameras()
+ {
+ for (int i = 0; i < ShipStatus.Instance.AllRooms.Length; i++)
+ {
+ ShipRoom shipRoom = ShipStatus.Instance.AllRooms[i];
+ if (shipRoom.survCamera)
+ {
+ if (this.InUse)
+ {
+ shipRoom.survCamera.Image.Play(shipRoom.survCamera.OnAnim, 1f);
+ }
+ else
+ {
+ shipRoom.survCamera.Image.Play(shipRoom.survCamera.OffAnim, 1f);
+ }
+ }
+ }
+ }
+
+ public void Serialize(MessageWriter writer, bool initialState)
+ {
+ writer.WritePacked(this.PlayersUsing.Count);
+ foreach (byte value in this.PlayersUsing)
+ {
+ writer.Write(value);
+ }
+ }
+
+ public void Deserialize(MessageReader reader, bool initialState)
+ {
+ this.PlayersUsing.Clear();
+ int num = reader.ReadPackedInt32();
+ for (int i = 0; i < num; i++)
+ {
+ this.PlayersUsing.Add(reader.ReadByte());
+ }
+ this.UpdateCameras();
+ }
+}
diff --git a/Client/Assembly-CSharp/ServerInfo.cs b/Client/Assembly-CSharp/ServerInfo.cs
new file mode 100644
index 0000000..f952619
--- /dev/null
+++ b/Client/Assembly-CSharp/ServerInfo.cs
@@ -0,0 +1,43 @@
+using System;
+using System.IO;
+using System.Net;
+
+public class ServerInfo
+{
+ public string Name = "Custom";
+
+ public string Ip = "0.0.0.0";
+
+ public bool Default;
+
+ public void Serialize(BinaryWriter writer)
+ {
+ writer.Write(this.Name);
+ writer.Write(this.Ip);
+ writer.Write(this.Default);
+ }
+
+ public static ServerInfo Deserialize(BinaryReader reader)
+ {
+ ServerInfo serverInfo = new ServerInfo();
+ serverInfo.Name = reader.ReadString();
+ serverInfo.Ip = reader.ReadString();
+ IPAddress ipaddress;
+ if (!IPAddress.TryParse(serverInfo.Ip, out ipaddress))
+ {
+ return null;
+ }
+ serverInfo.Default = reader.ReadBoolean();
+ return serverInfo;
+ }
+
+ internal static ServerInfo Deserialize(string[] parts)
+ {
+ return new ServerInfo
+ {
+ Name = parts[0],
+ Ip = parts[1],
+ Default = bool.Parse(parts[2])
+ };
+ }
+}
diff --git a/Client/Assembly-CSharp/ServerManager.cs b/Client/Assembly-CSharp/ServerManager.cs
new file mode 100644
index 0000000..0b28183
--- /dev/null
+++ b/Client/Assembly-CSharp/ServerManager.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Collections;
+using System.IO;
+using UnityEngine;
+
+public class ServerManager : DestroyableSingleton<ServerManager>
+{
+ public string OnlineNetAddress
+ {
+ get
+ {
+ return this.LastServer.Ip;
+ }
+ }
+
+ public const string DefaultOnlineServer = "50.116.1.42";
+
+ public static readonly ServerInfo DefaultServer = new ServerInfo
+ {
+ Name = "Primary",
+ Ip = "50.116.1.42",
+ Default = true
+ };
+
+ public ServerInfo[] availableServers;
+
+ public ServerInfo LastServer = ServerManager.DefaultServer;
+
+ private string serverInfoFile;
+
+ private ServerManager.UpdateState state;
+
+ private enum UpdateState
+ {
+ Connecting,
+ Failed,
+ Success
+ }
+
+ public void Start()
+ {
+ this.serverInfoFile = Path.Combine(Application.persistentDataPath, "serverInfo.dat");
+ this.LastServer = ServerManager.DefaultServer;
+ this.availableServers = new ServerInfo[]
+ {
+ this.LastServer
+ };
+ this.state = ServerManager.UpdateState.Success;
+ }
+
+ public IEnumerator WaitForServers()
+ {
+ while (this.state == ServerManager.UpdateState.Connecting)
+ {
+ yield return null;
+ }
+ yield break;
+ }
+}
diff --git a/Client/Assembly-CSharp/ServerSelectUi.cs b/Client/Assembly-CSharp/ServerSelectUi.cs
new file mode 100644
index 0000000..671ff7c
--- /dev/null
+++ b/Client/Assembly-CSharp/ServerSelectUi.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+public class ServerSelectUi : MonoBehaviour
+{
+ public ServerSelector ServerButtonPrefab;
+
+ public Vector3 ScrollStartArea;
+
+ public Scroller Slider;
+
+ public ServerSelector CustomServer;
+
+ private List<ServerSelector> myButtons = new List<ServerSelector>();
+
+ public void Start()
+ {
+ ServerInfo[] availableServers = DestroyableSingleton<ServerManager>.Instance.availableServers;
+ Vector3 scrollStartArea = this.ScrollStartArea;
+ for (int i = 0; i < availableServers.Length; i++)
+ {
+ if (!(availableServers[i].Name == "Custom"))
+ {
+ ServerSelector serverSelector = UnityEngine.Object.Instantiate<ServerSelector>(this.ServerButtonPrefab, this.Slider.Inner);
+ serverSelector.name = availableServers[i].Name;
+ this.myButtons.Add(serverSelector);
+ serverSelector.transform.localPosition = scrollStartArea;
+ serverSelector.Parent = this;
+ serverSelector.MyServer = availableServers[i];
+ if (availableServers[i].Default)
+ {
+ serverSelector.Select();
+ }
+ scrollStartArea.y -= 0.7f;
+ }
+ }
+ this.CustomServer.Parent = this;
+ this.myButtons.Add(this.CustomServer);
+ if (DestroyableSingleton<ServerManager>.Instance.LastServer.Name == "Custom")
+ {
+ this.CustomServer.Select();
+ }
+ this.Slider.YBounds.max = this.ScrollStartArea.y;
+ this.Slider.YBounds.min = scrollStartArea.y;
+ }
+
+ internal void SelectServer(ServerSelector selected)
+ {
+ for (int i = 0; i < this.myButtons.Count; i++)
+ {
+ ServerSelector serverSelector = this.myButtons[i];
+ if (!serverSelector.MyServer.Default)
+ {
+ serverSelector.Unselect();
+ }
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/ServerSelector.cs b/Client/Assembly-CSharp/ServerSelector.cs
new file mode 100644
index 0000000..b29aa30
--- /dev/null
+++ b/Client/Assembly-CSharp/ServerSelector.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Net;
+using UnityEngine;
+using UnityEngine.Events;
+
+public class ServerSelector : MonoBehaviour
+{
+ public ServerSelectUi Parent { get; set; }
+
+ public ServerInfo MyServer = new ServerInfo();
+
+ public TextRenderer Text;
+
+ public ButtonRolloverHandler Background;
+
+ public TextBox ipInput;
+
+ public void Start()
+ {
+ if (this.ipInput)
+ {
+ this.ipInput.SetText(this.MyServer.Ip, "");
+ this.ipInput.OnChange.AddListener(new UnityAction(this.OnIpChange));
+ return;
+ }
+ this.Text.Text = this.MyServer.Name;
+ }
+
+ private void OnIpChange()
+ {
+ IPAddress ipaddress;
+ if (!IPAddress.TryParse(this.ipInput.text, out ipaddress))
+ {
+ return;
+ }
+ this.MyServer.Name = "Custom";
+ this.MyServer.Ip = this.ipInput.text;
+ this.Select();
+ }
+
+ public void Select()
+ {
+ this.Background.OutColor = Color.green;
+ this.Background.DoMouseOut();
+ this.Parent.SelectServer(this);
+ }
+
+ internal void Unselect()
+ {
+ this.Background.OutColor = Color.white;
+ this.Background.DoMouseOut();
+ }
+}
diff --git a/Client/Assembly-CSharp/SettingsMode.cs b/Client/Assembly-CSharp/SettingsMode.cs
new file mode 100644
index 0000000..48c138b
--- /dev/null
+++ b/Client/Assembly-CSharp/SettingsMode.cs
@@ -0,0 +1,7 @@
+using System;
+
+public enum SettingsMode
+{
+ Host,
+ Search
+}
diff --git a/Client/Assembly-CSharp/ShadowCamera.cs b/Client/Assembly-CSharp/ShadowCamera.cs
new file mode 100644
index 0000000..468d259
--- /dev/null
+++ b/Client/Assembly-CSharp/ShadowCamera.cs
@@ -0,0 +1,17 @@
+using System;
+using UnityEngine;
+
+public class ShadowCamera : MonoBehaviour
+{
+ public Shader Shadozer;
+
+ public void OnEnable()
+ {
+ base.GetComponent<Camera>().SetReplacementShader(this.Shadozer, "RenderType");
+ }
+
+ public void OnDisable()
+ {
+ base.GetComponent<Camera>().ResetReplacementShader();
+ }
+}
diff --git a/Client/Assembly-CSharp/ShhhBehaviour.cs b/Client/Assembly-CSharp/ShhhBehaviour.cs
new file mode 100644
index 0000000..6164c57
--- /dev/null
+++ b/Client/Assembly-CSharp/ShhhBehaviour.cs
@@ -0,0 +1,135 @@
+using System;
+using System.Collections;
+using UnityEngine;
+
+public class ShhhBehaviour : MonoBehaviour
+{
+ public SpriteRenderer Background;
+
+ public SpriteRenderer Body;
+
+ public SpriteRenderer Hand;
+
+ public SpriteRenderer TextImage;
+
+ public float RotateSpeed = 15f;
+
+ public Vector2Range HandTarget;
+
+ public AnimationCurve PositionEasing;
+
+ public FloatRange HandRotate;
+
+ public AnimationCurve RotationEasing;
+
+ public Vector2Range TextTarget;
+
+ public AnimationCurve TextEasing;
+
+ public float Duration = 0.5f;
+
+ public float Delay = 0.1f;
+
+ public float TextDuration = 0.5f;
+
+ public float PulseDuration = 0.1f;
+
+ public float PulseSize = 0.1f;
+
+ public float HoldDuration = 2f;
+
+ public bool Autoplay;
+
+ public void OnEnable()
+ {
+ if (this.Autoplay)
+ {
+ Vector3 localScale = default(Vector3);
+ this.UpdateHand(ref localScale, 1f);
+ this.UpdateText(ref localScale, 1f);
+ localScale.Set(1f, 1f, 1f);
+ this.Body.transform.localScale = localScale;
+ this.TextImage.color = Color.white;
+ }
+ }
+
+ public IEnumerator PlayAnimation()
+ {
+ base.StartCoroutine(this.AnimateHand());
+ yield return this.AnimateText();
+ yield return ShhhBehaviour.WaitWithInterrupt(this.HoldDuration);
+ yield break;
+ }
+
+ public void Update()
+ {
+ this.Background.transform.Rotate(0f, 0f, Time.deltaTime * this.RotateSpeed);
+ }
+
+ private IEnumerator AnimateText()
+ {
+ this.TextImage.color = Palette.ClearWhite;
+ for (float t = 0f; t < this.Delay; t += Time.deltaTime)
+ {
+ yield return null;
+ }
+ Vector3 vec = default(Vector3);
+ for (float t = 0f; t < this.PulseDuration; t += Time.deltaTime)
+ {
+ float num = t / this.PulseDuration;
+ float num2 = 1f + Mathf.Sin(3.1415927f * num) * this.PulseSize;
+ vec.Set(num2, num2, 1f);
+ this.Body.transform.localScale = vec;
+ this.TextImage.color = Color.Lerp(Palette.ClearWhite, Palette.White, num * 2f);
+ yield return null;
+ }
+ vec.Set(1f, 1f, 1f);
+ this.Body.transform.localScale = vec;
+ this.TextImage.color = Color.white;
+ yield break;
+ }
+
+ private IEnumerator AnimateHand()
+ {
+ this.Hand.transform.localPosition = this.HandTarget.min;
+ Vector3 vec = default(Vector3);
+ for (float t = 0f; t < this.Duration; t += Time.deltaTime)
+ {
+ float p = t / this.Duration;
+ this.UpdateHand(ref vec, p);
+ yield return null;
+ }
+ this.UpdateHand(ref vec, 1f);
+ yield break;
+ }
+
+ private void UpdateHand(ref Vector3 vec, float p)
+ {
+ this.HandTarget.LerpUnclamped(ref vec, this.PositionEasing.Evaluate(p), -1f);
+ this.Hand.transform.localPosition = vec;
+ vec.Set(0f, 0f, this.HandRotate.LerpUnclamped(this.RotationEasing.Evaluate(p)));
+ this.Hand.transform.eulerAngles = vec;
+ }
+
+ private void UpdateText(ref Vector3 vec, float p)
+ {
+ this.TextTarget.LerpUnclamped(ref vec, p, -2f);
+ this.TextImage.transform.localPosition = vec;
+ }
+
+ public static IEnumerator WaitWithInterrupt(float duration)
+ {
+ float timer = 0f;
+ while (timer < duration && !ShhhBehaviour.CheckForInterrupt())
+ {
+ yield return null;
+ timer += Time.deltaTime;
+ }
+ yield break;
+ }
+
+ public static bool CheckForInterrupt()
+ {
+ return Input.anyKeyDown;
+ }
+}
diff --git a/Client/Assembly-CSharp/ShieldMinigame.cs b/Client/Assembly-CSharp/ShieldMinigame.cs
new file mode 100644
index 0000000..553c5fa
--- /dev/null
+++ b/Client/Assembly-CSharp/ShieldMinigame.cs
@@ -0,0 +1,84 @@
+using System;
+using UnityEngine;
+
+public class ShieldMinigame : Minigame
+{
+ public Color OnColor = Color.white;
+
+ public Color OffColor = Color.red;
+
+ public SpriteRenderer[] Shields;
+
+ public SpriteRenderer Gauge;
+
+ private byte shields;
+
+ public AudioClip ShieldOnSound;
+
+ public AudioClip ShieldOffSound;
+
+ public override void Begin(PlayerTask task)
+ {
+ base.Begin(task);
+ this.shields = this.MyNormTask.Data[0];
+ this.UpdateButtons();
+ }
+
+ public void ToggleShield(int i)
+ {
+ if (!this.MyNormTask.IsComplete)
+ {
+ byte b = (byte)(1 << i);
+ this.shields ^= b;
+ this.MyNormTask.Data[0] = this.shields;
+ if ((this.shields & b) != 0)
+ {
+ if (Constants.ShouldPlaySfx())
+ {
+ SoundManager.Instance.PlaySound(this.ShieldOnSound, false, 1f);
+ }
+ }
+ else if (Constants.ShouldPlaySfx())
+ {
+ SoundManager.Instance.PlaySound(this.ShieldOffSound, false, 1f);
+ }
+ if (this.shields == 127)
+ {
+ this.MyNormTask.NextStep();
+ base.StartCoroutine(base.CoStartClose(0.75f));
+ if (!ShipStatus.Instance.ShieldsImages[0].IsPlaying(null))
+ {
+ ShipStatus.Instance.StartShields();
+ PlayerControl.LocalPlayer.RpcPlayAnimation(1);
+ }
+ }
+ }
+ }
+
+ public void FixedUpdate()
+ {
+ this.UpdateButtons();
+ }
+
+ private void UpdateButtons()
+ {
+ int num = 0;
+ for (int i = 0; i < this.Shields.Length; i++)
+ {
+ bool flag = ((int)this.shields & 1 << i) == 0;
+ if (!flag)
+ {
+ num++;
+ }
+ this.Shields[i].color = (flag ? this.OffColor : this.OnColor);
+ }
+ if (this.shields == 127)
+ {
+ this.Gauge.transform.Rotate(0f, 0f, Time.fixedDeltaTime * 45f);
+ this.Gauge.color = new Color(1f, 1f, 1f, 1f);
+ return;
+ }
+ float num2 = Mathf.Lerp(0.1f, 0.5f, (float)num / 6f);
+ this.Gauge.color = new Color(1f, num2, num2, 1f);
+ }
+}
diff --git a/Client/Assembly-CSharp/ShipRoom.cs b/Client/Assembly-CSharp/ShipRoom.cs
new file mode 100644
index 0000000..8dae7a6
--- /dev/null
+++ b/Client/Assembly-CSharp/ShipRoom.cs
@@ -0,0 +1,15 @@
+using System;
+using UnityEngine;
+
+public class ShipRoom : MonoBehaviour
+{
+ public SystemTypes RoomId;
+
+ public SurvCamera survCamera;
+
+ public Collider2D roomArea;
+
+ public AudioClip AmbientSound;
+
+ public SoundGroup FootStepSounds;
+}
diff --git a/Client/Assembly-CSharp/ShipStatus.cs b/Client/Assembly-CSharp/ShipStatus.cs
new file mode 100644
index 0000000..81d8b4a
--- /dev/null
+++ b/Client/Assembly-CSharp/ShipStatus.cs
@@ -0,0 +1,738 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Assets.CoreScripts;
+using Hazel;
+using InnerNet;
+using PowerTools;
+using UnityEngine;
+
+//场景中的物件,包含门的开启等,在这里同步
+public class ShipStatus : InnerNetObject
+{
+ public ShipRoom[] AllRooms { get; private set; }
+
+ public Vent[] AllVents { get; private set; }
+
+ public static ShipStatus Instance;
+
+ public Color CameraColor = Color.black;
+
+ public float MaxLightRadius = 100f;
+
+ public float MinLightRadius;
+
+ public float MapScale = 4.4f;
+
+ public Vector2 MapOffset = new Vector2(0.54f, 1.25f);
+
+ public MapBehaviour MapPrefab;
+
+ public Transform SpawnCenter;
+
+ public float SpawnRadius = 1.55f;
+
+ public AudioClip shipHum;
+
+ public NormalPlayerTask[] CommonTasks;
+
+ public NormalPlayerTask[] LongTasks;
+
+ public NormalPlayerTask[] NormalTasks;
+
+ public PlayerTask[] SpecialTasks;
+
+ public AutoOpenDoor[] AllDoors;
+
+ public global::Console[] AllConsoles;
+
+ //c 场景中的门、反应堆等物件的状态
+ public Dictionary<SystemTypes, ISystemType> Systems;
+
+ public AnimationClip[] WeaponFires;
+
+ public SpriteAnim WeaponsImage;
+
+ public AnimationClip HatchActive;
+
+ public SpriteAnim Hatch;
+
+ public ParticleSystem HatchParticles;
+
+ public AnimationClip ShieldsActive;
+
+ public SpriteAnim[] ShieldsImages;
+
+ public SpriteRenderer ShieldBorder;
+
+ public Sprite ShieldBorderOn;
+
+ public SpriteRenderer MedScanner;
+
+ private int WeaponFireIdx;
+
+ public float Timer;
+
+ public float EmergencyCooldown;
+
+ private RaycastHit2D[] volumeBuffer = new RaycastHit2D[5];
+
+ public class SystemTypeComparer : IEqualityComparer<SystemTypes>
+ {
+ public static readonly ShipStatus.SystemTypeComparer Instance = new ShipStatus.SystemTypeComparer();
+
+ public bool Equals(SystemTypes x, SystemTypes y)
+ {
+ return x == y;
+ }
+
+ public int GetHashCode(SystemTypes obj)
+ {
+ return (int)obj;
+ }
+ }
+
+ private enum RpcCalls
+ {
+ CloseDoorsOfType,
+ RepairSystem
+ }
+
+ public ShipStatus()
+ {
+ //c 所有的systems
+ this.Systems = new Dictionary<SystemTypes, ISystemType>(ShipStatus.SystemTypeComparer.Instance)
+ {
+ {
+ SystemTypes.Electrical,
+ new SwitchSystem()
+ },
+ {
+ SystemTypes.MedBay,
+ new MedScanSystem()
+ },
+ {
+ SystemTypes.Reactor,
+ new ReactorSystemType()
+ },
+ {
+ SystemTypes.LifeSupp,
+ new LifeSuppSystemType()
+ },
+ {
+ SystemTypes.Security,
+ new SecurityCameraSystemType()
+ },
+ {
+ SystemTypes.Comms,
+ new HudOverrideSystemType()
+ },
+ {
+ SystemTypes.Doors,
+ new DoorsSystemType()
+ }
+ };
+ this.Systems.Add(SystemTypes.Sabotage, new SabotageSystemType(new IActivatable[]
+ {
+ (IActivatable)this.Systems[SystemTypes.Comms],
+ (IActivatable)this.Systems[SystemTypes.Reactor],
+ (IActivatable)this.Systems[SystemTypes.LifeSupp],
+ (IActivatable)this.Systems[SystemTypes.Electrical]
+ }));
+ }
+
+ private void Awake()
+ {
+ this.AllRooms = base.GetComponentsInChildren<ShipRoom>();
+ this.AllConsoles = base.GetComponentsInChildren<global::Console>();
+ this.AllVents = base.GetComponentsInChildren<Vent>();
+ this.AssignTaskIndexes();
+ ShipStatus.Instance = this;
+ }
+
+ public void Start()
+ {
+ Camera.main.backgroundColor = this.CameraColor;
+ if (DestroyableSingleton<HudManager>.InstanceExists)
+ {
+ DestroyableSingleton<HudManager>.Instance.Chat.ForceClosed();
+ DestroyableSingleton<HudManager>.Instance.Chat.SetVisible(false);
+ DestroyableSingleton<HudManager>.Instance.GameSettings.gameObject.SetActive(false);
+ }
+ DeconSystem componentInChildren = base.GetComponentInChildren<DeconSystem>();
+ if (componentInChildren)
+ {
+ this.Systems.Add(SystemTypes.Decontamination, componentInChildren);
+ }
+ SoundManager.Instance.StopAllSound();
+ AudioSource audioSource = SoundManager.Instance.PlaySound(this.shipHum, true, 1f);
+ if (audioSource)
+ {
+ audioSource.pitch = 0.8f;
+ }
+ if (Constants.ShouldPlaySfx())
+ {
+ for (int i = 0; i < this.AllRooms.Length; i++)
+ {
+ ShipRoom room = this.AllRooms[i];
+ if (room.AmbientSound)
+ {
+ SoundManager.Instance.PlayDynamicSound("Amb " + room.RoomId, room.AmbientSound, true, delegate(AudioSource player, float dt)
+ {
+ this.GetAmbientSoundVolume(room, player, dt);
+ }, false);
+ }
+ }
+ }
+ }
+
+ public override void OnDestroy()
+ {
+ SoundManager.Instance.StopAllSound();
+ base.OnDestroy();
+ }
+
+ public Vector2 GetSpawnLocation(int playerId, int numPlayer)
+ {
+ Vector2 vector = Vector2.up;
+ vector = vector.Rotate((float)(playerId - 1) * (360f / (float)numPlayer));
+ vector *= this.SpawnRadius;
+ return this.SpawnCenter.position + vector + new Vector2(0f, 0.3636f);
+ }
+
+ public void StartShields()
+ {
+ for (int i = 0; i < this.ShieldsImages.Length; i++)
+ {
+ this.ShieldsImages[i].Play(this.ShieldsActive, 1f);
+ }
+ this.ShieldBorder.sprite = this.ShieldBorderOn;
+ }
+
+ public void FireWeapon()
+ {
+ if (!this.WeaponsImage.IsPlaying(null))
+ {
+ this.WeaponsImage.Play(this.WeaponFires[this.WeaponFireIdx], 1f);
+ this.WeaponFireIdx = (this.WeaponFireIdx + 1) % 2;
+ }
+ }
+
+ public NormalPlayerTask GetTaskById(byte idx)
+ {
+ NormalPlayerTask result;
+ if ((result = this.CommonTasks.FirstOrDefault((NormalPlayerTask t) => t.Index == (int)idx)) == null)
+ {
+ result = (this.LongTasks.FirstOrDefault((NormalPlayerTask t) => t.Index == (int)idx) ?? this.NormalTasks.FirstOrDefault((NormalPlayerTask t) => t.Index == (int)idx));
+ }
+ return result;
+ }
+
+ public void OpenHatch()
+ {
+ if (!this.Hatch.IsPlaying(null))
+ {
+ this.Hatch.Play(this.HatchActive, 1f);
+ this.HatchParticles.Play();
+ }
+ }
+
+ public void CloseDoorsOfType(SystemTypes room)
+ {
+ (this.Systems[SystemTypes.Doors] as DoorsSystemType).CloseDoorsOfType(room);
+ base.SetDirtyBit(65536U);
+ }
+
+ public void RepairSystem(SystemTypes systemType, PlayerControl player, byte amount)
+ {
+ this.Systems[systemType].RepairDamage(player, amount);
+ base.SetDirtyBit(1U << (int)systemType);
+ }
+
+ internal void SelectInfected()
+ {
+ List<GameData.PlayerInfo> list = (from pcd in GameData.Instance.AllPlayers
+ where !pcd.Disconnected
+ select pcd into pc
+ where !pc.IsDead
+ select pc).ToList<GameData.PlayerInfo>();
+ int adjustedNumImpostors = PlayerControl.GameOptions.GetAdjustedNumImpostors(GameData.Instance.PlayerCount);
+ list.RemoveDupes<GameData.PlayerInfo>();
+ GameData.PlayerInfo[] array = new GameData.PlayerInfo[Mathf.Min(list.Count, adjustedNumImpostors)];
+ for (int i = 0; i < array.Length; i++)
+ {
+ int index = HashRandom.FastNext(list.Count);
+ array[i] = list[index];
+ list.RemoveAt(index);
+ }
+ foreach (GameData.PlayerInfo playerInfo in array)
+ {
+ DestroyableSingleton<Telemetry>.Instance.SelectInfected((int)playerInfo.ColorId, playerInfo.HatId);
+ }
+ PlayerControl.LocalPlayer.RpcSetInfected(array);
+ }
+
+ private void AssignTaskIndexes()
+ {
+ int num = 0;
+ for (int i = 0; i < this.CommonTasks.Length; i++)
+ {
+ this.CommonTasks[i].Index = num++;
+ }
+ for (int j = 0; j < this.LongTasks.Length; j++)
+ {
+ this.LongTasks[j].Index = num++;
+ }
+ for (int k = 0; k < this.NormalTasks.Length; k++)
+ {
+ this.NormalTasks[k].Index = num++;
+ }
+ }
+
+ public void Begin()
+ {
+ this.AssignTaskIndexes();
+ GameOptionsData gameOptions = PlayerControl.GameOptions;
+ List<GameData.PlayerInfo> allPlayers = GameData.Instance.AllPlayers;
+ HashSet<TaskTypes> hashSet = new HashSet<TaskTypes>();
+ List<byte> list = new List<byte>(10);
+ List<NormalPlayerTask> list2 = this.CommonTasks.ToList<NormalPlayerTask>();
+ list2.Shuffle<NormalPlayerTask>();
+ int num = 0;
+ this.AddTasksFromList(ref num, gameOptions.NumCommonTasks, list, hashSet, list2);
+ for (int i = 0; i < gameOptions.NumCommonTasks; i++)
+ {
+ if (list2.Count == 0)
+ {
+ Debug.LogWarning("Not enough common tasks");
+ break;
+ }
+ int index = list2.RandomIdx<NormalPlayerTask>();
+ list.Add((byte)list2[index].Index);
+ list2.RemoveAt(index);
+ }
+ List<NormalPlayerTask> list3 = this.LongTasks.ToList<NormalPlayerTask>();
+ list3.Shuffle<NormalPlayerTask>();
+ List<NormalPlayerTask> list4 = this.NormalTasks.ToList<NormalPlayerTask>();
+ list4.Shuffle<NormalPlayerTask>();
+ int num2 = 0;
+ int num3 = 0;
+ int count = gameOptions.NumShortTasks;
+ if (gameOptions.NumCommonTasks + gameOptions.NumLongTasks + gameOptions.NumShortTasks == 0)
+ {
+ count = 1;
+ }
+ byte b = 0;
+ while ((int)b < allPlayers.Count)
+ {
+ hashSet.Clear();
+ list.RemoveRange(gameOptions.NumCommonTasks, list.Count - gameOptions.NumCommonTasks);
+ this.AddTasksFromList(ref num2, gameOptions.NumLongTasks, list, hashSet, list3);
+ this.AddTasksFromList(ref num3, count, list, hashSet, list4);
+ GameData.PlayerInfo playerInfo = allPlayers[(int)b];
+ if (playerInfo.Object && !playerInfo.Object.GetComponent<DummyBehaviour>().enabled)
+ {
+ byte[] taskTypeIds = list.ToArray();
+ GameData.Instance.RpcSetTasks(playerInfo.PlayerId, taskTypeIds);
+ }
+ b += 1;
+ }
+ base.enabled = true;
+ }
+
+ private void AddTasksFromList(ref int start, int count, List<byte> tasks, HashSet<TaskTypes> usedTaskTypes, List<NormalPlayerTask> unusedTasks)
+ {
+ int num = 0;
+ int num2 = 0;
+ Func<NormalPlayerTask, bool> <>9__0;
+ while (num2 < count && num++ != 1000)
+ {
+ if (start >= unusedTasks.Count)
+ {
+ start = 0;
+ unusedTasks.Shuffle<NormalPlayerTask>();
+ Func<NormalPlayerTask, bool> predicate;
+ if ((predicate = <>9__0) == null)
+ {
+ predicate = (<>9__0 = ((NormalPlayerTask t) => usedTaskTypes.Contains(t.TaskType)));
+ }
+ if (unusedTasks.All(predicate))
+ {
+ Debug.Log("Not enough task types");
+ usedTaskTypes.Clear();
+ }
+ }
+ int num3 = start;
+ start = num3 + 1;
+ NormalPlayerTask normalPlayerTask = unusedTasks[num3];
+ if (!usedTaskTypes.Add(normalPlayerTask.TaskType))
+ {
+ num2--;
+ }
+ else
+ {
+ tasks.Add((byte)normalPlayerTask.Index);
+ }
+ num2++;
+ }
+ }
+
+ public void FixedUpdate()
+ {
+ if (!AmongUsClient.Instance)
+ {
+ return;
+ }
+ this.Timer += Time.fixedDeltaTime;
+ this.EmergencyCooldown -= Time.fixedDeltaTime;
+ if (GameData.Instance)
+ {
+ GameData.Instance.RecomputeTaskCounts();
+ }
+ if (AmongUsClient.Instance.AmHost)
+ {
+ this.CheckEndCriteria();
+ }
+ if (AmongUsClient.Instance.AmClient)
+ {
+ for (int i = 0; i < SystemTypeHelpers.AllTypes.Length; i++)
+ {
+ SystemTypes systemTypes = SystemTypeHelpers.AllTypes[i];
+ ISystemType systemType;
+ if (this.Systems.TryGetValue(systemTypes, out systemType) && systemType.Detoriorate(Time.fixedDeltaTime))
+ {
+ base.SetDirtyBit(1U << (int)systemTypes);
+ }
+ }
+ }
+ }
+
+ private void GetAmbientSoundVolume(ShipRoom room, AudioSource player, float dt)
+ {
+ if (!PlayerControl.LocalPlayer)
+ {
+ player.volume = 0f;
+ return;
+ }
+ Vector2 vector = room.transform.position;
+ Vector2 truePosition = PlayerControl.LocalPlayer.GetTruePosition();
+ float num = Vector2.Distance(vector, truePosition);
+ if (num > 8f)
+ {
+ player.volume = 0f;
+ return;
+ }
+ Vector2 direction = truePosition - vector;
+ int num2 = Physics2D.RaycastNonAlloc(vector, direction, this.volumeBuffer, num, Constants.ShipOnlyMask);
+ float num3 = 1f - num / 8f - (float)num2 * 0.25f;
+ player.volume = Mathf.Lerp(player.volume, num3 * 0.7f, dt);
+ }
+
+ public float CalculateLightRadius(GameData.PlayerInfo player)
+ {
+ if (player.IsDead)
+ {
+ return this.MaxLightRadius;
+ }
+ SwitchSystem switchSystem = (SwitchSystem)this.Systems[SystemTypes.Electrical];
+ if (player.IsImpostor)
+ {
+ return this.MaxLightRadius * PlayerControl.GameOptions.ImpostorLightMod;
+ }
+ float t = (float)switchSystem.Value / 255f;
+ return Mathf.Lerp(this.MinLightRadius, this.MaxLightRadius, t) * PlayerControl.GameOptions.CrewLightMod;
+ }
+
+ //c 序列化所有物件的状态
+ public override bool Serialize(MessageWriter writer, bool initialState)
+ {
+ if (initialState)
+ {
+ (this.Systems[SystemTypes.Doors] as DoorsSystemType).SetDoors(this.AllDoors);
+ short num = 0;
+ while ((int)num < SystemTypeHelpers.AllTypes.Length)
+ {
+ SystemTypes key = SystemTypeHelpers.AllTypes[(int)num];
+ ISystemType systemType;
+ if (this.Systems.TryGetValue(key, out systemType))
+ {
+ systemType.Serialize(writer, true);
+ }
+ num += 1;
+ }
+ return true;
+ }
+ if (this.DirtyBits != 0U)
+ {
+ writer.WritePacked(this.DirtyBits);
+ short num2 = 0;
+ while ((int)num2 < SystemTypeHelpers.AllTypes.Length)
+ {
+ SystemTypes systemTypes = SystemTypeHelpers.AllTypes[(int)num2];
+ ISystemType systemType2;
+ if (((ulong)this.DirtyBits & (ulong)(1L << (int)(systemTypes & (SystemTypes)31))) != 0UL && this.Systems.TryGetValue(systemTypes, out systemType2))
+ {
+ systemType2.Serialize(writer, false);
+ }
+ num2 += 1;
+ }
+ this.DirtyBits = 0U;
+ return true;
+ }
+ return false;
+ }
+
+ public override void Deserialize(MessageReader reader, bool initialState)
+ {
+ if (initialState)
+ {
+ (this.Systems[SystemTypes.Doors] as DoorsSystemType).SetDoors(this.AllDoors);
+ short num = 0;
+ while ((int)num < SystemTypeHelpers.AllTypes.Length)
+ {
+ SystemTypes key = (SystemTypes)num;
+ ISystemType systemType;
+ if (this.Systems.TryGetValue(key, out systemType))
+ {
+ systemType.Deserialize(reader, true);
+ }
+ num += 1;
+ }
+ return;
+ }
+ uint num2 = reader.ReadPackedUInt32();
+ short num3 = 0;
+ while ((int)num3 < SystemTypeHelpers.AllTypes.Length)
+ {
+ SystemTypes systemTypes = SystemTypeHelpers.AllTypes[(int)num3];
+ ISystemType systemType2;
+ if (((ulong)num2 & (ulong)(1L << (int)(systemTypes & (SystemTypes)31))) != 0UL && this.Systems.TryGetValue(systemTypes, out systemType2))
+ {
+ systemType2.Deserialize(reader, false);
+ }
+ num3 += 1;
+ }
+ }
+
+ private void CheckEndCriteria()
+ {
+ if (!GameData.Instance)
+ {
+ return;
+ }
+ LifeSuppSystemType lifeSuppSystemType = (LifeSuppSystemType)this.Systems[SystemTypes.LifeSupp];
+ if (lifeSuppSystemType.Countdown < 0f)
+ {
+ this.EndGameForSabotage();
+ lifeSuppSystemType.Countdown = 10000f;
+ }
+ ReactorSystemType reactorSystemType = (ReactorSystemType)this.Systems[SystemTypes.Reactor];
+ if (reactorSystemType.Countdown < 0f)
+ {
+ this.EndGameForSabotage();
+ reactorSystemType.Countdown = 10000f;
+ }
+ int num = 0;
+ int num2 = 0;
+ int num3 = 0;
+ for (int i = 0; i < GameData.Instance.PlayerCount; i++)
+ {
+ GameData.PlayerInfo playerInfo = GameData.Instance.AllPlayers[i];
+ if (!playerInfo.Disconnected)
+ {
+ if (playerInfo.IsImpostor)
+ {
+ num3++;
+ }
+ if (!playerInfo.IsDead)
+ {
+ if (playerInfo.IsImpostor)
+ {
+ num2++;
+ }
+ else
+ {
+ num++;
+ }
+ }
+ }
+ }
+ if (num2 <= 0 && (!DestroyableSingleton<TutorialManager>.InstanceExists || num3 > 0))
+ {
+ if (!DestroyableSingleton<TutorialManager>.InstanceExists)
+ {
+ base.enabled = false;
+ ShipStatus.RpcEndGame((TempData.LastDeathReason == DeathReason.Disconnect) ? GameOverReason.ImpostorDisconnect : GameOverReason.HumansByVote, !SaveManager.BoughtNoAds);
+ return;
+ }
+ DestroyableSingleton<HudManager>.Instance.ShowPopUp("Normally The Crew would have just won because The Impostor is dead. For free play, we revive everyone instead.");
+ ShipStatus.ReviveEveryone();
+ return;
+ }
+ else
+ {
+ if (num > num2)
+ {
+ if (!DestroyableSingleton<TutorialManager>.InstanceExists)
+ {
+ if (GameData.Instance.TotalTasks <= GameData.Instance.CompletedTasks)
+ {
+ base.enabled = false;
+ ShipStatus.RpcEndGame(GameOverReason.HumansByTask, !SaveManager.BoughtNoAds);
+ return;
+ }
+ }
+ else if (PlayerControl.LocalPlayer.myTasks.All((PlayerTask t) => t.IsComplete))
+ {
+ DestroyableSingleton<HudManager>.Instance.ShowPopUp("Normally The Crew would have just won because the task bar is full. For free play, we issue new tasks instead.");
+ this.Begin();
+ }
+ return;
+ }
+ if (!DestroyableSingleton<TutorialManager>.InstanceExists)
+ {
+ base.enabled = false;
+ GameOverReason endReason;
+ switch (TempData.LastDeathReason)
+ {
+ case DeathReason.Exile:
+ endReason = GameOverReason.ImpostorByVote;
+ break;
+ case DeathReason.Kill:
+ endReason = GameOverReason.ImpostorByKill;
+ break;
+ default:
+ endReason = GameOverReason.HumansDisconnect;
+ break;
+ }
+ ShipStatus.RpcEndGame(endReason, !SaveManager.BoughtNoAds);
+ return;
+ }
+ DestroyableSingleton<HudManager>.Instance.ShowPopUp("Normally The Impostor would have just won because The Crew can no longer win. For free play, we revive everyone instead.");
+ ShipStatus.ReviveEveryone();
+ return;
+ }
+ }
+
+ private void EndGameForSabotage()
+ {
+ if (!DestroyableSingleton<TutorialManager>.InstanceExists)
+ {
+ base.enabled = false;
+ ShipStatus.RpcEndGame(GameOverReason.ImpostorBySabotage, !SaveManager.BoughtNoAds);
+ return;
+ }
+ DestroyableSingleton<HudManager>.Instance.ShowPopUp("Normally The Impostor would have just won because of the critical sabotage. Instead we just shut it off.");
+ }
+
+ public bool IsGameOverDueToDeath()
+ {
+ int num = 0;
+ int num2 = 0;
+ int num3 = 0;
+ for (int i = 0; i < GameData.Instance.PlayerCount; i++)
+ {
+ GameData.PlayerInfo playerInfo = GameData.Instance.AllPlayers[i];
+ if (!playerInfo.Disconnected)
+ {
+ if (playerInfo.IsImpostor)
+ {
+ num3++;
+ }
+ if (!playerInfo.IsDead)
+ {
+ if (playerInfo.IsImpostor)
+ {
+ num2++;
+ }
+ else
+ {
+ num++;
+ }
+ }
+ }
+ }
+ return (num2 <= 0 && (!DestroyableSingleton<TutorialManager>.InstanceExists || num3 > 0)) || num <= num2;
+ }
+
+ private static void RpcEndGame(GameOverReason endReason, bool showAd)
+ {
+ MessageWriter messageWriter = AmongUsClient.Instance.StartEndGame();
+ messageWriter.Write((byte)endReason);
+ messageWriter.Write(showAd);
+ AmongUsClient.Instance.FinishEndGame(messageWriter);
+ }
+
+ private static void ReviveEveryone()
+ {
+ for (int i = 0; i < GameData.Instance.PlayerCount; i++)
+ {
+ GameData.Instance.AllPlayers[i].Object.Revive();
+ }
+ UnityEngine.Object.FindObjectsOfType<DeadBody>().ForEach(delegate(DeadBody b)
+ {
+ UnityEngine.Object.Destroy(b.gameObject);
+ });
+ }
+
+ public bool CheckTaskCompletion()
+ {
+ if (DestroyableSingleton<TutorialManager>.InstanceExists)
+ {
+ if (PlayerControl.LocalPlayer.myTasks.All((PlayerTask t) => t.IsComplete))
+ {
+ DestroyableSingleton<HudManager>.Instance.ShowPopUp("Normally The Crew would have just won because the task bar is full. For free play, we issue new tasks instead.");
+ this.Begin();
+ }
+ return false;
+ }
+ GameData.Instance.RecomputeTaskCounts();
+ if (GameData.Instance.TotalTasks <= GameData.Instance.CompletedTasks)
+ {
+ base.enabled = false;
+ ShipStatus.RpcEndGame(GameOverReason.HumansByTask, !SaveManager.BoughtNoAds);
+ return true;
+ }
+ return false;
+ }
+
+ public void RpcCloseDoorsOfType(SystemTypes type)
+ {
+ if (AmongUsClient.Instance.AmHost)
+ {
+ this.CloseDoorsOfType(type);
+ return;
+ }
+ MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(this.NetId, 0, SendOption.Reliable, AmongUsClient.Instance.HostId);
+ messageWriter.Write((byte)type);
+ AmongUsClient.Instance.FinishRpcImmediately(messageWriter);
+ }
+
+ public void RpcRepairSystem(SystemTypes systemType, int amount)
+ {
+ if (AmongUsClient.Instance.AmHost)
+ {
+ this.RepairSystem(systemType, PlayerControl.LocalPlayer, (byte)amount);
+ return;
+ }
+ MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(this.NetId, 1, SendOption.Reliable, AmongUsClient.Instance.HostId);
+ messageWriter.Write((byte)systemType);
+ messageWriter.WriteNetObject(PlayerControl.LocalPlayer);
+ messageWriter.Write((byte)amount);
+ AmongUsClient.Instance.FinishRpcImmediately(messageWriter);
+ }
+
+ public override void HandleRpc(byte callId, MessageReader reader)
+ {
+ if (callId == 0)
+ {
+ this.CloseDoorsOfType((SystemTypes)reader.ReadByte());
+ return;
+ }
+ if (callId != 1)
+ {
+ return;
+ }
+ this.RepairSystem((SystemTypes)reader.ReadByte(), reader.ReadNetObject<PlayerControl>(), reader.ReadByte());
+ }
+}
diff --git a/Client/Assembly-CSharp/ShowAdsState.cs b/Client/Assembly-CSharp/ShowAdsState.cs
new file mode 100644
index 0000000..3515783
--- /dev/null
+++ b/Client/Assembly-CSharp/ShowAdsState.cs
@@ -0,0 +1,9 @@
+using System;
+
+public enum ShowAdsState : byte
+{
+ Personalized,
+ Accepted = 128,
+ NonPersonalized = 1,
+ Purchased = 130
+}
diff --git a/Client/Assembly-CSharp/SimonSaysGame.cs b/Client/Assembly-CSharp/SimonSaysGame.cs
new file mode 100644
index 0000000..a66114f
--- /dev/null
+++ b/Client/Assembly-CSharp/SimonSaysGame.cs
@@ -0,0 +1,243 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+
+public class SimonSaysGame : Minigame
+{
+ private int IndexCount
+ {
+ get
+ {
+ return (int)this.MyNormTask.Data[0];
+ }
+ set
+ {
+ this.MyNormTask.Data[0] = (byte)value;
+ }
+ }
+
+ private byte this[int idx]
+ {
+ get
+ {
+ return this.MyNormTask.Data[idx + 1];
+ }
+ set
+ {
+ this.MyNormTask.Data[idx + 1] = value;
+ }
+ }
+
+ private Queue<int> operations = new Queue<int>();
+
+ private const int FlashOp = 256;
+
+ private const int AnimateOp = 128;
+
+ private const int ReAnimateOp = 32;
+
+ private const int FailOp = 64;
+
+ private Color gray = new Color32(141, 141, 141, byte.MaxValue);
+
+ private Color blue = new Color32(68, 168, byte.MaxValue, byte.MaxValue);
+
+ private Color red = new Color32(byte.MaxValue, 58, 0, byte.MaxValue);
+
+ private Color green = Color.green;
+
+ public SpriteRenderer[] LeftSide;
+
+ public SpriteRenderer[] Buttons;
+
+ public SpriteRenderer[] LeftLights;
+
+ public SpriteRenderer[] RightLights;
+
+ private float flashTime = 0.25f;
+
+ private float userButtonFlashTime = 0.175f;
+
+ public AudioClip ButtonPressSound;
+
+ public AudioClip FailSound;
+
+ public override void Begin(PlayerTask task)
+ {
+ for (int i = 0; i < this.LeftSide.Length; i++)
+ {
+ this.LeftSide[i].color = Color.clear;
+ }
+ base.Begin(task);
+ if (this.IndexCount == 0)
+ {
+ this.operations.Enqueue(128);
+ }
+ else
+ {
+ this.operations.Enqueue(32);
+ }
+ base.StartCoroutine(this.CoRun());
+ }
+
+ public void HitButton(int bIdx)
+ {
+ if (this.MyNormTask.IsComplete || this.MyNormTask.taskStep >= this.IndexCount)
+ {
+ return;
+ }
+ if ((int)this[this.MyNormTask.taskStep] == bIdx)
+ {
+ this.MyNormTask.NextStep();
+ this.SetLights(this.RightLights, this.MyNormTask.taskStep);
+ if (this.MyNormTask.IsComplete)
+ {
+ this.SetLights(this.LeftLights, this.LeftLights.Length);
+ for (int i = 0; i < this.Buttons.Length; i++)
+ {
+ SpriteRenderer spriteRenderer = this.Buttons[i];
+ spriteRenderer.color = this.gray;
+ base.StartCoroutine(this.FlashButton(-1, spriteRenderer, this.flashTime));
+ }
+ base.StartCoroutine(base.CoStartClose(0.75f));
+ return;
+ }
+ this.operations.Enqueue(256 | bIdx);
+ if (this.MyNormTask.taskStep >= this.IndexCount)
+ {
+ this.operations.Enqueue(128);
+ return;
+ }
+ }
+ else
+ {
+ this.IndexCount = 0;
+ this.operations.Enqueue(64);
+ this.operations.Enqueue(128);
+ }
+ }
+
+ private IEnumerator CoRun()
+ {
+ for (;;)
+ {
+ if (this.operations.Count <= 0)
+ {
+ yield return null;
+ }
+ else
+ {
+ int num = this.operations.Dequeue();
+ if (num.HasAnyBit(256))
+ {
+ int num2 = num & -257;
+ yield return this.FlashButton(num2, this.Buttons[num2], this.userButtonFlashTime);
+ }
+ else if (num.HasAnyBit(128))
+ {
+ yield return this.CoAnimateNewLeftSide();
+ }
+ else if (num.HasAnyBit(32))
+ {
+ yield return this.CoAnimateOldLeftSide();
+ }
+ else if (num.HasAnyBit(64))
+ {
+ if (Constants.ShouldPlaySfx())
+ {
+ SoundManager.Instance.PlaySound(this.FailSound, false, 1f);
+ }
+ this.SetAllColor(this.red);
+ yield return new WaitForSeconds(this.flashTime);
+ this.SetAllColor(Color.white);
+ yield return new WaitForSeconds(this.flashTime);
+ this.SetAllColor(this.red);
+ yield return new WaitForSeconds(this.flashTime);
+ this.SetAllColor(Color.white);
+ yield return new WaitForSeconds(this.flashTime / 2f);
+ }
+ }
+ }
+ yield break;
+ }
+
+ private void AddIndex(int idxToAdd)
+ {
+ this[this.IndexCount] = (byte)idxToAdd;
+ int indexCount = this.IndexCount;
+ this.IndexCount = indexCount + 1;
+ }
+
+ private IEnumerator CoAnimateNewLeftSide()
+ {
+ this.SetLights(this.RightLights, 0);
+ for (int i = 0; i < this.Buttons.Length; i++)
+ {
+ this.Buttons[i].color = this.gray;
+ }
+ this.AddIndex(this.Buttons.RandomIdx<SpriteRenderer>());
+ yield return this.CoAnimateOldLeftSide();
+ yield break;
+ }
+
+ private IEnumerator CoAnimateOldLeftSide()
+ {
+ yield return new WaitForSeconds(1f);
+ this.SetLights(this.LeftLights, this.IndexCount);
+ int num2;
+ for (int i = 0; i < this.IndexCount; i = num2)
+ {
+ int num = (int)this[i];
+ yield return this.FlashButton(num, this.LeftSide[num], this.flashTime);
+ yield return new WaitForSeconds(0.1f);
+ num2 = i + 1;
+ }
+ this.MyNormTask.taskStep = 0;
+ for (int j = 0; j < this.Buttons.Length; j++)
+ {
+ this.Buttons[j].color = Color.white;
+ }
+ yield break;
+ }
+
+ private IEnumerator FlashButton(int id, SpriteRenderer butt, float flashTime)
+ {
+ if (id > -1 && Constants.ShouldPlaySfx())
+ {
+ SoundManager.Instance.PlaySound(this.ButtonPressSound, false, 1f).pitch = Mathf.Lerp(0.5f, 1.5f, (float)id / 9f);
+ }
+ Color c = butt.color;
+ butt.color = this.blue;
+ yield return new WaitForSeconds(flashTime);
+ butt.color = c;
+ yield break;
+ }
+
+ private void SetLights(SpriteRenderer[] lights, int num)
+ {
+ for (int i = 0; i < lights.Length; i++)
+ {
+ if (i < num)
+ {
+ lights[i].color = this.green;
+ }
+ else
+ {
+ lights[i].color = this.gray;
+ }
+ }
+ }
+
+ private void SetAllColor(Color color)
+ {
+ for (int i = 0; i < this.Buttons.Length; i++)
+ {
+ this.Buttons[i].color = color;
+ }
+ for (int j = 0; j < this.RightLights.Length; j++)
+ {
+ this.RightLights[j].color = color;
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/SinglePopHelp.cs b/Client/Assembly-CSharp/SinglePopHelp.cs
new file mode 100644
index 0000000..4a8d086
--- /dev/null
+++ b/Client/Assembly-CSharp/SinglePopHelp.cs
@@ -0,0 +1,6 @@
+using System;
+using UnityEngine;
+
+public class SinglePopHelp : MonoBehaviour
+{
+}
diff --git a/Client/Assembly-CSharp/SkinData.cs b/Client/Assembly-CSharp/SkinData.cs
new file mode 100644
index 0000000..0137d23
--- /dev/null
+++ b/Client/Assembly-CSharp/SkinData.cs
@@ -0,0 +1,48 @@
+using System;
+using UnityEngine;
+
+[CreateAssetMenu]
+public class SkinData : ScriptableObject, IBuyable
+{
+ public string ProdId
+ {
+ get
+ {
+ return this.RelatedHat.ProductId;
+ }
+ }
+
+ public Sprite IdleFrame;
+
+ public AnimationClip IdleAnim;
+
+ public AnimationClip RunAnim;
+
+ public AnimationClip EnterVentAnim;
+
+ public AnimationClip ExitVentAnim;
+
+ public AnimationClip KillTongueImpostor;
+
+ public AnimationClip KillTongueVictim;
+
+ public AnimationClip KillShootImpostor;
+
+ public AnimationClip KillShootVictim;
+
+ public AnimationClip KillStabVictim;
+
+ public AnimationClip KillNeckVictim;
+
+ public Sprite EjectFrame;
+
+ public AnimationClip SpawnAnim;
+
+ public bool Free;
+
+ public HatBehaviour RelatedHat;
+
+ public string StoreName;
+
+ public int Order;
+}
diff --git a/Client/Assembly-CSharp/SkinLayer.cs b/Client/Assembly-CSharp/SkinLayer.cs
new file mode 100644
index 0000000..80f0cf6
--- /dev/null
+++ b/Client/Assembly-CSharp/SkinLayer.cs
@@ -0,0 +1,101 @@
+using System;
+using PowerTools;
+using UnityEngine;
+
+public class SkinLayer : MonoBehaviour
+{
+ public bool Flipped
+ {
+ set
+ {
+ this.layer.flipX = value;
+ }
+ }
+
+ public bool Visible
+ {
+ set
+ {
+ this.layer.enabled = value;
+ }
+ }
+
+ public SpriteRenderer layer;
+
+ public SpriteAnim animator;
+
+ public SkinData skin;
+
+ public void SetRun()
+ {
+ if (!this.skin || !this.animator)
+ {
+ this.SetGhost();
+ return;
+ }
+ if (!this.animator.IsPlaying(this.skin.RunAnim))
+ {
+ this.animator.Play(this.skin.RunAnim, 1f);
+ }
+ }
+
+ public void SetSpawn(float time = 0f)
+ {
+ if (!this.skin || !this.animator)
+ {
+ this.SetGhost();
+ return;
+ }
+ this.animator.Play(this.skin.SpawnAnim, 1f);
+ this.animator.Time = time;
+ }
+
+ public void SetExitVent()
+ {
+ if (!this.skin || !this.animator)
+ {
+ this.SetGhost();
+ return;
+ }
+ this.animator.Play(this.skin.ExitVentAnim, 1f);
+ }
+
+ public void SetEnterVent()
+ {
+ if (!this.skin || !this.animator)
+ {
+ this.SetGhost();
+ return;
+ }
+ this.animator.Play(this.skin.EnterVentAnim, 1f);
+ }
+
+ public void SetIdle()
+ {
+ if (!this.skin || !this.animator)
+ {
+ this.SetGhost();
+ return;
+ }
+ if (!this.animator.IsPlaying(this.skin.IdleAnim))
+ {
+ this.animator.Play(this.skin.IdleAnim, 1f);
+ }
+ }
+
+ public void SetGhost()
+ {
+ if (!this.animator)
+ {
+ return;
+ }
+ this.animator.Stop();
+ this.layer.sprite = null;
+ }
+
+ internal void SetSkin(uint skinId)
+ {
+ this.skin = DestroyableSingleton<HatManager>.Instance.GetSkinById(skinId);
+ this.SetIdle();
+ }
+}
diff --git a/Client/Assembly-CSharp/SkinsTab.cs b/Client/Assembly-CSharp/SkinsTab.cs
new file mode 100644
index 0000000..8eecb09
--- /dev/null
+++ b/Client/Assembly-CSharp/SkinsTab.cs
@@ -0,0 +1,83 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+public class SkinsTab : MonoBehaviour
+{
+ public ColorChip ColorTabPrefab;
+
+ public SpriteRenderer DemoImage;
+
+ public SpriteRenderer HatImage;
+
+ public SpriteRenderer SkinImage;
+
+ public SpriteRenderer PetImage;
+
+ public FloatRange XRange = new FloatRange(1.5f, 3f);
+
+ public float YStart = 0.8f;
+
+ public float YOffset = 0.8f;
+
+ public int NumPerRow = 4;
+
+ public Scroller scroller;
+
+ private List<ColorChip> ColorChips = new List<ColorChip>();
+
+ public void OnEnable()
+ {
+ PlayerControl.SetPlayerMaterialColors((int)PlayerControl.LocalPlayer.Data.ColorId, this.DemoImage);
+ PlayerControl.SetHatImage(SaveManager.LastHat, this.HatImage);
+ PlayerControl.SetSkinImage(SaveManager.LastSkin, this.SkinImage);
+ PlayerControl.SetPetImage(SaveManager.LastPet, (int)PlayerControl.LocalPlayer.Data.ColorId, this.PetImage);
+ SkinData[] unlockedSkins = DestroyableSingleton<HatManager>.Instance.GetUnlockedSkins();
+ for (int i = 0; i < unlockedSkins.Length; i++)
+ {
+ SkinData skin = unlockedSkins[i];
+ float x = this.XRange.Lerp((float)(i % this.NumPerRow) / ((float)this.NumPerRow - 1f));
+ float y = this.YStart - (float)(i / this.NumPerRow) * this.YOffset;
+ ColorChip colorChip = UnityEngine.Object.Instantiate<ColorChip>(this.ColorTabPrefab, this.scroller.Inner);
+ colorChip.transform.localPosition = new Vector3(x, y, -1f);
+ colorChip.Button.OnClick.AddListener(delegate()
+ {
+ this.SelectHat(skin);
+ });
+ colorChip.Inner.sprite = skin.IdleFrame;
+ this.ColorChips.Add(colorChip);
+ }
+ this.scroller.YBounds.max = -(this.YStart - (float)(unlockedSkins.Length / this.NumPerRow) * this.YOffset) - 3f;
+ }
+
+ public void OnDisable()
+ {
+ for (int i = 0; i < this.ColorChips.Count; i++)
+ {
+ UnityEngine.Object.Destroy(this.ColorChips[i].gameObject);
+ }
+ this.ColorChips.Clear();
+ }
+
+ public void Update()
+ {
+ PlayerControl.SetPlayerMaterialColors((int)PlayerControl.LocalPlayer.Data.ColorId, this.DemoImage);
+ SkinData skinById = DestroyableSingleton<HatManager>.Instance.GetSkinById(SaveManager.LastSkin);
+ for (int i = 0; i < this.ColorChips.Count; i++)
+ {
+ ColorChip colorChip = this.ColorChips[i];
+ colorChip.InUseForeground.SetActive(skinById.IdleFrame == colorChip.Inner.sprite);
+ }
+ }
+
+ private void SelectHat(SkinData skin)
+ {
+ uint idFromSkin = DestroyableSingleton<HatManager>.Instance.GetIdFromSkin(skin);
+ SaveManager.LastSkin = idFromSkin;
+ PlayerControl.SetSkinImage(SaveManager.LastSkin, this.SkinImage);
+ if (PlayerControl.LocalPlayer)
+ {
+ PlayerControl.LocalPlayer.RpcSetSkin(idFromSkin);
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/SlideBar.cs b/Client/Assembly-CSharp/SlideBar.cs
new file mode 100644
index 0000000..da36f58
--- /dev/null
+++ b/Client/Assembly-CSharp/SlideBar.cs
@@ -0,0 +1,78 @@
+using System;
+using UnityEngine;
+using UnityEngine.Events;
+
+public class SlideBar : MonoBehaviour
+{
+ public TextRenderer Title;
+
+ public SpriteRenderer Bar;
+
+ public Collider2D HitBox;
+
+ public SpriteRenderer Dot;
+
+ public FloatRange Range;
+
+ public bool Vertical;
+
+ public float Value;
+
+ public UnityEvent OnValueChange;
+
+ public void OnEnable()
+ {
+ if (this.Title)
+ {
+ this.Title.Color = Color.white;
+ }
+ this.Bar.color = Color.white;
+ this.Dot.color = Color.white;
+ }
+
+ public void OnDisable()
+ {
+ if (this.Title)
+ {
+ this.Title.Color = Color.gray;
+ }
+ this.Bar.color = Color.gray;
+ this.Dot.color = Color.gray;
+ }
+
+ public void Update()
+ {
+ Vector3 localPosition = this.Dot.transform.localPosition;
+ switch (DestroyableSingleton<PassiveButtonManager>.Instance.Controller.CheckDrag(this.HitBox, false))
+ {
+ case DragState.Dragging:
+ {
+ Vector2 vector = DestroyableSingleton<PassiveButtonManager>.Instance.Controller.DragPosition - this.Bar.transform.position;
+ if (this.Vertical)
+ {
+ localPosition.y = this.Range.Clamp(vector.y);
+ this.Value = this.Range.ReverseLerp(localPosition.y);
+ }
+ else
+ {
+ localPosition.x = this.Range.Clamp(vector.x);
+ this.Value = this.Range.ReverseLerp(localPosition.x);
+ }
+ this.OnValueChange.Invoke();
+ break;
+ }
+ case DragState.Released:
+ this.OnValueChange.Invoke();
+ break;
+ }
+ if (this.Vertical)
+ {
+ localPosition.y = this.Range.Lerp(this.Value);
+ }
+ else
+ {
+ localPosition.x = this.Range.Lerp(this.Value);
+ }
+ this.Dot.transform.localPosition = localPosition;
+ }
+}
diff --git a/Client/Assembly-CSharp/SortGameObject.cs b/Client/Assembly-CSharp/SortGameObject.cs
new file mode 100644
index 0000000..40a2fc8
--- /dev/null
+++ b/Client/Assembly-CSharp/SortGameObject.cs
@@ -0,0 +1,16 @@
+using System;
+using UnityEngine;
+
+public class SortGameObject : MonoBehaviour
+{
+ public SortGameObject.ObjType MyType;
+
+ public Collider2D Collider;
+
+ public enum ObjType
+ {
+ Plant,
+ Mineral,
+ Animal
+ }
+}
diff --git a/Client/Assembly-CSharp/SortMinigame.cs b/Client/Assembly-CSharp/SortMinigame.cs
new file mode 100644
index 0000000..829f527
--- /dev/null
+++ b/Client/Assembly-CSharp/SortMinigame.cs
@@ -0,0 +1,72 @@
+using System;
+using UnityEngine;
+
+public class SortMinigame : Minigame
+{
+ public SortGameObject[] Objects;
+
+ public BoxCollider2D AnimalBox;
+
+ public BoxCollider2D PlantBox;
+
+ public BoxCollider2D MineralBox;
+
+ private Controller myController = new Controller();
+
+ public void Start()
+ {
+ this.Objects.Shuffle<SortGameObject>();
+ for (int i = 0; i < this.Objects.Length; i++)
+ {
+ this.Objects[i].transform.localPosition = new Vector3(Mathf.Lerp(-2f, 2f, (float)i / ((float)this.Objects.Length - 1f)), FloatRange.Next(-1.75f, -1f), -1f);
+ }
+ }
+
+ public void Update()
+ {
+ if (this.amClosing != Minigame.CloseState.None)
+ {
+ return;
+ }
+ this.myController.Update();
+ for (int i = 0; i < this.Objects.Length; i++)
+ {
+ SortGameObject sortGameObject = this.Objects[i];
+ switch (this.myController.CheckDrag(sortGameObject.Collider, false))
+ {
+ case DragState.Dragging:
+ {
+ Vector2 dragPosition = this.myController.DragPosition;
+ sortGameObject.Collider.attachedRigidbody.position = dragPosition;
+ break;
+ }
+ case DragState.Released:
+ {
+ bool flag = true;
+ for (int j = 0; j < this.Objects.Length; j++)
+ {
+ SortGameObject sortGameObject2 = this.Objects[j];
+ switch (sortGameObject2.MyType)
+ {
+ case SortGameObject.ObjType.Plant:
+ flag &= sortGameObject2.Collider.IsTouching(this.PlantBox);
+ break;
+ case SortGameObject.ObjType.Mineral:
+ flag &= sortGameObject2.Collider.IsTouching(this.MineralBox);
+ break;
+ case SortGameObject.ObjType.Animal:
+ flag &= sortGameObject2.Collider.IsTouching(this.AnimalBox);
+ break;
+ }
+ }
+ if (flag)
+ {
+ this.MyTask.Complete();
+ base.StartCoroutine(base.CoStartClose(0.75f));
+ }
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/SoundGroup.cs b/Client/Assembly-CSharp/SoundGroup.cs
new file mode 100644
index 0000000..b7ec876
--- /dev/null
+++ b/Client/Assembly-CSharp/SoundGroup.cs
@@ -0,0 +1,13 @@
+using System;
+using UnityEngine;
+
+[CreateAssetMenu]
+public class SoundGroup : ScriptableObject
+{
+ public AudioClip[] Clips;
+
+ public AudioClip Random()
+ {
+ return this.Clips.Random<AudioClip>();
+ }
+}
diff --git a/Client/Assembly-CSharp/SoundManager.cs b/Client/Assembly-CSharp/SoundManager.cs
new file mode 100644
index 0000000..4d03573
--- /dev/null
+++ b/Client/Assembly-CSharp/SoundManager.cs
@@ -0,0 +1,239 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+using UnityEngine.Audio;
+
+public class SoundManager : MonoBehaviour
+{
+ public static SoundManager Instance
+ {
+ get
+ {
+ if (!SoundManager._Instance)
+ {
+ SoundManager._Instance = (UnityEngine.Object.FindObjectOfType<SoundManager>() ?? new GameObject("SoundManager").AddComponent<SoundManager>());
+ }
+ return SoundManager._Instance;
+ }
+ }
+
+ private static SoundManager _Instance;
+
+ public AudioMixerGroup musicMixer;
+
+ public AudioMixerGroup sfxMixer;
+
+ public static float MusicVolume = 1f;
+
+ public static float SfxVolume = 1f;
+
+ private Dictionary<AudioClip, AudioSource> allSources = new Dictionary<AudioClip, AudioSource>();
+
+ private List<ISoundPlayer> soundPlayers = new List<ISoundPlayer>();
+
+ public void Start()
+ {
+ if (SoundManager._Instance && SoundManager._Instance != this)
+ {
+ UnityEngine.Object.Destroy(base.gameObject);
+ return;
+ }
+ SoundManager._Instance = this;
+ this.UpdateVolume();
+ UnityEngine.Object.DontDestroyOnLoad(base.gameObject);
+ }
+
+ public void Update()
+ {
+ for (int i = 0; i < this.soundPlayers.Count; i++)
+ {
+ this.soundPlayers[i].Update(Time.deltaTime);
+ }
+ }
+
+ private void UpdateVolume()
+ {
+ this.ChangeSfxVolume(SaveManager.SfxVolume);
+ this.ChangeMusicVolume(SaveManager.MusicVolume);
+ }
+
+ public void ChangeSfxVolume(float volume)
+ {
+ if (volume <= 0f)
+ {
+ SoundManager.SfxVolume = -80f;
+ }
+ else
+ {
+ SoundManager.SfxVolume = Mathf.Log10(volume) * 20f;
+ }
+ this.musicMixer.audioMixer.SetFloat("SfxVolume", SoundManager.SfxVolume);
+ }
+
+ public void ChangeMusicVolume(float volume)
+ {
+ if (volume <= 0f)
+ {
+ SoundManager.MusicVolume = -80f;
+ }
+ else
+ {
+ SoundManager.MusicVolume = Mathf.Log10(volume) * 20f;
+ }
+ this.musicMixer.audioMixer.SetFloat("MusicVolume", SoundManager.MusicVolume);
+ }
+
+ public void StopSound(AudioClip clip)
+ {
+ AudioSource audioSource;
+ if (this.allSources.TryGetValue(clip, out audioSource))
+ {
+ this.allSources.Remove(clip);
+ audioSource.Stop();
+ UnityEngine.Object.Destroy(audioSource);
+ }
+ for (int i = 0; i < this.soundPlayers.Count; i++)
+ {
+ ISoundPlayer soundPlayer = this.soundPlayers[i];
+ if (soundPlayer.Player.clip == clip)
+ {
+ UnityEngine.Object.Destroy(soundPlayer.Player);
+ this.soundPlayers.RemoveAt(i);
+ return;
+ }
+ }
+ }
+
+ public void StopAllSound()
+ {
+ for (int i = 0; i < this.soundPlayers.Count; i++)
+ {
+ UnityEngine.Object.Destroy(this.soundPlayers[i].Player);
+ }
+ this.soundPlayers.Clear();
+ foreach (KeyValuePair<AudioClip, AudioSource> keyValuePair in this.allSources)
+ {
+ AudioSource value = keyValuePair.Value;
+ value.volume = 0f;
+ value.Stop();
+ UnityEngine.Object.Destroy(keyValuePair.Value);
+ }
+ this.allSources.Clear();
+ }
+
+ public void PlayDynamicSound(string name, AudioClip clip, bool loop, DynamicSound.GetDynamicsFunction volumeFunc, bool playAsSfx = false)
+ {
+ DynamicSound dynamicSound = null;
+ for (int i = 0; i < this.soundPlayers.Count; i++)
+ {
+ ISoundPlayer soundPlayer = this.soundPlayers[i];
+ if (soundPlayer.Name == name && soundPlayer is DynamicSound)
+ {
+ dynamicSound = (DynamicSound)soundPlayer;
+ break;
+ }
+ }
+ if (dynamicSound == null)
+ {
+ dynamicSound = new DynamicSound();
+ dynamicSound.Name = name;
+ dynamicSound.Player = base.gameObject.AddComponent<AudioSource>();
+ dynamicSound.Player.outputAudioMixerGroup = ((loop && !playAsSfx) ? this.musicMixer : this.sfxMixer);
+ dynamicSound.Player.playOnAwake = false;
+ this.soundPlayers.Add(dynamicSound);
+ }
+ dynamicSound.Player.loop = loop;
+ dynamicSound.SetTarget(clip, volumeFunc);
+ }
+
+ public void CrossFadeSound(string name, AudioClip clip, float maxVolume, float duration = 1.5f)
+ {
+ CrossFader crossFader = null;
+ for (int i = 0; i < this.soundPlayers.Count; i++)
+ {
+ ISoundPlayer soundPlayer = this.soundPlayers[i];
+ if (soundPlayer.Name == name && soundPlayer is CrossFader)
+ {
+ crossFader = (CrossFader)soundPlayer;
+ break;
+ }
+ }
+ if (crossFader == null)
+ {
+ crossFader = new CrossFader();
+ crossFader.Name = name;
+ crossFader.MaxVolume = maxVolume;
+ crossFader.Player = base.gameObject.AddComponent<AudioSource>();
+ crossFader.Player.outputAudioMixerGroup = this.musicMixer;
+ crossFader.Player.playOnAwake = false;
+ crossFader.Player.loop = true;
+ this.soundPlayers.Add(crossFader);
+ }
+ crossFader.SetTarget(clip);
+ }
+
+ public AudioSource PlaySoundImmediate(AudioClip clip, bool loop, float volume = 1f, float pitch = 1f)
+ {
+ if (clip == null)
+ {
+ Debug.LogWarning("Missing audio clip");
+ return null;
+ }
+ AudioSource audioSource;
+ if (this.allSources.TryGetValue(clip, out audioSource))
+ {
+ audioSource.pitch = pitch;
+ audioSource.loop = loop;
+ audioSource.Play();
+ }
+ else
+ {
+ audioSource = base.gameObject.AddComponent<AudioSource>();
+ audioSource.outputAudioMixerGroup = (loop ? this.musicMixer : this.sfxMixer);
+ audioSource.playOnAwake = false;
+ audioSource.volume = volume;
+ audioSource.pitch = pitch;
+ audioSource.loop = loop;
+ audioSource.clip = clip;
+ audioSource.Play();
+ this.allSources.Add(clip, audioSource);
+ }
+ return audioSource;
+ }
+
+ public bool SoundIsPlaying(AudioClip clip)
+ {
+ AudioSource audioSource;
+ return this.allSources.TryGetValue(clip, out audioSource) && !audioSource.isPlaying;
+ }
+
+ public AudioSource PlaySound(AudioClip clip, bool loop, float volume = 1f)
+ {
+ if (clip == null)
+ {
+ Debug.LogWarning("Missing audio clip");
+ return null;
+ }
+ AudioSource audioSource;
+ if (this.allSources.TryGetValue(clip, out audioSource))
+ {
+ if (!audioSource.isPlaying)
+ {
+ audioSource.loop = loop;
+ audioSource.Play();
+ }
+ }
+ else
+ {
+ audioSource = base.gameObject.AddComponent<AudioSource>();
+ audioSource.outputAudioMixerGroup = (loop ? this.musicMixer : this.sfxMixer);
+ audioSource.playOnAwake = false;
+ audioSource.volume = volume;
+ audioSource.loop = loop;
+ audioSource.clip = clip;
+ audioSource.Play();
+ this.allSources.Add(clip, audioSource);
+ }
+ return audioSource;
+ }
+}
diff --git a/Client/Assembly-CSharp/SoundStarter.cs b/Client/Assembly-CSharp/SoundStarter.cs
new file mode 100644
index 0000000..0a1258e
--- /dev/null
+++ b/Client/Assembly-CSharp/SoundStarter.cs
@@ -0,0 +1,23 @@
+using System;
+using UnityEngine;
+
+public class SoundStarter : MonoBehaviour
+{
+ public string Name;
+
+ public AudioClip SoundToPlay;
+
+ public bool StopAll;
+
+ [Range(0f, 1f)]
+ public float Volume = 1f;
+
+ public void Awake()
+ {
+ if (this.StopAll)
+ {
+ SoundManager.Instance.StopAllSound();
+ }
+ SoundManager.Instance.CrossFadeSound(this.Name, this.SoundToPlay, this.Volume, 1.5f);
+ }
+}
diff --git a/Client/Assembly-CSharp/SpinAnimator.cs b/Client/Assembly-CSharp/SpinAnimator.cs
new file mode 100644
index 0000000..e3762f2
--- /dev/null
+++ b/Client/Assembly-CSharp/SpinAnimator.cs
@@ -0,0 +1,72 @@
+using System;
+using System.Collections;
+using UnityEngine;
+
+public class SpinAnimator : MonoBehaviour
+{
+ public float Speed = 60f;
+
+ private SpinAnimator.States curState;
+
+ private enum States
+ {
+ Visible,
+ Invisible,
+ Spinning,
+ Pulsing
+ }
+
+ private void Update()
+ {
+ if (this.curState == SpinAnimator.States.Spinning)
+ {
+ base.transform.Rotate(0f, 0f, this.Speed * Time.deltaTime);
+ }
+ }
+
+ public void Appear()
+ {
+ if (this.curState != SpinAnimator.States.Invisible)
+ {
+ return;
+ }
+ this.curState = SpinAnimator.States.Visible;
+ base.gameObject.SetActive(true);
+ base.StopAllCoroutines();
+ base.StartCoroutine(Effects.ScaleIn(base.transform, 0f, 1f, 0.125f));
+ }
+
+ public void Disappear()
+ {
+ if (this.curState == SpinAnimator.States.Invisible)
+ {
+ return;
+ }
+ this.curState = SpinAnimator.States.Invisible;
+ base.StopAllCoroutines();
+ base.StartCoroutine(this.CoDisappear());
+ }
+
+ private IEnumerator CoDisappear()
+ {
+ yield return Effects.ScaleIn(base.transform, 1f, 0f, 0.125f);
+ base.gameObject.SetActive(false);
+ yield break;
+ }
+
+ public void StartPulse()
+ {
+ if (this.curState == SpinAnimator.States.Pulsing)
+ {
+ return;
+ }
+ this.curState = SpinAnimator.States.Pulsing;
+ SpriteRenderer component = base.GetComponent<SpriteRenderer>();
+ base.StartCoroutine(Effects.CycleColors(component, Color.white, Color.green, 1f, float.MaxValue));
+ }
+
+ internal void Play()
+ {
+ this.curState = SpinAnimator.States.Spinning;
+ }
+}
diff --git a/Client/Assembly-CSharp/SpriteParticle.cs b/Client/Assembly-CSharp/SpriteParticle.cs
new file mode 100644
index 0000000..8661403
--- /dev/null
+++ b/Client/Assembly-CSharp/SpriteParticle.cs
@@ -0,0 +1,128 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+[RequireComponent(typeof(MeshFilter))]
+[RequireComponent(typeof(MeshRenderer))]
+public class SpriteParticle : MonoBehaviour
+{
+ private const float FrameRate = 24f;
+
+ public Sprite[] Sprites;
+
+ public ParticleInfo[] Particles;
+
+ public ushort[][] TriangleCache;
+
+ private Vector3[] verts;
+
+ private Vector2[] uvs;
+
+ private List<int> tris = new List<int>();
+
+ private Mesh mesh;
+
+ private int MaxVerts;
+
+ private Dictionary<int, Vector2[]> VertCache = new Dictionary<int, Vector2[]>();
+
+ private Dictionary<int, Vector2[]> UvCache = new Dictionary<int, Vector2[]>();
+
+ public void OnDrawGizmos()
+ {
+ if (this.Particles == null || this.Sprites == null)
+ {
+ return;
+ }
+ if (this.Sprites.Length == 0)
+ {
+ return;
+ }
+ Sprite sprite = this.Sprites[0];
+ for (int i = 0; i < this.Particles.Length; i++)
+ {
+ ParticleInfo particleInfo = this.Particles[i];
+ Gizmos.DrawCube(particleInfo.Position + base.transform.position, new Vector3(sprite.bounds.size.x * particleInfo.Scale, sprite.bounds.size.y * particleInfo.Scale, sprite.bounds.size.x * particleInfo.Scale));
+ }
+ }
+
+ public void Start()
+ {
+ MeshFilter component = base.GetComponent<MeshFilter>();
+ this.mesh = new Mesh();
+ this.mesh.MarkDynamic();
+ component.mesh = this.mesh;
+ this.TriangleCache = new ushort[this.Sprites.Length][];
+ for (int i = 0; i < this.Sprites.Length; i++)
+ {
+ Sprite sprite = this.Sprites[i];
+ Vector2[] vertices = sprite.vertices;
+ this.TriangleCache[i] = sprite.triangles;
+ this.VertCache[i] = vertices;
+ this.UvCache[i] = sprite.uv;
+ if (this.MaxVerts < vertices.Length)
+ {
+ this.MaxVerts = vertices.Length;
+ }
+ }
+ this.verts = new Vector3[this.Particles.Length * this.MaxVerts];
+ this.uvs = new Vector2[this.verts.Length];
+ for (int j = 0; j < this.Particles.Length; j++)
+ {
+ int num = j * this.MaxVerts;
+ int num2 = (int)this.Particles[j].Timer;
+ Sprite sprite2 = this.Sprites[num2];
+ Vector2[] vertices2 = sprite2.vertices;
+ Vector2[] uv = sprite2.uv;
+ for (int k = 0; k < sprite2.vertices.Length; k++)
+ {
+ int num3 = num + k;
+ this.verts[num3].x = vertices2[k].x * this.Particles[j].Scale + this.Particles[j].Position.x;
+ this.verts[num3].y = vertices2[k].y * this.Particles[j].Scale + this.Particles[j].Position.y;
+ this.uvs[num3] = uv[k];
+ }
+ ushort[] triangles = sprite2.triangles;
+ for (int l = 0; l < triangles.Length; l++)
+ {
+ this.tris.Add((int)triangles[l]);
+ }
+ }
+ this.mesh.vertices = this.verts;
+ this.mesh.uv = this.uvs;
+ this.mesh.SetTriangles(this.tris, 0);
+ }
+
+ public void Update()
+ {
+ float num = Time.deltaTime * 24f;
+ this.tris.Clear();
+ for (int i = 0; i < this.Particles.Length; i++)
+ {
+ float num2 = this.Particles[i].Timer + num;
+ if (num2 >= (float)this.Sprites.Length)
+ {
+ num2 %= 24f;
+ }
+ this.Particles[i].Timer = num2;
+ int num3 = i * this.MaxVerts;
+ int num4 = (int)this.Particles[i].Timer;
+ Vector2[] array = this.VertCache[num4];
+ Vector2[] array2 = this.UvCache[num4];
+ for (int j = 0; j < array.Length; j++)
+ {
+ int num5 = num3 + j;
+ this.verts[num5].x = array[j].x * this.Particles[i].Scale + this.Particles[i].Position.x;
+ this.verts[num5].y = array[j].y * this.Particles[i].Scale + this.Particles[i].Position.y;
+ this.uvs[num5] = array2[j];
+ }
+ ushort[] array3 = this.TriangleCache[num4];
+ for (int k = 0; k < array3.Length; k++)
+ {
+ this.tris.Add((int)array3[k] + num3);
+ }
+ }
+ this.mesh.vertices = this.verts;
+ this.mesh.uv = this.uvs;
+ this.mesh.SetTriangles(this.tris, 0);
+ }
+}
diff --git a/Client/Assembly-CSharp/StarGen.cs b/Client/Assembly-CSharp/StarGen.cs
new file mode 100644
index 0000000..2ecd529
--- /dev/null
+++ b/Client/Assembly-CSharp/StarGen.cs
@@ -0,0 +1,170 @@
+using System;
+using UnityEngine;
+
+[RequireComponent(typeof(MeshFilter))]
+[RequireComponent(typeof(MeshRenderer))]
+public class StarGen : MonoBehaviour
+{
+ private const float MaxStarRadius = 0.05f;
+
+ public int NumStars = 500;
+
+ public float Length = 25f;
+
+ public float Width = 25f;
+
+ public Vector2 Direction = new Vector2(1f, 0f);
+
+ private Vector2 NormDir = new Vector2(1f, 0f);
+
+ private Vector2 Tangent = new Vector2(0f, 1f);
+
+ private float tanLen;
+
+ public FloatRange Rates = new FloatRange(0.25f, 1f);
+
+ [HideInInspector]
+ private StarGen.Stars[] stars;
+
+ [HideInInspector]
+ private Vector3[] verts;
+
+ [HideInInspector]
+ private Mesh mesh;
+
+ [Serializable]
+ private struct Stars
+ {
+ public float Size;
+
+ public float Rate;
+
+ public float PositionX;
+
+ public float PositionY;
+ }
+
+ public void Start()
+ {
+ this.stars = new StarGen.Stars[this.NumStars];
+ this.verts = new Vector3[this.NumStars * 4];
+ Vector2[] array = new Vector2[this.NumStars * 4];
+ int[] array2 = new int[this.NumStars * 6];
+ this.SetDirection(this.Direction);
+ MeshFilter component = base.GetComponent<MeshFilter>();
+ this.mesh = new Mesh();
+ this.mesh.MarkDynamic();
+ component.mesh = this.mesh;
+ Vector3 vector = default(Vector3);
+ Vector2 vector2 = default(Vector2);
+ for (int i = 0; i < this.stars.Length; i++)
+ {
+ StarGen.Stars stars = this.stars[i];
+ float num = FloatRange.Next(-1f, 1f) * this.Length;
+ float num2 = FloatRange.Next(-1f, 1f) * this.Width;
+ float num3 = stars.PositionX = num * this.NormDir.x + num2 * this.Tangent.x;
+ float num4 = stars.PositionY = num * this.NormDir.y + num2 * this.Tangent.y;
+ float num5 = FloatRange.Next(0.01f, 0.05f);
+ stars.Size = num5;
+ stars.Rate = this.Rates.Next();
+ this.stars[i] = stars;
+ int num6 = i * 4;
+ vector.x = num3 - num5;
+ vector.y = num4 + num5;
+ this.verts[num6] = vector;
+ vector.y = num4 - num5;
+ this.verts[num6 + 1] = vector;
+ vector.x = num3 + num5;
+ this.verts[num6 + 2] = vector;
+ vector.y = num4 + num5;
+ this.verts[num6 + 3] = vector;
+ vector2.x = -1f;
+ vector2.y = 1f;
+ array[num6] = vector2;
+ vector2.y = -1f;
+ array[num6 + 1] = vector2;
+ vector2.x = 1f;
+ array[num6 + 2] = vector2;
+ vector2.y = 1f;
+ array[num6 + 3] = vector2;
+ int num7 = i * 6;
+ array2[num7] = num6;
+ array2[num7 + 1] = num6 + 1;
+ array2[num7 + 2] = num6 + 2;
+ array2[num7 + 3] = num6 + 2;
+ array2[num7 + 4] = num6;
+ array2[num7 + 5] = num6 + 3;
+ }
+ this.mesh.vertices = this.verts;
+ this.mesh.uv = array;
+ this.mesh.SetIndices(array2, MeshTopology.Triangles, 0);
+ }
+
+ private void FixedUpdate()
+ {
+ float num = -0.99f * this.Length;
+ Vector2 vector = this.Direction * Time.fixedDeltaTime;
+ for (int i = 0; i < this.stars.Length; i++)
+ {
+ StarGen.Stars stars = this.stars[i];
+ float size = stars.Size;
+ float num2 = stars.PositionX;
+ float num3 = stars.PositionY;
+ float num4 = stars.Rate * (size / 0.05f);
+ num2 += num4 * vector.x;
+ num3 += num4 * vector.y;
+ if (this.OrthoDistance(num2, num3) > this.Length)
+ {
+ float num5 = FloatRange.Next(-1f, 1f) * this.Width;
+ num2 = num * this.NormDir.x + num5 * this.Tangent.x;
+ num3 = num * this.NormDir.y + num5 * this.Tangent.y;
+ this.stars[i].Rate = this.Rates.Next();
+ }
+ stars.PositionX = num2;
+ stars.PositionY = num3;
+ this.stars[i] = stars;
+ int num6 = i * 4;
+ float x = num2 - size;
+ float x2 = num2 + size;
+ float y = num3 + size;
+ float y2 = num3 - size;
+ this.verts[num6].x = x;
+ this.verts[num6].y = y;
+ this.verts[num6 + 1].x = x;
+ this.verts[num6 + 1].y = y2;
+ this.verts[num6 + 2].x = x2;
+ this.verts[num6 + 2].y = y2;
+ this.verts[num6 + 3].x = x2;
+ this.verts[num6 + 3].y = y;
+ }
+ this.mesh.vertices = this.verts;
+ }
+
+ public void SetDirection(Vector2 dir)
+ {
+ this.Direction = dir;
+ this.NormDir = this.Direction.normalized;
+ this.Tangent = new Vector2(-this.NormDir.y, this.NormDir.x);
+ this.tanLen = Mathf.Sqrt(this.Tangent.y * this.Tangent.y + this.Tangent.x * this.Tangent.x);
+ }
+
+ public void RegenPositions()
+ {
+ if (this.stars == null)
+ {
+ return;
+ }
+ for (int i = 0; i < this.stars.Length; i++)
+ {
+ float num = FloatRange.Next(-1f, 1f) * this.Length;
+ float num2 = FloatRange.Next(-1f, 1f) * this.Width;
+ this.stars[i].PositionX = num * this.NormDir.x + num2 * this.Tangent.x;
+ this.stars[i].PositionY = num * this.NormDir.y + num2 * this.Tangent.y;
+ }
+ }
+
+ private float OrthoDistance(float pointx, float pointy)
+ {
+ return (this.Tangent.y * pointx - this.Tangent.x * pointy) / this.tanLen;
+ }
+}
diff --git a/Client/Assembly-CSharp/StatsManager.cs b/Client/Assembly-CSharp/StatsManager.cs
new file mode 100644
index 0000000..1b26ce2
--- /dev/null
+++ b/Client/Assembly-CSharp/StatsManager.cs
@@ -0,0 +1,439 @@
+using System;
+using System.IO;
+using UnityEngine;
+
+public class StatsManager
+{
+ public uint BodiesReported
+ {
+ get
+ {
+ this.LoadStats();
+ return this.bodiesReported;
+ }
+ set
+ {
+ this.LoadStats();
+ this.bodiesReported = value;
+ this.SaveStats();
+ }
+ }
+
+ public uint EmergenciesCalled
+ {
+ get
+ {
+ this.LoadStats();
+ return this.emergenciesCalls;
+ }
+ set
+ {
+ this.LoadStats();
+ this.emergenciesCalls = value;
+ this.SaveStats();
+ }
+ }
+
+ public uint TasksCompleted
+ {
+ get
+ {
+ this.LoadStats();
+ return this.tasksCompleted;
+ }
+ set
+ {
+ this.LoadStats();
+ this.tasksCompleted = value;
+ this.SaveStats();
+ }
+ }
+
+ public uint CompletedAllTasks
+ {
+ get
+ {
+ this.LoadStats();
+ return this.completedAllTasks;
+ }
+ set
+ {
+ this.LoadStats();
+ this.completedAllTasks = value;
+ this.SaveStats();
+ }
+ }
+
+ public uint SabsFixed
+ {
+ get
+ {
+ this.LoadStats();
+ return this.sabsFixed;
+ }
+ set
+ {
+ this.LoadStats();
+ this.sabsFixed = value;
+ this.SaveStats();
+ }
+ }
+
+ public uint ImpostorKills
+ {
+ get
+ {
+ this.LoadStats();
+ return this.impostorKills;
+ }
+ set
+ {
+ this.LoadStats();
+ this.impostorKills = value;
+ this.SaveStats();
+ }
+ }
+
+ public uint TimesMurdered
+ {
+ get
+ {
+ this.LoadStats();
+ return this.timesMurdered;
+ }
+ set
+ {
+ this.LoadStats();
+ this.timesMurdered = value;
+ this.SaveStats();
+ }
+ }
+
+ public uint TimesEjected
+ {
+ get
+ {
+ this.LoadStats();
+ return this.timesEjected;
+ }
+ set
+ {
+ this.LoadStats();
+ this.timesEjected = value;
+ this.SaveStats();
+ }
+ }
+
+ public uint CrewmateStreak
+ {
+ get
+ {
+ this.LoadStats();
+ return this.crewmateStreak;
+ }
+ set
+ {
+ this.LoadStats();
+ this.crewmateStreak = value;
+ this.SaveStats();
+ }
+ }
+
+ public uint TimesImpostor
+ {
+ get
+ {
+ this.LoadStats();
+ return this.timesImpostor;
+ }
+ set
+ {
+ this.LoadStats();
+ this.timesImpostor = value;
+ this.SaveStats();
+ }
+ }
+
+ public uint TimesCrewmate
+ {
+ get
+ {
+ this.LoadStats();
+ return this.timesCrewmate;
+ }
+ set
+ {
+ this.LoadStats();
+ this.timesCrewmate = value;
+ this.SaveStats();
+ }
+ }
+
+ public uint GamesStarted
+ {
+ get
+ {
+ this.LoadStats();
+ return this.gamesStarted;
+ }
+ set
+ {
+ this.LoadStats();
+ this.gamesStarted = value;
+ this.SaveStats();
+ }
+ }
+
+ public uint GamesFinished
+ {
+ get
+ {
+ this.LoadStats();
+ return this.gamesFinished;
+ }
+ set
+ {
+ this.LoadStats();
+ this.gamesFinished = value;
+ this.SaveStats();
+ }
+ }
+
+ public float BanPoints
+ {
+ get
+ {
+ this.LoadStats();
+ return this.banPoints;
+ }
+ set
+ {
+ this.LoadStats();
+ this.banPoints = Mathf.Max(0f, value);
+ this.SaveStats();
+ }
+ }
+
+ public DateTime LastGameStarted
+ {
+ get
+ {
+ this.LoadStats();
+ return new DateTime(this.lastGameStarted);
+ }
+ set
+ {
+ this.LoadStats();
+ this.lastGameStarted = value.Ticks;
+ this.SaveStats();
+ }
+ }
+
+ public float BanMinutes
+ {
+ get
+ {
+ return Mathf.Max(this.BanPoints - 2f, 0f) * 5f;
+ }
+ }
+
+ public bool AmBanned
+ {
+ get
+ {
+ return this.BanMinutesLeft > 0;
+ }
+ }
+
+ public int BanMinutesLeft
+ {
+ get
+ {
+ TimeSpan timeSpan = DateTime.UtcNow - this.LastGameStarted;
+ if (timeSpan.TotalDays > 1.0)
+ {
+ return 0;
+ }
+ return Mathf.CeilToInt(this.BanMinutes - (float)timeSpan.TotalMinutes);
+ }
+ }
+
+ public static StatsManager Instance = new StatsManager();
+
+ private const byte StatsVersion = 3;
+
+ private bool loadedStats;
+
+ private uint bodiesReported;
+
+ private uint emergenciesCalls;
+
+ private uint tasksCompleted;
+
+ private uint completedAllTasks;
+
+ private uint sabsFixed;
+
+ private uint impostorKills;
+
+ private uint timesMurdered;
+
+ private uint timesEjected;
+
+ private uint crewmateStreak;
+
+ private uint timesImpostor;
+
+ private uint timesCrewmate;
+
+ private uint gamesStarted;
+
+ private uint gamesFinished;
+
+ private float banPoints;
+
+ private long lastGameStarted;
+
+ private const int PointsUntilBanStarts = 2;
+
+ private const int MinutesPerBanPoint = 5;
+
+ private uint[] WinReasons = new uint[7];
+
+ private uint[] DrawReasons = new uint[7];
+
+ private uint[] LoseReasons = new uint[7];
+
+ public void AddDrawReason(GameOverReason reason)
+ {
+ this.LoadStats();
+ this.DrawReasons[(int)reason] += 1U;
+ this.SaveStats();
+ }
+
+ public void AddWinReason(GameOverReason reason)
+ {
+ this.LoadStats();
+ this.WinReasons[(int)reason] += 1U;
+ this.SaveStats();
+ }
+
+ public uint GetWinReason(GameOverReason reason)
+ {
+ this.LoadStats();
+ return this.WinReasons[(int)reason];
+ }
+
+ public void AddLoseReason(GameOverReason reason)
+ {
+ this.LoadStats();
+ this.LoseReasons[(int)reason] += 1U;
+ this.SaveStats();
+ }
+
+ public uint GetLoseReason(GameOverReason reason)
+ {
+ this.LoadStats();
+ return this.LoseReasons[(int)reason];
+ }
+
+ protected virtual void LoadStats()
+ {
+ if (this.loadedStats)
+ {
+ return;
+ }
+ this.loadedStats = true;
+ string path = Path.Combine(Application.persistentDataPath, "playerStats2");
+ if (File.Exists(path))
+ {
+ try
+ {
+ using (BinaryReader binaryReader = new BinaryReader(File.OpenRead(path)))
+ {
+ byte b = binaryReader.ReadByte();
+ this.bodiesReported = binaryReader.ReadUInt32();
+ this.emergenciesCalls = binaryReader.ReadUInt32();
+ this.tasksCompleted = binaryReader.ReadUInt32();
+ this.completedAllTasks = binaryReader.ReadUInt32();
+ this.sabsFixed = binaryReader.ReadUInt32();
+ this.impostorKills = binaryReader.ReadUInt32();
+ this.timesMurdered = binaryReader.ReadUInt32();
+ this.timesEjected = binaryReader.ReadUInt32();
+ this.crewmateStreak = binaryReader.ReadUInt32();
+ this.timesImpostor = binaryReader.ReadUInt32();
+ this.timesCrewmate = binaryReader.ReadUInt32();
+ this.gamesStarted = binaryReader.ReadUInt32();
+ this.gamesFinished = binaryReader.ReadUInt32();
+ for (int i = 0; i < this.WinReasons.Length; i++)
+ {
+ this.WinReasons[i] = binaryReader.ReadUInt32();
+ }
+ for (int j = 0; j < this.LoseReasons.Length; j++)
+ {
+ this.LoseReasons[j] = binaryReader.ReadUInt32();
+ }
+ if (b > 1)
+ {
+ for (int k = 0; k < this.DrawReasons.Length; k++)
+ {
+ this.DrawReasons[k] = binaryReader.ReadUInt32();
+ }
+ }
+ if (b > 2)
+ {
+ this.banPoints = binaryReader.ReadSingle();
+ this.lastGameStarted = binaryReader.ReadInt64();
+ }
+ }
+ }
+ catch
+ {
+ Debug.LogError("Deleting corrupted stats file");
+ File.Delete(path);
+ }
+ }
+ }
+
+ protected virtual void SaveStats()
+ {
+ try
+ {
+ using (BinaryWriter binaryWriter = new BinaryWriter(File.OpenWrite(Path.Combine(Application.persistentDataPath, "playerStats2"))))
+ {
+ binaryWriter.Write(3);
+ binaryWriter.Write(this.bodiesReported);
+ binaryWriter.Write(this.emergenciesCalls);
+ binaryWriter.Write(this.tasksCompleted);
+ binaryWriter.Write(this.completedAllTasks);
+ binaryWriter.Write(this.sabsFixed);
+ binaryWriter.Write(this.impostorKills);
+ binaryWriter.Write(this.timesMurdered);
+ binaryWriter.Write(this.timesEjected);
+ binaryWriter.Write(this.crewmateStreak);
+ binaryWriter.Write(this.timesImpostor);
+ binaryWriter.Write(this.timesCrewmate);
+ binaryWriter.Write(this.gamesStarted);
+ binaryWriter.Write(this.gamesFinished);
+ for (int i = 0; i < this.WinReasons.Length; i++)
+ {
+ binaryWriter.Write(this.WinReasons[i]);
+ }
+ for (int j = 0; j < this.LoseReasons.Length; j++)
+ {
+ binaryWriter.Write(this.LoseReasons[j]);
+ }
+ for (int k = 0; k < this.DrawReasons.Length; k++)
+ {
+ binaryWriter.Write(this.DrawReasons[k]);
+ }
+ binaryWriter.Write(this.banPoints);
+ binaryWriter.Write(this.lastGameStarted);
+ }
+ }
+ catch (Exception ex)
+ {
+ Debug.Log("Failed to write out stats: " + ex.Message);
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/StatsPopup.cs b/Client/Assembly-CSharp/StatsPopup.cs
new file mode 100644
index 0000000..4ad2885
--- /dev/null
+++ b/Client/Assembly-CSharp/StatsPopup.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Text;
+using UnityEngine;
+
+public class StatsPopup : MonoBehaviour
+{
+ public TextRenderer StatsText;
+
+ private void OnEnable()
+ {
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.AppendLine(string.Format("Bodies Reported:\t{0}", StatsManager.Instance.BodiesReported));
+ stringBuilder.AppendLine(string.Format("Emergencies Called:\t{0}", StatsManager.Instance.EmergenciesCalled));
+ stringBuilder.AppendLine(string.Format("Tasks Completed:\t{0}", StatsManager.Instance.TasksCompleted));
+ stringBuilder.AppendLine(string.Format("All Tasks Completed:\t{0}", StatsManager.Instance.CompletedAllTasks));
+ stringBuilder.AppendLine(string.Format("Sabotages Fixed:\t{0}", StatsManager.Instance.SabsFixed));
+ stringBuilder.AppendLine(string.Format("Impostor Kills: \t{0}", StatsManager.Instance.ImpostorKills));
+ stringBuilder.AppendLine(string.Format("Times Murdered:\t{0}", StatsManager.Instance.TimesMurdered));
+ stringBuilder.AppendLine(string.Format("Times Ejected: \t{0}", StatsManager.Instance.TimesEjected));
+ stringBuilder.AppendLine(string.Format("Crewmate Streak:\t{0}", StatsManager.Instance.CrewmateStreak));
+ stringBuilder.AppendLine(string.Format("Games Impostor:\t{0}", StatsManager.Instance.TimesImpostor));
+ stringBuilder.AppendLine(string.Format("Games Crewmate:\t{0}", StatsManager.Instance.TimesCrewmate));
+ stringBuilder.AppendLine(string.Format("Games Started: \t{0}", StatsManager.Instance.GamesStarted));
+ stringBuilder.AppendLine(string.Format("Games Finished:\t{0}", StatsManager.Instance.GamesFinished));
+ stringBuilder.AppendLine(string.Format("Impostor Vote Wins:\t{0}", StatsManager.Instance.GetWinReason(GameOverReason.ImpostorByVote)));
+ stringBuilder.AppendLine(string.Format("Impostor Kill Wins:\t{0}", StatsManager.Instance.GetWinReason(GameOverReason.ImpostorByKill)));
+ stringBuilder.AppendLine(string.Format("Impostor Sabotage Wins:\t{0}", StatsManager.Instance.GetWinReason(GameOverReason.ImpostorBySabotage)));
+ stringBuilder.AppendLine(string.Format("Crewmate Vote Wins:\t{0}", StatsManager.Instance.GetWinReason(GameOverReason.HumansByVote)));
+ stringBuilder.AppendLine(string.Format("Crewmate Task Wins:\t{0}", StatsManager.Instance.GetWinReason(GameOverReason.HumansByTask)));
+ this.StatsText.Text = stringBuilder.ToString();
+ }
+}
diff --git a/Client/Assembly-CSharp/SteamBehaviour.cs b/Client/Assembly-CSharp/SteamBehaviour.cs
new file mode 100644
index 0000000..515d641
--- /dev/null
+++ b/Client/Assembly-CSharp/SteamBehaviour.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Collections;
+using PowerTools;
+using UnityEngine;
+
+public class SteamBehaviour : MonoBehaviour
+{
+ public SpriteAnim anim;
+
+ public FloatRange PlayRate = new FloatRange(0.5f, 1f);
+
+ public void OnEnable()
+ {
+ base.StartCoroutine(this.Run());
+ }
+
+ private IEnumerator Run()
+ {
+ for (;;)
+ {
+ float time = this.PlayRate.Next();
+ while (time > 0f)
+ {
+ time -= Time.deltaTime;
+ yield return null;
+ }
+ this.anim.Play(null, 1f);
+ while (this.anim.IsPlaying(null))
+ {
+ yield return null;
+ }
+ }
+ yield break;
+ }
+}
diff --git a/Client/Assembly-CSharp/SteamManager.cs b/Client/Assembly-CSharp/SteamManager.cs
new file mode 100644
index 0000000..ce42f18
--- /dev/null
+++ b/Client/Assembly-CSharp/SteamManager.cs
@@ -0,0 +1,131 @@
+using System;
+using System.Text;
+using Steamworks;
+using UnityEngine;
+
+[DisallowMultipleComponent]
+public class SteamManager : MonoBehaviour
+{
+ private static SteamManager Instance
+ {
+ get
+ {
+ if (SteamManager.s_instance == null)
+ {
+ return new GameObject("SteamManager").AddComponent<SteamManager>();
+ }
+ return SteamManager.s_instance;
+ }
+ }
+
+ public static bool Initialized
+ {
+ get
+ {
+ return SteamManager.Instance.m_bInitialized;
+ }
+ }
+
+ private static SteamManager s_instance;
+
+ private static bool s_EverInitialized;
+
+ private bool m_bInitialized;
+
+ private SteamAPIWarningMessageHook_t m_SteamAPIWarningMessageHook;
+
+ private static void SteamAPIDebugTextHook(int nSeverity, StringBuilder pchDebugText)
+ {
+ Debug.LogWarning(pchDebugText);
+ }
+
+ private void Awake()
+ {
+ if (SteamManager.s_instance != null)
+ {
+ UnityEngine.Object.Destroy(base.gameObject);
+ return;
+ }
+ SteamManager.s_instance = this;
+ if (SteamManager.s_EverInitialized)
+ {
+ throw new Exception("Tried to Initialize the SteamAPI twice in one session!");
+ }
+ UnityEngine.Object.DontDestroyOnLoad(base.gameObject);
+ if (!Packsize.Test())
+ {
+ Debug.LogError("[Steamworks.NET] Packsize Test returned false, the wrong version of Steamworks.NET is being run in this platform.", this);
+ }
+ if (!DllCheck.Test())
+ {
+ Debug.LogError("[Steamworks.NET] DllCheck Test returned false, One or more of the Steamworks binaries seems to be the wrong version.", this);
+ }
+ try
+ {
+ if (SteamAPI.RestartAppIfNecessary(new AppId_t(945360U)))
+ {
+ Application.Quit();
+ return;
+ }
+ }
+ catch (DllNotFoundException arg)
+ {
+ Debug.LogError("[Steamworks.NET] Could not load [lib]steam_api.dll/so/dylib. It's likely not in the correct location. Refer to the README for more details.\n" + arg, this);
+ Application.Quit();
+ return;
+ }
+ this.m_bInitialized = SteamAPI.Init();
+ if (!this.m_bInitialized)
+ {
+ Debug.LogError("[Steamworks.NET] SteamAPI_Init() failed. Refer to Valve's documentation or the comment above this line for more information.", this);
+ return;
+ }
+ SteamManager.s_EverInitialized = true;
+ }
+
+ private void OnEnable()
+ {
+ if (SteamManager.s_instance == null)
+ {
+ SteamManager.s_instance = this;
+ }
+ if (!this.m_bInitialized)
+ {
+ return;
+ }
+ if (this.m_SteamAPIWarningMessageHook == null)
+ {
+ this.m_SteamAPIWarningMessageHook = new SteamAPIWarningMessageHook_t(SteamManager.SteamAPIDebugTextHook);
+ SteamClient.SetWarningMessageHook(this.m_SteamAPIWarningMessageHook);
+ }
+ }
+
+ private void OnDestroy()
+ {
+ if (SteamManager.s_instance != this)
+ {
+ return;
+ }
+ SteamManager.s_instance = null;
+ if (!this.m_bInitialized)
+ {
+ return;
+ }
+ SteamAPI.Shutdown();
+ }
+
+ [ContextMenu("Shutdown")]
+ private void DoShutdown()
+ {
+ SteamAPI.Shutdown();
+ }
+
+ private void Update()
+ {
+ if (!this.m_bInitialized)
+ {
+ return;
+ }
+ SteamAPI.RunCallbacks();
+ }
+}
diff --git a/Client/Assembly-CSharp/SteamPurchasingModule.cs b/Client/Assembly-CSharp/SteamPurchasingModule.cs
new file mode 100644
index 0000000..b3d80f1
--- /dev/null
+++ b/Client/Assembly-CSharp/SteamPurchasingModule.cs
@@ -0,0 +1,118 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using Steamworks;
+using UnityEngine;
+using UnityEngine.Purchasing;
+using UnityEngine.Purchasing.Extension;
+
+internal class SteamPurchasingModule : IPurchasingModule, IStore
+{
+ public const string Name = "Steam";
+
+ public Dictionary<string, uint> IdTranslator = new Dictionary<string, uint>(StringComparer.OrdinalIgnoreCase);
+
+ private IStoreCallback storeCallback;
+
+ private Callback<GameOverlayActivated_t> overlayCallback;
+
+ private bool steamOverlayOpen;
+
+ private StoreMenu parent;
+
+ public SteamPurchasingModule(StoreMenu parent)
+ {
+ this.parent = parent;
+ }
+
+ public void Configure(IPurchasingBinder binder)
+ {
+ binder.RegisterStore("Steam", this);
+ }
+
+ public void FinishTransaction(ProductDefinition product, string transactionId)
+ {
+ }
+
+ public void Initialize(IStoreCallback callback)
+ {
+ this.storeCallback = callback;
+ if (!SteamManager.Initialized || !SteamUtils.IsOverlayEnabled())
+ {
+ this.storeCallback.OnSetupFailed(InitializationFailureReason.PurchasingUnavailable);
+ }
+ this.overlayCallback = Callback<GameOverlayActivated_t>.Create(new Callback<GameOverlayActivated_t>.DispatchDelegate(this.HandleOverlayActivate));
+ }
+
+ private void HandleOverlayActivate(GameOverlayActivated_t param)
+ {
+ this.steamOverlayOpen = (param.m_bActive > 0);
+ }
+
+ public void Purchase(ProductDefinition product, string developerPayload)
+ {
+ if (!SteamUtils.IsOverlayEnabled())
+ {
+ this.storeCallback.OnPurchaseFailed(new PurchaseFailureDescription(product.storeSpecificId, PurchaseFailureReason.PurchasingUnavailable, "Steam overlay is disabled, but required for in-game purchasing."));
+ return;
+ }
+ uint value;
+ if (this.IdTranslator.TryGetValue(product.id, out value))
+ {
+ AppId_t appId_t = new AppId_t(value);
+ SteamFriends.ActivateGameOverlayToStore(appId_t, EOverlayToStoreFlag.k_EOverlayToStoreFlag_AddToCartAndShow);
+ this.parent.StartCoroutine(this.WaitForDlcPurchase(product, appId_t));
+ return;
+ }
+ this.storeCallback.OnPurchaseFailed(new PurchaseFailureDescription(product.storeSpecificId, PurchaseFailureReason.ProductUnavailable, "Couldn't find Product Id for " + product.id));
+ }
+
+ private IEnumerator WaitForDlcPurchase(ProductDefinition product, AppId_t appId)
+ {
+ while (!this.steamOverlayOpen)
+ {
+ SteamAPI.RunCallbacks();
+ yield return null;
+ }
+ while (this.steamOverlayOpen)
+ {
+ SteamAPI.RunCallbacks();
+ yield return null;
+ }
+ ulong num;
+ while (SteamApps.GetDlcDownloadProgress(appId, out num, out num))
+ {
+ yield return null;
+ }
+ if (SteamApps.BIsDlcInstalled(appId))
+ {
+ this.storeCallback.OnPurchaseSucceeded(product.id, "FakeReceipt", UnityEngine.Random.value.ToString());
+ }
+ else
+ {
+ this.storeCallback.OnPurchaseFailed(new PurchaseFailureDescription(product.id, PurchaseFailureReason.UserCancelled, "Steam overlay closed without purchase completing"));
+ }
+ yield break;
+ }
+
+ public void RetrieveProducts(ReadOnlyCollection<ProductDefinition> products)
+ {
+ if (!SteamManager.Initialized)
+ {
+ return;
+ }
+ List<ProductDescription> list = new List<ProductDescription>(products.Count);
+ for (int i = 0; i < products.Count; i++)
+ {
+ ProductDefinition productDefinition = products[i];
+ uint value;
+ if (this.IdTranslator.TryGetValue(productDefinition.id, out value))
+ {
+ bool flag = SteamApps.BIsDlcInstalled(new AppId_t(value));
+ list.Add(new ProductDescription(productDefinition.id, new ProductMetadata("$2.99", null, null, "USD", 1m), flag ? "Bought" : null, null));
+ }
+ }
+ this.storeCallback.OnProductsRetrieved(list);
+ }
+}
diff --git a/Client/Assembly-CSharp/StoreMenu.cs b/Client/Assembly-CSharp/StoreMenu.cs
new file mode 100644
index 0000000..a67399d
--- /dev/null
+++ b/Client/Assembly-CSharp/StoreMenu.cs
@@ -0,0 +1,409 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using PowerTools;
+using UnityEngine;
+using UnityEngine.Purchasing;
+using UnityEngine.Purchasing.Extension;
+
+public class StoreMenu : MonoBehaviour, IStoreListener
+{
+ public PurchaseStates PurchaseState { get; private set; }
+
+ public SpriteRenderer HatSlot;
+
+ public SpriteRenderer SkinSlot;
+
+ public SpriteAnim PetSlot;
+
+ public TextRenderer ItemName;
+
+ public SpriteRenderer PurchaseBackground;
+
+ public TextRenderer PriceText;
+
+ public PurchaseButton PurchasablePrefab;
+
+ public SpriteRenderer HortLinePrefab;
+
+ public TextRenderer LoadingText;
+
+ public TextRenderer RestorePurchasesButton;
+
+ public GameObject RestorePurchasesObj;
+
+ public SpriteRenderer BannerPrefab;
+
+ public Sprite HatBanner;
+
+ public Sprite SkinsBanner;
+
+ public Sprite HolidayBanner;
+
+ public Sprite PetsBanner;
+
+ public SpriteRenderer TopArrow;
+
+ public SpriteRenderer BottomArrow;
+
+ public const string BoughtAdsProductId = "bought_ads";
+
+ private IStoreController controller;
+
+ private IExtensionProvider extensions;
+
+ public Scroller Scroller;
+
+ public Vector2 StartPositionVertical;
+
+ public FloatRange XRange = new FloatRange(-1f, 1f);
+
+ public int NumPerRow = 4;
+
+ private PurchaseButton CurrentButton;
+
+ private List<GameObject> AllObjects = new List<GameObject>();
+
+ private const float NormalHeight = -0.45f;
+
+ private const float BoxHeight = -0.75f;
+
+ public void Start()
+ {
+ this.PetSlot.gameObject.SetActive(false);
+ SteamPurchasingModule steamPurchasingModule = new SteamPurchasingModule(this);
+ foreach (PetBehaviour petBehaviour in DestroyableSingleton<HatManager>.Instance.AllPets)
+ {
+ if (petBehaviour.SteamId != 0U)
+ {
+ steamPurchasingModule.IdTranslator.Add(petBehaviour.ProdId, petBehaviour.SteamId);
+ }
+ }
+ ConfigurationBuilder configurationBuilder = ConfigurationBuilder.Instance(steamPurchasingModule, Array.Empty<IPurchasingModule>());
+ foreach (PetBehaviour petBehaviour2 in DestroyableSingleton<HatManager>.Instance.AllPets)
+ {
+ if (!petBehaviour2.Free)
+ {
+ configurationBuilder.AddProduct(petBehaviour2.ProdId, ProductType.NonConsumable);
+ }
+ }
+ UnityPurchasing.Initialize(this, configurationBuilder);
+ this.PurchaseBackground.color = new Color(0.5f, 0.5f, 0.5f, 1f);
+ this.PriceText.Color = new Color(0.8f, 0.8f, 0.8f, 1f);
+ this.PriceText.Text = "";
+ }
+
+ public void Update()
+ {
+ this.TopArrow.enabled = !this.Scroller.AtTop;
+ this.BottomArrow.enabled = !this.Scroller.AtBottom;
+ }
+
+ public void RestorePurchases()
+ {
+ }
+
+ private void DestroySliderObjects()
+ {
+ for (int i = 0; i < this.AllObjects.Count; i++)
+ {
+ UnityEngine.Object.Destroy(this.AllObjects[i]);
+ }
+ this.AllObjects.Clear();
+ }
+
+ private void FinishRestoring()
+ {
+ this.ShowAllButtons();
+ this.RestorePurchasesButton.Text = "Purchases Restored";
+ }
+
+ public void SetProduct(PurchaseButton button)
+ {
+ if (this.PurchaseState == PurchaseStates.Started)
+ {
+ return;
+ }
+ this.CurrentButton = button;
+ if (this.CurrentButton.Product is HatBehaviour)
+ {
+ HatBehaviour hatBehaviour = (HatBehaviour)this.CurrentButton.Product;
+ this.HatSlot.gameObject.SetActive(true);
+ this.SkinSlot.gameObject.SetActive(false);
+ this.PetSlot.gameObject.SetActive(false);
+ PlayerControl.SetHatImage(hatBehaviour, this.HatSlot);
+ this.ItemName.Text = (string.IsNullOrWhiteSpace(hatBehaviour.StoreName) ? hatBehaviour.name : hatBehaviour.StoreName);
+ if (hatBehaviour.RelatedSkin)
+ {
+ TextRenderer itemName = this.ItemName;
+ itemName.Text += " (Includes skin!)";
+ this.SkinSlot.gameObject.SetActive(true);
+ PlayerControl.SetSkinImage(hatBehaviour.RelatedSkin, this.SkinSlot);
+ }
+ }
+ else if (this.CurrentButton.Product is SkinData)
+ {
+ SkinData skinData = (SkinData)this.CurrentButton.Product;
+ this.SkinSlot.gameObject.SetActive(true);
+ this.HatSlot.gameObject.SetActive(true);
+ this.PetSlot.gameObject.SetActive(false);
+ PlayerControl.SetHatImage(skinData.RelatedHat, this.HatSlot);
+ PlayerControl.SetSkinImage(skinData, this.SkinSlot);
+ this.ItemName.Text = (string.IsNullOrWhiteSpace(skinData.StoreName) ? skinData.name : skinData.StoreName);
+ }
+ else if (this.CurrentButton.Product is PetBehaviour)
+ {
+ PetBehaviour petBehaviour = (PetBehaviour)this.CurrentButton.Product;
+ this.SkinSlot.gameObject.SetActive(false);
+ this.HatSlot.gameObject.SetActive(false);
+ this.PetSlot.gameObject.SetActive(true);
+ SpriteRenderer component = this.PetSlot.GetComponent<SpriteRenderer>();
+ component.material = new Material(petBehaviour.rend.sharedMaterial);
+ PlayerControl.SetPlayerMaterialColors((int)SaveManager.BodyColor, component);
+ this.PetSlot.Play(petBehaviour.idleClip, 1f);
+ this.ItemName.Text = (string.IsNullOrWhiteSpace(petBehaviour.StoreName) ? petBehaviour.name : petBehaviour.StoreName);
+ }
+ else
+ {
+ this.HatSlot.gameObject.SetActive(false);
+ this.SkinSlot.gameObject.SetActive(false);
+ this.PetSlot.gameObject.SetActive(false);
+ this.ItemName.Text = "Remove All Ads";
+ }
+ if (button.Purchased)
+ {
+ this.PurchaseBackground.color = new Color(0.5f, 0.5f, 0.5f, 1f);
+ this.PriceText.Color = new Color(0.8f, 0.8f, 0.8f, 1f);
+ this.PriceText.Text = "Owned";
+ return;
+ }
+ this.PurchaseBackground.color = Color.white;
+ this.PriceText.Color = Color.white;
+ this.PriceText.Text = button.Price;
+ }
+
+ public void BuyProduct()
+ {
+ if (!this.CurrentButton || this.CurrentButton.Purchased || this.PurchaseState == PurchaseStates.Started)
+ {
+ return;
+ }
+ base.StartCoroutine(this.WaitForPurchaseAds(this.CurrentButton));
+ }
+
+ public IEnumerator WaitForPurchaseAds(PurchaseButton button)
+ {
+ this.PurchaseState = PurchaseStates.Started;
+ this.controller.InitiatePurchase(button.ProductId);
+ while (this.PurchaseState == PurchaseStates.Started)
+ {
+ yield return null;
+ }
+ if (this.PurchaseState == PurchaseStates.Success)
+ {
+ foreach (PurchaseButton purchaseButton in from p in this.AllObjects
+ select p.GetComponent<PurchaseButton>() into h
+ where h && h.ProductId == button.ProductId
+ select h)
+ {
+ purchaseButton.SetPurchased();
+ }
+ }
+ this.SetProduct(button);
+ yield break;
+ }
+
+ public void Close()
+ {
+ HatsTab hatsTab = UnityEngine.Object.FindObjectOfType<HatsTab>();
+ if (hatsTab)
+ {
+ hatsTab.OnDisable();
+ hatsTab.OnEnable();
+ }
+ base.gameObject.SetActive(false);
+ }
+
+ private void ShowAllButtons()
+ {
+ this.DestroySliderObjects();
+ string text = "";
+ try
+ {
+ text = "Couldn't fetch products";
+ foreach (Product product in this.controller.products.all)
+ {
+ if (product != null && product.hasReceipt)
+ {
+ try
+ {
+ SaveManager.SetPurchased(product.definition.id);
+ }
+ catch
+ {
+ }
+ }
+ }
+ Vector3 vector = this.StartPositionVertical;
+ UnityEngine.Object.Destroy(this.RestorePurchasesObj);
+ text = "Couldn't fetch products";
+ text = "Couldn't fetch pet data";
+ vector.y += -0.375f;
+ PetBehaviour[] array = (from h in DestroyableSingleton<HatManager>.Instance.AllPets
+ where !h.Free
+ select h into p
+ orderby p.StoreName
+ select p).ToArray<PetBehaviour>();
+ vector = this.InsertBanner(vector, this.PetsBanner);
+ Vector3 position = vector;
+ Product[] all;
+ Product[] allProducts = all;
+ IBuyable[] hats = array;
+ vector = this.InsertHatsFromList(position, allProducts, hats);
+ text = "Couldn't finalize menu";
+ this.Scroller.YBounds.max = Mathf.Max(0f, -vector.y - 2.5f);
+ this.LoadingText.gameObject.SetActive(false);
+ }
+ catch (Exception ex)
+ {
+ Debug.Log(string.Concat(new object[]
+ {
+ "Exception: ",
+ text,
+ ": ",
+ ex
+ }));
+ this.DestroySliderObjects();
+ this.LoadingText.gameObject.SetActive(true);
+ this.LoadingText.Text = "Loading Failed:\r\n" + text;
+ }
+ }
+
+ private Vector3 InsertHortLine(Vector3 position)
+ {
+ position.x = 1.2f;
+ SpriteRenderer spriteRenderer = UnityEngine.Object.Instantiate<SpriteRenderer>(this.HortLinePrefab, this.Scroller.Inner);
+ spriteRenderer.transform.localPosition = position;
+ spriteRenderer.gameObject.SetActive(true);
+ position.y += -0.33749998f;
+ return position;
+ }
+
+ private Vector3 InsertHatsFromList(Vector3 position, Product[] allProducts, IBuyable[] hats)
+ {
+ int num = 0;
+ for (int i = 0; i < hats.Length; i++)
+ {
+ IBuyable item = hats[i];
+ Product product = allProducts.FirstOrDefault((Product p) => item.ProdId == p.definition.id);
+ if (product != null && product.definition != null && product.availableToPurchase)
+ {
+ int num2 = num % this.NumPerRow;
+ position.x = this.StartPositionVertical.x + this.XRange.Lerp((float)num2 / ((float)this.NumPerRow - 1f));
+ if (num2 == 0 && num > 1)
+ {
+ position.y += -0.75f;
+ }
+ this.InsertProduct(position, product, item);
+ num++;
+ }
+ }
+ position.y += -0.75f;
+ return position;
+ }
+
+ private void InsertProduct(Vector3 position, Product product, IBuyable item)
+ {
+ PurchaseButton purchaseButton = UnityEngine.Object.Instantiate<PurchaseButton>(this.PurchasablePrefab, this.Scroller.Inner);
+ this.AllObjects.Add(purchaseButton.gameObject);
+ purchaseButton.transform.localPosition = position;
+ purchaseButton.Parent = this;
+ PurchaseButton purchaseButton2 = purchaseButton;
+ string id = product.definition.id;
+ ProductMetadata metadata = product.metadata;
+ string name;
+ if (metadata == null)
+ {
+ name = null;
+ }
+ else
+ {
+ string localizedTitle = metadata.localizedTitle;
+ name = ((localizedTitle != null) ? localizedTitle.Replace("(Among Us)", "") : null);
+ }
+ ProductMetadata metadata2 = product.metadata;
+ purchaseButton2.SetItem(item, id, name, (metadata2 != null) ? metadata2.localizedPriceString : null, product.hasReceipt || SaveManager.GetPurchase(product.definition.id));
+ }
+
+ private Vector3 InsertBanner(Vector3 position, Sprite s)
+ {
+ position.x = this.StartPositionVertical.x;
+ SpriteRenderer spriteRenderer = UnityEngine.Object.Instantiate<SpriteRenderer>(this.BannerPrefab, this.Scroller.Inner);
+ spriteRenderer.sprite = s;
+ spriteRenderer.transform.localPosition = position;
+ position.y += -spriteRenderer.sprite.bounds.size.y;
+ this.AllObjects.Add(spriteRenderer.gameObject);
+ return position;
+ }
+
+ public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
+ {
+ this.controller = controller;
+ this.extensions = extensions;
+ if (this.controller == null || this.controller.products == null)
+ {
+ this.LoadingText.Text = "Product controller\r\nfailed to load";
+ return;
+ }
+ this.ShowAllButtons();
+ }
+
+ public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs e)
+ {
+ this.PurchaseState = PurchaseStates.Success;
+ SaveManager.SetPurchased(e.purchasedProduct.definition.id);
+ return PurchaseProcessingResult.Complete;
+ }
+
+ public void OnInitializeFailed(InitializationFailureReason error)
+ {
+ this.RestorePurchasesObj.SetActive(false);
+ this.LoadingText.gameObject.SetActive(true);
+ if (error == InitializationFailureReason.NoProductsAvailable)
+ {
+ this.LoadingText.Text = "Coming Soon!";
+ return;
+ }
+ if (error == InitializationFailureReason.PurchasingUnavailable)
+ {
+ this.LoadingText.Text = "Loading Failed:\r\nSteam must be running and logged in to view products.";
+ return;
+ }
+ this.LoadingText.Text = "Loading Failed:\r\n" + error;
+ }
+
+ public void OnPurchaseFailed(Product i, PurchaseFailureReason error)
+ {
+ if (error == PurchaseFailureReason.ProductUnavailable)
+ {
+ this.DestroySliderObjects();
+ this.LoadingText.gameObject.SetActive(true);
+ this.LoadingText.Text = "Coming Soon!";
+ }
+ else if (error == PurchaseFailureReason.PurchasingUnavailable)
+ {
+ this.DestroySliderObjects();
+ this.LoadingText.gameObject.SetActive(true);
+ this.LoadingText.Text = "Steam overlay is required for in-game purchasing. You can still buy and install DLC in Steam.";
+ }
+ else
+ {
+ this.DestroySliderObjects();
+ this.LoadingText.gameObject.SetActive(true);
+ this.LoadingText.Text = "Loading Failed:\r\n" + error;
+ }
+ Debug.LogError("Failed: " + error);
+ this.PurchaseState = PurchaseStates.Fail;
+ }
+}
diff --git a/Client/Assembly-CSharp/StringExtensions.cs b/Client/Assembly-CSharp/StringExtensions.cs
new file mode 100644
index 0000000..dd37904
--- /dev/null
+++ b/Client/Assembly-CSharp/StringExtensions.cs
@@ -0,0 +1,32 @@
+using System;
+using UnityEngine;
+
+public static class StringExtensions
+{
+ private static char[] buffer = new char[256];
+
+ public static string Lerp(string a, string b, float t)
+ {
+ int num = Mathf.Max(a.Length, b.Length);
+ int num2 = (int)Mathf.Lerp(0f, (float)num, t);
+ for (int i = 0; i < num; i++)
+ {
+ if (i < num2)
+ {
+ if (i < b.Length)
+ {
+ StringExtensions.buffer[i] = b[i];
+ }
+ else
+ {
+ StringExtensions.buffer[i] = ' ';
+ }
+ }
+ else if (i < a.Length)
+ {
+ StringExtensions.buffer[i] = a[i];
+ }
+ }
+ return new string(StringExtensions.buffer, 0, num);
+ }
+}
diff --git a/Client/Assembly-CSharp/StringNames.cs b/Client/Assembly-CSharp/StringNames.cs
new file mode 100644
index 0000000..5c3c08b
--- /dev/null
+++ b/Client/Assembly-CSharp/StringNames.cs
@@ -0,0 +1,168 @@
+using System;
+
+public enum StringNames
+{
+ ExitButton,
+ BackButton,
+ AvailableGamesLabel,
+ CreateGameButton,
+ FindGameButton,
+ EnterCode,
+ GhostIgnoreTasks,
+ GhostDoTasks,
+ GhostImpostor,
+ ImpostorTask,
+ FakeTasks,
+ TaskComplete,
+ ExileTextSP,
+ ExileTextSN,
+ ExileTextPP,
+ ExileTextPN,
+ NoExileSkip,
+ NoExileTie,
+ ImpostorsRemainS,
+ ImpostorsRemainP,
+ Hallway,
+ Storage,
+ Cafeteria,
+ Reactor,
+ UpperEngine,
+ Nav,
+ Admin,
+ Electrical,
+ LifeSupp,
+ Shields,
+ MedBay,
+ Security,
+ Weapons,
+ LowerEngine,
+ Comms,
+ Decontamination,
+ Launchpad,
+ LockerRoom,
+ Laboratory,
+ Balcony,
+ Office,
+ Greenhouse,
+ DivertPowerTo,
+ AcceptDivertedPower,
+ SubmitScan,
+ PrimeShields,
+ FuelEngines,
+ ChartCourse,
+ StartReactor,
+ SwipeCard,
+ ClearAsteroids,
+ UploadData,
+ DownloadData,
+ InspectSample,
+ EmptyChute,
+ EmptyGarbage,
+ AlignEngineOutput,
+ FixWiring,
+ CalibrateDistributor,
+ UnlockManifolds,
+ ResetReactor,
+ FixLights,
+ FixComms,
+ RestoreOxy,
+ CleanO2Filter,
+ StabilizeSteering,
+ AssembleArtifact,
+ SortSamples,
+ MeasureWeather,
+ EnterIdCode,
+ HowToPlayText1,
+ HowToPlayText2,
+ HowToPlayText5,
+ HowToPlayText6,
+ HowToPlayText7,
+ HowToPlayText81,
+ HowToPlayText82,
+ NumImpostorsS,
+ NumImpostorsP,
+ Crewmate,
+ Impostor,
+ Victory,
+ Defeat,
+ CrewmatesDisconnected,
+ ImpostorDisconnected,
+ HowToPlayText41,
+ HowToPlayText42,
+ HowToPlayText43,
+ HowToPlayText44,
+ HowToPlayTextMap,
+ HowToPlayTextCrew1,
+ HowToPlayTextCrew2,
+ HowToPlayTextCrew3,
+ HowToPlayTextCrew4,
+ HowToPlayTextCrew5,
+ HowToPlayTextCrew6,
+ HowToPlayTextImp1,
+ HowToPlayTextImp2,
+ HowToPlayTextImp3,
+ HowToPlayTextImp4,
+ HowToPlayTextImp5,
+ HowToPlayTextImp6,
+ HowToPlayTextImp7,
+ SettingsGeneral,
+ SettingsControls,
+ SettingsSound,
+ SettingsGraphics,
+ SettingsData,
+ SettingsCensorChat,
+ SettingsMusic,
+ SettingsSFX,
+ SettingsOn,
+ SettingsOff,
+ SettingsSendTelemetry,
+ SettingsControlMode,
+ SettingsTouchMode,
+ SettingsJoystickMode,
+ SettingsKeyboardMode,
+ SettingsFullscreen,
+ SettingsResolution,
+ SettingsApply,
+ SettingsPersonalizeAds,
+ SettingsLanguage,
+ SettingsJoystickSize,
+ SettingsMouseMode,
+ PlayerColor,
+ PlayerHat,
+ PlayerSkin,
+ PlayerPet,
+ GameSettings,
+ GameRecommendedSettings,
+ GameCustomSettings,
+ GameMapName,
+ GameNumImpostors,
+ GameNumMeetings,
+ GameDiscussTime,
+ GameVotingTime,
+ GamePlayerSpeed,
+ GameCrewLight,
+ GameImpostorLight,
+ GameKillCooldown,
+ GameKillDistance,
+ GameCommonTasks,
+ GameLongTasks,
+ GameShortTasks,
+ MatchMapName,
+ MatchLanguage,
+ MatchImpostors,
+ MatchMaxPlayers,
+ Cancel,
+ Confirm,
+ Limit,
+ RoomCode,
+ LeaveGame,
+ ReturnToGame,
+ LocalHelp,
+ OnlineHelp,
+ SettingsVSync,
+ EmergencyCount,
+ EmergencyNotReady,
+ EmergencyDuringCrisis,
+ EmergencyRequested,
+ GameEmergencyCooldown
+}
diff --git a/Client/Assembly-CSharp/StringOption.cs b/Client/Assembly-CSharp/StringOption.cs
new file mode 100644
index 0000000..e2faa3e
--- /dev/null
+++ b/Client/Assembly-CSharp/StringOption.cs
@@ -0,0 +1,60 @@
+using System;
+using UnityEngine;
+
+public class StringOption : OptionBehaviour
+{
+ public TextRenderer TitleText;
+
+ public TextRenderer ValueText;
+
+ public string[] Values;
+
+ public int Value;
+
+ private int oldValue = -1;
+
+ public void OnEnable()
+ {
+ this.TitleText.Text = DestroyableSingleton<TranslationController>.Instance.GetString(this.Title, Array.Empty<object>());
+ this.ValueText.Text = this.Values[this.Value];
+ GameOptionsData gameOptions = PlayerControl.GameOptions;
+ StringNames title = this.Title;
+ if (title == StringNames.GameMapName)
+ {
+ this.Value = (int)gameOptions.MapId;
+ return;
+ }
+ if (title == StringNames.GameKillDistance)
+ {
+ this.Value = gameOptions.KillDistance;
+ return;
+ }
+ Debug.Log("Ono, unrecognized setting: " + this.Title);
+ }
+
+ private void FixedUpdate()
+ {
+ if (this.oldValue != this.Value)
+ {
+ this.oldValue = this.Value;
+ this.ValueText.Text = this.Values[this.Value];
+ }
+ }
+
+ public void Increase()
+ {
+ this.Value = Mathf.Clamp(this.Value + 1, 0, this.Values.Length - 1);
+ this.OnValueChanged(this);
+ }
+
+ public void Decrease()
+ {
+ this.Value = Mathf.Clamp(this.Value - 1, 0, this.Values.Length - 1);
+ this.OnValueChanged(this);
+ }
+
+ public override int GetInt()
+ {
+ return this.Value;
+ }
+}
diff --git a/Client/Assembly-CSharp/SubString.cs b/Client/Assembly-CSharp/SubString.cs
new file mode 100644
index 0000000..6270112
--- /dev/null
+++ b/Client/Assembly-CSharp/SubString.cs
@@ -0,0 +1,77 @@
+using System;
+
+public struct SubString
+{
+ public readonly int Start;
+
+ public readonly int Length;
+
+ public readonly string Source;
+
+ public SubString(string source, int start, int length)
+ {
+ this.Source = source;
+ this.Start = start;
+ this.Length = length;
+ }
+
+ public override string ToString()
+ {
+ return this.Source.Substring(this.Start, this.Length);
+ }
+
+ public int GetKvpValue()
+ {
+ int num = this.Start + this.Length;
+ for (int i = this.Start; i < num; i++)
+ {
+ if (this.Source[i] == '=')
+ {
+ i++;
+ return new SubString(this.Source, i, num - i).ToInt();
+ }
+ }
+ throw new InvalidCastException();
+ }
+
+ public int ToInt()
+ {
+ int num = 0;
+ int num2 = this.Start + this.Length;
+ bool flag = false;
+ for (int i = this.Start; i < num2; i++)
+ {
+ char c = this.Source[i];
+ if (c == '-')
+ {
+ flag = true;
+ }
+ else if (c >= '0' && c <= '9')
+ {
+ int num3 = (int)(c - '0');
+ num = 10 * num + num3;
+ }
+ }
+ if (!flag)
+ {
+ return num;
+ }
+ return -num;
+ }
+
+ public bool StartsWith(string v)
+ {
+ if (v.Length > this.Length)
+ {
+ return false;
+ }
+ for (int i = 0; i < v.Length; i++)
+ {
+ if (this.Source[i + this.Start] != v[i])
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/Client/Assembly-CSharp/SubStringReader.cs b/Client/Assembly-CSharp/SubStringReader.cs
new file mode 100644
index 0000000..3d90f31
--- /dev/null
+++ b/Client/Assembly-CSharp/SubStringReader.cs
@@ -0,0 +1,51 @@
+using System;
+
+public class SubStringReader
+{
+ private readonly string Source;
+
+ private int Position;
+
+ public SubStringReader(string source)
+ {
+ this.Source = source;
+ }
+
+ public SubString ReadLine()
+ {
+ int position = this.Position;
+ if (position >= this.Source.Length)
+ {
+ return default(SubString);
+ }
+ int num = this.Position;
+ int i = position;
+ while (i < this.Source.Length)
+ {
+ char c = this.Source[i];
+ if (c == '\r')
+ {
+ num = i - 1;
+ this.Position = i + 1;
+ if (i + 1 < this.Source.Length && this.Source[i + 1] == '\n')
+ {
+ this.Position = i + 2;
+ break;
+ }
+ break;
+ }
+ else
+ {
+ if (c == '\n')
+ {
+ num = i - 1;
+ this.Position = i + 1;
+ break;
+ }
+ this.Position++;
+ i++;
+ }
+ }
+ return new SubString(this.Source, position, num - position);
+ }
+}
diff --git a/Client/Assembly-CSharp/SurvCamera.cs b/Client/Assembly-CSharp/SurvCamera.cs
new file mode 100644
index 0000000..a5f0e1d
--- /dev/null
+++ b/Client/Assembly-CSharp/SurvCamera.cs
@@ -0,0 +1,28 @@
+using System;
+using PowerTools;
+using UnityEngine;
+
+public class SurvCamera : MonoBehaviour
+{
+ public SpriteAnim Image;
+
+ public float CamSize = 3f;
+
+ public float CamAspect = 1f;
+
+ public Vector3 Offset;
+
+ public AnimationClip OnAnim;
+
+ public AnimationClip OffAnim;
+
+ public void Awake()
+ {
+ this.Image = base.GetComponent<SpriteAnim>();
+ }
+
+ public void SetAnimation(bool on)
+ {
+ this.Image.Play(on ? this.OnAnim : this.OffAnim, 1f);
+ }
+}
diff --git a/Client/Assembly-CSharp/SurveillanceMinigame.cs b/Client/Assembly-CSharp/SurveillanceMinigame.cs
new file mode 100644
index 0000000..1fbde55
--- /dev/null
+++ b/Client/Assembly-CSharp/SurveillanceMinigame.cs
@@ -0,0 +1,140 @@
+using System;
+using System.Collections;
+using System.Linq;
+using UnityEngine;
+
+public class SurveillanceMinigame : Minigame
+{
+ public Camera CameraPrefab;
+
+ public GameObject Viewables;
+
+ public MeshRenderer[] ViewPorts;
+
+ public TextRenderer[] SabText;
+
+ private ShipRoom[] FilteredRooms;
+
+ private RenderTexture[] textures;
+
+ public MeshRenderer FillQuad;
+
+ public Material DefaultMaterial;
+
+ public Material StaticMaterial;
+
+ private bool isStatic;
+
+ public override void Begin(PlayerTask task)
+ {
+ base.Begin(task);
+ this.FilteredRooms = (from i in ShipStatus.Instance.AllRooms
+ where i.survCamera
+ select i).ToArray<ShipRoom>();
+ this.textures = new RenderTexture[this.FilteredRooms.Length];
+ for (int j = 0; j < this.FilteredRooms.Length; j++)
+ {
+ ShipRoom shipRoom = this.FilteredRooms[j];
+ Camera camera = UnityEngine.Object.Instantiate<Camera>(this.CameraPrefab);
+ camera.transform.SetParent(base.transform);
+ camera.transform.position = shipRoom.transform.position + shipRoom.survCamera.Offset;
+ camera.orthographicSize = shipRoom.survCamera.CamSize;
+ RenderTexture temporary = RenderTexture.GetTemporary((int)(256f * shipRoom.survCamera.CamAspect), 256, 16, RenderTextureFormat.ARGB32);
+ this.textures[j] = temporary;
+ camera.targetTexture = temporary;
+ this.ViewPorts[j].material.SetTexture("_MainTex", temporary);
+ }
+ if (!PlayerControl.LocalPlayer.Data.IsDead)
+ {
+ ShipStatus.Instance.RpcRepairSystem(SystemTypes.Security, 1);
+ }
+ }
+
+ public void Update()
+ {
+ if (this.isStatic && !PlayerTask.PlayerHasHudTask(PlayerControl.LocalPlayer))
+ {
+ this.isStatic = false;
+ for (int i = 0; i < this.ViewPorts.Length; i++)
+ {
+ this.ViewPorts[i].sharedMaterial = this.DefaultMaterial;
+ this.ViewPorts[i].material.SetTexture("_MainTex", this.textures[i]);
+ this.SabText[i].gameObject.SetActive(false);
+ }
+ return;
+ }
+ if (!this.isStatic && PlayerTask.PlayerHasHudTask(PlayerControl.LocalPlayer))
+ {
+ this.isStatic = true;
+ for (int j = 0; j < this.ViewPorts.Length; j++)
+ {
+ this.ViewPorts[j].sharedMaterial = this.StaticMaterial;
+ this.SabText[j].gameObject.SetActive(true);
+ }
+ }
+ }
+
+ protected override IEnumerator CoAnimateOpen()
+ {
+ this.Viewables.SetActive(false);
+ this.FillQuad.material.SetFloat("_Center", -5f);
+ this.FillQuad.material.SetColor("_Color2", Color.clear);
+ for (float timer = 0f; timer < 0.25f; timer += Time.deltaTime)
+ {
+ this.FillQuad.material.SetColor("_Color2", Color.Lerp(Color.clear, Color.black, timer / 0.25f));
+ yield return null;
+ }
+ this.FillQuad.material.SetColor("_Color2", Color.black);
+ this.Viewables.SetActive(true);
+ for (float timer = 0f; timer < 0.1f; timer += Time.deltaTime)
+ {
+ this.FillQuad.material.SetFloat("_Center", Mathf.Lerp(-5f, 0f, timer / 0.1f));
+ yield return null;
+ }
+ for (float timer = 0f; timer < 0.15f; timer += Time.deltaTime)
+ {
+ this.FillQuad.material.SetFloat("_Center", Mathf.Lerp(-3f, 0.4f, timer / 0.15f));
+ yield return null;
+ }
+ this.FillQuad.material.SetFloat("_Center", 0.4f);
+ yield break;
+ }
+
+ private IEnumerator CoAnimateClose()
+ {
+ for (float timer = 0f; timer < 0.1f; timer += Time.deltaTime)
+ {
+ this.FillQuad.material.SetFloat("_Center", Mathf.Lerp(0.4f, -5f, timer / 0.1f));
+ yield return null;
+ }
+ this.Viewables.SetActive(false);
+ for (float timer = 0f; timer < 0.3f; timer += Time.deltaTime)
+ {
+ this.FillQuad.material.SetColor("_Color2", Color.Lerp(Color.black, Color.clear, timer / 0.3f));
+ yield return null;
+ }
+ this.FillQuad.material.SetColor("_Color2", Color.clear);
+ yield break;
+ }
+
+ protected override IEnumerator CoDestroySelf()
+ {
+ yield return this.CoAnimateClose();
+ UnityEngine.Object.Destroy(base.gameObject);
+ yield break;
+ }
+
+ public override void Close()
+ {
+ base.Close();
+ ShipStatus.Instance.RpcRepairSystem(SystemTypes.Security, 2);
+ }
+
+ public void OnDestroy()
+ {
+ for (int i = 0; i < this.textures.Length; i++)
+ {
+ this.textures[i].Release();
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/SweepMinigame.cs b/Client/Assembly-CSharp/SweepMinigame.cs
new file mode 100644
index 0000000..6499a3f
--- /dev/null
+++ b/Client/Assembly-CSharp/SweepMinigame.cs
@@ -0,0 +1,123 @@
+using System;
+using UnityEngine;
+
+public class SweepMinigame : Minigame
+{
+ public SpriteRenderer[] Spinners;
+
+ public SpriteRenderer[] Shadows;
+
+ public SpriteRenderer[] Lights;
+
+ public HorizontalGauge[] Gauges;
+
+ private int spinnerIdx;
+
+ private float timer;
+
+ public float SpinRate = 45f;
+
+ private float initialTimer;
+
+ public AudioClip SpinningSound;
+
+ public AudioClip AcceptSound;
+
+ public AudioClip RejectSound;
+
+ public override void Begin(PlayerTask task)
+ {
+ base.Begin(task);
+ this.ResetGauges();
+ if (Constants.ShouldPlaySfx())
+ {
+ SoundManager.Instance.PlaySound(this.SpinningSound, true, 1f);
+ }
+ }
+
+ public override void Close()
+ {
+ SoundManager.Instance.StopSound(this.SpinningSound);
+ base.Close();
+ }
+
+ public void FixedUpdate()
+ {
+ float num = Mathf.Clamp(2f - this.timer / 30f, 1f, 2f);
+ this.timer += Time.fixedDeltaTime * num;
+ if (this.spinnerIdx < this.Spinners.Length)
+ {
+ float num2 = this.CalcXPerc();
+ this.Gauges[this.spinnerIdx].Value = ((num2 < 13f) ? 0.9f : 0.1f);
+ Quaternion localRotation = Quaternion.Euler(0f, 0f, this.timer * this.SpinRate);
+ this.Spinners[this.spinnerIdx].transform.localRotation = localRotation;
+ this.Shadows[this.spinnerIdx].transform.localRotation = localRotation;
+ this.Lights[this.spinnerIdx].enabled = (num2 < 13f);
+ }
+ for (int i = 0; i < this.Gauges.Length; i++)
+ {
+ HorizontalGauge horizontalGauge = this.Gauges[i];
+ if (i < this.spinnerIdx)
+ {
+ horizontalGauge.Value = 0.95f;
+ }
+ if (i > this.spinnerIdx)
+ {
+ horizontalGauge.Value = 0.05f;
+ }
+ horizontalGauge.Value += (Mathf.PerlinNoise((float)i, Time.time * 51f) - 0.5f) * 0.025f;
+ }
+ }
+
+ private float CalcXPerc()
+ {
+ int num = (int)(this.timer * this.SpinRate) % 360;
+ return (float)Mathf.Min(360 - num, num);
+ }
+
+ public void HitButton(int i)
+ {
+ if (i != this.spinnerIdx)
+ {
+ return;
+ }
+ if (this.CalcXPerc() < 13f)
+ {
+ if (Constants.ShouldPlaySfx())
+ {
+ SoundManager.Instance.PlaySound(this.AcceptSound, false, 1f);
+ }
+ this.Spinners[this.spinnerIdx].transform.localRotation = Quaternion.identity;
+ this.Shadows[this.spinnerIdx].transform.localRotation = Quaternion.identity;
+ this.spinnerIdx++;
+ this.timer = this.initialTimer;
+ if (this.spinnerIdx >= this.Gauges.Length)
+ {
+ this.MyNormTask.NextStep();
+ base.StartCoroutine(base.CoStartClose(0.75f));
+ return;
+ }
+ }
+ else
+ {
+ if (Constants.ShouldPlaySfx())
+ {
+ SoundManager.Instance.PlaySound(this.RejectSound, false, 1f);
+ }
+ this.ResetGauges();
+ }
+ }
+
+ private void ResetGauges()
+ {
+ this.spinnerIdx = 0;
+ this.timer = FloatRange.Next(1f, 3f);
+ this.initialTimer = this.timer;
+ for (int i = 0; i < this.Gauges.Length; i++)
+ {
+ this.Lights[i].enabled = false;
+ this.Spinners[i].transform.localRotation = Quaternion.Euler(0f, 0f, this.timer * this.SpinRate);
+ this.Shadows[i].transform.localRotation = Quaternion.Euler(0f, 0f, this.timer * this.SpinRate);
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/SwitchMinigame.cs b/Client/Assembly-CSharp/SwitchMinigame.cs
new file mode 100644
index 0000000..4013635
--- /dev/null
+++ b/Client/Assembly-CSharp/SwitchMinigame.cs
@@ -0,0 +1,97 @@
+using System;
+using UnityEngine;
+
+public class SwitchMinigame : Minigame
+{
+ public Color OnColor = Color.green;
+
+ public Color OffColor = new Color(0.1f, 0.3f, 0.1f);
+
+ private ShipStatus ship;
+
+ public SpriteRenderer[] switches;
+
+ public SpriteRenderer[] lights;
+
+ public RadioWaveBehaviour top;
+
+ public HorizontalGauge middle;
+
+ public FlatWaveBehaviour bottom;
+
+ public override void Begin(PlayerTask task)
+ {
+ this.ship = UnityEngine.Object.FindObjectOfType<ShipStatus>();
+ SwitchSystem switchSystem = this.ship.Systems[SystemTypes.Electrical] as SwitchSystem;
+ for (int i = 0; i < this.switches.Length; i++)
+ {
+ byte b = (byte)(1 << i);
+ int num = (int)(switchSystem.ActualSwitches & b);
+ this.lights[i].color = ((num == (int)(switchSystem.ExpectedSwitches & b)) ? this.OnColor : this.OffColor);
+ this.switches[i].flipY = (num >> i == 0);
+ }
+ }
+
+ public void FixedUpdate()
+ {
+ if (this.amClosing != Minigame.CloseState.None)
+ {
+ return;
+ }
+ int num = 0;
+ SwitchSystem switchSystem = this.ship.Systems[SystemTypes.Electrical] as SwitchSystem;
+ for (int i = 0; i < this.switches.Length; i++)
+ {
+ byte b = (byte)(1 << i);
+ int num2 = (int)(switchSystem.ActualSwitches & b);
+ if (num2 == (int)(switchSystem.ExpectedSwitches & b))
+ {
+ num++;
+ this.lights[i].color = this.OnColor;
+ }
+ else
+ {
+ this.lights[i].color = this.OffColor;
+ }
+ this.switches[i].flipY = (num2 >> i == 0);
+ }
+ float num3 = (float)num / (float)this.switches.Length;
+ this.bottom.Center = 0.47f * num3;
+ this.top.NoiseLevel = 1f - num3;
+ this.middle.Value = switchSystem.Level + (Mathf.PerlinNoise(0f, Time.time * 51f) - 0.5f) * 0.04f;
+ if (num == this.switches.Length)
+ {
+ base.StartCoroutine(base.CoStartClose(0.5f));
+ }
+ }
+
+ public void FlipSwitch(int switchIdx)
+ {
+ if (this.amClosing != Minigame.CloseState.None)
+ {
+ return;
+ }
+ int num = 0;
+ SwitchSystem switchSystem = this.ship.Systems[SystemTypes.Electrical] as SwitchSystem;
+ for (int i = 0; i < this.switches.Length; i++)
+ {
+ byte b = (byte)(1 << i);
+ if ((switchSystem.ActualSwitches & b) == (switchSystem.ExpectedSwitches & b))
+ {
+ num++;
+ }
+ }
+ if (num == this.switches.Length)
+ {
+ return;
+ }
+ ShipStatus.Instance.RpcRepairSystem(SystemTypes.Electrical, (int)((byte)switchIdx));
+ try
+ {
+ ((SabotageTask)this.MyTask).MarkContributed();
+ }
+ catch
+ {
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/SwitchSystem.cs b/Client/Assembly-CSharp/SwitchSystem.cs
new file mode 100644
index 0000000..0e400ad
--- /dev/null
+++ b/Client/Assembly-CSharp/SwitchSystem.cs
@@ -0,0 +1,107 @@
+using System;
+using Hazel;
+
+public class SwitchSystem : ISystemType, IActivatable
+{
+ public float Level
+ {
+ get
+ {
+ return (float)this.Value / 255f;
+ }
+ }
+
+ public bool IsActive
+ {
+ get
+ {
+ return this.ExpectedSwitches != this.ActualSwitches;
+ }
+ }
+
+ public const byte MaxValue = 255;
+
+ public const int NumSwitches = 5;
+
+ public const byte DamageSystem = 128;
+
+ public const byte SwitchesMask = 31;
+
+ public float DetoriorationTime = 0.03f;
+
+ public byte Value = byte.MaxValue;
+
+ private float timer;
+
+ public byte ExpectedSwitches;
+
+ public byte ActualSwitches;
+
+ public SwitchSystem()
+ {
+ Random random = new Random();
+ this.ExpectedSwitches = (byte)(random.Next() & 31);
+ this.ActualSwitches = this.ExpectedSwitches;
+ }
+
+ public bool Detoriorate(float deltaTime)
+ {
+ this.timer += deltaTime;
+ if (this.timer >= this.DetoriorationTime)
+ {
+ this.timer = 0f;
+ if (this.ExpectedSwitches != this.ActualSwitches)
+ {
+ if (this.Value > 0)
+ {
+ this.Value = (byte)Math.Max((int)(this.Value - 3), 0);
+ }
+ if (!SwitchSystem.HasTask<ElectricTask>())
+ {
+ PlayerControl.LocalPlayer.AddSystemTask(SystemTypes.Electrical);
+ }
+ }
+ else if (this.Value < 255)
+ {
+ this.Value = (byte)Math.Min((int)(this.Value + 3), 255);
+ }
+ }
+ return false;
+ }
+
+ public void RepairDamage(PlayerControl player, byte amount)
+ {
+ if (amount.HasBit(128))
+ {
+ this.ActualSwitches ^= (amount & 31);
+ return;
+ }
+ this.ActualSwitches ^= (byte)(1 << (int)amount);
+ }
+
+ public void Serialize(MessageWriter writer, bool initialState)
+ {
+ writer.Write(this.ExpectedSwitches);
+ writer.Write(this.ActualSwitches);
+ writer.Write(this.Value);
+ }
+
+ public void Deserialize(MessageReader reader, bool initialState)
+ {
+ this.ExpectedSwitches = reader.ReadByte();
+ this.ActualSwitches = reader.ReadByte();
+ this.Value = reader.ReadByte();
+ }
+
+ protected static bool HasTask<T>()
+ {
+ for (int i = PlayerControl.LocalPlayer.myTasks.Count - 1; i > 0; i--)
+ {
+ if (PlayerControl.LocalPlayer.myTasks[i] is T)
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/Client/Assembly-CSharp/SystemConsole.cs b/Client/Assembly-CSharp/SystemConsole.cs
new file mode 100644
index 0000000..e0b4c10
--- /dev/null
+++ b/Client/Assembly-CSharp/SystemConsole.cs
@@ -0,0 +1,77 @@
+using System;
+using UnityEngine;
+
+public class SystemConsole : MonoBehaviour, IUsable
+{
+ public float UsableDistance
+ {
+ get
+ {
+ return this.usableDistance;
+ }
+ }
+
+ public float PercentCool
+ {
+ get
+ {
+ return 0f;
+ }
+ }
+
+ public float usableDistance = 1f;
+
+ public bool FreeplayOnly;
+
+ public SpriteRenderer Image;
+
+ public Minigame MinigamePrefab;
+
+ public void Start()
+ {
+ if (this.FreeplayOnly && !DestroyableSingleton<TutorialManager>.InstanceExists)
+ {
+ UnityEngine.Object.Destroy(base.gameObject);
+ }
+ }
+
+ public void SetOutline(bool on, bool mainTarget)
+ {
+ if (this.Image)
+ {
+ this.Image.material.SetFloat("_Outline", (float)(on ? 1 : 0));
+ this.Image.material.SetColor("_OutlineColor", Color.white);
+ this.Image.material.SetColor("_AddColor", mainTarget ? Color.white : Color.clear);
+ }
+ }
+
+ public float CanUse(GameData.PlayerInfo pc, out bool canUse, out bool couldUse)
+ {
+ float num = float.MaxValue;
+ PlayerControl @object = pc.Object;
+ couldUse = (pc.Object.CanMove && (!pc.IsDead || !(this.MinigamePrefab is EmergencyMinigame)));
+ canUse = couldUse;
+ if (canUse)
+ {
+ num = Vector2.Distance(@object.GetTruePosition(), base.transform.position);
+ canUse &= (num <= this.UsableDistance);
+ }
+ return num;
+ }
+
+ public void Use()
+ {
+ bool flag;
+ bool flag2;
+ this.CanUse(PlayerControl.LocalPlayer.Data, out flag, out flag2);
+ if (!flag)
+ {
+ return;
+ }
+ PlayerControl.LocalPlayer.NetTransform.Halt();
+ Minigame minigame = UnityEngine.Object.Instantiate<Minigame>(this.MinigamePrefab);
+ minigame.transform.SetParent(Camera.main.transform, false);
+ minigame.transform.localPosition = new Vector3(0f, 0f, -50f);
+ minigame.Begin(null);
+ }
+}
diff --git a/Client/Assembly-CSharp/SystemTypeHelpers.cs b/Client/Assembly-CSharp/SystemTypeHelpers.cs
new file mode 100644
index 0000000..d462322
--- /dev/null
+++ b/Client/Assembly-CSharp/SystemTypeHelpers.cs
@@ -0,0 +1,7 @@
+using System;
+using System.Linq;
+
+public static class SystemTypeHelpers
+{
+ public static readonly SystemTypes[] AllTypes = Enum.GetValues(typeof(SystemTypes)).Cast<SystemTypes>().ToArray<SystemTypes>();
+}
diff --git a/Client/Assembly-CSharp/SystemTypes.cs b/Client/Assembly-CSharp/SystemTypes.cs
new file mode 100644
index 0000000..f536ac2
--- /dev/null
+++ b/Client/Assembly-CSharp/SystemTypes.cs
@@ -0,0 +1,30 @@
+using System;
+
+public enum SystemTypes : byte
+{
+ Hallway,
+ Storage,
+ Cafeteria,
+ Reactor,
+ UpperEngine,
+ Nav,
+ Admin,
+ Electrical,
+ LifeSupp,
+ Shields,
+ MedBay,
+ Security,
+ Weapons,
+ LowerEngine,
+ Comms,
+ ShipTasks,
+ Doors,
+ Sabotage,
+ Decontamination,
+ Launchpad,
+ LockerRoom,
+ Laboratory,
+ Balcony,
+ Office,
+ Greenhouse
+}
diff --git a/Client/Assembly-CSharp/TabButton.cs b/Client/Assembly-CSharp/TabButton.cs
new file mode 100644
index 0000000..0d6efb5
--- /dev/null
+++ b/Client/Assembly-CSharp/TabButton.cs
@@ -0,0 +1,10 @@
+using System;
+using UnityEngine;
+
+[Serializable]
+public class TabButton
+{
+ public SpriteRenderer Button;
+
+ public GameObject Tab;
+}
diff --git a/Client/Assembly-CSharp/TabGroup.cs b/Client/Assembly-CSharp/TabGroup.cs
new file mode 100644
index 0000000..066e250
--- /dev/null
+++ b/Client/Assembly-CSharp/TabGroup.cs
@@ -0,0 +1,25 @@
+using System;
+using UnityEngine;
+
+public class TabGroup : MonoBehaviour
+{
+ public SpriteRenderer Button;
+
+ public ButtonRolloverHandler Rollover;
+
+ public GameObject Content;
+
+ internal void Close()
+ {
+ this.Button.color = Color.white;
+ this.Rollover.OutColor = Color.white;
+ this.Content.SetActive(false);
+ }
+
+ internal void Open()
+ {
+ this.Button.color = Color.green;
+ this.Rollover.OutColor = Color.green;
+ this.Content.SetActive(true);
+ }
+}
diff --git a/Client/Assembly-CSharp/TaskAddButton.cs b/Client/Assembly-CSharp/TaskAddButton.cs
new file mode 100644
index 0000000..60e54fc
--- /dev/null
+++ b/Client/Assembly-CSharp/TaskAddButton.cs
@@ -0,0 +1,109 @@
+using System;
+using UnityEngine;
+
+public class TaskAddButton : MonoBehaviour
+{
+ public TextRenderer Text;
+
+ public SpriteRenderer Overlay;
+
+ public Sprite CheckImage;
+
+ public Sprite ExImage;
+
+ public PlayerTask MyTask;
+
+ public bool ImpostorTask;
+
+ public void Start()
+ {
+ if (this.ImpostorTask)
+ {
+ GameData.PlayerInfo data = PlayerControl.LocalPlayer.Data;
+ this.Overlay.enabled = data.IsImpostor;
+ this.Overlay.sprite = this.CheckImage;
+ return;
+ }
+ PlayerTask playerTask = this.FindTaskByType();
+ if (playerTask)
+ {
+ this.Overlay.enabled = true;
+ this.Overlay.sprite = (playerTask.IsComplete ? this.CheckImage : this.ExImage);
+ return;
+ }
+ this.Overlay.enabled = false;
+ }
+
+ public void AddTask()
+ {
+ if (this.ImpostorTask)
+ {
+ GameData.PlayerInfo data = PlayerControl.LocalPlayer.Data;
+ if (data.IsImpostor)
+ {
+ PlayerControl.LocalPlayer.RemoveInfected();
+ this.Overlay.enabled = false;
+ return;
+ }
+ PlayerControl.LocalPlayer.RpcSetInfected(new GameData.PlayerInfo[]
+ {
+ data
+ });
+ this.Overlay.enabled = true;
+ return;
+ }
+ else
+ {
+ PlayerTask playerTask = this.FindTaskByType();
+ if (!playerTask)
+ {
+ PlayerTask playerTask2 = UnityEngine.Object.Instantiate<PlayerTask>(this.MyTask, PlayerControl.LocalPlayer.transform);
+ PlayerTask playerTask3 = playerTask2;
+ PlayerControl localPlayer = PlayerControl.LocalPlayer;
+ uint taskIdCount = localPlayer.TaskIdCount;
+ localPlayer.TaskIdCount = taskIdCount + 1U;
+ playerTask3.Id = taskIdCount;
+ playerTask2.Owner = PlayerControl.LocalPlayer;
+ playerTask2.Initialize();
+ PlayerControl.LocalPlayer.myTasks.Add(playerTask2);
+ GameData.Instance.TutOnlyAddTask(PlayerControl.LocalPlayer.PlayerId, playerTask2.Id);
+ this.Overlay.sprite = this.ExImage;
+ this.Overlay.enabled = true;
+ return;
+ }
+ PlayerControl.LocalPlayer.RemoveTask(playerTask);
+ this.Overlay.enabled = false;
+ return;
+ }
+ }
+
+ private PlayerTask FindTaskByType()
+ {
+ for (int i = PlayerControl.LocalPlayer.myTasks.Count - 1; i > -1; i--)
+ {
+ PlayerTask playerTask = PlayerControl.LocalPlayer.myTasks[i];
+ if (playerTask.TaskType == this.MyTask.TaskType)
+ {
+ if (playerTask.TaskType == TaskTypes.DivertPower)
+ {
+ if (((DivertPowerTask)playerTask).TargetSystem == ((DivertPowerTask)this.MyTask).TargetSystem)
+ {
+ return playerTask;
+ }
+ }
+ else
+ {
+ if (playerTask.TaskType != TaskTypes.UploadData)
+ {
+ return playerTask;
+ }
+ if (playerTask.StartAt == this.MyTask.StartAt)
+ {
+ return playerTask;
+ }
+ }
+ }
+ }
+ return null;
+ }
+}
diff --git a/Client/Assembly-CSharp/TaskAdderGame.cs b/Client/Assembly-CSharp/TaskAdderGame.cs
new file mode 100644
index 0000000..b40a715
--- /dev/null
+++ b/Client/Assembly-CSharp/TaskAdderGame.cs
@@ -0,0 +1,168 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using UnityEngine;
+
+public class TaskAdderGame : Minigame
+{
+ public TextRenderer PathText;
+
+ public TaskFolder RootFolderPrefab;
+
+ public TaskAddButton TaskPrefab;
+
+ public Transform TaskParent;
+
+ public List<TaskFolder> Heirarchy = new List<TaskFolder>();
+
+ public List<Transform> ActiveItems = new List<Transform>();
+
+ public TaskAddButton InfectedButton;
+
+ public float folderWidth;
+
+ public float fileWidth;
+
+ public float lineWidth;
+
+ public float lineHeight;
+
+ private TaskFolder Root;
+
+ public override void Begin(PlayerTask t)
+ {
+ base.Begin(t);
+ this.Root = UnityEngine.Object.Instantiate<TaskFolder>(this.RootFolderPrefab, base.transform);
+ this.Root.gameObject.SetActive(false);
+ Dictionary<SystemTypes, TaskFolder> folders = new Dictionary<SystemTypes, TaskFolder>();
+ this.PopulateRoot(this.Root, folders, ShipStatus.Instance.CommonTasks);
+ this.PopulateRoot(this.Root, folders, ShipStatus.Instance.LongTasks);
+ this.PopulateRoot(this.Root, folders, ShipStatus.Instance.NormalTasks);
+ this.Root.SubFolders = (from f in this.Root.SubFolders
+ orderby f.FolderName
+ select f).ToList<TaskFolder>();
+ this.ShowFolder(this.Root);
+ }
+
+ private void PopulateRoot(TaskFolder rootFolder, Dictionary<SystemTypes, TaskFolder> folders, NormalPlayerTask[] taskList)
+ {
+ foreach (NormalPlayerTask normalPlayerTask in taskList)
+ {
+ SystemTypes systemTypes = normalPlayerTask.StartAt;
+ if (normalPlayerTask is DivertPowerTask)
+ {
+ systemTypes = ((DivertPowerTask)normalPlayerTask).TargetSystem;
+ }
+ if (systemTypes == SystemTypes.LowerEngine)
+ {
+ systemTypes = SystemTypes.UpperEngine;
+ }
+ TaskFolder taskFolder;
+ if (!folders.TryGetValue(systemTypes, out taskFolder))
+ {
+ taskFolder = (folders[systemTypes] = UnityEngine.Object.Instantiate<TaskFolder>(this.RootFolderPrefab, base.transform));
+ taskFolder.gameObject.SetActive(false);
+ if (systemTypes == SystemTypes.UpperEngine)
+ {
+ taskFolder.FolderName = "Engines";
+ }
+ else
+ {
+ taskFolder.FolderName = DestroyableSingleton<TranslationController>.Instance.GetString(systemTypes);
+ }
+ rootFolder.SubFolders.Add(taskFolder);
+ }
+ taskFolder.Children.Add(normalPlayerTask);
+ }
+ }
+
+ public void GoToRoot()
+ {
+ this.Heirarchy.Clear();
+ this.ShowFolder(this.Root);
+ }
+
+ public void GoUpOne()
+ {
+ if (this.Heirarchy.Count > 1)
+ {
+ TaskFolder taskFolder = this.Heirarchy[this.Heirarchy.Count - 2];
+ this.Heirarchy.RemoveAt(this.Heirarchy.Count - 1);
+ this.Heirarchy.RemoveAt(this.Heirarchy.Count - 1);
+ this.ShowFolder(taskFolder);
+ }
+ }
+
+ public void ShowFolder(TaskFolder taskFolder)
+ {
+ StringBuilder stringBuilder = new StringBuilder(64);
+ this.Heirarchy.Add(taskFolder);
+ for (int i = 0; i < this.Heirarchy.Count; i++)
+ {
+ stringBuilder.Append(this.Heirarchy[i].FolderName);
+ stringBuilder.Append("\\");
+ }
+ this.PathText.Text = stringBuilder.ToString();
+ for (int j = 0; j < this.ActiveItems.Count; j++)
+ {
+ UnityEngine.Object.Destroy(this.ActiveItems[j].gameObject);
+ }
+ this.ActiveItems.Clear();
+ float num = 0f;
+ float num2 = 0f;
+ for (int k = 0; k < taskFolder.SubFolders.Count; k++)
+ {
+ TaskFolder taskFolder2 = UnityEngine.Object.Instantiate<TaskFolder>(taskFolder.SubFolders[k], this.TaskParent);
+ taskFolder2.gameObject.SetActive(true);
+ taskFolder2.Parent = this;
+ taskFolder2.transform.localPosition = new Vector3(num, num2, 0f);
+ taskFolder2.transform.localScale = Vector3.one;
+ num += this.folderWidth;
+ if (num > this.lineWidth)
+ {
+ num = 0f;
+ num2 += this.lineHeight;
+ }
+ this.ActiveItems.Add(taskFolder2.transform);
+ }
+ List<PlayerTask> list = (from t in taskFolder.Children
+ orderby t.TaskType.ToString()
+ select t).ToList<PlayerTask>();
+ for (int l = 0; l < list.Count; l++)
+ {
+ TaskAddButton taskAddButton = UnityEngine.Object.Instantiate<TaskAddButton>(this.TaskPrefab);
+ taskAddButton.MyTask = list[l];
+ if (taskAddButton.MyTask.TaskType == TaskTypes.DivertPower)
+ {
+ SystemTypes targetSystem = ((DivertPowerTask)taskAddButton.MyTask).TargetSystem;
+ taskAddButton.Text.Text = DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.DivertPowerTo, Array.Empty<object>()) + " " + DestroyableSingleton<TranslationController>.Instance.GetString(targetSystem);
+ }
+ else
+ {
+ taskAddButton.Text.Text = DestroyableSingleton<TranslationController>.Instance.GetString(taskAddButton.MyTask.TaskType);
+ }
+ this.AddFileAsChild(taskAddButton, ref num, ref num2);
+ }
+ if (this.Heirarchy.Count == 1)
+ {
+ TaskAddButton taskAddButton2 = UnityEngine.Object.Instantiate<TaskAddButton>(this.InfectedButton);
+ taskAddButton2.Text.Text = "Be_Impostor.exe";
+ this.AddFileAsChild(taskAddButton2, ref num, ref num2);
+ }
+ }
+
+ private void AddFileAsChild(TaskAddButton item, ref float xCursor, ref float yCursor)
+ {
+ item.transform.SetParent(this.TaskParent);
+ item.transform.localPosition = new Vector3(xCursor, yCursor, 0f);
+ item.transform.localScale = Vector3.one;
+ xCursor += this.fileWidth;
+ if (xCursor > this.lineWidth)
+ {
+ xCursor = 0f;
+ yCursor -= this.lineHeight;
+ }
+ this.ActiveItems.Add(item.transform);
+ }
+}
diff --git a/Client/Assembly-CSharp/TaskFolder.cs b/Client/Assembly-CSharp/TaskFolder.cs
new file mode 100644
index 0000000..91f3f08
--- /dev/null
+++ b/Client/Assembly-CSharp/TaskFolder.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+public class TaskFolder : MonoBehaviour
+{
+ public string FolderName;
+
+ public TextRenderer Text;
+
+ public TaskAdderGame Parent;
+
+ public List<TaskFolder> SubFolders = new List<TaskFolder>();
+
+ public List<PlayerTask> Children = new List<PlayerTask>();
+
+ public void Start()
+ {
+ this.Text.Text = this.FolderName;
+ }
+
+ public void OnClick()
+ {
+ this.Parent.ShowFolder(this);
+ }
+
+ internal List<TaskFolder> OrderBy()
+ {
+ throw new NotImplementedException();
+ }
+}
diff --git a/Client/Assembly-CSharp/TaskPanelBehaviour.cs b/Client/Assembly-CSharp/TaskPanelBehaviour.cs
new file mode 100644
index 0000000..65fb0fd
--- /dev/null
+++ b/Client/Assembly-CSharp/TaskPanelBehaviour.cs
@@ -0,0 +1,52 @@
+using System;
+using UnityEngine;
+
+public class TaskPanelBehaviour : MonoBehaviour
+{
+ public Vector3 OpenPosition;
+
+ public Vector3 ClosedPosition;
+
+ public SpriteRenderer background;
+
+ public SpriteRenderer tab;
+
+ public TextRenderer TaskText;
+
+ public bool open;
+
+ private float timer;
+
+ public float Duration;
+
+ private void Update()
+ {
+ this.background.transform.localScale = new Vector3(this.TaskText.Width + 0.2f, this.TaskText.Height + 0.2f, 1f);
+ Vector3 vector = this.background.sprite.bounds.extents;
+ vector.y = -vector.y;
+ vector = vector.Mul(this.background.transform.localScale);
+ this.background.transform.localPosition = vector;
+ Vector3 vector2 = this.tab.sprite.bounds.extents;
+ vector2 = vector2.Mul(this.tab.transform.localScale);
+ vector2.y = -vector2.y;
+ vector2.x += vector.x * 2f;
+ this.tab.transform.localPosition = vector2;
+ this.ClosedPosition.y = (this.OpenPosition.y = 0.6f);
+ this.ClosedPosition.x = -this.background.sprite.bounds.size.x * this.background.transform.localScale.x;
+ if (this.open)
+ {
+ this.timer = Mathf.Min(1f, this.timer + Time.deltaTime / this.Duration);
+ }
+ else
+ {
+ this.timer = Mathf.Max(0f, this.timer - Time.deltaTime / this.Duration);
+ }
+ Vector3 relativePos = new Vector3(Mathf.SmoothStep(this.ClosedPosition.x, this.OpenPosition.x, this.timer), Mathf.SmoothStep(this.ClosedPosition.y, this.OpenPosition.y, this.timer), this.OpenPosition.z);
+ base.transform.localPosition = AspectPosition.ComputePosition(AspectPosition.EdgeAlignments.LeftTop, relativePos);
+ }
+
+ public void ToggleOpen()
+ {
+ this.open = !this.open;
+ }
+}
diff --git a/Client/Assembly-CSharp/TaskSet.cs b/Client/Assembly-CSharp/TaskSet.cs
new file mode 100644
index 0000000..9585c7b
--- /dev/null
+++ b/Client/Assembly-CSharp/TaskSet.cs
@@ -0,0 +1,9 @@
+using System;
+
+[Serializable]
+public class TaskSet
+{
+ public TaskTypes taskType;
+
+ public IntRange taskStep = new IntRange(0, 0);
+}
diff --git a/Client/Assembly-CSharp/TaskTypes.cs b/Client/Assembly-CSharp/TaskTypes.cs
new file mode 100644
index 0000000..67d35db
--- /dev/null
+++ b/Client/Assembly-CSharp/TaskTypes.cs
@@ -0,0 +1,31 @@
+using System;
+
+public enum TaskTypes
+{
+ SubmitScan,
+ PrimeShields,
+ FuelEngines,
+ ChartCourse,
+ StartReactor,
+ SwipeCard,
+ ClearAsteroids,
+ UploadData,
+ InspectSample,
+ EmptyChute,
+ EmptyGarbage,
+ AlignEngineOutput,
+ FixWiring,
+ CalibrateDistributor,
+ DivertPower,
+ UnlockManifolds,
+ ResetReactor,
+ FixLights,
+ CleanO2Filter,
+ FixComms,
+ RestoreOxy,
+ StabilizeSteering,
+ AssembleArtifact,
+ SortSamples,
+ MeasureWeather,
+ EnterIdCode
+}
diff --git a/Client/Assembly-CSharp/TaskTypesHelpers.cs b/Client/Assembly-CSharp/TaskTypesHelpers.cs
new file mode 100644
index 0000000..ba8eda0
--- /dev/null
+++ b/Client/Assembly-CSharp/TaskTypesHelpers.cs
@@ -0,0 +1,7 @@
+using System;
+using System.Linq;
+
+public static class TaskTypesHelpers
+{
+ public static readonly TaskTypes[] AllTypes = Enum.GetValues(typeof(TaskTypes)).Cast<TaskTypes>().ToArray<TaskTypes>();
+}
diff --git a/Client/Assembly-CSharp/TempData.cs b/Client/Assembly-CSharp/TempData.cs
new file mode 100644
index 0000000..46d333e
--- /dev/null
+++ b/Client/Assembly-CSharp/TempData.cs
@@ -0,0 +1,74 @@
+using System;
+using System.Collections.Generic;
+
+public static class TempData
+{
+ public static DeathReason LastDeathReason;
+
+ public static GameOverReason EndReason = GameOverReason.HumansByTask;
+
+ public static bool showAd;
+
+ public static List<WinningPlayerData> winners = new List<WinningPlayerData>
+ {
+ new WinningPlayerData
+ {
+ Name = "WWWWWWWWWW",
+ ColorId = 0,
+ SkinId = 0U,
+ IsDead = true
+ },
+ new WinningPlayerData
+ {
+ Name = "WWWWWWWWWW",
+ ColorId = 1,
+ SkinId = 1U,
+ IsDead = true
+ },
+ new WinningPlayerData
+ {
+ Name = "WWWWWWWWWW",
+ ColorId = 2,
+ SkinId = 2U,
+ IsDead = true
+ },
+ new WinningPlayerData
+ {
+ Name = "WWWWWWWWWW",
+ ColorId = 3,
+ SkinId = 0U
+ },
+ new WinningPlayerData
+ {
+ Name = "WWWWWWWWWW",
+ ColorId = 4,
+ SkinId = 1U
+ },
+ new WinningPlayerData
+ {
+ Name = "WWWWWWWWWW",
+ ColorId = 5,
+ SkinId = 2U
+ },
+ new WinningPlayerData
+ {
+ Name = "WWWWWWWWWW",
+ ColorId = 6
+ },
+ new WinningPlayerData
+ {
+ Name = "WWWWWWWWWW",
+ ColorId = 7
+ },
+ new WinningPlayerData
+ {
+ Name = "WWWWWWWWWW",
+ ColorId = 8
+ }
+ };
+
+ public static bool DidHumansWin(GameOverReason reason)
+ {
+ return reason == GameOverReason.HumansByTask || reason == GameOverReason.HumansByVote;
+ }
+}
diff --git a/Client/Assembly-CSharp/TextBox.cs b/Client/Assembly-CSharp/TextBox.cs
new file mode 100644
index 0000000..29e98ab
--- /dev/null
+++ b/Client/Assembly-CSharp/TextBox.cs
@@ -0,0 +1,257 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using UnityEngine;
+using UnityEngine.UI;
+
+public class TextBox : MonoBehaviour, IFocusHolder
+{
+ public float TextHeight
+ {
+ get
+ {
+ return this.outputText.Height;
+ }
+ }
+
+ public static readonly HashSet<char> SymbolChars = new HashSet<char>
+ {
+ '?',
+ '!',
+ ',',
+ '.',
+ '\'',
+ ':',
+ ';',
+ '(',
+ ')',
+ '/',
+ '\\',
+ '%',
+ '^',
+ '&',
+ '-',
+ '='
+ };
+
+ public string text;
+
+ private string compoText = "";
+
+ public int characterLimit = -1;
+
+ [SerializeField]
+ private TextRenderer outputText;
+
+ public SpriteRenderer Background;
+
+ public MeshRenderer Pipe;
+
+ private float pipeBlinkTimer;
+
+ public bool ClearOnFocus;
+
+ public bool ForceUppercase;
+
+ public Button.ButtonClickedEvent OnEnter;
+
+ public Button.ButtonClickedEvent OnChange;
+
+ public Button.ButtonClickedEvent OnFocusLost;
+
+ private TouchScreenKeyboard keyboard;
+
+ public bool AllowSymbols;
+
+ public bool IpMode;
+
+ private Collider2D[] colliders;
+
+ private bool hasFocus;
+
+ private StringBuilder tempTxt = new StringBuilder();
+
+ public void Start()
+ {
+ this.colliders = base.GetComponents<Collider2D>();
+ DestroyableSingleton<PassiveButtonManager>.Instance.RegisterOne(this);
+ if (this.Pipe)
+ {
+ this.Pipe.enabled = false;
+ }
+ }
+
+ public void OnDestroy()
+ {
+ if (this.keyboard != null)
+ {
+ this.keyboard.active = false;
+ this.keyboard = null;
+ }
+ if (DestroyableSingleton<PassiveButtonManager>.InstanceExists)
+ {
+ DestroyableSingleton<PassiveButtonManager>.Instance.RemoveOne(this);
+ }
+ }
+
+ public void Clear()
+ {
+ this.SetText(string.Empty, string.Empty);
+ }
+
+ public void Update()
+ {
+ if (!this.hasFocus)
+ {
+ return;
+ }
+ string inputString = Input.inputString;
+ if (inputString.Length > 0 || this.compoText != Input.compositionString)
+ {
+ if (this.text == null || this.text == "Enter Name")
+ {
+ this.text = "";
+ }
+ this.SetText(this.text + inputString, Input.compositionString);
+ }
+ if (this.Pipe && this.hasFocus)
+ {
+ this.pipeBlinkTimer += Time.deltaTime * 2f;
+ this.Pipe.enabled = ((int)this.pipeBlinkTimer % 2 == 0);
+ }
+ }
+
+ public void GiveFocus()
+ {
+ Input.imeCompositionMode = IMECompositionMode.On;
+ if (this.hasFocus)
+ {
+ return;
+ }
+ if (this.ClearOnFocus)
+ {
+ this.text = string.Empty;
+ this.compoText = string.Empty;
+ this.outputText.Text = string.Empty;
+ }
+ this.hasFocus = true;
+ if (TouchScreenKeyboard.isSupported)
+ {
+ this.keyboard = TouchScreenKeyboard.Open(this.text);
+ }
+ if (this.Background)
+ {
+ this.Background.color = Color.green;
+ }
+ this.pipeBlinkTimer = 0f;
+ if (this.Pipe)
+ {
+ this.Pipe.transform.localPosition = this.outputText.CursorPos;
+ }
+ }
+
+ public void LoseFocus()
+ {
+ if (!this.hasFocus)
+ {
+ return;
+ }
+ Input.imeCompositionMode = IMECompositionMode.Off;
+ if (this.compoText.Length > 0)
+ {
+ this.SetText(this.text + this.compoText, "");
+ this.compoText = string.Empty;
+ }
+ this.hasFocus = false;
+ if (this.keyboard != null)
+ {
+ this.keyboard.active = false;
+ this.keyboard = null;
+ }
+ if (this.Background)
+ {
+ this.Background.color = Color.white;
+ }
+ if (this.Pipe)
+ {
+ this.Pipe.enabled = false;
+ }
+ this.OnFocusLost.Invoke();
+ }
+
+ public bool CheckCollision(Vector2 pt)
+ {
+ for (int i = 0; i < this.colliders.Length; i++)
+ {
+ if (this.colliders[i].OverlapPoint(pt))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void SetText(string input, string inputCompo = "")
+ {
+ bool flag = false;
+ char c = ' ';
+ this.tempTxt.Clear();
+ foreach (char c2 in input)
+ {
+ if (c != ' ' || c2 != ' ')
+ {
+ if (c2 == '\r' || c2 == '\n')
+ {
+ flag = true;
+ }
+ if (c2 == '\b')
+ {
+ this.tempTxt.Length = Math.Max(this.tempTxt.Length - 1, 0);
+ }
+ if (this.ForceUppercase)
+ {
+ c2 = char.ToUpperInvariant(c2);
+ }
+ if (this.IsCharAllowed(c2))
+ {
+ this.tempTxt.Append(c2);
+ c = c2;
+ }
+ }
+ }
+ if (this.characterLimit > 0)
+ {
+ this.tempTxt.Length = Math.Min(this.tempTxt.Length, this.characterLimit);
+ }
+ input = this.tempTxt.ToString();
+ if (!input.Equals(this.text) || !inputCompo.Equals(this.compoText))
+ {
+ this.text = input;
+ this.compoText = inputCompo;
+ this.outputText.Text = this.text + "[FF0000FF]" + this.compoText + "[]";
+ this.outputText.RefreshMesh();
+ if (this.keyboard != null)
+ {
+ this.keyboard.text = this.text;
+ }
+ this.OnChange.Invoke();
+ }
+ if (flag)
+ {
+ this.OnEnter.Invoke();
+ }
+ if (this.Pipe)
+ {
+ this.Pipe.transform.localPosition = this.outputText.CursorPos;
+ }
+ }
+
+ public bool IsCharAllowed(char i)
+ {
+ if (this.IpMode)
+ {
+ return (i >= '0' && i <= '9') || i == '.';
+ }
+ return i == ' ' || (i >= 'A' && i <= 'Z') || (i >= 'a' && i <= 'z') || (i >= '0' && i <= '9') || (i >= 'À' && i <= 'ÿ') || (i >= 'Ѐ' && i <= 'џ') || (i >= 'ㄱ' && i <= 'ㆎ') || (i >= '가' && i <= '힣') || (this.AllowSymbols && TextBox.SymbolChars.Contains(i));
+ }
+}
diff --git a/Client/Assembly-CSharp/TextController.cs b/Client/Assembly-CSharp/TextController.cs
new file mode 100644
index 0000000..c98db4e
--- /dev/null
+++ b/Client/Assembly-CSharp/TextController.cs
@@ -0,0 +1,130 @@
+using System;
+using UnityEngine;
+
+[RequireComponent(typeof(MeshFilter))]
+[RequireComponent(typeof(MeshRenderer))]
+public class TextController : MonoBehaviour
+{
+ public float Scale = 1f;
+
+ [Multiline]
+ public string Text;
+
+ private string displaying;
+
+ [HideInInspector]
+ private Texture2D texture;
+
+ [HideInInspector]
+ private Texture2D colorTexture;
+
+ private MeshRenderer rend;
+
+ private float _scale = float.NegativeInfinity;
+
+ public Color Color = Color.white;
+
+ private Color lastColor;
+
+ public Vector3 Offset;
+
+ public bool topAligned;
+
+ public void Update()
+ {
+ if (!this.rend)
+ {
+ this.rend = base.GetComponent<MeshRenderer>();
+ }
+ if (string.IsNullOrEmpty(this.Text))
+ {
+ this.rend.enabled = false;
+ return;
+ }
+ if (this.displaying == null || this.displaying.GetHashCode() != this.Text.GetHashCode() || this.Color != this.lastColor)
+ {
+ int num = 0;
+ int num2 = 0;
+ int num3 = 1;
+ for (int i = 0; i < this.Text.Length; i++)
+ {
+ if (this.Text[i] == '\n')
+ {
+ num2 = 0;
+ num3++;
+ }
+ else
+ {
+ num2++;
+ if (num2 > num)
+ {
+ num = num2;
+ }
+ }
+ }
+ if (!this.texture || !this.colorTexture)
+ {
+ if (!this.texture)
+ {
+ this.texture = new Texture2D(num, num3, TextureFormat.ARGB32, false);
+ this.texture.filterMode = FilterMode.Point;
+ this.texture.wrapMode = TextureWrapMode.Clamp;
+ }
+ if (!this.colorTexture)
+ {
+ this.colorTexture = new Texture2D(num, num3, TextureFormat.ARGB32, false);
+ this.colorTexture.filterMode = FilterMode.Point;
+ this.colorTexture.wrapMode = TextureWrapMode.Clamp;
+ }
+ }
+ else if (this.texture.width != num || this.texture.height != num3)
+ {
+ this.texture.Resize(num, num3, TextureFormat.ARGB32, false);
+ this.colorTexture.Resize(num, num3, TextureFormat.ARGB32, false);
+ }
+ Color[] array = new Color[num * num3];
+ array.SetAll(this.Color);
+ this.colorTexture.SetPixels(array);
+ array.SetAll(new Color(0.125f, 0f, 0f));
+ this.texture.SetPixels(array);
+ int num4 = 0;
+ int num5 = this.texture.height - 1;
+ Color color = this.Color;
+ for (int j = 0; j < this.Text.Length; j++)
+ {
+ char c = this.Text[j];
+ if (c != '\r')
+ {
+ if (c == '\n')
+ {
+ num4 = 0;
+ num5--;
+ }
+ else
+ {
+ this.texture.SetPixel(num4, num5, new Color((float)c / 256f, 0f, 0f));
+ this.colorTexture.SetPixel(num4, num5, color);
+ num4++;
+ }
+ }
+ }
+ this.texture.Apply(false, false);
+ this.colorTexture.Apply(false, false);
+ this.rend.enabled = true;
+ this.rend.material.SetTexture("_InputTex", this.texture);
+ this.rend.material.SetTexture("_ColorTex", this.colorTexture);
+ this._scale = float.NegativeInfinity;
+ this.displaying = this.Text;
+ this.lastColor = this.Color;
+ }
+ if (this._scale != this.Scale)
+ {
+ this._scale = this.Scale;
+ base.transform.localScale = new Vector3((float)this.texture.width, (float)this.texture.height, 1f) * this.Scale;
+ if (this.topAligned)
+ {
+ base.transform.localPosition = this.Offset + new Vector3((float)this.texture.width * this.Scale / 2f, (float)(-(float)this.texture.height) * this.Scale / 2f, 0f);
+ }
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/TextLink.cs b/Client/Assembly-CSharp/TextLink.cs
new file mode 100644
index 0000000..6ad9e25
--- /dev/null
+++ b/Client/Assembly-CSharp/TextLink.cs
@@ -0,0 +1,26 @@
+using System;
+using UnityEngine;
+
+public class TextLink : MonoBehaviour
+{
+ public BoxCollider2D boxCollider;
+
+ public string targetUrl;
+
+ public bool needed;
+
+ public void Set(Vector2 from, Vector2 to, string target)
+ {
+ this.targetUrl = target;
+ Vector2 vector = to + from;
+ base.transform.localPosition = new Vector3(vector.x / 2f, vector.y / 2f, -1f);
+ vector = to - from;
+ vector.y = -vector.y;
+ this.boxCollider.size = vector;
+ }
+
+ public void Click()
+ {
+ Application.OpenURL(this.targetUrl);
+ }
+}
diff --git a/Client/Assembly-CSharp/TextRenderer.cs b/Client/Assembly-CSharp/TextRenderer.cs
new file mode 100644
index 0000000..e0893f1
--- /dev/null
+++ b/Client/Assembly-CSharp/TextRenderer.cs
@@ -0,0 +1,482 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+
+[RequireComponent(typeof(MeshRenderer))]
+[RequireComponent(typeof(MeshFilter))]
+public class TextRenderer : MonoBehaviour
+{
+ public float Width { get; private set; }
+
+ public float Height { get; private set; }
+
+ public Vector3 CursorPos
+ {
+ get
+ {
+ return new Vector3(this.cursorLocation.x / 100f * this.scale, this.cursorLocation.y / 100f * this.scale, -0.001f);
+ }
+ }
+
+ public TextAsset FontData;
+
+ public float scale = 1f;
+
+ public float TabWidth = 0.5f;
+
+ public bool Centered;
+
+ public bool RightAligned;
+
+ public TextLink textLinkPrefab;
+
+ [HideInInspector]
+ private Mesh mesh;
+
+ [HideInInspector]
+ private MeshRenderer render;
+
+ [Multiline]
+ public string Text;
+
+ private string lastText;
+
+ public Color Color = Color.white;
+
+ private Color lastColor = Color.white;
+
+ public Color OutlineColor = Color.black;
+
+ private Color lastOutlineColor = Color.white;
+
+ public float maxWidth = -1f;
+
+ public bool scaleToFit;
+
+ public bool paragraphSpacing;
+
+ private Vector2 cursorLocation;
+
+ public void Start()
+ {
+ this.render = base.GetComponent<MeshRenderer>();
+ MeshFilter component = base.GetComponent<MeshFilter>();
+ if (!component.mesh)
+ {
+ this.mesh = new Mesh();
+ this.mesh.name = "Text" + base.name;
+ component.mesh = this.mesh;
+ this.render.material.SetColor("_OutlineColor", this.OutlineColor);
+ return;
+ }
+ this.mesh = component.mesh;
+ }
+
+ [ContextMenu("Generate Mesh")]
+ public void GenerateMesh()
+ {
+ this.render = base.GetComponent<MeshRenderer>();
+ MeshFilter component = base.GetComponent<MeshFilter>();
+ if (!component.sharedMesh)
+ {
+ this.mesh = new Mesh();
+ this.mesh.name = "Text" + base.name;
+ component.mesh = this.mesh;
+ }
+ else
+ {
+ this.mesh = component.sharedMesh;
+ }
+ this.lastText = null;
+ this.lastOutlineColor = this.OutlineColor;
+ this.Update();
+ }
+
+ private void Update()
+ {
+ if (this.lastOutlineColor != this.OutlineColor)
+ {
+ this.lastOutlineColor = this.OutlineColor;
+ this.render.material.SetColor("_OutlineColor", this.OutlineColor);
+ }
+ if (this.lastText != this.Text || this.lastColor != this.Color)
+ {
+ this.RefreshMesh();
+ }
+ }
+
+ public void RefreshMesh()
+ {
+ if (this.render == null)
+ {
+ this.Start();
+ }
+ if (this.Text != null)
+ {
+ if (this.Text.Any((char c) => c > '✐'))
+ {
+ FontCache.Instance.SetFont(this, "Korean");
+ }
+ }
+ FontData fontData = FontCache.Instance.LoadFont(this.FontData);
+ this.lastText = this.Text;
+ this.lastColor = this.Color;
+ float num = this.scale;
+ if (this.scaleToFit)
+ {
+ num = Mathf.Min(this.scale, this.maxWidth / this.GetMaxWidth(fontData, this.lastText));
+ }
+ else if (this.maxWidth > 0f)
+ {
+ this.lastText = (this.Text = TextRenderer.WrapText(fontData, this.lastText, this.maxWidth));
+ }
+ List<Vector3> list = new List<Vector3>(this.lastText.Length * 4);
+ List<Vector2> list2 = new List<Vector2>(this.lastText.Length * 4);
+ List<Vector4> list3 = new List<Vector4>(this.lastText.Length * 4);
+ List<Color> list4 = new List<Color>(this.lastText.Length * 4);
+ int[] array = new int[this.lastText.Length * 6];
+ this.Width = 0f;
+ this.cursorLocation.x = (this.cursorLocation.y = 0f);
+ int num2 = -1;
+ Vector2 from = default(Vector2);
+ string text = null;
+ int lineStart = 0;
+ int num3 = 0;
+ Color item = this.Color;
+ int? num4 = null;
+ int i = 0;
+ while (i < this.lastText.Length)
+ {
+ int num5 = (int)this.lastText[i];
+ if (num5 == 91)
+ {
+ if (num2 != 91)
+ {
+ if (this.lastText[i + 1] == '[')
+ {
+ goto IL_422;
+ }
+ num4 = new int?(0);
+ num2 = num5;
+ }
+ }
+ else
+ {
+ if (num4 == null)
+ {
+ goto IL_422;
+ }
+ if (num5 == 93)
+ {
+ if (num2 != 91)
+ {
+ int? num6 = num4;
+ byte r = (byte)((num6 != null) ? new int?(num6.GetValueOrDefault() >> 24 & 255) : null).Value;
+ int? num7 = num4;
+ byte g = (byte)((num7 != null) ? new int?(num7.GetValueOrDefault() >> 16 & 255) : null).Value;
+ int? num8 = num4;
+ item = new Color32(r, g, (byte)((num8 != null) ? new int?(num8.GetValueOrDefault() >> 8 & 255) : null).Value, (byte)(num4 & 255).Value);
+ item.a *= this.Color.a;
+ }
+ else
+ {
+ item = this.Color;
+ }
+ num4 = null;
+ if (text != null)
+ {
+ TextLink textLink = UnityEngine.Object.Instantiate<TextLink>(this.textLinkPrefab, base.transform);
+ textLink.transform.localScale = Vector3.one;
+ Vector3 v = list.Last<Vector3>();
+ textLink.Set(from, v, text);
+ text = null;
+ }
+ }
+ else if (num5 == 104)
+ {
+ int num9 = this.lastText.IndexOf(']', i);
+ text = this.lastText.Substring(i, num9 - i);
+ from = list[list.Count - 2];
+ item = new Color(0.5f, 0.5f, 1f);
+ num4 = null;
+ i = num9;
+ }
+ else
+ {
+ num4 = (num4 << 4 | this.CharToInt(num5));
+ }
+ num2 = num5;
+ }
+ IL_81F:
+ i++;
+ continue;
+ IL_422:
+ if (num5 == 13)
+ {
+ goto IL_81F;
+ }
+ if (num5 == 10)
+ {
+ if (this.Centered)
+ {
+ this.CenterVerts(list, this.cursorLocation.x, lineStart, num);
+ }
+ else if (this.RightAligned)
+ {
+ this.RightAlignVerts(list, this.cursorLocation.x, lineStart, num);
+ }
+ bool flag = this.cursorLocation.x == 0f;
+ this.cursorLocation.x = 0f;
+ if (flag)
+ {
+ this.cursorLocation.y = this.cursorLocation.y - fontData.LineHeight / 2f;
+ }
+ else
+ {
+ this.cursorLocation.y = this.cursorLocation.y - fontData.LineHeight;
+ }
+ lineStart = list.Count;
+ goto IL_81F;
+ }
+ if (num5 == 9)
+ {
+ float num10 = this.cursorLocation.x / 100f;
+ num10 = Mathf.Ceil(num10 / this.TabWidth) * this.TabWidth;
+ this.cursorLocation.x = num10 * 100f;
+ goto IL_81F;
+ }
+ int index;
+ if (!fontData.charMap.TryGetValue(num5, out index))
+ {
+ Debug.Log("Missing char :" + num5);
+ num5 = -1;
+ index = fontData.charMap[-1];
+ }
+ Vector4 vector = fontData.bounds[index];
+ Vector2 textureSize = fontData.TextureSize;
+ Vector3 vector2 = fontData.offsets[index];
+ float kerning = fontData.GetKerning(num2, num5);
+ float num11 = this.cursorLocation.x + vector2.x + kerning;
+ float num12 = this.cursorLocation.y - vector2.y;
+ list.Add(new Vector3(num11, num12 - vector.w) / 100f * num);
+ list.Add(new Vector3(num11, num12) / 100f * num);
+ list.Add(new Vector3(num11 + vector.z, num12) / 100f * num);
+ list.Add(new Vector3(num11 + vector.z, num12 - vector.w) / 100f * num);
+ list4.Add(item);
+ list4.Add(item);
+ list4.Add(item);
+ list4.Add(item);
+ list2.Add(new Vector2(vector.x / textureSize.x, 1f - (vector.y + vector.w) / textureSize.y));
+ list2.Add(new Vector2(vector.x / textureSize.x, 1f - vector.y / textureSize.y));
+ list2.Add(new Vector2((vector.x + vector.z) / textureSize.x, 1f - vector.y / textureSize.y));
+ list2.Add(new Vector2((vector.x + vector.z) / textureSize.x, 1f - (vector.y + vector.w) / textureSize.y));
+ Vector4 item2 = fontData.Channels[index];
+ list3.Add(item2);
+ list3.Add(item2);
+ list3.Add(item2);
+ list3.Add(item2);
+ array[num3 * 6] = num3 * 4;
+ array[num3 * 6 + 1] = num3 * 4 + 1;
+ array[num3 * 6 + 2] = num3 * 4 + 2;
+ array[num3 * 6 + 3] = num3 * 4;
+ array[num3 * 6 + 4] = num3 * 4 + 2;
+ array[num3 * 6 + 5] = num3 * 4 + 3;
+ this.cursorLocation.x = this.cursorLocation.x + (vector2.z + kerning);
+ float num13 = this.cursorLocation.x / 100f * num;
+ if (this.Width < num13)
+ {
+ this.Width = num13;
+ }
+ num2 = num5;
+ num3++;
+ goto IL_81F;
+ }
+ if (this.Centered)
+ {
+ this.CenterVerts(list, this.cursorLocation.x, lineStart, num);
+ this.cursorLocation.x = this.cursorLocation.x / 2f;
+ this.Width /= 2f;
+ }
+ else if (this.RightAligned)
+ {
+ this.RightAlignVerts(list, this.cursorLocation.x, lineStart, num);
+ }
+ this.Height = -(this.cursorLocation.y - fontData.LineHeight) / 100f * num;
+ this.mesh.Clear();
+ if (list.Count > 0)
+ {
+ this.mesh.SetVertices(list);
+ this.mesh.SetColors(list4);
+ this.mesh.SetUVs(0, list2);
+ this.mesh.SetUVs(1, list3);
+ this.mesh.SetIndices(array, MeshTopology.Triangles, 0);
+ }
+ }
+
+ private float GetMaxWidth(FontData data, string lastText)
+ {
+ float num = 0f;
+ float num2 = 0f;
+ int last = -1;
+ bool flag = false;
+ int num3 = 0;
+ int num4 = 0;
+ while (num4 < lastText.Length && num3++ <= 1000)
+ {
+ int num5 = (int)lastText[num4];
+ if (num5 == 91)
+ {
+ flag = true;
+ goto IL_4D;
+ }
+ if (num5 != 93)
+ {
+ goto IL_4D;
+ }
+ flag = false;
+ IL_100:
+ num4++;
+ continue;
+ IL_4D:
+ if (flag || num5 == 13)
+ {
+ goto IL_100;
+ }
+ if (num5 == 10)
+ {
+ last = -1;
+ num2 = 0f;
+ goto IL_100;
+ }
+ if (num5 == 9)
+ {
+ num2 = Mathf.Ceil(num2 / 100f / 0.5f) * 0.5f * 100f;
+ goto IL_100;
+ }
+ int index;
+ if (!data.charMap.TryGetValue(num5, out index))
+ {
+ Debug.Log("Missing char :" + num5);
+ num5 = -1;
+ index = data.charMap[-1];
+ }
+ Vector3 vector = data.offsets[index];
+ num2 += vector.z + data.GetKerning(last, num5);
+ if (num2 > num)
+ {
+ num = num2;
+ }
+ last = num5;
+ goto IL_100;
+ }
+ return num / 100f;
+ }
+
+ private void RightAlignVerts(List<Vector3> verts, float baseX, int lineStart, float scale)
+ {
+ for (int i = lineStart; i < verts.Count; i++)
+ {
+ Vector3 value = verts[i];
+ value.x -= baseX / 100f * scale;
+ verts[i] = value;
+ }
+ }
+
+ private void CenterVerts(List<Vector3> verts, float baseX, int lineStart, float scale)
+ {
+ for (int i = lineStart; i < verts.Count; i++)
+ {
+ Vector3 value = verts[i];
+ value.x -= baseX / 200f * scale;
+ verts[i] = value;
+ }
+ }
+
+ private int CharToInt(int c)
+ {
+ if (c < 65)
+ {
+ return c - 48;
+ }
+ if (c < 97)
+ {
+ return 10 + (c - 65);
+ }
+ return 10 + (c - 97);
+ }
+
+ public static string WrapText(FontData data, string displayTxt, float maxWidth)
+ {
+ float num = 0f;
+ int num2 = -1;
+ int last = -1;
+ bool flag = false;
+ int num3 = 0;
+ int num4 = 0;
+ while (num4 < displayTxt.Length && num3++ <= 1000)
+ {
+ int num5 = (int)displayTxt[num4];
+ if (num5 == 91)
+ {
+ flag = true;
+ goto IL_49;
+ }
+ if (num5 != 93)
+ {
+ goto IL_49;
+ }
+ flag = false;
+ IL_155:
+ num4++;
+ continue;
+ IL_49:
+ if (flag || num5 == 13)
+ {
+ goto IL_155;
+ }
+ if (num5 == 10)
+ {
+ num2 = -1;
+ last = -1;
+ num = 0f;
+ goto IL_155;
+ }
+ if (num5 == 9)
+ {
+ num = Mathf.Ceil(num / 100f / 0.5f) * 0.5f * 100f;
+ goto IL_155;
+ }
+ int index;
+ if (!data.charMap.TryGetValue(num5, out index))
+ {
+ Debug.Log("Missing char :" + num5);
+ num5 = -1;
+ index = data.charMap[-1];
+ }
+ if (num5 == 32)
+ {
+ num2 = num4;
+ }
+ Vector3 vector = data.offsets[index];
+ num += vector.z + data.GetKerning(last, num5);
+ if (num > maxWidth * 100f)
+ {
+ if (num2 != -1)
+ {
+ displayTxt = displayTxt.Substring(0, num2) + "\n" + displayTxt.Substring(num2 + 1);
+ num4 = num2;
+ }
+ else
+ {
+ displayTxt = displayTxt.Substring(0, num4) + "\n" + displayTxt.Substring(num4);
+ }
+ num2 = -1;
+ num = 0f;
+ }
+ last = num5;
+ goto IL_155;
+ }
+ return displayTxt;
+ }
+}
diff --git a/Client/Assembly-CSharp/TextTranslator.cs b/Client/Assembly-CSharp/TextTranslator.cs
new file mode 100644
index 0000000..bb51e1b
--- /dev/null
+++ b/Client/Assembly-CSharp/TextTranslator.cs
@@ -0,0 +1,24 @@
+using System;
+using UnityEngine;
+
+[RequireComponent(typeof(TextRenderer))]
+public class TextTranslator : MonoBehaviour, ITranslatedText
+{
+ public StringNames TargetText;
+
+ public void ResetText()
+ {
+ base.GetComponent<TextRenderer>().Text = DestroyableSingleton<TranslationController>.Instance.GetString(this.TargetText, Array.Empty<object>());
+ }
+
+ public void Start()
+ {
+ DestroyableSingleton<TranslationController>.Instance.ActiveTexts.Add(this);
+ this.ResetText();
+ }
+
+ public void OnDestroy()
+ {
+ DestroyableSingleton<TranslationController>.Instance.ActiveTexts.Remove(this);
+ }
+}
diff --git a/Client/Assembly-CSharp/ToggleButtonBehaviour.cs b/Client/Assembly-CSharp/ToggleButtonBehaviour.cs
new file mode 100644
index 0000000..4703e3e
--- /dev/null
+++ b/Client/Assembly-CSharp/ToggleButtonBehaviour.cs
@@ -0,0 +1,42 @@
+using System;
+using UnityEngine;
+
+public class ToggleButtonBehaviour : MonoBehaviour, ITranslatedText
+{
+ public StringNames BaseText;
+
+ public TextRenderer Text;
+
+ public SpriteRenderer Background;
+
+ public ButtonRolloverHandler Rollover;
+
+ private bool onState;
+
+ public void Start()
+ {
+ DestroyableSingleton<TranslationController>.Instance.ActiveTexts.Add(this);
+ }
+
+ public void OnDestroy()
+ {
+ DestroyableSingleton<TranslationController>.Instance.ActiveTexts.Remove(this);
+ }
+
+ public void ResetText()
+ {
+ this.Text.Text = DestroyableSingleton<TranslationController>.Instance.GetString(this.BaseText, Array.Empty<object>()) + ": " + DestroyableSingleton<TranslationController>.Instance.GetString(this.onState ? StringNames.SettingsOn : StringNames.SettingsOff, Array.Empty<object>());
+ }
+
+ public void UpdateText(bool on)
+ {
+ this.onState = on;
+ Color color = on ? new Color(0f, 1f, 0.16470589f, 1f) : Color.white;
+ this.Background.color = color;
+ this.ResetText();
+ if (this.Rollover)
+ {
+ this.Rollover.OutColor = color;
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/ToggleOption.cs b/Client/Assembly-CSharp/ToggleOption.cs
new file mode 100644
index 0000000..25ffbe7
--- /dev/null
+++ b/Client/Assembly-CSharp/ToggleOption.cs
@@ -0,0 +1,45 @@
+using System;
+using UnityEngine;
+
+public class ToggleOption : OptionBehaviour
+{
+ public TextRenderer TitleText;
+
+ public SpriteRenderer CheckMark;
+
+ private bool oldValue;
+
+ public void OnEnable()
+ {
+ this.TitleText.Text = DestroyableSingleton<TranslationController>.Instance.GetString(this.Title, Array.Empty<object>());
+ GameOptionsData gameOptions = PlayerControl.GameOptions;
+ StringNames title = this.Title;
+ if (title == StringNames.GameRecommendedSettings)
+ {
+ this.CheckMark.enabled = gameOptions.isDefaults;
+ return;
+ }
+ Debug.Log("Ono, unrecognized setting: " + this.Title);
+ }
+
+ private void FixedUpdate()
+ {
+ bool @bool = this.GetBool();
+ if (this.oldValue != @bool)
+ {
+ this.oldValue = @bool;
+ this.CheckMark.enabled = @bool;
+ }
+ }
+
+ public void Toggle()
+ {
+ this.CheckMark.enabled = !this.CheckMark.enabled;
+ this.OnValueChanged(this);
+ }
+
+ public override bool GetBool()
+ {
+ return this.CheckMark.enabled;
+ }
+}
diff --git a/Client/Assembly-CSharp/TowerBehaviour.cs b/Client/Assembly-CSharp/TowerBehaviour.cs
new file mode 100644
index 0000000..dbf85b2
--- /dev/null
+++ b/Client/Assembly-CSharp/TowerBehaviour.cs
@@ -0,0 +1,43 @@
+using System;
+using UnityEngine;
+
+public class TowerBehaviour : MonoBehaviour
+{
+ public float timer;
+
+ public float frameTime = 0.2f;
+
+ public SpriteRenderer circle;
+
+ public SpriteRenderer middle1;
+
+ public SpriteRenderer middle2;
+
+ public SpriteRenderer outer1;
+
+ public SpriteRenderer outer2;
+
+ public void Update()
+ {
+ this.timer += Time.deltaTime;
+ if (this.timer < this.frameTime)
+ {
+ this.circle.color = Color.white;
+ this.middle1.color = (this.middle2.color = (this.outer1.color = (this.outer2.color = Color.black)));
+ return;
+ }
+ if (this.timer < 2f * this.frameTime)
+ {
+ this.middle1.color = (this.middle2.color = Color.white);
+ this.circle.color = (this.outer1.color = (this.outer2.color = Color.black));
+ return;
+ }
+ if (this.timer < 3f * this.frameTime)
+ {
+ this.outer1.color = (this.outer2.color = Color.white);
+ this.middle1.color = (this.middle2.color = (this.circle.color = Color.black));
+ return;
+ }
+ this.timer = 0f;
+ }
+}
diff --git a/Client/Assembly-CSharp/TransitionOpen.cs b/Client/Assembly-CSharp/TransitionOpen.cs
new file mode 100644
index 0000000..b846e44
--- /dev/null
+++ b/Client/Assembly-CSharp/TransitionOpen.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Collections;
+using UnityEngine;
+using UnityEngine.UI;
+
+public class TransitionOpen : MonoBehaviour
+{
+ public float duration = 0.2f;
+
+ public Button.ButtonClickedEvent OnClose = new Button.ButtonClickedEvent();
+
+ public void OnEnable()
+ {
+ base.StartCoroutine(this.AnimateOpen());
+ }
+
+ public void Close()
+ {
+ base.StartCoroutine(this.AnimateClose());
+ }
+
+ private IEnumerator AnimateClose()
+ {
+ Vector3 vec = default(Vector3);
+ for (float t = 0f; t < this.duration; t += Time.deltaTime)
+ {
+ float t2 = t / this.duration;
+ float num = Mathf.SmoothStep(1f, 0f, t2);
+ vec.Set(num, num, num);
+ base.transform.localScale = vec;
+ yield return null;
+ }
+ vec.Set(0f, 0f, 0f);
+ base.transform.localScale = vec;
+ this.OnClose.Invoke();
+ yield break;
+ }
+
+ private IEnumerator AnimateOpen()
+ {
+ Vector3 vec = default(Vector3);
+ for (float t = 0f; t < this.duration; t += Time.deltaTime)
+ {
+ float t2 = t / this.duration;
+ float num = Mathf.SmoothStep(0f, 1f, t2);
+ vec.Set(num, num, num);
+ base.transform.localScale = vec;
+ yield return null;
+ }
+ vec.Set(1f, 1f, 1f);
+ base.transform.localScale = vec;
+ yield break;
+ }
+}
diff --git a/Client/Assembly-CSharp/TransitionType.cs b/Client/Assembly-CSharp/TransitionType.cs
new file mode 100644
index 0000000..07e5b99
--- /dev/null
+++ b/Client/Assembly-CSharp/TransitionType.cs
@@ -0,0 +1,7 @@
+using System;
+
+public enum TransitionType
+{
+ SlideBottom,
+ Alpha
+}
diff --git a/Client/Assembly-CSharp/TranslatedImageSet.cs b/Client/Assembly-CSharp/TranslatedImageSet.cs
new file mode 100644
index 0000000..2a43583
--- /dev/null
+++ b/Client/Assembly-CSharp/TranslatedImageSet.cs
@@ -0,0 +1,8 @@
+using System;
+using UnityEngine;
+
+[CreateAssetMenu]
+public class TranslatedImageSet : ScriptableObject
+{
+ public ImageData[] Images;
+}
diff --git a/Client/Assembly-CSharp/TranslationController.cs b/Client/Assembly-CSharp/TranslationController.cs
new file mode 100644
index 0000000..66686f7
--- /dev/null
+++ b/Client/Assembly-CSharp/TranslationController.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+
+public class TranslationController : DestroyableSingleton<TranslationController>
+{
+ private static readonly StringNames[] SystemTypesToStringNames = SystemTypeHelpers.AllTypes.Select(delegate(SystemTypes t)
+ {
+ StringNames result;
+ Enum.TryParse<StringNames>(t.ToString(), out result);
+ return result;
+ }).ToArray<StringNames>();
+
+ private static readonly StringNames[] TaskTypesToStringNames = TaskTypesHelpers.AllTypes.Select(delegate(TaskTypes t)
+ {
+ StringNames result;
+ Enum.TryParse<StringNames>(t.ToString(), out result);
+ return result;
+ }).ToArray<StringNames>();
+
+ public TextAsset[] Languages;
+
+ public TranslatedImageSet[] Images;
+
+ public LanguageUnit CurrentLanguage;
+
+ public List<ITranslatedText> ActiveTexts = new List<ITranslatedText>();
+
+ public override void Awake()
+ {
+ base.Awake();
+ if (DestroyableSingleton<TranslationController>.Instance == this)
+ {
+ this.CurrentLanguage = new LanguageUnit(this.Languages[(int)SaveManager.LastLanguage], this.Images[(int)SaveManager.LastLanguage].Images);
+ }
+ }
+
+ public void SetLanguage(TextAsset lang)
+ {
+ int num = this.Languages.IndexOf(lang);
+ Debug.Log("Set language to " + num);
+ SaveManager.LastLanguage = (uint)num;
+ this.CurrentLanguage = new LanguageUnit(this.Languages[num], this.Images[num].Images);
+ for (int i = 0; i < this.ActiveTexts.Count; i++)
+ {
+ this.ActiveTexts[i].ResetText();
+ }
+ }
+
+ public Sprite GetImage(ImageNames id)
+ {
+ return this.CurrentLanguage.GetImage(id);
+ }
+
+ public string GetString(StringNames id, params object[] parts)
+ {
+ return this.CurrentLanguage.GetString(id, parts);
+ }
+
+ public string GetString(SystemTypes room)
+ {
+ return this.GetString(TranslationController.SystemTypesToStringNames[(int)room], Array.Empty<object>());
+ }
+
+ public string GetString(TaskTypes task)
+ {
+ return this.GetString(TranslationController.TaskTypesToStringNames[(int)((byte)task)], Array.Empty<object>());
+ }
+}
diff --git a/Client/Assembly-CSharp/TumbleBoxBehaviour.cs b/Client/Assembly-CSharp/TumbleBoxBehaviour.cs
new file mode 100644
index 0000000..8d9f722
--- /dev/null
+++ b/Client/Assembly-CSharp/TumbleBoxBehaviour.cs
@@ -0,0 +1,24 @@
+using System;
+using UnityEngine;
+
+public class TumbleBoxBehaviour : MonoBehaviour
+{
+ public FloatRange BoxHeight;
+
+ public FloatRange shadowScale;
+
+ public SpriteRenderer Shadow;
+
+ public SpriteRenderer Box;
+
+ public void FixedUpdate()
+ {
+ float z = Time.time * 15f;
+ float v = Mathf.Cos(Time.time * 3.1415927f / 10f) / 2f + 0.5f;
+ float num = this.shadowScale.Lerp(v);
+ this.Shadow.transform.localScale = new Vector3(num, num, num);
+ float y = this.BoxHeight.Lerp(v);
+ this.Box.transform.localPosition = new Vector3(0f, y, -0.01f);
+ this.Box.transform.eulerAngles = new Vector3(0f, 0f, z);
+ }
+}
diff --git a/Client/Assembly-CSharp/TuneRadioMinigame.cs b/Client/Assembly-CSharp/TuneRadioMinigame.cs
new file mode 100644
index 0000000..ac2eb58
--- /dev/null
+++ b/Client/Assembly-CSharp/TuneRadioMinigame.cs
@@ -0,0 +1,104 @@
+using System;
+using UnityEngine;
+
+public class TuneRadioMinigame : Minigame
+{
+ public RadioWaveBehaviour actualSignal;
+
+ public DialBehaviour dial;
+
+ public SpriteRenderer redLight;
+
+ public SpriteRenderer greenLight;
+
+ public float Tolerance = 0.1f;
+
+ public float targetAngle;
+
+ public bool finished;
+
+ private float steadyTimer;
+
+ public AudioClip StaticSound;
+
+ public AudioClip RadioSound;
+
+ public override void Begin(PlayerTask task)
+ {
+ base.Begin(task);
+ this.targetAngle = this.dial.DialRange.Next();
+ if (Constants.ShouldPlaySfx())
+ {
+ SoundManager.Instance.PlayDynamicSound("CommsRadio", this.RadioSound, true, new DynamicSound.GetDynamicsFunction(this.GetRadioVolume), true);
+ SoundManager.Instance.PlayDynamicSound("RadioStatic", this.StaticSound, true, new DynamicSound.GetDynamicsFunction(this.GetStaticVolume), true);
+ }
+ }
+
+ private void GetRadioVolume(AudioSource player, float dt)
+ {
+ player.volume = 1f - this.actualSignal.NoiseLevel;
+ }
+
+ private void GetStaticVolume(AudioSource player, float dt)
+ {
+ player.volume = this.actualSignal.NoiseLevel;
+ }
+
+ public void Update()
+ {
+ if (this.finished)
+ {
+ return;
+ }
+ float f = Mathf.Abs((this.targetAngle - this.dial.Value) / this.dial.DialRange.Width) * 2f;
+ this.actualSignal.NoiseLevel = Mathf.Clamp(Mathf.Sqrt(f), 0f, 1f);
+ if (this.actualSignal.NoiseLevel <= this.Tolerance)
+ {
+ this.redLight.color = new Color(0.35f, 0f, 0f);
+ if (!this.dial.Engaged)
+ {
+ this.FinishGame();
+ return;
+ }
+ this.steadyTimer += Time.deltaTime;
+ if (this.steadyTimer > 1.5f)
+ {
+ this.FinishGame();
+ return;
+ }
+ }
+ else
+ {
+ this.redLight.color = new Color(1f, 0f, 0f);
+ this.steadyTimer = 0f;
+ }
+ }
+
+ private void FinishGame()
+ {
+ this.greenLight.color = Color.green;
+ this.finished = true;
+ this.dial.enabled = false;
+ this.dial.SetValue(this.targetAngle);
+ this.actualSignal.NoiseLevel = 0f;
+ if (PlayerControl.LocalPlayer)
+ {
+ ShipStatus.Instance.RpcRepairSystem(SystemTypes.Comms, 0);
+ }
+ base.StartCoroutine(base.CoStartClose(0.75f));
+ try
+ {
+ ((SabotageTask)this.MyTask).MarkContributed();
+ }
+ catch
+ {
+ }
+ }
+
+ public override void Close()
+ {
+ SoundManager.Instance.StopSound(this.StaticSound);
+ SoundManager.Instance.StopSound(this.RadioSound);
+ base.Close();
+ }
+}
diff --git a/Client/Assembly-CSharp/TutorialManager.cs b/Client/Assembly-CSharp/TutorialManager.cs
new file mode 100644
index 0000000..ba89235
--- /dev/null
+++ b/Client/Assembly-CSharp/TutorialManager.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Collections;
+using InnerNet;
+using UnityEngine;
+
+public class TutorialManager : DestroyableSingleton<TutorialManager>
+{
+ public PlayerControl PlayerPrefab;
+
+ public Transform[] DummyLocations;
+
+ public override void Awake()
+ {
+ base.Awake();
+ StatsManager.Instance = new TutorialStatsManager();
+ base.StartCoroutine(this.RunTutorial());
+ }
+
+ public override void OnDestroy()
+ {
+ StatsManager.Instance = new StatsManager();
+ base.OnDestroy();
+ }
+
+ private IEnumerator RunTutorial()
+ {
+ while (!ShipStatus.Instance)
+ {
+ yield return null;
+ }
+ ShipStatus.Instance.enabled = false;
+ ShipStatus.Instance.Timer = 15f;
+ while (!PlayerControl.LocalPlayer)
+ {
+ yield return null;
+ }
+ if (DestroyableSingleton<DiscordManager>.InstanceExists)
+ {
+ DestroyableSingleton<DiscordManager>.Instance.SetHowToPlay();
+ }
+ PlayerControl.GameOptions = new GameOptionsData
+ {
+ NumImpostors = 0,
+ DiscussionTime = 0
+ };
+ PlayerControl.LocalPlayer.RpcSetInfected(new GameData.PlayerInfo[0]);
+ for (int i = 0; i < this.DummyLocations.Length; i++)
+ {
+ PlayerControl playerControl = UnityEngine.Object.Instantiate<PlayerControl>(this.PlayerPrefab);
+ playerControl.PlayerId = (byte)GameData.Instance.GetAvailableId();
+ GameData.Instance.AddPlayer(playerControl);
+ AmongUsClient.Instance.Spawn(playerControl, -2, SpawnFlags.None);
+ playerControl.transform.position = this.DummyLocations[i].position;
+ playerControl.GetComponent<DummyBehaviour>().enabled = true;
+ playerControl.NetTransform.enabled = false;
+ playerControl.SetName("Dummy " + (i + 1));
+ playerControl.SetColor((byte)((i < (int)SaveManager.BodyColor) ? i : (i + 1)));
+ GameData.Instance.RpcSetTasks(playerControl.PlayerId, new byte[0]);
+ }
+ ShipStatus.Instance.Begin();
+ yield break;
+ }
+}
diff --git a/Client/Assembly-CSharp/TutorialStatsManager.cs b/Client/Assembly-CSharp/TutorialStatsManager.cs
new file mode 100644
index 0000000..6d473ed
--- /dev/null
+++ b/Client/Assembly-CSharp/TutorialStatsManager.cs
@@ -0,0 +1,12 @@
+using System;
+
+public class TutorialStatsManager : StatsManager
+{
+ protected override void LoadStats()
+ {
+ }
+
+ protected override void SaveStats()
+ {
+ }
+}
diff --git a/Client/Assembly-CSharp/TwitterLink.cs b/Client/Assembly-CSharp/TwitterLink.cs
new file mode 100644
index 0000000..816c0fb
--- /dev/null
+++ b/Client/Assembly-CSharp/TwitterLink.cs
@@ -0,0 +1,12 @@
+using System;
+using UnityEngine;
+
+public class TwitterLink : MonoBehaviour
+{
+ public string LinkUrl = "https://www.twitter.com/InnerslothDevs";
+
+ public void Click()
+ {
+ Application.OpenURL(this.LinkUrl);
+ }
+}
diff --git a/Client/Assembly-CSharp/UnlockManifoldsMinigame.cs b/Client/Assembly-CSharp/UnlockManifoldsMinigame.cs
new file mode 100644
index 0000000..16f6269
--- /dev/null
+++ b/Client/Assembly-CSharp/UnlockManifoldsMinigame.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Collections;
+using System.Linq;
+using UnityEngine;
+
+public class UnlockManifoldsMinigame : Minigame
+{
+ public SpriteRenderer[] Buttons;
+
+ public byte SystemId;
+
+ private int buttonCounter;
+
+ private bool animating;
+
+ public AudioClip PressButtonSound;
+
+ public AudioClip FailSound;
+
+ public override void Begin(PlayerTask task)
+ {
+ base.Begin(task);
+ int num = 2;
+ int num2 = this.Buttons.Length / num;
+ float[] array = FloatRange.SpreadToEdges(-1.7f, 1.7f, num2).ToArray<float>();
+ float[] array2 = FloatRange.SpreadToEdges(-0.43f, 0.43f, num).ToArray<float>();
+ SpriteRenderer[] array3 = this.Buttons.ToArray<SpriteRenderer>();
+ array3.Shuffle<SpriteRenderer>();
+ for (int i = 0; i < num2; i++)
+ {
+ for (int j = 0; j < num; j++)
+ {
+ int num3 = i + j * num2;
+ array3[num3].transform.localPosition = new Vector3(array[i], array2[j], 0f);
+ }
+ }
+ }
+
+ public void HitButton(int idx)
+ {
+ if (this.MyNormTask.IsComplete)
+ {
+ return;
+ }
+ if (this.animating)
+ {
+ return;
+ }
+ if (Constants.ShouldPlaySfx())
+ {
+ SoundManager.Instance.PlaySound(this.PressButtonSound, false, 1f).pitch = Mathf.Lerp(0.5f, 1.5f, (float)idx / 10f);
+ }
+ if (idx == this.buttonCounter)
+ {
+ this.Buttons[idx].color = Color.green;
+ this.buttonCounter++;
+ if (this.buttonCounter == this.Buttons.Length)
+ {
+ this.MyNormTask.NextStep();
+ base.StartCoroutine(base.CoStartClose(0.75f));
+ return;
+ }
+ }
+ else
+ {
+ this.buttonCounter = 0;
+ base.StartCoroutine(this.ResetAll());
+ }
+ }
+
+ private IEnumerator ResetAll()
+ {
+ if (Constants.ShouldPlaySfx())
+ {
+ SoundManager.Instance.PlaySound(this.FailSound, false, 1f);
+ }
+ this.animating = true;
+ for (int i = 0; i < this.Buttons.Length; i++)
+ {
+ this.Buttons[i].color = Color.red;
+ }
+ yield return new WaitForSeconds(0.25f);
+ for (int j = 0; j < this.Buttons.Length; j++)
+ {
+ this.Buttons[j].color = Color.white;
+ }
+ yield return new WaitForSeconds(0.25f);
+ for (int k = 0; k < this.Buttons.Length; k++)
+ {
+ this.Buttons[k].color = Color.red;
+ }
+ yield return new WaitForSeconds(0.25f);
+ for (int l = 0; l < this.Buttons.Length; l++)
+ {
+ this.Buttons[l].color = Color.white;
+ }
+ this.animating = false;
+ yield break;
+ }
+}
diff --git a/Client/Assembly-CSharp/UnlockPopUp.cs b/Client/Assembly-CSharp/UnlockPopUp.cs
new file mode 100644
index 0000000..22675fa
--- /dev/null
+++ b/Client/Assembly-CSharp/UnlockPopUp.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections;
+using UnityEngine;
+
+public class UnlockPopUp : MonoBehaviour
+{
+ public IEnumerator Show()
+ {
+ DateTime utcNow = DateTime.UtcNow;
+ if ((utcNow.DayOfYear < 350 && utcNow.DayOfYear > 4) || SaveManager.GetPurchase("hats_newyears2018"))
+ {
+ yield break;
+ }
+ base.gameObject.SetActive(true);
+ SaveManager.SetPurchased("hats_newyears2018");
+ while (base.isActiveAndEnabled)
+ {
+ yield return null;
+ }
+ yield break;
+ }
+}
diff --git a/Client/Assembly-CSharp/UploadDataGame.cs b/Client/Assembly-CSharp/UploadDataGame.cs
new file mode 100644
index 0000000..0f3d95c
--- /dev/null
+++ b/Client/Assembly-CSharp/UploadDataGame.cs
@@ -0,0 +1,203 @@
+using System;
+using System.Collections;
+using System.Text;
+using PowerTools;
+using UnityEngine;
+
+public class UploadDataGame : Minigame
+{
+ public SpriteAnim LeftFolder;
+
+ public SpriteAnim RightFolder;
+
+ public AnimationClip FolderOpen;
+
+ public AnimationClip FolderClose;
+
+ public SpriteRenderer Runner;
+
+ public HorizontalGauge Gauge;
+
+ public TextRenderer PercentText;
+
+ public TextRenderer EstimatedText;
+
+ public TextRenderer SourceText;
+
+ public TextRenderer TargetText;
+
+ public SpriteRenderer Button;
+
+ public Sprite DownloadImage;
+
+ public GameObject Status;
+
+ public GameObject Tower;
+
+ private int count;
+
+ private float timer;
+
+ public const float RandomChunks = 5f;
+
+ public const float ConstantTime = 3f;
+
+ private bool running = true;
+
+ public override void Begin(PlayerTask task)
+ {
+ PlayerControl.LocalPlayer.SetPlayerMaterialColors(this.Runner);
+ base.Begin(task);
+ if (this.MyNormTask.taskStep == 0)
+ {
+ this.Button.sprite = this.DownloadImage;
+ this.Tower.SetActive(false);
+ this.SourceText.Text = this.MyTask.StartAt.ToString();
+ this.TargetText.Text = "My Tablet";
+ return;
+ }
+ this.SourceText.Text = "My Tablet";
+ this.TargetText.Text = "Headquarters";
+ }
+
+ public void Click()
+ {
+ base.StartCoroutine(this.Transition());
+ }
+
+ private IEnumerator Transition()
+ {
+ this.Button.gameObject.SetActive(false);
+ this.Status.SetActive(true);
+ float target = this.Gauge.transform.localScale.x;
+ for (float t = 0f; t < 0.15f; t += Time.deltaTime)
+ {
+ this.Gauge.transform.localScale = new Vector3(t / 0.15f * target, 1f, 1f);
+ yield return null;
+ }
+ base.StartCoroutine(this.PulseText());
+ base.StartCoroutine(this.DoRun());
+ base.StartCoroutine(this.DoText());
+ base.StartCoroutine(this.DoPercent());
+ yield break;
+ }
+
+ private IEnumerator PulseText()
+ {
+ MeshRenderer rend2 = this.PercentText.GetComponent<MeshRenderer>();
+ MeshRenderer rend1 = this.EstimatedText.GetComponent<MeshRenderer>();
+ Color gray = new Color(0.3f, 0.3f, 0.3f, 1f);
+ while (this.running)
+ {
+ yield return new WaitForLerp(0.4f, delegate(float t)
+ {
+ Color value = Color.Lerp(Color.black, gray, t);
+ rend2.material.SetColor("_OutlineColor", value);
+ rend1.material.SetColor("_OutlineColor", value);
+ });
+ yield return new WaitForLerp(0.4f, delegate(float t)
+ {
+ Color value = Color.Lerp(gray, Color.black, t);
+ rend2.material.SetColor("_OutlineColor", value);
+ rend1.material.SetColor("_OutlineColor", value);
+ });
+ }
+ rend2.material.SetColor("_OutlineColor", Color.black);
+ rend1.material.SetColor("_OutlineColor", Color.black);
+ yield break;
+ }
+
+ private IEnumerator DoPercent()
+ {
+ while (this.running)
+ {
+ float num = (float)this.count / 5f * 0.7f + this.timer / 3f * 0.3f;
+ if (num >= 1f)
+ {
+ this.running = false;
+ }
+ num = Mathf.Clamp(num, 0f, 1f);
+ this.Gauge.Value = num;
+ this.PercentText.Text = Mathf.RoundToInt(num * 100f) + "%";
+ yield return null;
+ }
+ yield break;
+ }
+
+ private IEnumerator DoText()
+ {
+ StringBuilder txt = new StringBuilder("Estimated Time: ");
+ int baselen = txt.Length;
+ int max = 604800;
+ this.count = 0;
+ while ((float)this.count < 5f)
+ {
+ txt.Length = baselen;
+ int num = IntRange.Next(max / 6, max);
+ int num2 = num / 86400;
+ if (num2 > 0)
+ {
+ txt.Append(num2 + "d ");
+ }
+ int num3 = num / 3600 % 24;
+ if (num3 > 0)
+ {
+ txt.Append(num3 + "hr ");
+ }
+ int num4 = num / 60 % 60;
+ if (num4 > 0)
+ {
+ txt.Append(num4 + "m ");
+ }
+ int num5 = num % 60;
+ if (num5 > 0)
+ {
+ txt.Append(num5 + "s");
+ }
+ this.EstimatedText.Text = txt.ToString();
+ max /= 4;
+ yield return new WaitForSeconds(FloatRange.Next(0.6f, 1.2f));
+ this.count++;
+ }
+ this.timer = 0f;
+ while (this.timer < 3f)
+ {
+ txt.Length = baselen;
+ int num6 = Mathf.RoundToInt(3f - this.timer);
+ txt.Append(num6 + "s");
+ this.EstimatedText.Text = txt.ToString();
+ yield return null;
+ this.timer += Time.deltaTime;
+ }
+ yield break;
+ }
+
+ private IEnumerator DoRun()
+ {
+ while (this.running)
+ {
+ UploadDataGame.<>c__DisplayClass25_0 CS$<>8__locals1 = new UploadDataGame.<>c__DisplayClass25_0();
+ CS$<>8__locals1.<>4__this = this;
+ this.LeftFolder.Play(this.FolderOpen, 1f);
+ CS$<>8__locals1.pos = this.Runner.transform.localPosition;
+ yield return new WaitForLerp(1.125f, delegate(float t)
+ {
+ CS$<>8__locals1.pos.x = Mathf.Lerp(-1.25f, 0.5625f, t);
+ CS$<>8__locals1.<>4__this.Runner.transform.localPosition = CS$<>8__locals1.pos;
+ });
+ this.LeftFolder.Play(this.FolderClose, 1f);
+ this.RightFolder.Play(this.FolderOpen, 1f);
+ yield return new WaitForLerp(1.375f, delegate(float t)
+ {
+ CS$<>8__locals1.pos.x = Mathf.Lerp(0.5625f, 1.25f, t);
+ CS$<>8__locals1.<>4__this.Runner.transform.localPosition = CS$<>8__locals1.pos;
+ });
+ yield return new WaitForAnimationFinish(this.RightFolder, this.FolderClose);
+ CS$<>8__locals1 = null;
+ }
+ this.EstimatedText.Text = "Complete";
+ this.MyNormTask.NextStep();
+ base.StartCoroutine(base.CoStartClose(0.75f));
+ yield break;
+ }
+}
diff --git a/Client/Assembly-CSharp/UploadDataTask.cs b/Client/Assembly-CSharp/UploadDataTask.cs
new file mode 100644
index 0000000..8d4aafa
--- /dev/null
+++ b/Client/Assembly-CSharp/UploadDataTask.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Linq;
+using System.Text;
+
+public class UploadDataTask : NormalPlayerTask
+{
+ public override bool ValidConsole(global::Console console)
+ {
+ return (console.Room == this.StartAt && console.ValidTasks.Any((TaskSet set) => this.TaskType == set.taskType && set.taskStep.Contains(this.taskStep))) || (this.taskStep == 1 && console.TaskTypes.Contains(this.TaskType));
+ }
+
+ public override void AppendTaskText(StringBuilder sb)
+ {
+ if (this.taskStep > 0)
+ {
+ if (this.IsComplete)
+ {
+ sb.Append("[00DD00FF]");
+ }
+ else
+ {
+ sb.Append("[FFFF00FF]");
+ }
+ }
+ sb.Append(DestroyableSingleton<TranslationController>.Instance.GetString((this.taskStep == 0) ? this.StartAt : SystemTypes.Admin));
+ sb.Append(": ");
+ sb.Append(DestroyableSingleton<TranslationController>.Instance.GetString((this.taskStep == 0) ? StringNames.DownloadData : StringNames.UploadData, Array.Empty<object>()));
+ sb.Append(" (");
+ sb.Append(this.taskStep);
+ sb.Append("/");
+ sb.Append(this.MaxStep);
+ sb.AppendLine(") []");
+ }
+}
diff --git a/Client/Assembly-CSharp/UseButtonManager.cs b/Client/Assembly-CSharp/UseButtonManager.cs
new file mode 100644
index 0000000..c6a7f65
--- /dev/null
+++ b/Client/Assembly-CSharp/UseButtonManager.cs
@@ -0,0 +1,108 @@
+using System;
+using UnityEngine;
+
+public class UseButtonManager : MonoBehaviour
+{
+ private static readonly Color DisabledColor = new Color(1f, 1f, 1f, 0.3f);
+
+ private static readonly Color EnabledColor = new Color(1f, 1f, 1f, 1f);
+
+ public SpriteRenderer UseButton;
+
+ public Sprite UseImage;
+
+ public Sprite SabotageImage;
+
+ public Sprite VentImage;
+
+ public Sprite AdminMapImage;
+
+ public Sprite SecurityImage;
+
+ public Sprite OptionsImage;
+
+ private IUsable currentTarget;
+
+ public void SetTarget(IUsable target)
+ {
+ this.currentTarget = target;
+ if (target != null)
+ {
+ if (target is Vent)
+ {
+ this.UseButton.sprite = this.VentImage;
+ }
+ else if (target is MapConsole)
+ {
+ this.UseButton.sprite = this.AdminMapImage;
+ }
+ else if (target is OptionsConsole)
+ {
+ this.UseButton.sprite = this.OptionsImage;
+ }
+ else if (target is SystemConsole)
+ {
+ SystemConsole systemConsole = (SystemConsole)target;
+ if (systemConsole.name.StartsWith("Surv"))
+ {
+ this.UseButton.sprite = this.SecurityImage;
+ }
+ else if (systemConsole.name.StartsWith("TaskAdd"))
+ {
+ this.UseButton.sprite = this.OptionsImage;
+ }
+ else
+ {
+ this.UseButton.sprite = this.UseImage;
+ }
+ }
+ else
+ {
+ this.UseButton.sprite = this.UseImage;
+ }
+ this.UseButton.SetCooldownNormalizedUvs();
+ this.UseButton.material.SetFloat("_Percent", target.PercentCool);
+ this.UseButton.color = UseButtonManager.EnabledColor;
+ return;
+ }
+ if (PlayerControl.LocalPlayer.Data.IsImpostor && PlayerControl.LocalPlayer.CanMove)
+ {
+ this.UseButton.sprite = this.SabotageImage;
+ this.UseButton.SetCooldownNormalizedUvs();
+ this.UseButton.color = UseButtonManager.EnabledColor;
+ return;
+ }
+ this.UseButton.sprite = this.UseImage;
+ this.UseButton.color = UseButtonManager.DisabledColor;
+ }
+
+ public void DoClick()
+ {
+ if (!base.isActiveAndEnabled)
+ {
+ return;
+ }
+ if (!PlayerControl.LocalPlayer)
+ {
+ return;
+ }
+ GameData.PlayerInfo data = PlayerControl.LocalPlayer.Data;
+ if (this.currentTarget != null)
+ {
+ PlayerControl.LocalPlayer.UseClosest();
+ return;
+ }
+ if (data != null && data.IsImpostor)
+ {
+ DestroyableSingleton<HudManager>.Instance.ShowMap(delegate(MapBehaviour m)
+ {
+ m.ShowInfectedMap();
+ });
+ }
+ }
+
+ internal void Refresh()
+ {
+ this.SetTarget(this.currentTarget);
+ }
+}
diff --git a/Client/Assembly-CSharp/Vector2Range.cs b/Client/Assembly-CSharp/Vector2Range.cs
new file mode 100644
index 0000000..af83d56
--- /dev/null
+++ b/Client/Assembly-CSharp/Vector2Range.cs
@@ -0,0 +1,55 @@
+using System;
+using UnityEngine;
+
+[Serializable]
+public struct Vector2Range
+{
+ public float Width
+ {
+ get
+ {
+ return this.max.x - this.min.x;
+ }
+ }
+
+ public float Height
+ {
+ get
+ {
+ return this.max.y - this.min.y;
+ }
+ }
+
+ public Vector2 min;
+
+ public Vector2 max;
+
+ public Vector2Range(Vector2 min, Vector2 max)
+ {
+ this.min = min;
+ this.max = max;
+ }
+
+ public void LerpUnclamped(ref Vector3 output, float t, float z)
+ {
+ output.Set(Mathf.LerpUnclamped(this.min.x, this.max.x, t), Mathf.LerpUnclamped(this.min.y, this.max.y, t), z);
+ }
+
+ public void Lerp(ref Vector3 output, float t, float z)
+ {
+ output.Set(Mathf.Lerp(this.min.x, this.max.x, t), Mathf.Lerp(this.min.y, this.max.y, t), z);
+ }
+
+ public Vector2 Next()
+ {
+ return new Vector2(UnityEngine.Random.Range(this.min.x, this.max.x), UnityEngine.Random.Range(this.min.y, this.max.y));
+ }
+
+ public static Vector2 NextEdge()
+ {
+ float f = 6.2831855f * UnityEngine.Random.value;
+ float x = Mathf.Cos(f);
+ float y = Mathf.Sin(f);
+ return new Vector2(x, y);
+ }
+}
diff --git a/Client/Assembly-CSharp/VendingMinigame.cs b/Client/Assembly-CSharp/VendingMinigame.cs
new file mode 100644
index 0000000..19ad09b
--- /dev/null
+++ b/Client/Assembly-CSharp/VendingMinigame.cs
@@ -0,0 +1,188 @@
+using System;
+using System.Collections;
+using System.Linq;
+using UnityEngine;
+
+public class VendingMinigame : Minigame
+{
+ public static readonly string[] Letters = new string[]
+ {
+ "a",
+ "b",
+ "c"
+ };
+
+ public TextRenderer NumberText;
+
+ public SpriteRenderer TargetImage;
+
+ public string enteredCode = string.Empty;
+
+ private bool animating;
+
+ private bool done;
+
+ private string targetCode;
+
+ public SpriteRenderer AcceptButton;
+
+ public VendingSlot[] Slots;
+
+ public Sprite[] Drinks;
+
+ public Sprite[] DrawnDrinks;
+
+ public void OnEnable()
+ {
+ this.Begin(null);
+ }
+
+ public override void Begin(PlayerTask task)
+ {
+ base.Begin(task);
+ int num = this.Drinks.RandomIdx<Sprite>();
+ this.TargetImage.sprite = this.DrawnDrinks[num];
+ for (int i = 0; i < this.Drinks.Length; i++)
+ {
+ Sprite sprite = this.Drinks[i];
+ int num2;
+ while (!this.PickARandomSlot(sprite, out num2))
+ {
+ }
+ this.Slots[num2].DrinkImage.enabled = true;
+ this.Slots[num2].DrinkImage.sprite = sprite;
+ if (num == i)
+ {
+ this.targetCode = VendingMinigame.SlotIdToString(num2);
+ }
+ }
+ this.NumberText.Text = string.Empty;
+ }
+
+ private static int StringToSlotId(string code)
+ {
+ int num;
+ if (int.TryParse(code[0].ToString(), out num) || VendingMinigame.Letters.Any(new Func<string, bool>(code.EndsWith)))
+ {
+ return -1;
+ }
+ int num2 = VendingMinigame.Letters.IndexOf(new Predicate<string>(code.StartsWith));
+ return int.Parse(code[1].ToString()) - 1 + num2 * 4;
+ }
+
+ private static string SlotIdToString(int slotId)
+ {
+ int num = slotId % 4 + 1;
+ int num2 = slotId / 4;
+ return VendingMinigame.Letters[num2] + num;
+ }
+
+ private bool PickARandomSlot(Sprite drink, out int slotId)
+ {
+ slotId = this.Slots.RandomIdx<VendingSlot>();
+ return !this.Slots[slotId].DrinkImage.enabled;
+ }
+
+ public void EnterDigit(string s)
+ {
+ if (this.animating)
+ {
+ return;
+ }
+ if (this.done)
+ {
+ return;
+ }
+ if (this.enteredCode.Length >= 2)
+ {
+ base.StartCoroutine(this.BlinkAccept());
+ return;
+ }
+ this.enteredCode += s;
+ this.NumberText.Text = this.enteredCode;
+ }
+
+ public void ClearDigits()
+ {
+ if (this.animating)
+ {
+ return;
+ }
+ this.enteredCode = string.Empty;
+ this.NumberText.Text = string.Empty;
+ }
+
+ public void AcceptDigits()
+ {
+ if (this.animating)
+ {
+ return;
+ }
+ base.StartCoroutine(this.Animate());
+ }
+
+ private IEnumerator BlinkAccept()
+ {
+ int num;
+ for (int i = 0; i < 5; i = num)
+ {
+ this.AcceptButton.color = Color.gray;
+ yield return null;
+ yield return null;
+ this.AcceptButton.color = Color.white;
+ yield return null;
+ yield return null;
+ num = i + 1;
+ }
+ yield break;
+ }
+
+ private IEnumerator Animate()
+ {
+ this.animating = true;
+ int num = VendingMinigame.StringToSlotId(this.enteredCode);
+ if (num >= 0 && this.Slots[num].DrinkImage.enabled)
+ {
+ yield return Effects.All(new IEnumerator[]
+ {
+ this.CoBlinkVend(),
+ this.Slots[num].CoBuy()
+ });
+ if (this.targetCode == this.enteredCode)
+ {
+ this.done = true;
+ this.MyNormTask.NextStep();
+ yield return base.CoStartClose(0.25f);
+ }
+ }
+ else
+ {
+ WaitForSeconds wait = new WaitForSeconds(0.1f);
+ this.NumberText.Text = "XXXXXXXX";
+ yield return wait;
+ this.NumberText.Text = string.Empty;
+ yield return wait;
+ this.NumberText.Text = "XXXXXXXX";
+ yield return wait;
+ wait = null;
+ }
+ this.enteredCode = string.Empty;
+ this.NumberText.Text = this.enteredCode;
+ this.animating = false;
+ yield break;
+ }
+
+ private IEnumerator CoBlinkVend()
+ {
+ int num;
+ for (int i = 0; i < 5; i = num)
+ {
+ this.NumberText.Text = "Vending";
+ yield return Effects.Wait(0.1f);
+ this.NumberText.Text = string.Empty;
+ yield return Effects.Wait(0.1f);
+ num = i + 1;
+ }
+ yield break;
+ }
+}
diff --git a/Client/Assembly-CSharp/VendingSlot.cs b/Client/Assembly-CSharp/VendingSlot.cs
new file mode 100644
index 0000000..f2b43e6
--- /dev/null
+++ b/Client/Assembly-CSharp/VendingSlot.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Collections;
+using UnityEngine;
+
+public class VendingSlot : MonoBehaviour
+{
+ public SpriteRenderer DrinkImage;
+
+ public SpriteRenderer GlassImage;
+
+ public IEnumerator CoBuy()
+ {
+ yield return new WaitForLerp(0.75f, delegate(float v)
+ {
+ this.GlassImage.size = new Vector2(1f, Mathf.Lerp(1.7f, 0f, v));
+ this.GlassImage.transform.localPosition = new Vector3(0f, Mathf.Lerp(0f, 0.85f, v), -1f);
+ });
+ yield return Effects.Shake(this.DrinkImage.transform, 0.75f, 0.075f);
+ Vector3 localPosition = this.DrinkImage.transform.localPosition;
+ localPosition.z = -5f;
+ this.DrinkImage.transform.localPosition = localPosition;
+ Vector3 v2 = localPosition;
+ v2.y = -8f - localPosition.y;
+ yield return Effects.All(new IEnumerator[]
+ {
+ Effects.Slide2D(this.DrinkImage.transform, localPosition, v2, 0.75f),
+ Effects.Rotate2D(this.DrinkImage.transform, 0f, -FloatRange.Next(-45f, 45f), 0.75f)
+ });
+ yield return new WaitForLerp(0.75f, delegate(float v)
+ {
+ this.GlassImage.size = new Vector2(1f, Mathf.Lerp(0f, 1.7f, v));
+ this.GlassImage.transform.localPosition = new Vector3(0f, Mathf.Lerp(0.85f, 0f, v), -1f);
+ });
+ this.DrinkImage.enabled = false;
+ yield break;
+ }
+}
diff --git a/Client/Assembly-CSharp/Vent.cs b/Client/Assembly-CSharp/Vent.cs
new file mode 100644
index 0000000..ce9c525
--- /dev/null
+++ b/Client/Assembly-CSharp/Vent.cs
@@ -0,0 +1,163 @@
+using System;
+using PowerTools;
+using UnityEngine;
+
+public class Vent : MonoBehaviour, IUsable
+{
+ public float UsableDistance
+ {
+ get
+ {
+ return 0.75f;
+ }
+ }
+
+ public float PercentCool
+ {
+ get
+ {
+ return 0f;
+ }
+ }
+
+ public int Id;
+
+ public Vent Left;
+
+ public Vent Right;
+
+ public ButtonBehavior[] Buttons;
+
+ public AnimationClip EnterVentAnim;
+
+ public AnimationClip ExitVentAnim;
+
+ private static readonly Vector3 CollOffset = new Vector3(0f, -0.3636057f, 0f);
+
+ private SpriteRenderer myRend;
+
+ private void Start()
+ {
+ this.SetButtons(false);
+ this.myRend = base.GetComponent<SpriteRenderer>();
+ }
+
+ public void SetButtons(bool enabled)
+ {
+ Vent[] array = new Vent[]
+ {
+ this.Right,
+ this.Left
+ };
+ for (int i = 0; i < this.Buttons.Length; i++)
+ {
+ ButtonBehavior buttonBehavior = this.Buttons[i];
+ if (enabled)
+ {
+ Vent vent = array[i];
+ if (vent)
+ {
+ buttonBehavior.gameObject.SetActive(true);
+ Vector3 localPosition = (vent.transform.position - base.transform.position).normalized * 0.7f;
+ localPosition.y -= 0.08f;
+ localPosition.z = -10f;
+ buttonBehavior.transform.localPosition = localPosition;
+ buttonBehavior.transform.LookAt2d(vent.transform);
+ }
+ else
+ {
+ buttonBehavior.gameObject.SetActive(false);
+ }
+ }
+ else
+ {
+ buttonBehavior.gameObject.SetActive(false);
+ }
+ }
+ }
+
+ public float CanUse(GameData.PlayerInfo pc, out bool canUse, out bool couldUse)
+ {
+ float num = float.MaxValue;
+ PlayerControl @object = pc.Object;
+ couldUse = (pc.IsImpostor && !pc.IsDead && (@object.CanMove || @object.inVent));
+ canUse = couldUse;
+ if (canUse)
+ {
+ num = Vector2.Distance(@object.GetTruePosition(), base.transform.position);
+ canUse &= (num <= this.UsableDistance);
+ }
+ return num;
+ }
+
+ public void SetOutline(bool on, bool mainTarget)
+ {
+ this.myRend.material.SetFloat("_Outline", (float)(on ? 1 : 0));
+ this.myRend.material.SetColor("_OutlineColor", Color.red);
+ this.myRend.material.SetColor("_AddColor", mainTarget ? Color.red : Color.clear);
+ }
+
+ public void ClickRight()
+ {
+ if (this.Right)
+ {
+ Vent.DoMove(this.Right.transform.position - Vent.CollOffset);
+ this.SetButtons(false);
+ this.Right.SetButtons(true);
+ }
+ }
+
+ public void ClickLeft()
+ {
+ if (this.Left)
+ {
+ Vent.DoMove(this.Left.transform.position - Vent.CollOffset);
+ this.SetButtons(false);
+ this.Left.SetButtons(true);
+ }
+ }
+
+ private static void DoMove(Vector3 pos)
+ {
+ PlayerControl.LocalPlayer.NetTransform.RpcSnapTo(pos);
+ if (Constants.ShouldPlaySfx())
+ {
+ SoundManager.Instance.PlaySound(PlayerControl.LocalPlayer.VentMoveSounds.Random<AudioClip>(), false, 1f).pitch = FloatRange.Next(0.8f, 1.2f);
+ }
+ }
+
+ public void Use()
+ {
+ bool flag;
+ bool flag2;
+ this.CanUse(PlayerControl.LocalPlayer.Data, out flag, out flag2);
+ if (!flag)
+ {
+ return;
+ }
+ PlayerControl localPlayer = PlayerControl.LocalPlayer;
+ if (Constants.ShouldPlaySfx())
+ {
+ SoundManager.Instance.StopSound(localPlayer.VentEnterSound);
+ SoundManager.Instance.PlaySound(localPlayer.VentEnterSound, false, 1f).pitch = FloatRange.Next(0.8f, 1.2f);
+ }
+ if (localPlayer.inVent)
+ {
+ localPlayer.MyPhysics.RpcExitVent(this.Id);
+ this.SetButtons(false);
+ return;
+ }
+ localPlayer.MyPhysics.RpcEnterVent(this.Id);
+ this.SetButtons(true);
+ }
+
+ internal void EnterVent()
+ {
+ base.GetComponent<SpriteAnim>().Play(this.EnterVentAnim, 1f);
+ }
+
+ internal void ExitVent()
+ {
+ base.GetComponent<SpriteAnim>().Play(this.ExitVentAnim, 1f);
+ }
+}
diff --git a/Client/Assembly-CSharp/VersionShower.cs b/Client/Assembly-CSharp/VersionShower.cs
new file mode 100644
index 0000000..8c87390
--- /dev/null
+++ b/Client/Assembly-CSharp/VersionShower.cs
@@ -0,0 +1,19 @@
+using System;
+using UnityEngine;
+
+public class VersionShower : MonoBehaviour
+{
+ public TextRenderer text;
+
+ public void Start()
+ {
+ string str = "v" + Application.version;
+ str += "s";
+ if (!DetectTamper.Detect())
+ {
+ str += "h";
+ }
+ this.text.Text = str;
+ Screen.sleepTimeout = -1;
+ }
+}
diff --git a/Client/Assembly-CSharp/VerticalGauge.cs b/Client/Assembly-CSharp/VerticalGauge.cs
new file mode 100644
index 0000000..08ebfec
--- /dev/null
+++ b/Client/Assembly-CSharp/VerticalGauge.cs
@@ -0,0 +1,28 @@
+using System;
+using UnityEngine;
+
+public class VerticalGauge : MonoBehaviour
+{
+ public float value = 0.5f;
+
+ public float MaxValue = 1f;
+
+ public float maskScale = 1f;
+
+ public SpriteMask Mask;
+
+ private float lastValue = float.MinValue;
+
+ public void Update()
+ {
+ if (this.lastValue != this.value)
+ {
+ this.lastValue = this.value;
+ float num = Mathf.Clamp(this.lastValue / this.MaxValue, 0f, 1f) * this.maskScale;
+ Vector3 localScale = this.Mask.transform.localScale;
+ localScale.y = num;
+ this.Mask.transform.localScale = localScale;
+ this.Mask.transform.localPosition = new Vector3(0f, -this.Mask.sprite.bounds.size.y * (this.maskScale - num) / 2f, 0f);
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/VirtualJoystick.cs b/Client/Assembly-CSharp/VirtualJoystick.cs
new file mode 100644
index 0000000..25aaa72
--- /dev/null
+++ b/Client/Assembly-CSharp/VirtualJoystick.cs
@@ -0,0 +1,59 @@
+using System;
+using UnityEngine;
+
+public class VirtualJoystick : MonoBehaviour, IVirtualJoystick
+{
+ public Vector2 Delta { get; private set; }
+
+ public float InnerRadius = 0.64f;
+
+ public float OuterRadius = 1.28f;
+
+ public CircleCollider2D Outer;
+
+ public SpriteRenderer Inner;
+
+ private Controller myController = new Controller();
+
+ protected virtual void FixedUpdate()
+ {
+ this.myController.Update();
+ DragState dragState = this.myController.CheckDrag(this.Outer, false);
+ if (dragState - DragState.TouchStart <= 1)
+ {
+ float maxLength = this.OuterRadius - this.InnerRadius;
+ Vector2 vector = this.myController.DragPosition - base.transform.position;
+ float magnitude = vector.magnitude;
+ Vector2 a = new Vector2(Mathf.Sqrt(Mathf.Abs(vector.x)) * Mathf.Sign(vector.x), Mathf.Sqrt(Mathf.Abs(vector.y)) * Mathf.Sign(vector.y));
+ this.Delta = Vector2.ClampMagnitude(a / this.OuterRadius, 1f);
+ this.Inner.transform.localPosition = Vector3.ClampMagnitude(vector, maxLength) + Vector3.back;
+ return;
+ }
+ if (dragState != DragState.Released)
+ {
+ return;
+ }
+ this.Delta = Vector2.zero;
+ this.Inner.transform.localPosition = Vector3.back;
+ }
+
+ public virtual void UpdateJoystick(FingerBehaviour finger, Vector2 velocity, bool syncFinger)
+ {
+ Vector3 vector = this.Inner.transform.localPosition;
+ Vector3 vector2 = velocity.normalized * this.InnerRadius;
+ vector2.z = vector.z;
+ if (syncFinger)
+ {
+ vector = Vector3.Lerp(vector, vector2, Time.fixedDeltaTime * 5f);
+ this.Inner.transform.localPosition = vector;
+ vector = this.Inner.transform.position;
+ vector.z = -26f;
+ finger.transform.position = vector;
+ return;
+ }
+ if (this.Inner.gameObject != finger.gameObject)
+ {
+ this.Inner.transform.localPosition = vector2;
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/VoteBanSystem.cs b/Client/Assembly-CSharp/VoteBanSystem.cs
new file mode 100644
index 0000000..23d030f
--- /dev/null
+++ b/Client/Assembly-CSharp/VoteBanSystem.cs
@@ -0,0 +1,111 @@
+using System;
+using System.Collections.Generic;
+using Hazel;
+using InnerNet;
+
+public class VoteBanSystem : InnerNetObject
+{
+ public static VoteBanSystem Instance;
+
+ public Dictionary<int, int[]> Votes = new Dictionary<int, int[]>();
+
+ public enum RpcCalls
+ {
+ AddVote
+ }
+
+ public void Awake()
+ {
+ VoteBanSystem.Instance = this;
+ }
+
+ public void CmdAddVote(int clientId)
+ {
+ this.AddVote(AmongUsClient.Instance.ClientId, clientId);
+ MessageWriter messageWriter = AmongUsClient.Instance.StartRpc(this.NetId, 0, SendOption.Reliable);
+ messageWriter.Write(AmongUsClient.Instance.ClientId);
+ messageWriter.Write(clientId);
+ messageWriter.EndMessage();
+ }
+
+ private void AddVote(int srcClient, int clientId)
+ {
+ int[] array;
+ if (!this.Votes.TryGetValue(clientId, out array))
+ {
+ array = (this.Votes[clientId] = new int[3]);
+ }
+ int num = -1;
+ for (int i = 0; i < array.Length; i++)
+ {
+ int num2 = array[i];
+ if (num2 == srcClient)
+ {
+ break;
+ }
+ if (num2 == 0)
+ {
+ num = i;
+ break;
+ }
+ }
+ if (num != -1)
+ {
+ array[num] = srcClient;
+ base.SetDirtyBit(1U);
+ if (num == array.Length - 1)
+ {
+ AmongUsClient.Instance.KickPlayer(clientId, false);
+ }
+ }
+ }
+
+ public bool HasMyVote(int clientId)
+ {
+ int[] array;
+ return this.Votes.TryGetValue(clientId, out array) && Array.IndexOf<int>(array, AmongUsClient.Instance.ClientId) != -1;
+ }
+
+ public override void HandleRpc(byte callId, MessageReader reader)
+ {
+ if (callId == 0)
+ {
+ int srcClient = reader.ReadInt32();
+ int clientId = reader.ReadInt32();
+ this.AddVote(srcClient, clientId);
+ }
+ }
+
+ public override bool Serialize(MessageWriter writer, bool initialState)
+ {
+ writer.Write((byte)this.Votes.Count);
+ foreach (KeyValuePair<int, int[]> keyValuePair in this.Votes)
+ {
+ writer.Write(keyValuePair.Key);
+ for (int i = 0; i < 3; i++)
+ {
+ writer.WritePacked(keyValuePair.Value[i]);
+ }
+ }
+ this.DirtyBits = 0U;
+ return true;
+ }
+
+ public override void Deserialize(MessageReader reader, bool initialState)
+ {
+ int num = (int)reader.ReadByte();
+ for (int i = 0; i < num; i++)
+ {
+ int key = reader.ReadInt32();
+ int[] array;
+ if (!this.Votes.TryGetValue(key, out array))
+ {
+ array = (this.Votes[key] = new int[3]);
+ }
+ for (int j = 0; j < 3; j++)
+ {
+ array[j] = reader.ReadPackedInt32();
+ }
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/WaitForHostPopup.cs b/Client/Assembly-CSharp/WaitForHostPopup.cs
new file mode 100644
index 0000000..5ccade5
--- /dev/null
+++ b/Client/Assembly-CSharp/WaitForHostPopup.cs
@@ -0,0 +1,27 @@
+using System;
+using InnerNet;
+using UnityEngine;
+
+public class WaitForHostPopup : DestroyableSingleton<WaitForHostPopup>
+{
+ public GameObject Content;
+
+ public void Show()
+ {
+ if (AmongUsClient.Instance && AmongUsClient.Instance.ClientId > 0)
+ {
+ this.Content.SetActive(true);
+ }
+ }
+
+ public void ExitGame()
+ {
+ AmongUsClient.Instance.ExitGame(DisconnectReasons.ExitGame);
+ this.Content.SetActive(false);
+ }
+
+ public void Hide()
+ {
+ this.Content.SetActive(false);
+ }
+}
diff --git a/Client/Assembly-CSharp/WaitForLerp.cs b/Client/Assembly-CSharp/WaitForLerp.cs
new file mode 100644
index 0000000..97c8bcd
--- /dev/null
+++ b/Client/Assembly-CSharp/WaitForLerp.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Collections;
+using UnityEngine;
+
+public class WaitForLerp : IEnumerator
+{
+ public object Current
+ {
+ get
+ {
+ return null;
+ }
+ }
+
+ private float duration;
+
+ private float timer;
+
+ private Action<float> act;
+
+ public WaitForLerp(float seconds, Action<float> act)
+ {
+ this.duration = seconds;
+ this.act = act;
+ }
+
+ public bool MoveNext()
+ {
+ this.timer = Mathf.Min(this.timer + Time.deltaTime, this.duration);
+ this.act(this.timer / this.duration);
+ return this.timer < this.duration;
+ }
+
+ public void Reset()
+ {
+ this.timer = 0f;
+ }
+}
diff --git a/Client/Assembly-CSharp/WeaponsMinigame.cs b/Client/Assembly-CSharp/WeaponsMinigame.cs
new file mode 100644
index 0000000..c6dc8f6
--- /dev/null
+++ b/Client/Assembly-CSharp/WeaponsMinigame.cs
@@ -0,0 +1,149 @@
+using System;
+using System.Collections;
+using UnityEngine;
+
+public class WeaponsMinigame : Minigame
+{
+ public FloatRange XSpan = new FloatRange(-1.15f, 1.15f);
+
+ public FloatRange YSpan = new FloatRange(-1.15f, 1.15f);
+
+ public FloatRange TimeToSpawn;
+
+ public ObjectPoolBehavior asteroidPool;
+
+ public TextController ScoreText;
+
+ public SpriteRenderer TargetReticle;
+
+ public LineRenderer TargetLines;
+
+ private Vector3 TargetCenter;
+
+ public Collider2D BackgroundCol;
+
+ public SpriteRenderer Background;
+
+ public Controller myController = new Controller();
+
+ private float Timer;
+
+ public AudioClip ShootSound;
+
+ public AudioClip[] ExplodeSounds;
+
+ public override void Begin(PlayerTask task)
+ {
+ base.Begin(task);
+ this.ScoreText.Text = "Destroyed: " + this.MyNormTask.taskStep;
+ this.TimeToSpawn.Next();
+ }
+
+ protected override IEnumerator CoAnimateOpen()
+ {
+ for (float timer = 0f; timer < 0.1f; timer += Time.deltaTime)
+ {
+ float num = timer / 0.1f;
+ base.transform.localScale = new Vector3(num, 0.1f, num);
+ yield return null;
+ }
+ for (float timer = 0.010000001f; timer < 0.1f; timer += Time.deltaTime)
+ {
+ float y = timer / 0.1f;
+ base.transform.localScale = new Vector3(1f, y, 1f);
+ yield return null;
+ }
+ base.transform.localScale = new Vector3(1f, 1f, 1f);
+ yield break;
+ }
+
+ protected override IEnumerator CoDestroySelf()
+ {
+ for (float timer = 0.010000001f; timer < 0.1f; timer += Time.deltaTime)
+ {
+ float y = 1f - timer / 0.1f;
+ base.transform.localScale = new Vector3(1f, y, 1f);
+ yield return null;
+ }
+ for (float timer = 0f; timer < 0.1f; timer += Time.deltaTime)
+ {
+ float num = 1f - timer / 0.1f;
+ base.transform.localScale = new Vector3(num, 0.1f, num);
+ yield return null;
+ }
+ UnityEngine.Object.Destroy(base.gameObject);
+ yield break;
+ }
+
+ public void FixedUpdate()
+ {
+ this.Background.color = Color.Lerp(Palette.ClearWhite, Color.white, Mathf.Sin(Time.time * 3f) * 0.1f + 0.79999995f);
+ if (this.MyNormTask && this.MyNormTask.IsComplete)
+ {
+ return;
+ }
+ this.Timer += Time.fixedDeltaTime;
+ if (this.Timer >= this.TimeToSpawn.Last)
+ {
+ this.Timer = 0f;
+ this.TimeToSpawn.Next();
+ if (this.asteroidPool.InUse < this.MyNormTask.MaxStep - this.MyNormTask.TaskStep)
+ {
+ Asteroid ast = this.asteroidPool.Get<Asteroid>();
+ ast.transform.localPosition = new Vector3(this.XSpan.max, this.YSpan.Next(), -1f);
+ ast.TargetPosition = new Vector3(this.XSpan.min, this.YSpan.Next(), -1f);
+ ast.GetComponent<ButtonBehavior>().OnClick.AddListener(delegate()
+ {
+ this.BreakApart(ast);
+ });
+ }
+ }
+ this.myController.Update();
+ if (this.myController.CheckDrag(this.BackgroundCol, false) == DragState.TouchStart)
+ {
+ if (Constants.ShouldPlaySfx())
+ {
+ SoundManager.Instance.PlaySound(this.ShootSound, false, 1f);
+ }
+ Vector3 vector = this.myController.DragPosition - base.transform.position;
+ vector.z = -2f;
+ this.TargetReticle.transform.localPosition = vector;
+ vector.z = 0f;
+ this.TargetLines.SetPosition(1, vector);
+ if (!ShipStatus.Instance.WeaponsImage.IsPlaying(null))
+ {
+ ShipStatus.Instance.FireWeapon();
+ PlayerControl.LocalPlayer.RpcPlayAnimation(6);
+ }
+ }
+ }
+
+ public void BreakApart(Asteroid ast)
+ {
+ if (Constants.ShouldPlaySfx())
+ {
+ SoundManager.Instance.PlaySound(this.ExplodeSounds.Random<AudioClip>(), false, 1f).pitch = FloatRange.Next(0.8f, 1.2f);
+ }
+ if (!this.MyNormTask.IsComplete)
+ {
+ base.StartCoroutine(ast.CoBreakApart());
+ if (this.MyNormTask)
+ {
+ this.MyNormTask.NextStep();
+ this.ScoreText.Text = "Destroyed: " + this.MyNormTask.taskStep;
+ }
+ if (this.MyNormTask && this.MyNormTask.IsComplete)
+ {
+ base.StartCoroutine(base.CoStartClose(0.75f));
+ foreach (PoolableBehavior poolableBehavior in this.asteroidPool.activeChildren)
+ {
+ Asteroid asteroid = (Asteroid)poolableBehavior;
+ if (!(asteroid == ast))
+ {
+ base.StartCoroutine(asteroid.CoBreakApart());
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/WeatherMinigame.cs b/Client/Assembly-CSharp/WeatherMinigame.cs
new file mode 100644
index 0000000..fe02976
--- /dev/null
+++ b/Client/Assembly-CSharp/WeatherMinigame.cs
@@ -0,0 +1,34 @@
+using System;
+using UnityEngine;
+
+public class WeatherMinigame : Minigame
+{
+ public float RefuelDuration = 5f;
+
+ public VerticalGauge destGauge;
+
+ private bool isDown;
+
+ private float timer;
+
+ public void FixedUpdate()
+ {
+ if (this.isDown && this.timer < 1f)
+ {
+ this.timer += Time.fixedDeltaTime / this.RefuelDuration;
+ this.MyNormTask.Data[0] = (byte)Mathf.Min(255f, this.timer * 255f);
+ if (this.timer >= 1f)
+ {
+ this.timer = 1f;
+ this.MyNormTask.NextStep();
+ base.StartCoroutine(base.CoStartClose(0.75f));
+ }
+ }
+ this.destGauge.value = this.timer;
+ }
+
+ public void StartStopFill()
+ {
+ this.isDown = !this.isDown;
+ }
+}
diff --git a/Client/Assembly-CSharp/WinningPlayerData.cs b/Client/Assembly-CSharp/WinningPlayerData.cs
new file mode 100644
index 0000000..dffd5e5
--- /dev/null
+++ b/Client/Assembly-CSharp/WinningPlayerData.cs
@@ -0,0 +1,36 @@
+using System;
+
+public class WinningPlayerData
+{
+ public string Name;
+
+ public bool IsDead;
+
+ public bool IsImpostor;
+
+ public int ColorId;
+
+ public uint SkinId;
+
+ public uint HatId;
+
+ public uint PetId;
+
+ public bool IsYou;
+
+ public WinningPlayerData()
+ {
+ }
+
+ public WinningPlayerData(GameData.PlayerInfo player)
+ {
+ this.IsYou = (player.Object == PlayerControl.LocalPlayer);
+ this.Name = player.PlayerName;
+ this.IsDead = (player.IsDead || player.Disconnected);
+ this.IsImpostor = player.IsImpostor;
+ this.ColorId = (int)player.ColorId;
+ this.SkinId = player.SkinId;
+ this.PetId = player.PetId;
+ this.HatId = player.HatId;
+ }
+}
diff --git a/Client/Assembly-CSharp/Wire.cs b/Client/Assembly-CSharp/Wire.cs
new file mode 100644
index 0000000..055bc41
--- /dev/null
+++ b/Client/Assembly-CSharp/Wire.cs
@@ -0,0 +1,61 @@
+using System;
+using UnityEngine;
+
+public class Wire : MonoBehaviour
+{
+ public Vector2 BaseWorldPos { get; internal set; }
+
+ private const int WireDepth = -14;
+
+ public SpriteRenderer Liner;
+
+ public SpriteRenderer ColorBase;
+
+ public SpriteRenderer ColorEnd;
+
+ public Collider2D hitbox;
+
+ public SpriteRenderer WireTip;
+
+ public sbyte WireId;
+
+ public void Start()
+ {
+ this.BaseWorldPos = base.transform.position;
+ }
+
+ public void ResetLine(Vector3 targetWorldPos, bool reset = false)
+ {
+ if (reset)
+ {
+ this.Liner.transform.localScale = new Vector3(0f, 0f, 0f);
+ this.WireTip.transform.eulerAngles = Vector3.zero;
+ this.WireTip.transform.position = base.transform.position;
+ return;
+ }
+ Vector2 vector = targetWorldPos - base.transform.position;
+ Vector2 normalized = vector.normalized;
+ Vector3 localPosition = default(Vector3);
+ localPosition = vector - normalized * 0.075f;
+ localPosition.z = -0.01f;
+ this.WireTip.transform.localPosition = localPosition;
+ float magnitude = vector.magnitude;
+ this.Liner.transform.localScale = new Vector3(magnitude, 1f, 1f);
+ this.Liner.transform.localPosition = vector / 2f;
+ this.WireTip.transform.LookAt2d(targetWorldPos);
+ this.Liner.transform.localEulerAngles = new Vector3(0f, 0f, Vector2.SignedAngle(Vector2.right, vector));
+ }
+
+ public void ConnectRight(WireNode node)
+ {
+ Vector3 position = node.transform.position;
+ this.ResetLine(position, false);
+ }
+
+ public void SetColor(Color color)
+ {
+ this.Liner.material.SetColor("_Color", color);
+ this.ColorBase.color = color;
+ this.ColorEnd.color = color;
+ }
+}
diff --git a/Client/Assembly-CSharp/WireMinigame.cs b/Client/Assembly-CSharp/WireMinigame.cs
new file mode 100644
index 0000000..27d0ecc
--- /dev/null
+++ b/Client/Assembly-CSharp/WireMinigame.cs
@@ -0,0 +1,157 @@
+using System;
+using UnityEngine;
+
+public class WireMinigame : Minigame
+{
+ private static readonly Color[] colors = new Color[]
+ {
+ Color.red,
+ Color.blue,
+ Color.yellow,
+ Color.magenta
+ };
+
+ public Wire[] LeftNodes;
+
+ public WireNode[] RightNodes;
+
+ public SpriteRenderer[] LeftLights;
+
+ public SpriteRenderer[] RightLights;
+
+ private Controller myController = new Controller();
+
+ private sbyte[] ExpectedWires = new sbyte[4];
+
+ private sbyte[] ActualWires = new sbyte[4];
+
+ public AudioClip[] WireSounds;
+
+ private bool TaskIsForThisPanel()
+ {
+ return this.MyNormTask.taskStep < this.MyNormTask.Data.Length && !this.MyNormTask.IsComplete && (int)this.MyNormTask.Data[this.MyNormTask.taskStep] == base.ConsoleId;
+ }
+
+ public override void Begin(PlayerTask task)
+ {
+ base.Begin(task);
+ IntRange.FillRandomRange(this.ExpectedWires);
+ for (int i = 0; i < this.LeftNodes.Length; i++)
+ {
+ this.ActualWires[i] = -1;
+ int num = (int)this.ExpectedWires[i];
+ Wire wire = this.LeftNodes[i];
+ wire.SetColor(WireMinigame.colors[num]);
+ wire.WireId = (sbyte)i;
+ this.RightNodes[i].SetColor(WireMinigame.colors[i]);
+ this.RightNodes[i].WireId = (sbyte)i;
+ int num2 = (int)this.ActualWires[i];
+ if (num2 > -1)
+ {
+ wire.ConnectRight(this.RightNodes[num2]);
+ }
+ else
+ {
+ wire.ResetLine(Vector3.zero, true);
+ }
+ }
+ this.UpdateLights();
+ }
+
+ public void Update()
+ {
+ if (!this.TaskIsForThisPanel())
+ {
+ return;
+ }
+ this.myController.Update();
+ base.transform.position;
+ for (int i = 0; i < this.LeftNodes.Length; i++)
+ {
+ Wire wire = this.LeftNodes[i];
+ DragState dragState = this.myController.CheckDrag(wire.hitbox, false);
+ if (dragState != DragState.Dragging)
+ {
+ if (dragState == DragState.Released)
+ {
+ if (this.ActualWires[(int)wire.WireId] == -1)
+ {
+ wire.ResetLine(wire.BaseWorldPos, true);
+ }
+ else if (Constants.ShouldPlaySfx())
+ {
+ SoundManager.Instance.PlaySound(this.WireSounds.Random<AudioClip>(), false, 1f);
+ }
+ this.CheckTask();
+ }
+ }
+ else
+ {
+ Vector2 vector = this.myController.DragPosition;
+ WireNode wireNode = this.CheckRightSide(vector);
+ if (wireNode)
+ {
+ vector = wireNode.transform.position;
+ this.ActualWires[(int)wire.WireId] = wireNode.WireId;
+ }
+ else
+ {
+ vector -= wire.BaseWorldPos.normalized * 0.05f;
+ this.ActualWires[(int)wire.WireId] = -1;
+ }
+ wire.ResetLine(vector, false);
+ }
+ }
+ this.UpdateLights();
+ }
+
+ private void UpdateLights()
+ {
+ for (int i = 0; i < this.ActualWires.Length; i++)
+ {
+ Color color = Color.yellow;
+ color *= 1f - Mathf.PerlinNoise((float)i, Time.time * 35f) * 0.3f;
+ color.a = 1f;
+ if (this.ActualWires[i] != this.ExpectedWires[i])
+ {
+ this.RightLights[(int)this.ExpectedWires[i]].color = new Color(0.2f, 0.2f, 0.2f);
+ }
+ else
+ {
+ this.RightLights[(int)this.ExpectedWires[i]].color = color;
+ }
+ this.LeftLights[i].color = color;
+ }
+ }
+
+ private WireNode CheckRightSide(Vector2 pos)
+ {
+ for (int i = 0; i < this.RightNodes.Length; i++)
+ {
+ WireNode wireNode = this.RightNodes[i];
+ if (wireNode.hitbox.OverlapPoint(pos))
+ {
+ return wireNode;
+ }
+ }
+ return null;
+ }
+
+ private void CheckTask()
+ {
+ bool flag = true;
+ for (int i = 0; i < this.ActualWires.Length; i++)
+ {
+ if (this.ActualWires[i] != this.ExpectedWires[i])
+ {
+ flag = false;
+ break;
+ }
+ }
+ if (flag)
+ {
+ this.MyNormTask.NextStep();
+ this.Close();
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/WireNode.cs b/Client/Assembly-CSharp/WireNode.cs
new file mode 100644
index 0000000..04db0bc
--- /dev/null
+++ b/Client/Assembly-CSharp/WireNode.cs
@@ -0,0 +1,19 @@
+using System;
+using UnityEngine;
+
+public class WireNode : MonoBehaviour
+{
+ public Collider2D hitbox;
+
+ public SpriteRenderer[] WireColors;
+
+ public sbyte WireId;
+
+ internal void SetColor(Color color)
+ {
+ for (int i = 0; i < this.WireColors.Length; i++)
+ {
+ this.WireColors[i].color = color;
+ }
+ }
+}
diff --git a/Client/Assembly-CSharp/XXHash.cs b/Client/Assembly-CSharp/XXHash.cs
new file mode 100644
index 0000000..5845560
--- /dev/null
+++ b/Client/Assembly-CSharp/XXHash.cs
@@ -0,0 +1,189 @@
+using System;
+
+public class XXHash
+{
+ private uint seed;
+
+ private const uint PRIME32_1 = 2654435761U;
+
+ private const uint PRIME32_2 = 2246822519U;
+
+ private const uint PRIME32_3 = 3266489917U;
+
+ private const uint PRIME32_4 = 668265263U;
+
+ private const uint PRIME32_5 = 374761393U;
+
+ public XXHash(int seed)
+ {
+ this.seed = (uint)seed;
+ }
+
+ public uint GetHash(byte[] buf)
+ {
+ int i = 0;
+ int num = buf.Length;
+ uint num3;
+ if (num >= 16)
+ {
+ int num2 = num - 16;
+ uint value = this.seed + 2654435761U + 2246822519U;
+ uint value2 = this.seed + 2246822519U;
+ uint value3 = this.seed;
+ uint value4 = this.seed - 2654435761U;
+ do
+ {
+ value = XXHash.CalcSubHash(value, buf, i);
+ i += 4;
+ value2 = XXHash.CalcSubHash(value2, buf, i);
+ i += 4;
+ value3 = XXHash.CalcSubHash(value3, buf, i);
+ i += 4;
+ value4 = XXHash.CalcSubHash(value4, buf, i);
+ i += 4;
+ }
+ while (i <= num2);
+ num3 = XXHash.RotateLeft(value, 1) + XXHash.RotateLeft(value2, 7) + XXHash.RotateLeft(value3, 12) + XXHash.RotateLeft(value4, 18);
+ }
+ else
+ {
+ num3 = this.seed + 374761393U;
+ }
+ num3 += (uint)num;
+ while (i <= num - 4)
+ {
+ num3 += BitConverter.ToUInt32(buf, i) * 3266489917U;
+ num3 = XXHash.RotateLeft(num3, 17) * 668265263U;
+ i += 4;
+ }
+ while (i < num)
+ {
+ num3 += (uint)buf[i] * 374761393U;
+ num3 = XXHash.RotateLeft(num3, 11) * 2654435761U;
+ i++;
+ }
+ num3 ^= num3 >> 15;
+ num3 *= 2246822519U;
+ num3 ^= num3 >> 13;
+ num3 *= 3266489917U;
+ return num3 ^ num3 >> 16;
+ }
+
+ public uint GetHash(params uint[] buf)
+ {
+ int i = 0;
+ int num = buf.Length;
+ uint num3;
+ if (num >= 4)
+ {
+ int num2 = num - 4;
+ uint value = this.seed + 2654435761U + 2246822519U;
+ uint value2 = this.seed + 2246822519U;
+ uint value3 = this.seed;
+ uint value4 = this.seed - 2654435761U;
+ do
+ {
+ value = XXHash.CalcSubHash(value, buf[i]);
+ i++;
+ value2 = XXHash.CalcSubHash(value2, buf[i]);
+ i++;
+ value3 = XXHash.CalcSubHash(value3, buf[i]);
+ i++;
+ value4 = XXHash.CalcSubHash(value4, buf[i]);
+ i++;
+ }
+ while (i <= num2);
+ num3 = XXHash.RotateLeft(value, 1) + XXHash.RotateLeft(value2, 7) + XXHash.RotateLeft(value3, 12) + XXHash.RotateLeft(value4, 18);
+ }
+ else
+ {
+ num3 = this.seed + 374761393U;
+ }
+ num3 += (uint)(num * 4);
+ while (i < num)
+ {
+ num3 += buf[i] * 3266489917U;
+ num3 = XXHash.RotateLeft(num3, 17) * 668265263U;
+ i++;
+ }
+ num3 ^= num3 >> 15;
+ num3 *= 2246822519U;
+ num3 ^= num3 >> 13;
+ num3 *= 3266489917U;
+ return num3 ^ num3 >> 16;
+ }
+
+ public uint GetHash(params int[] buf)
+ {
+ int i = 0;
+ int num = buf.Length;
+ uint num3;
+ if (num >= 4)
+ {
+ int num2 = num - 4;
+ uint value = this.seed + 2654435761U + 2246822519U;
+ uint value2 = this.seed + 2246822519U;
+ uint value3 = this.seed;
+ uint value4 = this.seed - 2654435761U;
+ do
+ {
+ value = XXHash.CalcSubHash(value, (uint)buf[i]);
+ i++;
+ value2 = XXHash.CalcSubHash(value2, (uint)buf[i]);
+ i++;
+ value3 = XXHash.CalcSubHash(value3, (uint)buf[i]);
+ i++;
+ value4 = XXHash.CalcSubHash(value4, (uint)buf[i]);
+ i++;
+ }
+ while (i <= num2);
+ num3 = XXHash.RotateLeft(value, 1) + XXHash.RotateLeft(value2, 7) + XXHash.RotateLeft(value3, 12) + XXHash.RotateLeft(value4, 18);
+ }
+ else
+ {
+ num3 = this.seed + 374761393U;
+ }
+ num3 += (uint)(num * 4);
+ while (i < num)
+ {
+ num3 += (uint)(buf[i] * -1028477379);
+ num3 = XXHash.RotateLeft(num3, 17) * 668265263U;
+ i++;
+ }
+ num3 ^= num3 >> 15;
+ num3 *= 2246822519U;
+ num3 ^= num3 >> 13;
+ num3 *= 3266489917U;
+ return num3 ^ num3 >> 16;
+ }
+
+ public uint GetHash(int buf)
+ {
+ uint num = XXHash.RotateLeft(this.seed + 374761393U + 4U + (uint)(buf * -1028477379), 17) * 668265263U;
+ uint num2 = (num ^ num >> 15) * 2246822519U;
+ uint num3 = (num2 ^ num2 >> 13) * 3266489917U;
+ return num3 ^ num3 >> 16;
+ }
+
+ private static uint CalcSubHash(uint value, byte[] buf, int index)
+ {
+ uint num = BitConverter.ToUInt32(buf, index);
+ value += num * 2246822519U;
+ value = XXHash.RotateLeft(value, 13);
+ value *= 2654435761U;
+ return value;
+ }
+
+ private static uint CalcSubHash(uint value, uint read_value)
+ {
+ value += read_value * 2246822519U;
+ value = XXHash.RotateLeft(value, 13);
+ value *= 2654435761U;
+ return value;
+ }
+
+ private static uint RotateLeft(uint value, int count)
+ {
+ return value << count | value >> 32 - count;
+ }
+}
diff --git a/Client/Assembly-CSharp/obj/Debug/Assembly-CSharp.csproj.CoreCompileInputs.cache b/Client/Assembly-CSharp/obj/Debug/Assembly-CSharp.csproj.CoreCompileInputs.cache
new file mode 100644
index 0000000..2d286a7
--- /dev/null
+++ b/Client/Assembly-CSharp/obj/Debug/Assembly-CSharp.csproj.CoreCompileInputs.cache
@@ -0,0 +1 @@
+b8fad2e40478c2524efc69c84e4392785708d392
diff --git a/Client/Assembly-CSharp/obj/Debug/Assembly-CSharp.csprojAssemblyReference.cache b/Client/Assembly-CSharp/obj/Debug/Assembly-CSharp.csprojAssemblyReference.cache
new file mode 100644
index 0000000..528e904
--- /dev/null
+++ b/Client/Assembly-CSharp/obj/Debug/Assembly-CSharp.csprojAssemblyReference.cache
Binary files differ
diff --git a/Impostor-dev/.config/dotnet-tools.json b/Impostor-dev/.config/dotnet-tools.json
new file mode 100644
index 0000000..727dfd7
--- /dev/null
+++ b/Impostor-dev/.config/dotnet-tools.json
@@ -0,0 +1,12 @@
+{
+ "version": 1,
+ "isRoot": true,
+ "tools": {
+ "cake.tool": {
+ "version": "0.38.5",
+ "commands": [
+ "dotnet-cake"
+ ]
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/.dockerignore b/Impostor-dev/.dockerignore
new file mode 100644
index 0000000..9163c62
--- /dev/null
+++ b/Impostor-dev/.dockerignore
@@ -0,0 +1,9 @@
+.dockerignore
+.env
+.gitignore
+.vs
+.vscode
+**/.git
+**/.idea
+**/bin
+**/obj \ No newline at end of file
diff --git a/Impostor-dev/.github/ISSUE_TEMPLATE/1--bug-reporting.md b/Impostor-dev/.github/ISSUE_TEMPLATE/1--bug-reporting.md
new file mode 100644
index 0000000..cd4dab5
--- /dev/null
+++ b/Impostor-dev/.github/ISSUE_TEMPLATE/1--bug-reporting.md
@@ -0,0 +1,35 @@
+---
+name: 1. Bug reporting
+about: Bugs within Impostor
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+# Bug Report
+
+## Base Information
+- Operating System
+- Impostor Version
+- Among Us Version
+
+## I confirm:
+- [ ] that I have searched for an existing bug report for this issue.
+
+
+## Symptoms
+
+<!--
+Write symptoms here.
+-->
+
+Enter Symptoms on this line.
+
+## Reproduction
+
+<!--
+ How do you reproduce the bug you have here.
+-->
+
+Enter reproductions steps here.
diff --git a/Impostor-dev/.github/ISSUE_TEMPLATE/2--feature-request.md b/Impostor-dev/.github/ISSUE_TEMPLATE/2--feature-request.md
new file mode 100644
index 0000000..8aa42e6
--- /dev/null
+++ b/Impostor-dev/.github/ISSUE_TEMPLATE/2--feature-request.md
@@ -0,0 +1,19 @@
+---
+name: 2. Feature request
+about: To ask and request new features with Impostor
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+# Feature Request
+
+## Feature Information:
+<!--
+ - One issue per post! Do not try and bring up multiple requests in a single post.
+ - What should it do?
+-->
+
+## I confirm:
+- [ ] that I have searched for an existing feature request matching the description.
diff --git a/Impostor-dev/.github/ISSUE_TEMPLATE/3--api-suggestion.md b/Impostor-dev/.github/ISSUE_TEMPLATE/3--api-suggestion.md
new file mode 100644
index 0000000..bfed62f
--- /dev/null
+++ b/Impostor-dev/.github/ISSUE_TEMPLATE/3--api-suggestion.md
@@ -0,0 +1,20 @@
+---
+name: 3. Api suggestion
+about: To make suggestions for the plugin api
+title: ''
+labels: api
+assignees: ''
+
+---
+
+# Api Suggestion
+
+## Suggestion
+<!--
+Describe your suggestion as detailed as possible.
+-->
+
+## Use case
+<!--
+Describe what you need it for.
+-->
diff --git a/Impostor-dev/.github/ISSUE_TEMPLATE/4--api-invalid-data.md b/Impostor-dev/.github/ISSUE_TEMPLATE/4--api-invalid-data.md
new file mode 100644
index 0000000..6ebe139
--- /dev/null
+++ b/Impostor-dev/.github/ISSUE_TEMPLATE/4--api-invalid-data.md
@@ -0,0 +1,25 @@
+---
+name: 4. Api invalid data
+about: To let us know about invalid data in the api
+title: ''
+labels: api
+assignees: ''
+
+---
+
+# Api missing data
+
+## Data
+<!--
+Describe about the missing data that you need.
+-->
+
+## Expectations
+<!--
+Describe what the api gave you and what you expected.
+-->
+
+## Reproduce
+<!--
+Describe how to reproduce the issue.
+-->
diff --git a/Impostor-dev/.github/ISSUE_TEMPLATE/5--api-unavailable-data.md b/Impostor-dev/.github/ISSUE_TEMPLATE/5--api-unavailable-data.md
new file mode 100644
index 0000000..698c9ab
--- /dev/null
+++ b/Impostor-dev/.github/ISSUE_TEMPLATE/5--api-unavailable-data.md
@@ -0,0 +1,20 @@
+---
+name: 5. Api unavailable data
+about: To let us know about unavailable data from the api that you would like to use
+title: ''
+labels: api
+assignees: ''
+
+---
+
+# Api missing data
+
+## Data
+<!--
+Describe about the unavailable data that you need.
+-->
+
+## Use-case
+<!--
+Describe what you need it for.
+-->
diff --git a/Impostor-dev/.github/ISSUE_TEMPLATE/6--api-other.md b/Impostor-dev/.github/ISSUE_TEMPLATE/6--api-other.md
new file mode 100644
index 0000000..9077852
--- /dev/null
+++ b/Impostor-dev/.github/ISSUE_TEMPLATE/6--api-other.md
@@ -0,0 +1,10 @@
+---
+name: 6. Api other
+about: For anything about the api that does not fit in the other issues
+title: ''
+labels: api
+assignees: ''
+
+---
+
+
diff --git a/Impostor-dev/.github/ISSUE_TEMPLATE/config.yml b/Impostor-dev/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000..0086358
--- /dev/null
+++ b/Impostor-dev/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1 @@
+blank_issues_enabled: true
diff --git a/Impostor-dev/.github/PULL_REQUEST_TEMPLATE.md b/Impostor-dev/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..a877a10
--- /dev/null
+++ b/Impostor-dev/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,16 @@
+### Description
+
+
+<!--
+
+If your pull request closes any issues, add them below with the `closes` keyword before them
+
+Example: closes #101
+
+See the following article for more information: https://docs.github.com/en/free-pro-team@latest/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword
+
+-->
+
+### Closes issues
+
+- closes #
diff --git a/Impostor-dev/.github/stale.yml b/Impostor-dev/.github/stale.yml
new file mode 100644
index 0000000..e556fa9
--- /dev/null
+++ b/Impostor-dev/.github/stale.yml
@@ -0,0 +1,17 @@
+# Number of days of inactivity before an issue becomes stale
+daysUntilStale: 60
+# Number of days of inactivity before a stale issue is closed
+daysUntilClose: 7
+# Issues with these labels will never be considered stale
+exemptLabels:
+ - pinned
+ - security
+# Label to use when marking an issue as stale
+staleLabel: stale
+# Comment to post when marking an issue as stale. Set to `false` to disable
+markComment: >
+ This issue has been automatically marked as stale because it has not had
+ recent activity. It will be closed if no further activity occurs. Thank you
+ for your contributions.
+# Comment to post when closing a stale issue. Set to `false` to disable
+closeComment: false
diff --git a/Impostor-dev/.github/workflows/docker.yml b/Impostor-dev/.github/workflows/docker.yml
new file mode 100644
index 0000000..b312075
--- /dev/null
+++ b/Impostor-dev/.github/workflows/docker.yml
@@ -0,0 +1,70 @@
+name: Docker
+
+on:
+ push:
+ branches: dev
+ paths:
+ - 'src/Impostor.Server/**'
+ - 'src/Impostor.Shared/**'
+ - '.gitmodules'
+ - '.github/workflows/docker.yml'
+ - 'Dockerfile'
+ tags:
+ - 'v*.*.*'
+ pull_request:
+ paths:
+ - 'src/Impostor.Server/**'
+ - 'src/Impostor.Shared/**'
+ - '.gitmodules'
+ - '.github/workflows/docker.yml'
+ - 'Dockerfile'
+
+jobs:
+ push_to_registry:
+ name: Push Docker image
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+ with:
+ submodules: true
+ - name: Prepare
+ id: prep
+ run: |
+ DOCKER_IMAGE=aeonlucid/impostor
+ VERSION=noop
+ if [[ $GITHUB_REF == refs/tags/* ]]; then
+ VERSION=${GITHUB_REF#refs/tags/}
+ elif [[ $GITHUB_REF == refs/heads/* ]]; then
+ VERSION=$(echo ${GITHUB_REF#refs/heads/} | sed -r 's#/+#-#g')
+ if [ "${{ github.event.repository.default_branch }}" = "dev" ]; then
+ VERSION=nightly
+ fi
+ elif [[ $GITHUB_REF == refs/pull/* ]]; then
+ VERSION=pr-${{ github.event.number }}
+ fi
+ TAGS="${DOCKER_IMAGE}:${VERSION}"
+ if [[ $VERSION =~ ^v[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
+ TAGS="$TAGS,${DOCKER_IMAGE}:latest"
+ fi
+ echo ::set-output name=version::${VERSION}
+ echo ::set-output name=tags::${TAGS}
+ echo ::set-output name=created::$(date -u +'%Y-%m-%dT%H:%M:%SZ')
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@v1
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v1
+ - name: Login to DockerHub
+ if: github.event_name != 'pull_request'
+ uses: docker/login-action@v1
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+ - name: Build and push
+ uses: docker/build-push-action@v2
+ with:
+ context: .
+ file: ./Dockerfile
+ platforms: linux/amd64,linux/arm/v7,linux/arm64
+ push: ${{ github.event_name != 'pull_request' }}
+ tags: ${{ steps.prep.outputs.tags }}
diff --git a/Impostor-dev/.gitignore b/Impostor-dev/.gitignore
new file mode 100644
index 0000000..1170bdb
--- /dev/null
+++ b/Impostor-dev/.gitignore
@@ -0,0 +1,7 @@
+/.vscode
+/build
+/.vs
+/src/Impostor.Plugins.Debugger/Properties/launchSettings.json
+/tools
+
+.local/
diff --git a/Impostor-dev/CONTRIBUTING.md b/Impostor-dev/CONTRIBUTING.md
new file mode 100644
index 0000000..16583a3
--- /dev/null
+++ b/Impostor-dev/CONTRIBUTING.md
@@ -0,0 +1,22 @@
+# Contribute guidelines
+
+We are always looking for people to help improve Impostor.
+
+## Code
+
+- If you are implementing or fixing an issue, please comment on the issue so work is not duplicated.
+- If you want to implementing a new feature, create an issue first describing the issue so we know about it. We are always open for discussion or questions on [Discord](https://discord.gg/Mk3w6Tb).
+- Don't commit unnecessary changes to the codebase or debugging code.
+- Write meaningful commits or squash them.
+- Please try to follow the code style of the rest of the codebase. An `.editorconfig` file has been provided to keep consistency.
+
+## Pull requests
+
+- Only make pull requests to the `dev` branch.
+- Only implement one feature per pull request to keep it easy to understand.
+- Expect comments or questions on your pull request from the project maintainers. We try to keep the code as consistent and maintainable as possible.
+- Each pull request should come from a new branch in your fork, it should have a meaningful name.
+- We try to respond to pull requests as fast as possible. If you think we might have missed it, let us know on [Discord](https://discord.gg/Mk3w6Tb).
+
+
+If you have any questions, let us know. \ No newline at end of file
diff --git a/Impostor-dev/Dockerfile b/Impostor-dev/Dockerfile
new file mode 100644
index 0000000..8f386da
--- /dev/null
+++ b/Impostor-dev/Dockerfile
@@ -0,0 +1,39 @@
+FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:5.0 AS build
+
+# See for all possible platforms
+# https://github.com/containerd/containerd/blob/master/platforms/platforms.go#L17
+ARG TARGETARCH
+
+WORKDIR /source
+
+# Copy csproj and restore.
+COPY src/Impostor.Server/Impostor.Server.csproj ./src/Impostor.Server/Impostor.Server.csproj
+COPY src/Impostor.Api/Impostor.Api.csproj ./src/Impostor.Api/Impostor.Api.csproj
+COPY src/Impostor.Hazel/Impostor.Hazel.csproj ./src/Impostor.Hazel/Impostor.Hazel.csproj
+
+RUN case "$TARGETARCH" in \
+ amd64) NETCORE_PLATFORM='linux-x64';; \
+ arm64) NETCORE_PLATFORM='linux-arm64';; \
+ arm) NETCORE_PLATFORM='linux-arm';; \
+ *) echo "unsupported architecture"; exit 1 ;; \
+ esac && \
+ dotnet restore -r "$NETCORE_PLATFORM" ./src/Impostor.Server/Impostor.Server.csproj && \
+ dotnet restore -r "$NETCORE_PLATFORM" ./src/Impostor.Api/Impostor.Api.csproj && \
+ dotnet restore -r "$NETCORE_PLATFORM" ./src/Impostor.Hazel/Impostor.Hazel.csproj
+
+# Copy everything else.
+COPY src/. ./src/
+RUN case "$TARGETARCH" in \
+ amd64) NETCORE_PLATFORM='linux-x64';; \
+ arm64) NETCORE_PLATFORM='linux-arm64';; \
+ arm) NETCORE_PLATFORM='linux-arm';; \
+ *) echo "unsupported architecture"; exit 1 ;; \
+ esac && \
+ dotnet publish -c release -o /app -r "$NETCORE_PLATFORM" --no-restore ./src/Impostor.Server/Impostor.Server.csproj
+
+# Final image.
+FROM --platform=$TARGETPLATFORM mcr.microsoft.com/dotnet/runtime:5.0
+WORKDIR /app
+COPY --from=build /app ./
+EXPOSE 22023/udp
+ENTRYPOINT ["./Impostor.Server"] \ No newline at end of file
diff --git a/Impostor-dev/LICENSE b/Impostor-dev/LICENSE
new file mode 100644
index 0000000..f288702
--- /dev/null
+++ b/Impostor-dev/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "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. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<https://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<https://www.gnu.org/licenses/why-not-lgpl.html>.
diff --git a/Impostor-dev/README.md b/Impostor-dev/README.md
new file mode 100644
index 0000000..1ce3f94
--- /dev/null
+++ b/Impostor-dev/README.md
@@ -0,0 +1,72 @@
+# Impostor
+
+[![Discord](https://img.shields.io/badge/Discord-chat-blue?style=flat-square)](https://discord.gg/Mk3w6Tb)
+[![AppVeyor](https://img.shields.io/appveyor/build/Impostor/Impostor/dev?style=flat-square)](https://ci.appveyor.com/project/Impostor/Impostor/branch/dev)
+
+Impostor is one of the first **Among Us** private servers, written in C#.
+
+We support Steam, Itch, Android and iOS. The latest version supported is `2020.9.22`, the `dev` build currently supports `2020.11.17`.
+
+| Impostor version | Among Us version | Experimental | Download |
+|-|-|-|-|
+| 1.1.0 | 2020.09.07 - 2020.09.22 | No | [![Download](https://img.shields.io/badge/Download-v1.1.0-blue?style=flat-square)](https://github.com/Impostor/Impostor/releases/tag/v1.1.0) |
+| 1.2.2 | 2020.09.22 - 2020.11.17 | Yes | [![Download](https://img.shields.io/badge/Download-v1.2.2-blue?style=flat-square)](https://ci.appveyor.com/project/Impostor/Impostor/branch/dev/artifacts) |
+
+## Features
+
+- All Among Us features are implemented. It is a full replacement for the official server.
+- Plugin support.
+- Server-sided anticheat.
+
+## Installation
+
+### Client
+
+If you just want to play on a server hosted by someone else, you need to follow these instructions.
+
+#### Windows
+
+1. Find the [latest release](https://github.com/AeonLucid/Impostor/releases/latest).
+2. Download `Impostor-Client-win-x64.zip`.
+3. Extract the zip.
+4. Run `Impostor.Client.exe`.
+5. Follow the instructions inside the application.
+
+![Client](docs/images/client.jpg)
+
+If you do not wish to execute any programs. Follow the instructions in [this website](https://impostor.github.io/Impostor)
+
+#### Android
+
+##### Android 10 and below.
+1. Go to [this website](https://impostor.github.io/Impostor) **(ON YOUR MOBILE DEVICE)**
+2. Follow the instructions listed there.
+
+##### Android 11.
+1. Connect your phone to a computer. Go to [this website](https://impostor.github.io/Impostor) on the computer and follow the steps 1 and 2 to generate a `regionInfo.dat` file.
+2. Instead of following the next steps, open the phone's internal storage on your computer and navigate to `/sdcard/Android/data/com.innersloth.spacemafia/files`.
+3. Copy the generated `regionInfo.dat` file into the `files` folder you just navigated to.
+
+#### iOS
+
+iOS devices need to be jailbroken in order to connect to Impostor servers.
+
+### Server
+
+See the [docs](docs/Running-the-server.md) for instructions on how to set it up.
+
+## Troubleshooting
+
+See [TROUBLESHOOTING](docs/TROUBLESHOOTING.md) to solve issues the Impostor client or the server.
+
+## Contributing
+
+See [CONTRIBUTING](CONTRIBUTING.md).
+
+## License
+
+This software is distributed under the **GNU GPLv3** License.
+
+## Credits
+
+- [willardf/Hazel-Networking](https://github.com/willardf/Hazel-Networking)
diff --git a/Impostor-dev/appveyor.yml b/Impostor-dev/appveyor.yml
new file mode 100644
index 0000000..e0c82f6
--- /dev/null
+++ b/Impostor-dev/appveyor.yml
@@ -0,0 +1,51 @@
+version: '{build}'
+
+environment:
+ IMPOSTOR_VERSION: '1.2.2'
+ DOTNET_CLI_TELEMETRY_OPTOUT: 1
+
+branches:
+ except:
+ - gh-pages
+
+pull_requests:
+ do_not_increment_build_number: true
+
+assembly_info:
+ patch: false
+
+dotnet_csproj:
+ patch: false
+
+image: Visual Studio 2019 Preview
+
+install:
+ - git submodule update --init --recursive
+ - ps: dotnet tool restore
+
+build_script:
+ - ps: dotnet cake build.cake --bootstrap
+ - ps: dotnet cake build.cake --pack
+
+test: off
+
+artifacts:
+ - path: ./build/*.zip
+ - path: ./build/*.tar.gz
+ - path: ./build/*.nupkg
+ - path: ./build/*.snupkg
+
+# deploy:
+# - provider: NuGet
+# artifact: /.*(\.|\.s)nupkg/
+# on:
+# branch: dev
+# api_key:
+# secure: 4OHXl+m1KVi60hB1j54MpdP8tVv0UNfkdF4rVP+2AhAjN4a2MVT+Bl90gJZ96xHF
+
+only_commits:
+ files:
+ - appveyor.yml
+ - build.cake
+ - src/**/*
+ - .gitmodules
diff --git a/Impostor-dev/build.cake b/Impostor-dev/build.cake
new file mode 100644
index 0000000..7b28698
--- /dev/null
+++ b/Impostor-dev/build.cake
@@ -0,0 +1,178 @@
+#addin "nuget:?package=SharpZipLib&Version=1.3.0"
+#addin "nuget:?package=Cake.Compression&Version=0.2.4"
+#addin "nuget:?package=Cake.FileHelpers&Version=3.3.0"
+
+
+var buildId = EnvironmentVariable("APPVEYOR_BUILD_VERSION") ?? "0";
+var buildVersion = EnvironmentVariable("IMPOSTOR_VERSION") ?? "1.0.0";
+var buildBranch = EnvironmentVariable("APPVEYOR_REPO_BRANCH") ?? "dev";
+var buildDir = MakeAbsolute(Directory("./build"));
+
+var prNumber = EnvironmentVariable("APPVEYOR_PULL_REQUEST_NUMBER");
+var target = Argument("target", "Deploy");
+var configuration = Argument("configuration", "Release");
+
+// On any branch that is not master, we need to tag the version as prerelease.
+if (buildBranch != "master") {
+ buildVersion = buildVersion + "-ci." + buildId;
+}
+
+//////////////////////////////////////////////////////////////////////
+// UTILS
+//////////////////////////////////////////////////////////////////////
+
+// Remove unnecessary files for packaging.
+private void ImpostorPublish(string name, string project, string runtime, bool isServer = false) {
+ var projBuildDir = buildDir.Combine(name + "_" + runtime);
+ var projBuildName = name + "_" + buildVersion + "_" + runtime;
+
+ DotNetCorePublish(project, new DotNetCorePublishSettings {
+ Configuration = configuration,
+ NoRestore = true,
+ Framework = "net5.0",
+ Runtime = runtime,
+ SelfContained = false,
+ PublishSingleFile = true,
+ PublishTrimmed = false,
+ OutputDirectory = projBuildDir
+ });
+
+ if (isServer) {
+ CreateDirectory(projBuildDir.Combine("plugins"));
+ CreateDirectory(projBuildDir.Combine("libraries"));
+
+ if (runtime == "win-x64") {
+ FileWriteText(projBuildDir.CombineWithFilePath("run.bat"), "@echo off\r\nImpostor.Server.exe\r\npause");
+ }
+ }
+
+ if (runtime == "win-x64") {
+ Zip(projBuildDir, buildDir.CombineWithFilePath(projBuildName + ".zip"));
+ } else {
+ GZipCompress(projBuildDir, buildDir.CombineWithFilePath(projBuildName + ".tar.gz"));
+ }
+}
+
+private void ImpostorPublishNF(string name, string project) {
+ var runtime = "win-x64";
+ var projBuildDir = buildDir.Combine(name + "_" + runtime);
+ var projBuildZip = buildDir.CombineWithFilePath(name + "_" + buildVersion + "_" + runtime + ".zip");
+
+ DotNetCorePublish(project, new DotNetCorePublishSettings {
+ Configuration = configuration,
+ NoRestore = true,
+ Framework = "net472",
+ OutputDirectory = projBuildDir
+ });
+
+ Zip(projBuildDir, projBuildZip);
+}
+
+//////////////////////////////////////////////////////////////////////
+// TASKS
+//////////////////////////////////////////////////////////////////////
+
+Task("Clean")
+ .Does(() => {
+ if (DirectoryExists(buildDir)) {
+ DeleteDirectory(buildDir, new DeleteDirectorySettings {
+ Recursive = true
+ });
+ }
+ });
+
+Task("Restore")
+ .Does(() => {
+ DotNetCoreRestore("./src/Impostor.sln");
+ });
+
+Task("Patch")
+ .WithCriteria(BuildSystem.AppVeyor.IsRunningOnAppVeyor)
+ .Does(() => {
+ ReplaceRegexInFiles("./src/**/*.csproj", @"<Version>.*?<\/Version>", "<Version>" + buildVersion + "</Version>");
+ ReplaceRegexInFiles("./src/**/*.props", @"<Version>.*?<\/Version>", "<Version>" + buildVersion + "</Version>");
+ });
+
+Task("Replay")
+ .Does(() => {
+ // D:\Projects\GitHub\Impostor\Impostor\src\Impostor.Tools.ServerReplay\sessions
+ DotNetCoreRun(
+ "./src/Impostor.Tools.ServerReplay/Impostor.Tools.ServerReplay.csproj",
+ "./src/Impostor.Tools.ServerReplay/sessions", new DotNetCoreRunSettings {
+ Configuration = configuration,
+ NoRestore = true,
+ Framework = "net5.0"
+ });
+ });
+
+Task("Build")
+ .IsDependentOn("Clean")
+ .IsDependentOn("Patch")
+ .IsDependentOn("Restore")
+ .IsDependentOn("Replay")
+ .Does(() => {
+ // Tests.
+ DotNetCoreBuild("./src/Impostor.Tests/Impostor.Tests.csproj", new DotNetCoreBuildSettings {
+ Configuration = configuration,
+ });
+
+ // Only build artifacts if;
+ // - buildBranch is master/dev
+ // - it is not a pull request
+ if ((buildBranch == "master" || buildBranch == "dev") && string.IsNullOrEmpty(prNumber)) {
+ // Client.
+ ImpostorPublishNF("Impostor-Patcher", "./src/Impostor.Patcher/Impostor.Patcher.WinForms/Impostor.Patcher.WinForms.csproj");
+
+ ImpostorPublish("Impostor-Patcher-Cli", "./src/Impostor.Patcher/Impostor.Patcher.Cli/Impostor.Patcher.Cli.csproj", "win-x64");
+ ImpostorPublish("Impostor-Patcher-Cli", "./src/Impostor.Patcher/Impostor.Patcher.Cli/Impostor.Patcher.Cli.csproj", "osx-x64");
+ ImpostorPublish("Impostor-Patcher-Cli", "./src/Impostor.Patcher/Impostor.Patcher.Cli/Impostor.Patcher.Cli.csproj", "linux-x64");
+
+ // Server.
+ ImpostorPublish("Impostor-Server", "./src/Impostor.Server/Impostor.Server.csproj", "win-x64", true);
+ ImpostorPublish("Impostor-Server", "./src/Impostor.Server/Impostor.Server.csproj", "osx-x64", true);
+ ImpostorPublish("Impostor-Server", "./src/Impostor.Server/Impostor.Server.csproj", "linux-x64", true);
+ ImpostorPublish("Impostor-Server", "./src/Impostor.Server/Impostor.Server.csproj", "linux-arm", true);
+ ImpostorPublish("Impostor-Server", "./src/Impostor.Server/Impostor.Server.csproj", "linux-arm64", true);
+
+ // API.
+ DotNetCorePack("./src/Impostor.Api/Impostor.Api.csproj", new DotNetCorePackSettings {
+ Configuration = configuration,
+ OutputDirectory = buildDir,
+ IncludeSource = true,
+ IncludeSymbols = true
+ });
+ } else {
+ DotNetCoreBuild("./src/Impostor.Patcher/Impostor.Patcher.WinForms/Impostor.Patcher.WinForms.csproj", new DotNetCoreBuildSettings {
+ Configuration = configuration,
+ NoRestore = true,
+ Framework = "net472"
+ });
+
+ DotNetCoreBuild("./src/Impostor.Server/Impostor.Server.csproj", new DotNetCoreBuildSettings {
+ Configuration = configuration,
+ NoRestore = true,
+ Framework = "net5.0"
+ });
+ }
+ });
+
+Task("Test")
+ .IsDependentOn("Build")
+ .Does(() => {
+ DotNetCoreTest("./src/Impostor.Tests/Impostor.Tests.csproj", new DotNetCoreTestSettings {
+ Configuration = configuration,
+ NoBuild = true
+ });
+ });
+
+Task("Deploy")
+ .IsDependentOn("Test")
+ .Does(() => {
+ Information("Finished.");
+ });
+
+//////////////////////////////////////////////////////////////////////
+// EXECUTION
+//////////////////////////////////////////////////////////////////////
+
+RunTarget(target); \ No newline at end of file
diff --git a/Impostor-dev/docs/Building-from-source.md b/Impostor-dev/docs/Building-from-source.md
new file mode 100644
index 0000000..cf3632f
--- /dev/null
+++ b/Impostor-dev/docs/Building-from-source.md
@@ -0,0 +1,45 @@
+# Building from source
+
+The solution contains two main projects, the Impostor client and server. The client is built using [.NET Framework 4.7.2](https://dotnet.microsoft.com/download/dotnet-framework/net472) and the server with [.NET 5](https://dotnet.microsoft.com/download/dotnet/5.0).
+
+Currently .NET 5 is not yet officially released, so in order to build using Visual Studio, you should have Visual Studio 2019 **Preview** installed.
+This documentation will go over building both the [Server](#building-the-server) and the [Client](#building-the-client) and their requirements.
+
+## Cloning Impostor
+
+You need to clone Impostor with all submodules.
+
+```bash
+git clone --recursive https://github.com/AeonLucid/Impostor.git
+```
+
+If you already have cloned Impostor but have errors related to Hazel, run the following.
+
+```bash
+git submodule update --init
+```
+
+## Building the server
+
+### Dependencies
+- [.NET 5 SDK](https://dotnet.microsoft.com/download/dotnet/5.0)
+- [Visual Studio Preview](https://visualstudio.microsoft.com/vs/preview/) (Optional, only if you want the full IDE experience)
+
+### Build using the CLI
+
+```bash
+cd src/Impostor.Server/
+dotnet build
+```
+To setup the server, please look at [Running the server](Running-the-server.md).
+
+## Building the client
+
+### Dependencies
+* [.NET Framework 4.7.2 Developer Pack](https://dotnet.microsoft.com/download/dotnet-framework/thank-you/net472-developer-pack-offline-installer)
+
+### Build using the CLI
+```bash
+cd src/Impostor.Client/Impostor.Client.WinForms
+dotnet build
+``` \ No newline at end of file
diff --git a/Impostor-dev/docs/FAQ.md b/Impostor-dev/docs/FAQ.md
new file mode 100644
index 0000000..14df95f
--- /dev/null
+++ b/Impostor-dev/docs/FAQ.md
@@ -0,0 +1,11 @@
+# Frequently Answered Questions
+
+## What is this?
+The Impostor project is a reverse engineered and open sourced server for the game Among Us. The game itself is developed by [InnerSloth](http://www.innersloth.com/) while this project is maintained by the community. This project was built out of frustration for the lack of server availability in certain regions and the inability for the core developer, [AeonLucid](https://github.com/AeonLucid), to join a public game. As of this time, it has not been officially endorsed by the studio.
+
+## Can this be used with the mobile version of the game?
+Yes, Impostor can be used with both the Android and iOS\* versions of the game.
+###### \* In order to play on an Impostor server with iOS, you _must_ have a jailbroken device.
+
+## How can I get started?
+See [Setting up your Server](Running-the-server.md) for more information on running the server and [Client Setup](https://impostor.github.io/Impostor/) for helping your friends join in!
diff --git a/Impostor-dev/docs/README.md b/Impostor-dev/docs/README.md
new file mode 100644
index 0000000..e32ff8c
--- /dev/null
+++ b/Impostor-dev/docs/README.md
@@ -0,0 +1,7 @@
+# Impostor Documentation
+
+1. [Running the server](Running-the-server.md)
+2. [Server configuration](Server-configuration.md)
+3. [Building from source](Building-from-source.md)
+4. [Writing a plugin](Writing-a-plugin.md)
+5. [Frequently answered questions](FAQ.md) \ No newline at end of file
diff --git a/Impostor-dev/docs/Running-the-server.md b/Impostor-dev/docs/Running-the-server.md
new file mode 100644
index 0000000..db0a618
--- /dev/null
+++ b/Impostor-dev/docs/Running-the-server.md
@@ -0,0 +1,101 @@
+# Running the server
+
+There are currently two modes to run the Impostor server in. The first way is the simplest one and is the one you should probably use. The other way will distribute players across other servers and is a more advanced configuration.
+
+## Single server
+
+### Without docker
+1. Install the **.NET 5.0 runtime**.
+ - [Windows x64](https://dotnet.microsoft.com/download/dotnet/thank-you/runtime-5.0.0-windows-x64-installer)
+ - [Linux x64](https://docs.microsoft.com/en-us/dotnet/core/install/linux)
+ - [macOS x64](https://dotnet.microsoft.com/download/dotnet/thank-you/runtime-5.0.0-macos-x64-installer)
+2. Find the [latest dev release](https://ci.appveyor.com/project/Impostor/Impostor/branch/dev/artifacts).
+3. Download either the Windows or the Linux version.
+4. Extract the zip.
+5. Modify `config.json` to your liking. Documentation can be found [here](Server-configuration.md) *(this step is mandatory if you want to expose this server to other devices)*
+6. Run `Impostor.Server.exe` (Windows) / `Impostor.Server` (Linux)
+
+### Using docker
+
+[![Docker Image](https://img.shields.io/docker/v/aeonlucid/impostor?sort=semver)](https://hub.docker.com/repository/docker/aeonlucid/impostor)
+[![Docker Image](https://img.shields.io/docker/v/aeonlucid/impostor/nightly)](https://hub.docker.com/repository/docker/aeonlucid/impostor)
+
+```
+docker run -p 22023:22023/udp aeonlucid/impostor:nightly
+```
+
+### Using docker-compose
+```
+version: '3.4'
+
+services:
+ impostor:
+ image: aeonlucid/impostor:nightly
+ container_name: impostor
+ ports:
+ - 22023:22023/udp
+ volumes:
+ - /path/to/local/config.json:/app/config.json # For easy editing of the config
+ - /path/to/local/plugins:/app/plugins # Only needed if using plugins
+ - /path/to/local/libraries:/app/libraries # Only needed if using external libraries
+```
+
+## Multiple servers
+
+Follow the steps from the single server on two or more servers.
+
+### Master server
+
+The master server will accept client connections and redirect them to the other servers listed in the configuration. It will not host any games itself.
+
+Example configuration:
+
+```json
+{
+ "Server": {
+ "PublicIp": "127.0.0.1",
+ "PublicPort": 22023,
+ "ListenIp": "0.0.0.0",
+ "ListenPort": 22023
+ },
+ "ServerRedirector": {
+ "Enabled": true,
+ "Master": true,
+ "Locator": {
+ "Redis": "",
+ "UdpMasterEndpoint": "127.0.0.1:22024"
+ },
+ "Nodes": [
+ {
+ "Ip": "127.0.0.1",
+ "Port": 22025
+ }
+ ]
+ }
+}
+```
+
+### Node servers
+
+The node server should have `ServerRedirector` enabled too, but `Master` **must be disabled**. Nodes do not need to be aware of each other.
+
+Example configuration:
+
+```json
+{
+ "Server": {
+ "PublicIp": "127.0.0.1",
+ "PublicPort": 22025,
+ "ListenIp": "0.0.0.0",
+ "ListenPort": 22025
+ },
+ "ServerRedirector": {
+ "Enabled": true,
+ "Master": false,
+ "Locator": {
+ "Redis": "",
+ "UdpMasterEndpoint": "127.0.0.1:22024"
+ }
+ }
+}
+```
diff --git a/Impostor-dev/docs/Server-configuration.md b/Impostor-dev/docs/Server-configuration.md
new file mode 100644
index 0000000..5841ca7
--- /dev/null
+++ b/Impostor-dev/docs/Server-configuration.md
@@ -0,0 +1,77 @@
+# Server configuration
+
+Some information about all the possible configurations. Click [here](https://github.com/AeonLucid/Impostor/blob/master/src/Impostor.Server/config.full.json) to see all the possible config options.
+
+## Options
+
+### Required Server Configuration
+
+| Key | Default | Description |
+|-|-|-|
+| **PublicIp** | `127.0.0.1` | This needs to the public IPv4 address of the server which you give to others to connect. You can find your IPv4 address [on this website](http://whatismyip.host/). Unless you are only planning to use Impostor privately, on your local network, you should change this to your public ip. It is also possible to use hostnames instead of IPv4 addresses, which will be resolved to IPv4 addresses. |
+| **PublicPort** | `22023` | The public port of the server which you give to others to connect. (**This is the external port you configure on your router when port forwarding.**) Usually `22023`. |
+| **ListenIp** | `0.0.0.0` | The network interface to listen on. If you do not know what to put here, use `0.0.0.0`. Since 1.2.2 it is also possible to use hostnames instead of IPv4 addresses, these must resolve to a valid IPv4 address. |
+| **ListenPort** | `22023` | The listen port of the server, usually `22023`. |
+
+### AntiCheat
+
+| Key | Default | Value |
+|-|-|-|
+| **BanIpFromGame** | `true` | When a player is caught hacking, they will be kicked from the server. If this value is set to `true`, the player will be banned instead and will not be able to rejoin that specific game. **(Setting this to false does not disable the anti-cheat!)** |
+
+### ServerRedirector
+In a multi-node setup these need to be specified.
+| Key | Default | Value |
+|-|-|-|
+| **Enabled** | `false` | Whether the server runs in multi-node setup. If this is `false`, all other options in this section do not have any effect. |
+| **Master** | `false` | Whether the current server is a master. A master is responsible for redirecting clients to nodes |
+| **Locator** | | Fill in either `Redis` or `UdpMasterEndpoint` to choose which method to use for locating other nodes. This must be the same across all servers. |
+| **>Redis** | | Format `127.0.0.1.6379`, you can also use a password like so: `127.0.0.1.6379,password=value`. |
+| **>UdpMasterEndpoint** | | On the master, this value acts as a listen ip and port. On a node, this should be the public ip and port of the master. Format `127.0.0.1:32320`. |
+| **Nodes** | | An array containing public ips and ports of nodes. Only needs to be set on the master. See above for an example. |
+
+## Config providers
+
+### File
+
+The simplest option to configure is by using the `config.json` file next to the server executable. For all possible options see the [config-full.json](https://github.com/Impostor/Impostor/blob/dev/src/Impostor.Server/config-full.json) file.
+
+### Command line arguments
+
+TODO
+
+```
+Server:PublicIp=127.0.0.1
+Server:PublicPort=22023
+Server:ListenIp=0.0.0.0
+Server:ListenPort=22023
+AntiCheat:BanIpFromGame=true
+ServerRedirector:Enabled=false
+ServerRedirector:Master=true
+ServerRedirector:Locator:Redis=127.0.0.1.6379
+ServerRedirector:Locator:UdpMasterEndpoint=127.0.0.1:32320
+ServerRedirector:Nodes:0:Ip=127.0.0.1
+ServerRedirector:Nodes:0:Port=22024
+ServerRedirector:Nodes:1:Ip=127.0.0.1
+ServerRedirector:Nodes:1:Port=22025
+```
+
+### Environment variables
+
+TODO
+
+```
+IMPOSTOR_Server__PublicIp=127.0.0.1
+IMPOSTOR_Server__PublicPort=22023
+IMPOSTOR_Server__ListenIp=0.0.0.0
+IMPOSTOR_Server__ListenPort=22023
+IMPOSTOR_AntiCheat__BanIpFromGame=true
+IMPOSTOR_ServerRedirector__Enabled=false
+IMPOSTOR_ServerRedirector__Master=true
+IMPOSTOR_ServerRedirector__Locator__Redis=127.0.0.1.6379
+IMPOSTOR_ServerRedirector__Locator__UdpMasterEndpoint=127.0.0.1:32320
+IMPOSTOR_ServerRedirector__Nodes__0__Ip=127.0.0.1
+IMPOSTOR_ServerRedirector__Nodes__0__Port=22024
+IMPOSTOR_ServerRedirector__Nodes__1__Ip=127.0.0.1
+IMPOSTOR_ServerRedirector__Nodes__1__Port=22025
+```
diff --git a/Impostor-dev/docs/TROUBLESHOOTING.md b/Impostor-dev/docs/TROUBLESHOOTING.md
new file mode 100644
index 0000000..8750159
--- /dev/null
+++ b/Impostor-dev/docs/TROUBLESHOOTING.md
@@ -0,0 +1,28 @@
+# Troubleshooting
+If you're reading this, something went wrong.
+Don't worry though, as this is the most thorough guide to help you!
+
+## `./Impostor.Server: line 1: ELF: not found` (plus other errors)
+No idea where you got that system. But we clearly do **NOT** support it.
+
+## `cannot execute binary file: Exec format error`
+Please check that you have downloaded the right version of Impostor, as we mantain two CPU architectures (x64 and ARM).
+Unless you are running Impostor on a SBC (Single-Board Computer), like the Raspberry Pi, you most likely want to use the x64 version.
+
+## `./Impostor.Server: Permission denied`
+This is an error related to Linux file permissions.
+Some files do not hold their executable bit (the permission that allows them to run) during a download.
+You can solve this by doing: `chmod +x Impostor.Server`
+
+## `You are using an older version of the game`
+You are using an older version of Impostor. The game does not really check who is outdated and blames it on the user.
+Make sure you got the latest working version of Impostor (probably in AppVeyor, not Github).
+
+## `You disconnected from the server. Reliable Packet 1 ...`
+Please double-check that you have followed the [Server Configuration](Server-configuration.md) correctly.
+**NOTE: Your public ip does not start with `127` nor `192`**
+Also check if the port Impostor (ListenPort) is listening on is correctly port-forwarded for UDP (or TCP/UDP).
+
+## `Could not load file or assembly...`
+Please check that you only have **working** plugins in the `plugins` folder.
+This error can be caused by non-plugin files or plugins that are not working correctly.
diff --git a/Impostor-dev/docs/Writing-a-plugin.md b/Impostor-dev/docs/Writing-a-plugin.md
new file mode 100644
index 0000000..a7ad21b
--- /dev/null
+++ b/Impostor-dev/docs/Writing-a-plugin.md
@@ -0,0 +1,343 @@
+# Writing a plugin
+
+Impostor has support for plugins. This document will help you to setup a development environment for writing a plugin.
+
+- [1. Install .NET Core SDK](#1-install-net-core-sdk)
+- [2. Create a C# project](#2-create-a-c-project)
+- [3. Add the Impostor.Api library](#3-add-the-impostorapi-library)
+ - [Quick](#quick)
+ - [Visual Studio](#visual-studio)
+- [4. The plugin class](#4-the-plugin-class)
+- [5. Adding an event listener](#5-adding-an-event-listener)
+- [6. Registering the event listener](#6-registering-the-event-listener)
+- [7. Build and run your plugin](#7-build-and-run-your-plugin)
+- [8. Extra](#8-extra)
+ - [Event listeners](#event-listeners)
+ - [Dependency injection](#dependency-injection)
+ - [Server configuration](#server-configuration)
+ - [Using other libraries](#using-other-libraries)
+ - [Impostor versions](#impostor-versions)
+- [9. Missing/invalid data or want more functions?](#9-missinginvalid-data-or-want-more-functions)
+
+## 1. Install .NET Core SDK
+
+Download and install the latest .NET Core SDK.
+
+https://dotnet.microsoft.com/download
+
+## 2. Create a C# project
+
+The first step is creating a new C# project, it must be a **Class Library (.NET Standard)**. The target framework can be any of those compatible with .NET 5, which includes:
+
+- .NET Standard 2.0
+- .NET Standard 2.1
+- .NET Core 3.1
+- .NET 5
+
+For more information about compatibility, see https://docs.microsoft.com/en-us/dotnet/standard/net-standard.
+
+> At the moment of writing this document, I recommend you to use **.NET Standard 2.1** until .NET 5 is released officially. This should give you enough functionality. If not, upgrade to .NET Core 3.1.
+
+When the project has been created, you should have `Class.cs` and `Project.csproj` files. Your `Project.csproj` should look something like this.
+
+```xml
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <TargetFramework>netstandard2.1</TargetFramework>
+ </PropertyGroup>
+</Project>
+```
+
+## 3. Add the Impostor.Api library
+
+You only have to follow the instructions of one below.
+
+### Quick
+
+Install the `Impostor.Api` NuGet package.
+Make sure to get a prerelease if you are writing a plugin for a dev release of the server.
+
+### Visual Studio
+
+1. Right click your project.
+2. Click `Manage NuGet Packages`.
+3. Click `Browse`.
+4. Next to the search bar, enable `Include prerelease`.
+5. Search for `Impostor.Api`.
+6. Click the `Impostor.Api` result and press install on the right side.
+
+### Dotnet CLI
+
+> Make sure to grab the latest (pre-)release version from NuGet [here](https://www.nuget.org/packages/Impostor.Api).
+
+1. Open your project folder in command prompt / bash.
+2. Run `dotnet add package Impostor.Api -v "1.2.0-ci.58"`.
+
+## 4. The plugin class
+
+Now the `Impostor.Api` is installed, you need to create a class for your plugin. A plugin **must** contain exactly one. See the code below for an example.
+
+```csharp
+using System.Threading.Tasks;
+using Impostor.Api.Events.Managers;
+using Impostor.Api.Plugins;
+using Microsoft.Extensions.Logging;
+
+namespace Impostor.Plugins.Example
+{
+ /// <summary>
+ /// The metadata information of your plugin, this is required.
+ /// </summary>
+ [ImpostorPlugin(
+ package: "gg.impostor.example",
+ name: "Example",
+ author: "AeonLucid",
+ version: "1.0.0")]
+ public class ExamplePlugin : PluginBase // This is also required ": PluginBase".
+ {
+ /// <summary>
+ /// A logger that works seamlessly with the server.
+ /// </summary>
+ private readonly ILogger<ExamplePlugin> _logger;
+
+ /// <summary>
+ /// The constructor of the plugin. There are a few parameters you can add here and they
+ /// will be injected automatically by the server, two examples are used here.
+ ///
+ /// They are not necessary but very recommended.
+ /// </summary>
+ /// <param name="logger">
+ /// A logger to write messages in the console.
+ /// </param>
+ /// <param name="eventManager">
+ /// An event manager to register event listeners.
+ /// Useful if you want your plugin to interact with the game.
+ /// </param>
+ public ExamplePlugin(ILogger<ExamplePlugin> logger, IEventManager eventManager)
+ {
+ _logger = logger;
+ }
+
+ /// <summary>
+ /// This is called when your plugin is enabled by the server.
+ /// </summary>
+ /// <returns></returns>
+ public override ValueTask EnableAsync()
+ {
+ _logger.LogInformation("Example is being enabled.");
+ return default;
+ }
+
+ /// <summary>
+ /// This is called when your plugin is disabled by the server.
+ /// Most likely because it is shutting down, this is the place to clean up any managed resources.
+ /// </summary>
+ /// <returns></returns>
+ public override ValueTask DisableAsync()
+ {
+ _logger.LogInformation("Example is being disabled.");
+ return default;
+ }
+ }
+}
+```
+
+## 5. Adding an event listener
+
+Currently you should have a plugin that loads and does nothing. In order to get some actual functionality, you need to add an event listener.
+
+Create a new class called `GameEventListener`. Example code:
+
+```csharp
+using Impostor.Api.Events;
+using Impostor.Api.Events.Player;
+using Microsoft.Extensions.Logging;
+
+namespace Impostor.Plugins.Example.Handlers
+{
+ /// <summary>
+ /// A class that listens for two events.
+ /// It may be more but this is just an example.
+ ///
+ /// Make sure your class implements <see cref="IEventListener"/>.
+ /// </summary>
+ public class GameEventListener : IEventListener
+ {
+ private readonly ILogger<ExamplePlugin> _logger;
+
+ public GameEventListener(ILogger<ExamplePlugin> logger)
+ {
+ _logger = logger;
+ }
+
+ /// <summary>
+ /// An example event listener.
+ /// </summary>
+ /// <param name="e">
+ /// The event you want to listen for.
+ /// </param>
+ [EventListener]
+ public void OnGameStarted(IGameStartedEvent e)
+ {
+ _logger.LogInformation($"Game is starting.");
+
+ // This prints out for all players if they are impostor or crewmate.
+ foreach (var player in e.Game.Players)
+ {
+ var info = player.Character.PlayerInfo;
+ var isImpostor = info.IsImpostor;
+ if (isImpostor)
+ {
+ _logger.LogInformation($"- {info.PlayerName} is an impostor.");
+ }
+ else
+ {
+ _logger.LogInformation($"- {info.PlayerName} is a crewmate.");
+ }
+ }
+ }
+
+ [EventListener]
+ public void OnGameEnded(IGameEndedEvent e)
+ {
+ _logger.LogInformation($"Game has ended.");
+ }
+
+ [EventListener]
+ public void OnPlayerChat(IPlayerChatEvent e)
+ {
+ _logger.LogInformation($"{e.PlayerControl.PlayerInfo.PlayerName} said {e.Message}");
+ }
+ }
+}
+```
+
+## 6. Registering the event listener
+
+The last step to get your plugin working is to register the event listener, so the server knows about it. Go back to your plugin class and modify it as below.
+
+```csharp
+using System;
+using System.Threading.Tasks;
+using Impostor.Api.Events.Managers;
+using Impostor.Api.Plugins;
+using Impostor.Plugins.Example.Handlers;
+using Microsoft.Extensions.Logging;
+
+namespace Impostor.Plugins.Example
+{
+ [ImpostorPlugin(
+ package: "gg.impostor.example",
+ name: "Example",
+ author: "AeonLucid",
+ version: "1.0.0")]
+ public class ExamplePlugin : PluginBase
+ {
+ private readonly ILogger<ExamplePlugin> _logger;
+ // Add the line below!
+ private readonly IEventManager _eventManager;
+ // Add the line below!
+ private IDisposable _unregister;
+
+ public ExamplePlugin(ILogger<ExamplePlugin> logger, IEventManager eventManager)
+ {
+ _logger = logger;
+ // Add the line below!
+ _eventManager = eventManager;
+ }
+
+ public override ValueTask EnableAsync()
+ {
+ _logger.LogInformation("Example is being enabled.");
+ // Add the line below!
+ _unregister = _eventManager.RegisterListener(new GameEventListener(_logger));
+ return default;
+ }
+
+ public override ValueTask DisableAsync()
+ {
+ _logger.LogInformation("Example is being disabled.");
+ // Add the line below!
+ _unregister.Dispose();
+ return default;
+ }
+ }
+}
+
+```
+
+## 7. Build and run your plugin
+
+Now your plugin is ready to be tested.
+
+1. Right click your project and press `Build`.
+2. Right click your project and press `Open Folder in File Explorer`.
+3. Go to `bin/Debug/netstandard2.1/`.
+4. In this directory, you should find your plugin named `Project.dll`.
+5. Copy the `Project.dll` to the `plugins` directory in your Impostor server directory.
+6. (Re)start your Impostor server.
+7. Open Among Us, create a game and send a chat message. In the console you should see your plugin being loaded and the messages from the example.
+
+## 8. Extra
+
+Some extra information that might be useful for those developing plugins.
+
+### Event listeners
+
+- You can have multiple event listener on the same event.
+- An event listener can be given a priority `[EventListener(EventPriority.Normal)]` and is called in order.
+- It is not recommended to block for a long time inside `EventListener` because the events are called from inside the packet handlers. Blocking too long causes the client to time out. You should create a new `Task` for operations that will take a lot of time.
+
+### Dependency injection
+
+- The main plugin class is constructed by the `IServiceProvider` of the server and can inject everything the server uses. A few examples are:
+ - `ILogger<T>`
+ - `IEventManager`
+ - `IClientManager`
+ - `IOptions<ServerConfig>`
+ - `IOptions<ServerRedirectorConfig>`
+- You can add your own classes and `EventListener` implementation to the `IServiceProvider` by creating a new class and implementing `IPluginStartup`. Make sure to register them as a singleton `services.AddSingleton<IEventListener, GameEventListener>();`.
+
+### Server configuration
+
+Constantly copying the plugin dll to your server directory can be pretty annoying. Luckily we have a solution for that. In your Impostor server open the `config.json` and add the `PluginLoader` from the example below, replace the path with the build destination of your plugin.
+
+```json
+{
+ "Server": {
+ "PublicIp": "127.0.0.1",
+ "PublicPort": 22023,
+ "ListenIp": "0.0.0.0",
+ "ListenPort": 22023
+ },
+ "PluginLoader": {
+ "Paths": [
+ "D:\\Projects\\Impostor\\src\\Impostor.Plugins.Example\\bin\\Debug\\netstandard2.1"
+ ],
+ "Libraries": []
+ }
+}
+```
+
+### Using other libraries
+
+Sometimes you need to use libraries that the original Impostor server does not provide. The dll files of these libraries must be placed in the `libraries` folder next to the server executable. You could also provide them by modifying the `PluginLoader.Libraries` option in the `config.json`, similarly to the `PluginLoader.Paths` option.
+
+### Impostor versions
+
+It is important to use the correct versions when working with `Impostor.Api` prereleases and the `Impostor` dev builds to reduce the chances of mismatching assemblies.
+
+**Example**
+
+The prerelease `Impostor.Api` package `1.2.0-ci.54` belongs to build `54` on AppVeyor, which can be found here https://ci.appveyor.com/project/Impostor/Impostor/build/54. Notice the `54` on the end of the url.
+
+## 9. Missing/invalid data or want more functions?
+
+The `Impostor.Api` is currently in beta. There are a lot of things still missing and we would like to hear from you what you need to develop a plugin.
+
+Create an issue:
+
+- [Suggest a function](https://github.com/Impostor/Impostor/issues/new?template=3--api-suggestion.md)
+- [Data is invalid](https://github.com/Impostor/Impostor/issues/new?template=4--api-invalid.md)
+- [Data is unavailable](https://github.com/Impostor/Impostor/issues/new?template=5--api-missing.md)
+- [Other](https://github.com/Impostor/Impostor/issues/new?template=6--api-other.md) \ No newline at end of file
diff --git a/Impostor-dev/docs/images/client.jpg b/Impostor-dev/docs/images/client.jpg
new file mode 100644
index 0000000..2d82f47
--- /dev/null
+++ b/Impostor-dev/docs/images/client.jpg
Binary files differ
diff --git a/Impostor-dev/docs/images/logo_458.png b/Impostor-dev/docs/images/logo_458.png
new file mode 100644
index 0000000..173f648
--- /dev/null
+++ b/Impostor-dev/docs/images/logo_458.png
Binary files differ
diff --git a/Impostor-dev/docs/images/logo_64.png b/Impostor-dev/docs/images/logo_64.png
new file mode 100644
index 0000000..74c0721
--- /dev/null
+++ b/Impostor-dev/docs/images/logo_64.png
Binary files differ
diff --git a/Impostor-dev/src/.editorconfig b/Impostor-dev/src/.editorconfig
new file mode 100644
index 0000000..ef7041d
--- /dev/null
+++ b/Impostor-dev/src/.editorconfig
@@ -0,0 +1,230 @@
+# top-most EditorConfig file
+root = true
+
+# Don't use tabs for indentation.
+[*]
+charset = utf-8
+end_of_line = crlf
+insert_final_newline = false
+indent_style = space
+
+# Code files
+[*.{cs,csx,vb,vbx}]
+indent_size = 4
+insert_final_newline = true
+
+# XML project files
+[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]
+indent_size = 2
+
+# XML config files
+[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}]
+indent_size = 2
+
+# JSON files
+[*.json]
+indent_size = 2
+
+# Dotnet code style settings:
+[*.{cs,vb}]
+
+# Sort using and Import directives with System.* appearing first
+dotnet_sort_system_directives_first = true
+dotnet_separate_import_directive_groups = false
+# Avoid "this." and "Me." if not necessary
+dotnet_style_qualification_for_field = false:refactoring
+dotnet_style_qualification_for_property = false:refactoring
+dotnet_style_qualification_for_method = false:refactoring
+dotnet_style_qualification_for_event = false:refactoring
+
+# Use language keywords instead of framework type names for type references
+dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
+dotnet_style_predefined_type_for_member_access = true:suggestion
+
+# Suggest more modern language features when available
+dotnet_style_object_initializer = true:suggestion
+dotnet_style_collection_initializer = true:suggestion
+dotnet_style_coalesce_expression = true:suggestion
+dotnet_style_null_propagation = true:suggestion
+dotnet_style_explicit_tuple_names = true:suggestion
+
+# Non-private static fields are PascalCase
+dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.symbols = non_private_static_fields
+dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.style = non_private_static_field_style
+
+dotnet_naming_symbols.non_private_static_fields.applicable_kinds = field
+dotnet_naming_symbols.non_private_static_fields.applicable_accessibilities = public, protected, internal, protected_internal, private_protected
+dotnet_naming_symbols.non_private_static_fields.required_modifiers = static
+
+dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case
+
+# Non-private readonly fields are PascalCase
+dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.symbols = non_private_readonly_fields
+dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.style = non_private_readonly_field_style
+
+dotnet_naming_symbols.non_private_readonly_fields.applicable_kinds = field
+dotnet_naming_symbols.non_private_readonly_fields.applicable_accessibilities = public, protected, internal, protected_internal, private_protected
+dotnet_naming_symbols.non_private_readonly_fields.required_modifiers = readonly
+
+dotnet_naming_style.non_private_readonly_field_style.capitalization = pascal_case
+
+# Constants are PascalCase
+dotnet_naming_rule.constants_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.constants_should_be_pascal_case.symbols = constants
+dotnet_naming_rule.constants_should_be_pascal_case.style = constant_style
+
+dotnet_naming_symbols.constants.applicable_kinds = field, local
+dotnet_naming_symbols.constants.required_modifiers = const
+
+dotnet_naming_style.constant_style.capitalization = pascal_case
+
+# Static readonly fields are PascalCase
+dotnet_naming_rule.static_fields_should_be_camel_case.severity = suggestion
+dotnet_naming_rule.static_fields_should_be_camel_case.symbols = static_fields
+dotnet_naming_rule.static_fields_should_be_camel_case.style = static_field_style
+
+dotnet_naming_symbols.static_fields.applicable_kinds = field
+dotnet_naming_symbols.static_fields.required_modifiers = static, readonly
+
+dotnet_naming_style.static_field_style.capitalization = pascal_case
+
+# Instance fields are camelCase and start with _
+dotnet_naming_rule.instance_fields_should_be_camel_case.severity = suggestion
+dotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields
+dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_style
+
+dotnet_naming_symbols.instance_fields.applicable_kinds = field
+
+dotnet_naming_style.instance_field_style.capitalization = camel_case
+dotnet_naming_style.instance_field_style.required_prefix = _
+
+# Locals and parameters are camelCase
+dotnet_naming_rule.locals_should_be_camel_case.severity = suggestion
+dotnet_naming_rule.locals_should_be_camel_case.symbols = locals_and_parameters
+dotnet_naming_rule.locals_should_be_camel_case.style = camel_case_style
+
+dotnet_naming_symbols.locals_and_parameters.applicable_kinds = parameter, local
+
+dotnet_naming_style.camel_case_style.capitalization = camel_case
+
+# Local functions are PascalCase
+dotnet_naming_rule.local_functions_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.local_functions_should_be_pascal_case.symbols = local_functions
+dotnet_naming_rule.local_functions_should_be_pascal_case.style = local_function_style
+
+dotnet_naming_symbols.local_functions.applicable_kinds = local_function
+
+dotnet_naming_style.local_function_style.capitalization = pascal_case
+
+# By default, name items with PascalCase
+dotnet_naming_rule.members_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.members_should_be_pascal_case.symbols = all_members
+dotnet_naming_rule.members_should_be_pascal_case.style = pascal_case_style
+
+dotnet_naming_symbols.all_members.applicable_kinds = *
+
+dotnet_naming_style.pascal_case_style.capitalization = pascal_case
+
+# Async methods should have "Async" suffix
+dotnet_naming_rule.async_methods_end_in_async.symbols = any_async_methods
+dotnet_naming_rule.async_methods_end_in_async.style = end_in_async
+dotnet_naming_rule.async_methods_end_in_async.severity = warning
+
+dotnet_naming_symbols.any_async_methods.applicable_kinds = method
+dotnet_naming_symbols.any_async_methods.applicable_accessibilities = *
+dotnet_naming_symbols.any_async_methods.required_modifiers = async
+
+dotnet_naming_style.end_in_async.required_prefix =
+dotnet_naming_style.end_in_async.required_suffix = Async
+dotnet_naming_style.end_in_async.capitalization = pascal_case
+dotnet_naming_style.end_in_async.word_separator =
+
+# error RS2008: Enable analyzer release tracking for the analyzer project containing rule '{0}'
+dotnet_diagnostic.RS2008.severity = none
+
+# IDE0035: Remove unreachable code
+dotnet_diagnostic.IDE0035.severity = warning
+
+# IDE0036: Order modifiers
+dotnet_diagnostic.IDE0036.severity = warning
+
+# IDE0043: Format string contains invalid placeholder
+dotnet_diagnostic.IDE0043.severity = warning
+
+# IDE0044: Make field readonly
+dotnet_diagnostic.IDE0044.severity = warning
+
+# CSharp code style settings:
+[*.cs]
+# Newline settings
+csharp_new_line_before_open_brace = all
+csharp_new_line_before_else = true
+csharp_new_line_before_catch = true
+csharp_new_line_before_finally = true
+csharp_new_line_before_members_in_object_initializers = true
+csharp_new_line_before_members_in_anonymous_types = true
+csharp_new_line_between_query_expression_clauses = true
+
+# Indentation preferences
+csharp_indent_block_contents = true
+csharp_indent_braces = false
+csharp_indent_case_contents = true
+csharp_indent_case_contents_when_block = true
+csharp_indent_switch_labels = true
+csharp_indent_labels = flush_left
+
+# Prefer "var" everywhere
+csharp_style_var_for_built_in_types = true:suggestion
+csharp_style_var_when_type_is_apparent = true:suggestion
+csharp_style_var_elsewhere = true:suggestion
+
+# Prefer method-like constructs to have a block body
+csharp_style_expression_bodied_methods = false:none
+csharp_style_expression_bodied_constructors = false:none
+csharp_style_expression_bodied_operators = false:none
+
+# Prefer property-like constructs to have an expression-body
+csharp_style_expression_bodied_properties = true:none
+csharp_style_expression_bodied_indexers = true:none
+csharp_style_expression_bodied_accessors = true:none
+
+# Suggest more modern language features when available
+csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
+csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
+csharp_style_inlined_variable_declaration = true:suggestion
+csharp_style_throw_expression = true:suggestion
+csharp_style_conditional_delegate_call = true:suggestion
+
+# Space preferences
+csharp_space_after_cast = false
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_after_comma = true
+csharp_space_after_dot = false
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_after_semicolon_in_for_statement = true
+csharp_space_around_binary_operators = before_and_after
+csharp_space_around_declaration_statements = do_not_ignore
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_before_comma = false
+csharp_space_before_dot = false
+csharp_space_before_open_square_brackets = false
+csharp_space_before_semicolon_in_for_statement = false
+csharp_space_between_empty_square_brackets = false
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_declaration_name_and_open_parenthesis = false
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+csharp_space_between_parentheses = false
+csharp_space_between_square_brackets = false
+
+# Blocks are allowed
+csharp_prefer_braces = true:silent
+csharp_preserve_single_line_blocks = true
+csharp_preserve_single_line_statements = true
+
+# warning RS0037: PublicAPI.txt is missing '#nullable enable'
+dotnet_diagnostic.RS0037.severity = none
diff --git a/Impostor-dev/src/.gitattributes b/Impostor-dev/src/.gitattributes
new file mode 100644
index 0000000..1ff0c42
--- /dev/null
+++ b/Impostor-dev/src/.gitattributes
@@ -0,0 +1,63 @@
+###############################################################################
+# Set default behavior to automatically normalize line endings.
+###############################################################################
+* text=auto
+
+###############################################################################
+# Set default behavior for command prompt diff.
+#
+# This is need for earlier builds of msysgit that does not have it on by
+# default for csharp files.
+# Note: This is only used by command line
+###############################################################################
+#*.cs diff=csharp
+
+###############################################################################
+# Set the merge driver for project and solution files
+#
+# Merging from the command prompt will add diff markers to the files if there
+# are conflicts (Merging from VS is not affected by the settings below, in VS
+# the diff markers are never inserted). Diff markers may cause the following
+# file extensions to fail to load in VS. An alternative would be to treat
+# these files as binary and thus will always conflict and require user
+# intervention with every merge. To do so, just uncomment the entries below
+###############################################################################
+#*.sln merge=binary
+#*.csproj merge=binary
+#*.vbproj merge=binary
+#*.vcxproj merge=binary
+#*.vcproj merge=binary
+#*.dbproj merge=binary
+#*.fsproj merge=binary
+#*.lsproj merge=binary
+#*.wixproj merge=binary
+#*.modelproj merge=binary
+#*.sqlproj merge=binary
+#*.wwaproj merge=binary
+
+###############################################################################
+# behavior for image files
+#
+# image files are treated as binary by default.
+###############################################################################
+#*.jpg binary
+#*.png binary
+#*.gif binary
+
+###############################################################################
+# diff behavior for common document formats
+#
+# Convert binary document formats to text before diffing them. This feature
+# is only available from the command line. Turn it on by uncommenting the
+# entries below.
+###############################################################################
+#*.doc diff=astextplain
+#*.DOC diff=astextplain
+#*.docx diff=astextplain
+#*.DOCX diff=astextplain
+#*.dot diff=astextplain
+#*.DOT diff=astextplain
+#*.pdf diff=astextplain
+#*.PDF diff=astextplain
+#*.rtf diff=astextplain
+#*.RTF diff=astextplain
diff --git a/Impostor-dev/src/.gitignore b/Impostor-dev/src/.gitignore
new file mode 100644
index 0000000..e0838ba
--- /dev/null
+++ b/Impostor-dev/src/.gitignore
@@ -0,0 +1,263 @@
+config.*.json
+
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# DNX
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# TODO: Comment the next line if you want to checkin your web deploy settings
+# but database connection strings (with potential passwords) will be unencrypted
+#*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/packages/repositories.config
+# NuGet v3's project.json files produces more ignoreable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+node_modules/
+orleans.codegen.cs
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+*.mdf
+*.ldf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+# CodeRush
+.cr/
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Events/Attributes/EventListenerAttribute.cs b/Impostor-dev/src/Impostor.Api/Events/Attributes/EventListenerAttribute.cs
new file mode 100644
index 0000000..b31d2d1
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Events/Attributes/EventListenerAttribute.cs
@@ -0,0 +1,34 @@
+using System;
+
+namespace Impostor.Api.Events
+{
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
+ public class EventListenerAttribute : Attribute
+ {
+ public EventListenerAttribute(EventPriority priority = EventPriority.Normal)
+ {
+ Priority = priority;
+ }
+
+ public EventListenerAttribute(Type @event, EventPriority priority = EventPriority.Normal)
+ {
+ Priority = priority;
+ Event = @event;
+ }
+
+ /// <summary>
+ /// The priority of the event listener.
+ /// </summary>
+ public EventPriority Priority { get; set; }
+
+ /// <summary>
+ /// The events that the listener is listening to.
+ /// </summary>
+ public Type? Event { get; set; }
+
+ /// <summary>
+ /// If set to true, the listener will be called regardless of the <see cref="IEventCancelable.IsCancelled"/>.
+ /// </summary>
+ public bool IgnoreCancelled { get; set; }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Events/EventPriority.cs b/Impostor-dev/src/Impostor.Api/Events/EventPriority.cs
new file mode 100644
index 0000000..7acdbeb
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Events/EventPriority.cs
@@ -0,0 +1,12 @@
+namespace Impostor.Api.Events
+{
+ public enum EventPriority
+ {
+ Lowest = 0,
+ Low = 1,
+ Normal = 2,
+ High = 3,
+ Highest = 4,
+ Monitor = 5,
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Api/Events/Game/IGameAlterEvent.cs b/Impostor-dev/src/Impostor.Api/Events/Game/IGameAlterEvent.cs
new file mode 100644
index 0000000..18956ea
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Events/Game/IGameAlterEvent.cs
@@ -0,0 +1,7 @@
+namespace Impostor.Api.Events
+{
+ public interface IGameAlterEvent : IGameEvent
+ {
+ bool IsPublic { get; }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Api/Events/Game/IGameCreatedEvent.cs b/Impostor-dev/src/Impostor.Api/Events/Game/IGameCreatedEvent.cs
new file mode 100644
index 0000000..ef45f1d
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Events/Game/IGameCreatedEvent.cs
@@ -0,0 +1,11 @@
+using Impostor.Api.Games;
+
+namespace Impostor.Api.Events
+{
+ /// <summary>
+ /// Called whenever a new <see cref="IGame"/> is created.
+ /// </summary>
+ public interface IGameCreatedEvent : IGameEvent
+ {
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Api/Events/Game/IGameDestroyedEvent.cs b/Impostor-dev/src/Impostor.Api/Events/Game/IGameDestroyedEvent.cs
new file mode 100644
index 0000000..7ff7b46
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Events/Game/IGameDestroyedEvent.cs
@@ -0,0 +1,11 @@
+using Impostor.Api.Games;
+
+namespace Impostor.Api.Events
+{
+ /// <summary>
+ /// Called whenever a new <see cref="IGame"/> is destroyed.
+ /// </summary>
+ public interface IGameDestroyedEvent : IGameEvent
+ {
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Api/Events/Game/IGameEndedEvent.cs b/Impostor-dev/src/Impostor.Api/Events/Game/IGameEndedEvent.cs
new file mode 100644
index 0000000..d8ae159
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Events/Game/IGameEndedEvent.cs
@@ -0,0 +1,9 @@
+using Impostor.Api.Innersloth;
+
+namespace Impostor.Api.Events
+{
+ public interface IGameEndedEvent : IGameEvent
+ {
+ public GameOverReason GameOverReason { get; }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Api/Events/Game/IGameEvent.cs b/Impostor-dev/src/Impostor.Api/Events/Game/IGameEvent.cs
new file mode 100644
index 0000000..c9ae579
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Events/Game/IGameEvent.cs
@@ -0,0 +1,12 @@
+using Impostor.Api.Games;
+
+namespace Impostor.Api.Events
+{
+ public interface IGameEvent : IEvent
+ {
+ /// <summary>
+ /// Gets the <see cref="IGame"/> this event belongs to.
+ /// </summary>
+ IGame Game { get; }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Api/Events/Game/IGamePlayerJoinedEvent.cs b/Impostor-dev/src/Impostor.Api/Events/Game/IGamePlayerJoinedEvent.cs
new file mode 100644
index 0000000..921568e
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Events/Game/IGamePlayerJoinedEvent.cs
@@ -0,0 +1,6 @@
+namespace Impostor.Api.Events
+{
+ public interface IGamePlayerJoinedEvent : IGameEvent
+ {
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Api/Events/Game/IGamePlayerLeftEvent.cs b/Impostor-dev/src/Impostor.Api/Events/Game/IGamePlayerLeftEvent.cs
new file mode 100644
index 0000000..21d8b7c
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Events/Game/IGamePlayerLeftEvent.cs
@@ -0,0 +1,6 @@
+namespace Impostor.Api.Events
+{
+ public interface IGamePlayerLeftEvent : IGameEvent
+ {
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Api/Events/Game/IGameStartedEvent.cs b/Impostor-dev/src/Impostor.Api/Events/Game/IGameStartedEvent.cs
new file mode 100644
index 0000000..b6e5111
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Events/Game/IGameStartedEvent.cs
@@ -0,0 +1,9 @@
+namespace Impostor.Api.Events
+{
+ /// <summary>
+ /// The game is started here and players have been initialized.
+ /// </summary>
+ public interface IGameStartedEvent : IGameEvent
+ {
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Api/Events/Game/IGameStartingEvent.cs b/Impostor-dev/src/Impostor.Api/Events/Game/IGameStartingEvent.cs
new file mode 100644
index 0000000..5998bf2
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Events/Game/IGameStartingEvent.cs
@@ -0,0 +1,11 @@
+namespace Impostor.Api.Events
+{
+ /// <summary>
+ /// Called when the game is going to start.
+ /// When this is called, not all players are initialized properly yet.
+ /// If you want to get correct player states, use <see cref="IGameStartedEvent"/>.
+ /// </summary>
+ public interface IGameStartingEvent : IGameEvent
+ {
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Api/Events/Game/Meeting/IMeetingEndedEvent.cs b/Impostor-dev/src/Impostor.Api/Events/Game/Meeting/IMeetingEndedEvent.cs
new file mode 100644
index 0000000..a217580
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Events/Game/Meeting/IMeetingEndedEvent.cs
@@ -0,0 +1,6 @@
+namespace Impostor.Api.Events.Meeting
+{
+ public interface IMeetingEndedEvent : IMeetingEvent
+ {
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Api/Events/Game/Meeting/IMeetingEvent.cs b/Impostor-dev/src/Impostor.Api/Events/Game/Meeting/IMeetingEvent.cs
new file mode 100644
index 0000000..4461318
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Events/Game/Meeting/IMeetingEvent.cs
@@ -0,0 +1,9 @@
+using Impostor.Api.Net.Inner.Objects;
+
+namespace Impostor.Api.Events.Meeting
+{
+ public interface IMeetingEvent : IGameEvent
+ {
+ IInnerMeetingHud MeetingHud { get; }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Api/Events/Game/Meeting/IMeetingStartedEvent.cs b/Impostor-dev/src/Impostor.Api/Events/Game/Meeting/IMeetingStartedEvent.cs
new file mode 100644
index 0000000..a237fff
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Events/Game/Meeting/IMeetingStartedEvent.cs
@@ -0,0 +1,6 @@
+namespace Impostor.Api.Events.Meeting
+{
+ public interface IMeetingStartedEvent : IMeetingEvent
+ {
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Api/Events/Game/Player/IPlayerChatEvent.cs b/Impostor-dev/src/Impostor.Api/Events/Game/Player/IPlayerChatEvent.cs
new file mode 100644
index 0000000..52efe96
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Events/Game/Player/IPlayerChatEvent.cs
@@ -0,0 +1,10 @@
+namespace Impostor.Api.Events.Player
+{
+ public interface IPlayerChatEvent : IPlayerEvent
+ {
+ /// <summary>
+ /// Gets the message sent by the player.
+ /// </summary>
+ string Message { get; }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Api/Events/Game/Player/IPlayerCompletedTaskEvent.cs b/Impostor-dev/src/Impostor.Api/Events/Game/Player/IPlayerCompletedTaskEvent.cs
new file mode 100644
index 0000000..78ccd2d
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Events/Game/Player/IPlayerCompletedTaskEvent.cs
@@ -0,0 +1,10 @@
+using Impostor.Api.Innersloth;
+using Impostor.Api.Net.Inner.Objects;
+
+namespace Impostor.Api.Events.Player
+{
+ public interface IPlayerCompletedTaskEvent : IPlayerEvent
+ {
+ ITaskInfo Task { get; }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Api/Events/Game/Player/IPlayerDestroyedEvent.cs b/Impostor-dev/src/Impostor.Api/Events/Game/Player/IPlayerDestroyedEvent.cs
new file mode 100644
index 0000000..ac80d64
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Events/Game/Player/IPlayerDestroyedEvent.cs
@@ -0,0 +1,6 @@
+namespace Impostor.Api.Events.Player
+{
+ public interface IPlayerDestroyedEvent : IPlayerEvent
+ {
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Api/Events/Game/Player/IPlayerEvent.cs b/Impostor-dev/src/Impostor.Api/Events/Game/Player/IPlayerEvent.cs
new file mode 100644
index 0000000..247fe64
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Events/Game/Player/IPlayerEvent.cs
@@ -0,0 +1,19 @@
+using Impostor.Api.Net;
+using Impostor.Api.Net.Inner.Objects;
+
+namespace Impostor.Api.Events.Player
+{
+ public interface IPlayerEvent : IGameEvent
+ {
+ /// <summary>
+ /// Gets the <see cref="IClientPlayer"/> that triggered this <see cref="IPlayerEvent"/>.
+ /// </summary>
+ IClientPlayer ClientPlayer { get; }
+
+ /// <summary>
+ /// Gets the networked <see cref="IInnerPlayerControl"/> that triggered this <see cref="IPlayerEvent"/>.
+ /// This <see cref="IInnerPlayerControl"/> belongs to the <see cref="IClientPlayer"/>.
+ /// </summary>
+ IInnerPlayerControl PlayerControl { get; }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Events/Game/Player/IPlayerExileEvent.cs b/Impostor-dev/src/Impostor.Api/Events/Game/Player/IPlayerExileEvent.cs
new file mode 100644
index 0000000..65ade4e
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Events/Game/Player/IPlayerExileEvent.cs
@@ -0,0 +1,9 @@
+namespace Impostor.Api.Events.Player
+{
+ /// <summary>
+ /// Called whenever a player gets exiled (voted out).
+ /// </summary>
+ public interface IPlayerExileEvent : IPlayerEvent
+ {
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Api/Events/Game/Player/IPlayerMurderEvent.cs b/Impostor-dev/src/Impostor.Api/Events/Game/Player/IPlayerMurderEvent.cs
new file mode 100644
index 0000000..c47c00b
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Events/Game/Player/IPlayerMurderEvent.cs
@@ -0,0 +1,12 @@
+using Impostor.Api.Net.Inner.Objects;
+
+namespace Impostor.Api.Events.Player
+{
+ public interface IPlayerMurderEvent : IPlayerEvent
+ {
+ /// <summary>
+ /// Gets the player who got murdered.
+ /// </summary>
+ IInnerPlayerControl Victim { get; }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Api/Events/Game/Player/IPlayerSetStartCounterEvent.cs b/Impostor-dev/src/Impostor.Api/Events/Game/Player/IPlayerSetStartCounterEvent.cs
new file mode 100644
index 0000000..c03d782
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Events/Game/Player/IPlayerSetStartCounterEvent.cs
@@ -0,0 +1,10 @@
+namespace Impostor.Api.Events.Player
+{
+ public interface IPlayerSetStartCounterEvent : IPlayerEvent
+ {
+ /// <summary>
+ /// Gets the current time of the start counter.
+ /// </summary>
+ byte SecondsLeft { get; }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Api/Events/Game/Player/IPlayerSpawnedEvent.cs b/Impostor-dev/src/Impostor.Api/Events/Game/Player/IPlayerSpawnedEvent.cs
new file mode 100644
index 0000000..a3be654
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Events/Game/Player/IPlayerSpawnedEvent.cs
@@ -0,0 +1,6 @@
+namespace Impostor.Api.Events.Player
+{
+ public interface IPlayerSpawnedEvent : IPlayerEvent
+ {
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Api/Events/Game/Player/IPlayerStartMeetingEvent.cs b/Impostor-dev/src/Impostor.Api/Events/Game/Player/IPlayerStartMeetingEvent.cs
new file mode 100644
index 0000000..1a28115
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Events/Game/Player/IPlayerStartMeetingEvent.cs
@@ -0,0 +1,12 @@
+using Impostor.Api.Net.Inner.Objects;
+
+namespace Impostor.Api.Events.Player
+{
+ public interface IPlayerStartMeetingEvent : IPlayerEvent
+ {
+ /// <summary>
+ /// Gets the player who's body got reported. Is null when the meeting started by Emergency call button
+ /// </summary>
+ IInnerPlayerControl? Body { get; }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Api/Events/Game/Player/IPlayerVentEvent.cs b/Impostor-dev/src/Impostor.Api/Events/Game/Player/IPlayerVentEvent.cs
new file mode 100644
index 0000000..81f178b
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Events/Game/Player/IPlayerVentEvent.cs
@@ -0,0 +1,17 @@
+using Impostor.Api.Innersloth;
+
+namespace Impostor.Api.Events.Player
+{
+ public interface IPlayerVentEvent : IPlayerEvent
+ {
+ /// <summary>
+ /// Gets get the id of the used vent.
+ /// </summary>
+ public VentLocation VentId { get; }
+
+ /// <summary>
+ /// Gets a value indicating whether the vent was entered or exited.
+ /// </summary>
+ public bool VentEnter { get; }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Api/Events/IEvent.cs b/Impostor-dev/src/Impostor.Api/Events/IEvent.cs
new file mode 100644
index 0000000..796898e
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Events/IEvent.cs
@@ -0,0 +1,6 @@
+namespace Impostor.Api.Events
+{
+ public interface IEvent
+ {
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Events/IEventCancelable.cs b/Impostor-dev/src/Impostor.Api/Events/IEventCancelable.cs
new file mode 100644
index 0000000..319f02a
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Events/IEventCancelable.cs
@@ -0,0 +1,10 @@
+namespace Impostor.Api.Events
+{
+ public interface IEventCancelable : IEvent
+ {
+ /// <summary>
+ /// True if the event was cancelled.
+ /// </summary>
+ bool IsCancelled { get; set; }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Events/IEventListener.cs b/Impostor-dev/src/Impostor.Api/Events/IEventListener.cs
new file mode 100644
index 0000000..76392fc
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Events/IEventListener.cs
@@ -0,0 +1,6 @@
+namespace Impostor.Api.Events
+{
+ public interface IEventListener
+ {
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Events/IManualEventListener.cs b/Impostor-dev/src/Impostor.Api/Events/IManualEventListener.cs
new file mode 100644
index 0000000..b5c140e
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Events/IManualEventListener.cs
@@ -0,0 +1,13 @@
+using System.Threading.Tasks;
+
+namespace Impostor.Api.Events
+{
+ public interface IManualEventListener : IEventListener
+ {
+ public bool CanExecute<T>();
+
+ public ValueTask Execute(IEvent @event);
+
+ EventPriority Priority { get; set; }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Events/Managers/IEventManager.cs b/Impostor-dev/src/Impostor.Api/Events/Managers/IEventManager.cs
new file mode 100644
index 0000000..07a7f7c
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Events/Managers/IEventManager.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Threading.Tasks;
+
+namespace Impostor.Api.Events.Managers
+{
+ public interface IEventManager
+ {
+ /// <summary>
+ /// Register a temporary event listener.
+ /// </summary>
+ /// <param name="listener">Event listener.</param>
+ /// <param name="invoker">Middleware between the events, which can be used to swap to the correct thread dispatcher.</param>
+ /// <returns>Disposable that unregisters the callback from the event manager.</returns>
+ /// <typeparam name="TListener">Type of the event listener.</typeparam>
+ IDisposable RegisterListener<TListener>(TListener listener, Func<Func<Task>, Task>? invoker = null)
+ where TListener : IEventListener;
+
+ /// <summary>
+ /// Returns true if an event with the type <see cref="TEvent"/> is registered.
+ /// </summary>
+ /// <returns>True if the <see cref="TEvent"/> is registered.</returns>
+ /// <typeparam name="TEvent">Type of the event.</typeparam>
+ bool IsRegistered<TEvent>()
+ where TEvent : IEvent;
+
+ /// <summary>
+ /// Call all the event listeners for the type <see cref="TEvent"/>.
+ /// </summary>
+ /// <param name="event">The event argument.</param>
+ /// <typeparam name="TEvent">Type of the event.</typeparam>
+ /// <returns>A <see cref="ValueTask"/> representing the asynchronous operation.</returns>
+ ValueTask CallAsync<TEvent>(TEvent @event)
+ where TEvent : IEvent;
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Exceptions/ImpostorCheatException.cs b/Impostor-dev/src/Impostor.Api/Exceptions/ImpostorCheatException.cs
new file mode 100644
index 0000000..8eb72f8
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Exceptions/ImpostorCheatException.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Runtime.Serialization;
+
+namespace Impostor.Api
+{
+ public class ImpostorCheatException : ImpostorException
+ {
+ public ImpostorCheatException()
+ {
+ }
+
+ protected ImpostorCheatException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+
+ public ImpostorCheatException(string? message) : base(message)
+ {
+ }
+
+ public ImpostorCheatException(string? message, Exception? innerException) : base(message, innerException)
+ {
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Exceptions/ImpostorConfigException.cs b/Impostor-dev/src/Impostor.Api/Exceptions/ImpostorConfigException.cs
new file mode 100644
index 0000000..1e59a9b
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Exceptions/ImpostorConfigException.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Runtime.Serialization;
+
+namespace Impostor.Api
+{
+ public class ImpostorConfigException : ImpostorException
+ {
+ public ImpostorConfigException()
+ {
+ }
+
+ protected ImpostorConfigException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+
+ public ImpostorConfigException(string? message) : base(message)
+ {
+ }
+
+ public ImpostorConfigException(string? message, Exception? innerException) : base(message, innerException)
+ {
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Api/Exceptions/ImpostorException.cs b/Impostor-dev/src/Impostor.Api/Exceptions/ImpostorException.cs
new file mode 100644
index 0000000..188c50e
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Exceptions/ImpostorException.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Runtime.Serialization;
+
+namespace Impostor.Api
+{
+ public class ImpostorException : Exception
+ {
+ public ImpostorException()
+ {
+ }
+
+ protected ImpostorException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+
+ public ImpostorException(string? message) : base(message)
+ {
+ }
+
+ public ImpostorException(string? message, Exception? innerException) : base(message, innerException)
+ {
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Exceptions/ImpostorProtocolException.cs b/Impostor-dev/src/Impostor.Api/Exceptions/ImpostorProtocolException.cs
new file mode 100644
index 0000000..864602d
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Exceptions/ImpostorProtocolException.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Runtime.Serialization;
+
+namespace Impostor.Api
+{
+ public class ImpostorProtocolException : ImpostorException
+ {
+ public ImpostorProtocolException()
+ {
+ }
+
+ protected ImpostorProtocolException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+
+ public ImpostorProtocolException(string? message) : base(message)
+ {
+ }
+
+ public ImpostorProtocolException(string? message, Exception? innerException) : base(message, innerException)
+ {
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Api/Extensions/SpanReaderExtensions.cs b/Impostor-dev/src/Impostor.Api/Extensions/SpanReaderExtensions.cs
new file mode 100644
index 0000000..c103ee7
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Extensions/SpanReaderExtensions.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Buffers.Binary;
+using System.Runtime.CompilerServices;
+
+namespace Impostor.Api
+{
+ /// <summary>
+ /// Priovides a StreamReader-like api throught extensions
+ /// </summary>
+ public static class SpanReaderExtensions
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static byte ReadByte(this ref ReadOnlySpan<byte> input)
+ {
+ var original = Advance<byte>(ref input);
+ return original[0];
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int ReadInt32(this ref ReadOnlySpan<byte> input)
+ {
+ var original = Advance<int>(ref input);
+ return BinaryPrimitives.ReadInt32LittleEndian(original);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static uint ReadUInt32(this ref ReadOnlySpan<byte> input)
+ {
+ var original = Advance<uint>(ref input);
+ return BinaryPrimitives.ReadUInt32LittleEndian(original);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float ReadSingle(this ref ReadOnlySpan<byte> input)
+ {
+ var original = Advance<float>(ref input);
+
+ // BitConverter.Int32BitsToSingle
+ // Doesn't exist in net 2.0 for some reason
+ return Int32BitsToSingle(BinaryPrimitives.ReadInt32LittleEndian(original));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool ReadBoolean(this ref ReadOnlySpan<byte> input)
+ {
+ return input.ReadByte() != 0;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static unsafe float Int32BitsToSingle(int value)
+ {
+ return *((float*)&value);
+ }
+
+ /// <summary>
+ /// Advances the position of <see cref="input"/> by the size of <see cref="T"/>.
+ /// </summary>
+ /// <typeparam name="T">Type that will be read.</typeparam>
+ /// <param name="input">input "stream"/span.</param>
+ /// <returns>The original input</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static unsafe ReadOnlySpan<byte> Advance<T>(ref ReadOnlySpan<byte> input)
+ where T : unmanaged
+ {
+ var original = input;
+ input = input.Slice(sizeof(T));
+ return original;
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Extensions/SystemTypesExtensions.cs b/Impostor-dev/src/Impostor.Api/Extensions/SystemTypesExtensions.cs
new file mode 100644
index 0000000..68f1efd
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Extensions/SystemTypesExtensions.cs
@@ -0,0 +1,12 @@
+using Impostor.Api.Innersloth;
+
+namespace Impostor.Api
+{
+ public static class SystemTypesExtensions
+ {
+ public static string GetFriendlyName(this SystemTypes type)
+ {
+ return SystemTypeHelpers.Names[(int)type];
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Games/Extensions/GameExtensions.cs b/Impostor-dev/src/Impostor.Api/Games/Extensions/GameExtensions.cs
new file mode 100644
index 0000000..a736978
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Games/Extensions/GameExtensions.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Threading.Tasks;
+using Impostor.Api.Net;
+using Impostor.Api.Net.Messages;
+
+namespace Impostor.Api.Games
+{
+ public static class GameExtensions
+ {
+ public static ValueTask SendToAllExceptAsync(this IGame game, IMessageWriter writer, LimboStates states, int? id)
+ {
+ return id.HasValue
+ ? game.SendToAllExceptAsync(writer, id.Value, states)
+ : game.SendToAllAsync(writer, states);
+ }
+
+ public static ValueTask SendToAllExceptAsync(this IGame game, IMessageWriter writer, LimboStates states, IClient client)
+ {
+ if (client == null)
+ {
+ throw new ArgumentNullException(nameof(client));
+ }
+
+ return game.SendToAllExceptAsync(writer, client.Id, states);
+ }
+
+ public static ValueTask SendToAsync(this IGame game, IMessageWriter writer, IClient client)
+ {
+ if (client == null)
+ {
+ throw new ArgumentNullException(nameof(client));
+ }
+
+ return game.SendToAsync(writer, client.Id);
+ }
+
+ public static ValueTask SendToAsync(this IGame game, IMessageWriter writer, IClientPlayer player)
+ {
+ return game.SendToAsync(writer, player.Client);
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Games/Extensions/GameManagerExtensions.cs b/Impostor-dev/src/Impostor.Api/Games/Extensions/GameManagerExtensions.cs
new file mode 100644
index 0000000..9a5a2b4
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Games/Extensions/GameManagerExtensions.cs
@@ -0,0 +1,14 @@
+using System.Linq;
+using Impostor.Api.Games.Managers;
+using Impostor.Api.Innersloth;
+
+namespace Impostor.Api.Games
+{
+ public static class GameManagerExtensions
+ {
+ public static int GetGameCount(this IGameManager manager, MapFlags map)
+ {
+ return manager.Games.Count(game => map.HasFlag((MapFlags)(1 << game.Options.MapId)));
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Games/GameCode.cs b/Impostor-dev/src/Impostor.Api/Games/GameCode.cs
new file mode 100644
index 0000000..2c30e7e
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Games/GameCode.cs
@@ -0,0 +1,74 @@
+using System;
+using Impostor.Api.Innersloth;
+
+namespace Impostor.Api.Games
+{
+ public readonly struct GameCode : IEquatable<GameCode>
+ {
+ public GameCode(int value)
+ {
+ Value = value;
+ Code = GameCodeParser.IntToGameName(value);
+ }
+
+ public GameCode(string code)
+ {
+ Value = GameCodeParser.GameNameToInt(code);
+ Code = code;
+ }
+
+ public string Code { get; }
+
+ public int Value { get; }
+
+ public static implicit operator string(GameCode code) => code.Code;
+
+ public static implicit operator int(GameCode code) => code.Value;
+
+ public static implicit operator GameCode(string code) => From(code);
+
+ public static implicit operator GameCode(int value) => From(value);
+
+ public static bool operator ==(GameCode left, GameCode right)
+ {
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(GameCode left, GameCode right)
+ {
+ return !left.Equals(right);
+ }
+
+ public static GameCode Create()
+ {
+ return new GameCode(GameCodeParser.GenerateCode(6));
+ }
+
+ public static GameCode From(int value) => new GameCode(value);
+
+ public static GameCode From(string value) => new GameCode(value);
+
+ /// <inheritdoc/>
+ public bool Equals(GameCode other)
+ {
+ return Code == other.Code && Value == other.Value;
+ }
+
+ /// <inheritdoc/>
+ public override bool Equals(object? obj)
+ {
+ return obj is GameCode other && Equals(other);
+ }
+
+ /// <inheritdoc/>
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(Code, Value);
+ }
+
+ public override string ToString()
+ {
+ return Code;
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Games/GameJoinError.cs b/Impostor-dev/src/Impostor.Api/Games/GameJoinError.cs
new file mode 100644
index 0000000..4889ea9
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Games/GameJoinError.cs
@@ -0,0 +1,48 @@
+namespace Impostor.Api.Games
+{
+ public enum GameJoinError
+ {
+ /// <summary>
+ /// No error occured while joining the game.
+ /// </summary>
+ None,
+
+ /// <summary>
+ /// The client is not registered in the client manager.
+ /// </summary>
+ InvalidClient,
+
+ /// <summary>
+ /// The client has been banned from the game.
+ /// </summary>
+ Banned,
+
+ /// <summary>
+ /// The game is full.
+ /// </summary>
+ GameFull,
+
+ /// <summary>
+ /// The limbo state of the player is incorrect.
+ /// </summary>
+ InvalidLimbo,
+
+ /// <summary>
+ /// The game is already started.
+ /// </summary>
+ GameStarted,
+
+ /// <summary>
+ /// The game has been destroyed.
+ /// </summary>
+ GameDestroyed,
+
+ /// <summary>
+ /// Custom error by a plugin.
+ /// </summary>
+ /// <remarks>
+ /// A custom message can be set in <see cref="GameJoinResult.Message"/>.
+ /// </remarks>
+ Custom,
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Games/GameJoinResult.cs b/Impostor-dev/src/Impostor.Api/Games/GameJoinResult.cs
new file mode 100644
index 0000000..b33a2b6
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Games/GameJoinResult.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using Impostor.Api.Net;
+
+namespace Impostor.Api.Games
+{
+ public readonly struct GameJoinResult
+ {
+ private GameJoinResult(GameJoinError error, string? message = null, IClientPlayer? player = null)
+ {
+ Error = error;
+ Message = message;
+ Player = player;
+ }
+
+ public GameJoinError Error { get; }
+
+ public bool IsSuccess => Error == GameJoinError.None;
+
+ public bool IsCustomError => Error == GameJoinError.Custom;
+
+ [MemberNotNullWhen(true, nameof(IsCustomError))]
+ public string? Message { get; }
+
+ [MemberNotNullWhen(true, nameof(IsSuccess))]
+ public IClientPlayer? Player { get; }
+
+ public static GameJoinResult CreateCustomError(string message)
+ {
+ return new GameJoinResult(GameJoinError.Custom, message);
+ }
+
+ public static GameJoinResult CreateSuccess(IClientPlayer player)
+ {
+ return new GameJoinResult(GameJoinError.None, player: player);
+ }
+
+ public static GameJoinResult FromError(GameJoinError error)
+ {
+ if (error == GameJoinError.Custom)
+ {
+ throw new InvalidOperationException($"Custom errors should provide a message, use {nameof(CreateCustomError)} instead.");
+ }
+
+ return new GameJoinResult(error);
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Games/IGame.cs b/Impostor-dev/src/Impostor.Api/Games/IGame.cs
new file mode 100644
index 0000000..ad71986
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Games/IGame.cs
@@ -0,0 +1,88 @@
+using System.Collections.Generic;
+using System.Net;
+using System.Threading.Tasks;
+using Impostor.Api.Innersloth;
+using Impostor.Api.Net;
+using Impostor.Api.Net.Inner;
+using Impostor.Api.Net.Inner.Objects;
+using Impostor.Api.Net.Messages;
+
+namespace Impostor.Api.Games
+{
+ public interface IGame
+ {
+ GameOptionsData Options { get; }
+
+ GameCode Code { get; }
+
+ GameStates GameState { get; }
+
+ IGameNet GameNet { get; }
+
+ IEnumerable<IClientPlayer> Players { get; }
+
+ IPEndPoint PublicIp { get; }
+
+ int PlayerCount { get; }
+
+ IClientPlayer Host { get; }
+
+ bool IsPublic { get; }
+
+ IDictionary<object, object> Items { get; }
+
+ int HostId { get; }
+
+ IClientPlayer GetClientPlayer(int clientId);
+
+ /// <summary>
+ /// Adds an <see cref="IPAddress"/> to the ban list of this game.
+ /// Prevents all future joins from this <see cref="IPAddress"/>.
+ ///
+ /// This does not kick the player with that <see cref="IPAddress"/> from the lobby.
+ /// </summary>
+ /// <param name="ipAddress">
+ /// The <see cref="IPAddress"/> to ban.
+ /// </param>
+ void BanIp(IPAddress ipAddress);
+
+ /// <summary>
+ /// Syncs the internal <see cref="GameOptionsData"/> to all players.
+ /// Necessary to do if you modified it, otherwise it won't be used.
+ /// </summary>
+ /// <returns>A <see cref="ValueTask"/> representing the asynchronous operation.</returns>
+ ValueTask SyncSettingsAsync();
+
+ /// <summary>
+ /// Sets the specified list as Impostor on all connected players.
+ /// </summary>
+ /// <param name="players">List of players to be Impostor.</param>
+ /// <returns>A <see cref="ValueTask"/> representing the asynchronous operation.</returns>
+ ValueTask SetInfectedAsync(IEnumerable<IInnerPlayerControl> players);
+
+ /// <summary>
+ /// Send the message to all players.
+ /// </summary>
+ /// <param name="writer">Message to send.</param>
+ /// <param name="states">Required limbo state of the player.</param>
+ /// <returns>A <see cref="ValueTask"/> representing the asynchronous operation.</returns>
+ ValueTask SendToAllAsync(IMessageWriter writer, LimboStates states = LimboStates.NotLimbo);
+
+ /// <summary>
+ /// Send the message to all players except one.
+ /// </summary>
+ /// <param name="writer">Message to send.</param>
+ /// <param name="senderId">The player to exclude from sending the message.</param>
+ /// <param name="states">Required limbo state of the player.</param>
+ /// <returns>A <see cref="ValueTask"/> representing the asynchronous operation.</returns>
+ ValueTask SendToAllExceptAsync(IMessageWriter writer, int senderId, LimboStates states = LimboStates.NotLimbo);
+
+ /// <summary>
+ /// Send a message to a specific player.
+ /// </summary>
+ /// <param name="writer">Message to send.</param>
+ /// <param name="id">ID of the client.</param>
+ /// <returns>A <see cref="ValueTask"/> representing the asynchronous operation.</returns>
+ ValueTask SendToAsync(IMessageWriter writer, int id);
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Api/Games/IGameCodeFactory.cs b/Impostor-dev/src/Impostor.Api/Games/IGameCodeFactory.cs
new file mode 100644
index 0000000..f264fe0
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Games/IGameCodeFactory.cs
@@ -0,0 +1,7 @@
+namespace Impostor.Api.Games
+{
+ public interface IGameCodeFactory
+ {
+ GameCode Create();
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Games/Managers/IGameManager.cs b/Impostor-dev/src/Impostor.Api/Games/Managers/IGameManager.cs
new file mode 100644
index 0000000..10a05f0
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Games/Managers/IGameManager.cs
@@ -0,0 +1,11 @@
+using System.Collections.Generic;
+
+namespace Impostor.Api.Games.Managers
+{
+ public interface IGameManager
+ {
+ IEnumerable<IGame> Games { get; }
+
+ IGame? Find(GameCode code);
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Impostor.Api.csproj b/Impostor-dev/src/Impostor.Api/Impostor.Api.csproj
new file mode 100644
index 0000000..8ad2582
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Impostor.Api.csproj
@@ -0,0 +1,38 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>netstandard2.0</TargetFramework>
+ <CodeAnalysisRuleSet>ProjectRules.ruleset</CodeAnalysisRuleSet>
+ <LangVersion>9</LangVersion>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ <Nullable>enable</Nullable>
+ <Version>1.0.0</Version>
+ <IncludeSymbols>true</IncludeSymbols>
+ <SymbolPackageFormat>snupkg</SymbolPackageFormat>
+ <AssemblyName>Impostor.Api</AssemblyName>
+ <AssemblyTitle>Impostor.Api</AssemblyTitle>
+ <Authors>AeonLucid</Authors>
+ <Description>An api library for Impostor, an Among Us private server. You need this package to write plugins for Impostor.</Description>
+ <PackageId>Impostor.Api</PackageId>
+ <PackageTags>Among Us;Impostor;Impostor Plugin</PackageTags>
+ <PackageIconUrl>https://raw.githubusercontent.com/Impostor/Impostor/dev/docs/images/logo_64.png</PackageIconUrl>
+ <PackageProjectUrl>https://github.com/Impostor/Impostor</PackageProjectUrl>
+ <RepositoryType>git</RepositoryType>
+ <RepositoryUrl>https://github.com/Impostor/Impostor</RepositoryUrl>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="Microsoft.Bcl.HashCode" Version="1.1.0" />
+ <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="5.0.0" />
+ <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" />
+ <PackageReference Include="Nullable" Version="1.3.0">
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ </PackageReference>
+ <PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ </PackageReference>
+ </ItemGroup>
+
+</Project> \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Impostor.Api.csproj.DotSettings b/Impostor-dev/src/Impostor.Api/Impostor.Api.csproj.DotSettings
new file mode 100644
index 0000000..2df7445
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Impostor.Api.csproj.DotSettings
@@ -0,0 +1,8 @@
+<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
+ <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=attributes/@EntryIndexedValue">True</s:Boolean>
+ <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=events_005Cattributes/@EntryIndexedValue">True</s:Boolean>
+ <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=events_005Cgame/@EntryIndexedValue">True</s:Boolean>
+ <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=exceptions/@EntryIndexedValue">True</s:Boolean>
+ <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=extensions/@EntryIndexedValue">True</s:Boolean>
+ <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=games_005Cextensions/@EntryIndexedValue">True</s:Boolean>
+ <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=net_005Cextensions/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary> \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Innersloth/AlterGameTags.cs b/Impostor-dev/src/Impostor.Api/Innersloth/AlterGameTags.cs
new file mode 100644
index 0000000..46d1b2e
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Innersloth/AlterGameTags.cs
@@ -0,0 +1,7 @@
+namespace Impostor.Api.Innersloth
+{
+ public enum AlterGameTags : byte
+ {
+ ChangePrivacy = 1,
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Innersloth/ChatNoteType.cs b/Impostor-dev/src/Impostor.Api/Innersloth/ChatNoteType.cs
new file mode 100644
index 0000000..c163601
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Innersloth/ChatNoteType.cs
@@ -0,0 +1,7 @@
+namespace Impostor.Api.Innersloth
+{
+ public enum ChatNoteType : byte
+ {
+ DidVote = 0,
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Innersloth/Customization/ColorType.cs b/Impostor-dev/src/Impostor.Api/Innersloth/Customization/ColorType.cs
new file mode 100644
index 0000000..fc7be4c
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Innersloth/Customization/ColorType.cs
@@ -0,0 +1,18 @@
+namespace Impostor.Api.Innersloth.Customization
+{
+ public enum ColorType : byte
+ {
+ Red = 0,
+ Blue = 1,
+ Green = 2,
+ Pink = 3,
+ Orange = 4,
+ Yellow = 5,
+ Black = 6,
+ White = 7,
+ Purple = 8,
+ Brown = 9,
+ Cyan = 10,
+ Lime = 11,
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Api/Innersloth/Customization/HatType.cs b/Impostor-dev/src/Impostor.Api/Innersloth/Customization/HatType.cs
new file mode 100644
index 0000000..5e0a3ef
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Innersloth/Customization/HatType.cs
@@ -0,0 +1,100 @@
+namespace Impostor.Api.Innersloth.Customization
+{
+ public enum HatType
+ {
+ NoHat = 0,
+ Astronaut = 1,
+ BaseballCap = 2,
+ BrainSlug = 3,
+ BushHat = 4,
+ CaptainsHat = 5,
+ DoubleTopHat = 6,
+ Flowerpot = 7,
+ Goggles = 8,
+ HardHat = 9,
+ Military = 10,
+ PaperHat = 11,
+ PartyHat = 12,
+ Police = 13,
+ Stethescope = 14,
+ TopHat = 15,
+ TowelWizard = 16,
+ Ushanka = 17,
+ Viking = 18,
+ WallCap = 19,
+ Snowman = 20,
+ Reindeer = 21,
+ Lights = 22,
+ Santa = 23,
+ Tree = 24,
+ Present = 25,
+ Candycanes = 26,
+ ElfHat = 27,
+ NewYears2018 = 28,
+ WhiteHat = 29,
+ Crown = 30,
+ Eyebrows = 31,
+ HaloHat = 32,
+ HeroCap = 33,
+ PipCap = 34,
+ PlungerHat = 35,
+ ScubaHat = 36,
+ StickminHat = 37,
+ StrawHat = 38,
+ TenGallonHat = 39,
+ ThirdEyeHat = 40,
+ ToiletPaperHat = 41,
+ Toppat = 42,
+ Fedora = 43,
+ Goggles2 = 44,
+ Headphones = 45,
+ MaskHat = 46,
+ PaperMask = 47,
+ Security = 48,
+ StrapHat = 49,
+ Banana = 50,
+ Beanie = 51,
+ Bear = 52,
+ Cheese = 53,
+ Cherry = 54,
+ Egg = 55,
+ Fedora2 = 56,
+ Flamingo = 57,
+ FlowerPin = 58,
+ Helmet = 59,
+ Plant = 60,
+ BatEyes = 61,
+ BatWings = 62,
+ Horns = 63,
+ Mohawk = 64,
+ Pumpkin = 65,
+ ScaryBag = 66,
+ Witch = 67,
+ Wolf = 68,
+ Pirate = 69,
+ Plague = 70,
+ Machete = 71,
+ Fred = 72,
+ MinerCap = 73,
+ WinterHat = 74,
+ Archae = 75,
+ Antenna = 76,
+ Balloon = 77,
+ BirdNest = 78,
+ BlackBelt = 79,
+ Caution = 80,
+ Chef = 81,
+ CopHat = 82,
+ DoRag = 83,
+ DumSticker = 84,
+ Fez = 85,
+ GeneralHat = 86,
+ GreyThing = 87,
+ HunterCap = 88,
+ JungleHat = 89,
+ MiniCrewmate = 90,
+ NinjaMask = 91,
+ RamHorns = 92,
+ Snowman2 = 93,
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Api/Innersloth/Customization/PetType.cs b/Impostor-dev/src/Impostor.Api/Innersloth/Customization/PetType.cs
new file mode 100644
index 0000000..456e327
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Innersloth/Customization/PetType.cs
@@ -0,0 +1,18 @@
+namespace Impostor.Api.Innersloth.Customization
+{
+ public enum PetType
+ {
+ NoPet = 0,
+ Alien = 1,
+ Crewmate = 2,
+ Doggy = 3,
+ Stickmin = 4,
+ Hamster = 5,
+ Robot = 6,
+ Ufo = 7,
+ Ellie = 8,
+ Squig = 9,
+ Bedcrab = 10,
+ Glitch = 11,
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Api/Innersloth/Customization/SkinType.cs b/Impostor-dev/src/Impostor.Api/Innersloth/Customization/SkinType.cs
new file mode 100644
index 0000000..35da312
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Innersloth/Customization/SkinType.cs
@@ -0,0 +1,22 @@
+namespace Impostor.Api.Innersloth.Customization
+{
+ public enum SkinType : byte
+ {
+ None = 0,
+ Astro = 1,
+ Capt = 2,
+ Mech = 3,
+ Military = 4,
+ Police = 5,
+ Science = 6,
+ SuitB = 7,
+ SuitW = 8,
+ Wall = 9,
+ Hazmat = 10,
+ Security = 11,
+ Tarmac = 12,
+ Miner = 13,
+ Winter = 14,
+ Archae = 15,
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Api/Innersloth/DeathReason.cs b/Impostor-dev/src/Impostor.Api/Innersloth/DeathReason.cs
new file mode 100644
index 0000000..a07ac05
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Innersloth/DeathReason.cs
@@ -0,0 +1,9 @@
+namespace Impostor.Api.Innersloth
+{
+ public enum DeathReason
+ {
+ Exile = 0,
+ Kill = 1,
+ Disconnect = 2,
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Innersloth/DisconnectReason.cs b/Impostor-dev/src/Impostor.Api/Innersloth/DisconnectReason.cs
new file mode 100644
index 0000000..9526f58
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Innersloth/DisconnectReason.cs
@@ -0,0 +1,54 @@
+namespace Impostor.Api.Innersloth
+{
+ public enum DisconnectReason
+ {
+ ExitGame = 0,
+ // The game you tried to join is full.
+ // Check with the host to see if you can join next round.
+ GameFull = 1,
+ // The game you tried to join already started.
+ // Check with the host to see if you can join next round.
+ GameStarted = 2,
+ // Could not find the game you're looking for.
+ GameMissing = 3,
+ IncorrectGame = 18,
+ // For these a message can be given, specifying an empty message shows
+ // "An unknown error disconnected you from the server."
+ CustomMessage1 = 4,
+ Custom = 8,
+ // CustomMessage3 = 11,
+ // CustomMessage4 = 12,
+ // CustomMessage5 = 13,
+ // CustomMessage6 = 14,
+ // CustomMessage7 = 15,
+ // You are running an older version of the game.
+ // Please update to play with others.
+ IncorrectVersion = 5,
+ // You cannot rejoin that room.
+ // You were banned
+ Banned = 6,
+ // You can rejoin if the room hasn't started
+ // You were kicked
+ Kicked = 7,
+ // You were banned for hacking.
+ // Please stop.
+ Hacking = 10,
+ Destroy = 16,
+ // You disconnected from the host.
+ // If this happens often, check your WiFi strength.
+ //
+ // You disconnected from the server.
+ // If this happens often, check your network strength.
+ // This may also be a server issue.
+ Error = 17,
+ // The server stopped this game. Possibly due to inactivity.
+ ServerRequest = 19,
+ // The Among Us servers are overloaded.
+ // Sorry! Please try again later!
+ ServerFull = 20,
+ FocusLostBackground = 207,
+ IntentionalLeaving = 208,
+ FocusLost = 209,
+ NewConnection = 210,
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Innersloth/FloatRange.cs b/Impostor-dev/src/Impostor.Api/Innersloth/FloatRange.cs
new file mode 100644
index 0000000..c8a0824
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Innersloth/FloatRange.cs
@@ -0,0 +1,22 @@
+using Impostor.Api.Unity;
+
+namespace Impostor.Api.Innersloth
+{
+ public class FloatRange
+ {
+ private readonly float _min;
+ private readonly float _max;
+
+ public FloatRange(float min, float max)
+ {
+ _min = min;
+ _max = max;
+ }
+
+ public float Width => _max - _min;
+
+ public float Lerp(float v) => Mathf.Lerp(_min, _max, v);
+
+ public float ReverseLerp(float t) => Mathf.Clamp((t - _min) / Width, 0.0f, 1f);
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Innersloth/GameCodeParser.cs b/Impostor-dev/src/Impostor.Api/Innersloth/GameCodeParser.cs
new file mode 100644
index 0000000..9717cff
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Innersloth/GameCodeParser.cs
@@ -0,0 +1,142 @@
+using System;
+using System.Buffers.Binary;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace Impostor.Api.Innersloth
+{
+ public static class GameCodeParser
+ {
+ private const string V2 = "QWXRTYLPESDFGHUJKZOCVBINMA";
+ private static readonly int[] V2Map = {
+ 25,
+ 21,
+ 19,
+ 10,
+ 8,
+ 11,
+ 12,
+ 13,
+ 22,
+ 15,
+ 16,
+ 6,
+ 24,
+ 23,
+ 18,
+ 7,
+ 0,
+ 3,
+ 9,
+ 4,
+ 14,
+ 20,
+ 1,
+ 2,
+ 5,
+ 17
+ };
+ private static readonly RNGCryptoServiceProvider Random = new RNGCryptoServiceProvider();
+
+ public static string IntToGameName(int input)
+ {
+ // V2.
+ if (input < -1)
+ {
+ return IntToGameNameV2(input);
+ }
+
+ // V1.
+ Span<byte> code = stackalloc byte[4];
+ BinaryPrimitives.WriteInt32LittleEndian(code, input);
+#if NETSTANDARD2_0
+ return Encoding.UTF8.GetString(code.Slice(0, 4).ToArray());
+#else
+ return Encoding.UTF8.GetString(code.Slice(0, 4));
+#endif
+ }
+
+ private static string IntToGameNameV2(int input)
+ {
+ var a = input & 0x3FF;
+ var b = (input >> 10) & 0xFFFFF;
+
+ return new string(new []
+ {
+ V2[a % 26],
+ V2[a / 26],
+ V2[b % 26],
+ V2[b / 26 % 26],
+ V2[b / (26 * 26) % 26],
+ V2[b / (26 * 26 * 26) % 26]
+ });
+ }
+
+ public static int GameNameToInt(string code)
+ {
+ var upper = code.ToUpperInvariant();
+ if (upper.Any(x => !char.IsLetter(x)))
+ {
+ return -1;
+ }
+
+ var len = code.Length;
+ if (len == 6)
+ {
+ return GameNameToIntV2(upper);
+ }
+
+ if (len == 4)
+ {
+ return code[0] | ((code[1] | ((code[2] | (code[3] << 8)) << 8)) << 8);
+ }
+
+ return -1;
+ }
+
+ private static int GameNameToIntV2(string code)
+ {
+ var a = V2Map[code[0] - 65];
+ var b = V2Map[code[1] - 65];
+ var c = V2Map[code[2] - 65];
+ var d = V2Map[code[3] - 65];
+ var e = V2Map[code[4] - 65];
+ var f = V2Map[code[5] - 65];
+
+ var one = (a + 26 * b) & 0x3FF;
+ var two = (c + 26 * (d + 26 * (e + 26 * f)));
+
+ return (int) (one | ((two << 10) & 0x3FFFFC00) | 0x80000000);
+ }
+
+ public static int GenerateCode(int len)
+ {
+ if (len != 4 && len != 6)
+ {
+ throw new ArgumentException("should be 4 or 6", nameof(len));
+ }
+
+ // Generate random bytes.
+#if NETSTANDARD2_0
+ var data = new byte[len];
+#else
+ Span<byte> data = stackalloc byte[len];
+#endif
+ Random.GetBytes(data);
+
+ // Convert to their char representation.
+ Span<char> dataChar = stackalloc char[len];
+ for (var i = 0; i < len; i++)
+ {
+ dataChar[i] = V2[V2Map[data[i] % 26]];
+ }
+
+#if NETSTANDARD2_0
+ return GameNameToInt(new string(dataChar.ToArray()));
+#else
+ return GameNameToInt(new string(dataChar));
+#endif
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Innersloth/GameKeywords.cs b/Impostor-dev/src/Impostor.Api/Innersloth/GameKeywords.cs
new file mode 100644
index 0000000..bbb463f
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Innersloth/GameKeywords.cs
@@ -0,0 +1,19 @@
+using System;
+
+namespace Impostor.Api.Innersloth
+{
+ [Flags]
+ public enum GameKeywords : uint
+ {
+ All = 0,
+ Other = 1,
+ Spanish = 2,
+ Korean = 4,
+ Russian = 8,
+ Portuguese = 16,
+ Arabic = 32,
+ Filipone = 64,
+ Polish = 128,
+ English = 256,
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Innersloth/GameOptionsData.cs b/Impostor-dev/src/Impostor.Api/Innersloth/GameOptionsData.cs
new file mode 100644
index 0000000..d18efd8
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Innersloth/GameOptionsData.cs
@@ -0,0 +1,261 @@
+using System;
+using System.IO;
+using Impostor.Api.Net.Messages;
+
+namespace Impostor.Api.Innersloth
+{
+ public class GameOptionsData
+ {
+ /// <summary>
+ /// The latest major version of the game client.
+ /// </summary>
+ public const int LatestVersion = 4;
+
+ /// <summary>
+ /// Gets or sets host's version of the game.
+ /// </summary>
+ public byte Version { get; set; }
+
+ /// <summary>
+ /// Gets or sets the maximum amount of players for this lobby.
+ /// </summary>
+ public byte MaxPlayers { get; set; }
+
+ /// <summary>
+ /// Gets or sets the language of the lobby as per <see cref="GameKeywords"/> enum.
+ /// </summary>
+ public GameKeywords Keywords { get; set; }
+
+ /// <summary>
+ /// Gets or sets the MapId selected for this lobby
+ /// </summary>
+ /// <remarks>
+ /// Skeld = 0, MiraHQ = 1, Polus = 2.
+ /// </remarks>
+ internal byte MapId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the map selected for this lobby
+ /// </summary>
+ public MapTypes Map
+ {
+ get => (MapTypes)MapId;
+ set => MapId = (byte)value;
+ }
+
+ /// <summary>
+ /// Gets or sets the Player speed modifier.
+ /// </summary>
+ public float PlayerSpeedMod { get; set; }
+
+ /// <summary>
+ /// Gets or sets the Light modifier for the players that are members of the crew as a multiplier value.
+ /// </summary>
+ public float CrewLightMod { get; set; }
+
+ /// <summary>
+ /// Gets or sets the Light modifier for the players that are Impostors as a multiplier value.
+ /// </summary>
+ public float ImpostorLightMod { get; set; }
+
+ /// <summary>
+ /// Gets or sets the Impostor cooldown to kill in seconds.
+ /// </summary>
+ public float KillCooldown { get; set; }
+
+ /// <summary>
+ /// Gets or sets the number of common tasks.
+ /// </summary>
+ public int NumCommonTasks { get; set; }
+
+ /// <summary>
+ /// Gets or sets the number of long tasks.
+ /// </summary>
+ public int NumLongTasks { get; set; }
+
+ /// <summary>
+ /// Gets or sets the number of short tasks.
+ /// </summary>
+ public int NumShortTasks { get; set; }
+
+ /// <summary>
+ /// Gets or sets the maximum amount of emergency meetings each player can call during the game in seconds.
+ /// </summary>
+ public int NumEmergencyMeetings { get; set; }
+
+ /// <summary>
+ /// Gets or sets the cooldown between each time any player can call an emergency meeting in seconds.
+ /// </summary>
+ public int EmergencyCooldown { get; set; }
+
+ /// <summary>
+ /// Gets or sets the number of impostors for this lobby.
+ /// </summary>
+ public int NumImpostors { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether ghosts (dead crew members) can do tasks.
+ /// </summary>
+ public bool GhostsDoTasks { get; set; }
+
+ /// <summary>
+ /// Gets or sets the Kill as per values in <see cref="KillDistances"/>.
+ /// </summary>
+ /// <remarks>
+ /// Short = 0, Normal = 1, Long = 2.
+ /// </remarks>
+ public KillDistances KillDistance { get; set; }
+
+ /// <summary>
+ /// Gets or sets the time for discussion before voting time in seconds.
+ /// </summary>
+ public int DiscussionTime { get; set; }
+
+ /// <summary>
+ /// Gets or sets the time for voting in seconds.
+ /// </summary>
+ public int VotingTime { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether an ejected player is an impostor or not.
+ /// </summary>
+ public bool ConfirmImpostor { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether players are able to see tasks being performed by other players.
+ /// </summary>
+ /// <remarks>
+ /// By being set to true, tasks such as Empty Garbage, Submit Scan, Clear asteroids, Prime shields execution will be visible to other players.
+ /// </remarks>
+ public bool VisualTasks { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the vote is anonymous.
+ /// </summary>
+ public bool AnonymousVotes { get; set; }
+
+ /// <summary>
+ /// Gets or sets the task bar update mode as per values in <see cref="Innersloth.TaskBarUpdate"/>.
+ /// </summary>
+ public TaskBarUpdate TaskBarUpdate { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the GameOptions are the default ones.
+ /// </summary>
+ public bool IsDefaults { get; set; }
+
+ /// <summary>
+ /// Deserialize a packet/message to a new GameOptionsData object.
+ /// </summary>
+ /// <param name="reader">Message reader object containing the raw message.</param>
+ /// <returns>GameOptionsData object.</returns>
+ public static GameOptionsData DeserializeCreate(IMessageReader reader)
+ {
+ var options = new GameOptionsData();
+ options.Deserialize(reader.ReadBytesAndSize());
+ return options;
+ }
+
+ /// <summary>
+ /// Serializes this instance of GameOptionsData object to a specified BinaryWriter.
+ /// </summary>
+ /// <param name="writer">The stream to write the message to.</param>
+ /// <param name="version">The version of the game.</param>
+ public void Serialize(BinaryWriter writer, byte version)
+ {
+ writer.Write((byte)version);
+ writer.Write((byte)MaxPlayers);
+ writer.Write((uint)Keywords);
+ writer.Write((byte)MapId);
+ writer.Write((float)PlayerSpeedMod);
+ writer.Write((float)CrewLightMod);
+ writer.Write((float)ImpostorLightMod);
+ writer.Write((float)KillCooldown);
+ writer.Write((byte)NumCommonTasks);
+ writer.Write((byte)NumLongTasks);
+ writer.Write((byte)NumShortTasks);
+ writer.Write((int)NumEmergencyMeetings);
+ writer.Write((byte)NumImpostors);
+ writer.Write((byte)KillDistance);
+ writer.Write((uint)DiscussionTime);
+ writer.Write((uint)VotingTime);
+ writer.Write((bool)IsDefaults);
+
+ if (version > 1)
+ {
+ writer.Write((byte)EmergencyCooldown);
+ }
+
+ if (version > 2)
+ {
+ writer.Write((bool)ConfirmImpostor);
+ writer.Write((bool)VisualTasks);
+ }
+
+ if (version > 3)
+ {
+ writer.Write((bool)AnonymousVotes);
+ writer.Write((byte)TaskBarUpdate);
+ }
+
+ if (version > 4)
+ {
+ throw new ImpostorException($"Unknown GameOptionsData version {Version}.");
+ }
+ }
+
+ /// <summary>
+ /// Deserialize a ReadOnlyMemory object to this instance of the GameOptionsData object.
+ /// </summary>
+ /// <param name="memory">Memory containing the message/packet.</param>
+ public void Deserialize(ReadOnlyMemory<byte> memory)
+ {
+ var bytes = memory.Span;
+
+ Version = bytes.ReadByte();
+ MaxPlayers = bytes.ReadByte();
+ Keywords = (GameKeywords)bytes.ReadUInt32();
+ MapId = bytes.ReadByte();
+ PlayerSpeedMod = bytes.ReadSingle();
+
+ CrewLightMod = bytes.ReadSingle();
+ ImpostorLightMod = bytes.ReadSingle();
+ KillCooldown = bytes.ReadSingle();
+
+ NumCommonTasks = bytes.ReadByte();
+ NumLongTasks = bytes.ReadByte();
+ NumShortTasks = bytes.ReadByte();
+
+ NumEmergencyMeetings = bytes.ReadInt32();
+
+ NumImpostors = bytes.ReadByte();
+ KillDistance = (KillDistances)bytes.ReadByte();
+ DiscussionTime = bytes.ReadInt32();
+ VotingTime = bytes.ReadInt32();
+
+ IsDefaults = bytes.ReadBoolean();
+
+ if (Version > 1)
+ {
+ EmergencyCooldown = bytes.ReadByte();
+ }
+
+ if (Version > 2)
+ {
+ ConfirmImpostor = bytes.ReadBoolean();
+ VisualTasks = bytes.ReadBoolean();
+ }
+
+ if (Version > 3)
+ {
+ AnonymousVotes = bytes.ReadBoolean();
+ TaskBarUpdate = (TaskBarUpdate)bytes.ReadByte();
+ }
+
+ if (Version > 4)
+ {
+ throw new ImpostorException($"Unknown GameOptionsData version {Version}.");
+ }
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Api/Innersloth/GameOverReason.cs b/Impostor-dev/src/Impostor.Api/Innersloth/GameOverReason.cs
new file mode 100644
index 0000000..6a95d37
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Innersloth/GameOverReason.cs
@@ -0,0 +1,15 @@
+namespace Impostor.Api.Innersloth
+{
+ public enum GameOverReason : byte
+ {
+ HumansByVote = 0,
+ HumansByTask = 1,
+ ImpostorByVote = 2,
+ ImpostorByKill = 3,
+ ImpostorBySabotage = 4,
+
+ // Unused (?)
+ ImpostorDisconnect = 5,
+ HumansDisconnect = 6,
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Innersloth/GameStates.cs b/Impostor-dev/src/Impostor.Api/Innersloth/GameStates.cs
new file mode 100644
index 0000000..f5aaa9c
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Innersloth/GameStates.cs
@@ -0,0 +1,11 @@
+namespace Impostor.Api.Innersloth
+{
+ public enum GameStates : byte
+ {
+ NotStarted = 0,
+ Starting = 1,
+ Started = 2,
+ Ended = 3,
+ Destroyed = 4,
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Innersloth/GameVersion.cs b/Impostor-dev/src/Impostor.Api/Innersloth/GameVersion.cs
new file mode 100644
index 0000000..c11933e
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Innersloth/GameVersion.cs
@@ -0,0 +1,10 @@
+namespace Impostor.Api.Innersloth
+{
+ public class GameVersion
+ {
+ public static int GetVersion(int year, int month, int day, int rev = 0)
+ {
+ return (year * 25000) + (month * 1800) + (day * 50) + rev;
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Innersloth/KillDistances.cs b/Impostor-dev/src/Impostor.Api/Innersloth/KillDistances.cs
new file mode 100644
index 0000000..b2ee5d9
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Innersloth/KillDistances.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Impostor.Api.Innersloth
+{
+ [Flags]
+ public enum KillDistances : byte
+ {
+ Short = 0,
+ Normal = 1,
+ Long = 2,
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Api/Innersloth/MapFlags.cs b/Impostor-dev/src/Impostor.Api/Innersloth/MapFlags.cs
new file mode 100644
index 0000000..ad462a2
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Innersloth/MapFlags.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Impostor.Api.Innersloth
+{
+ [Flags]
+ public enum MapFlags
+ {
+ Skeld = 1,
+ MiraHQ = 2,
+ Polus = 4,
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Innersloth/MapTypes.cs b/Impostor-dev/src/Impostor.Api/Innersloth/MapTypes.cs
new file mode 100644
index 0000000..8dc07b5
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Innersloth/MapTypes.cs
@@ -0,0 +1,9 @@
+namespace Impostor.Api.Innersloth
+{
+ public enum MapTypes
+ {
+ Skeld = 0,
+ MiraHQ = 1,
+ Polus = 2,
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Innersloth/RegionInfo.cs b/Impostor-dev/src/Impostor.Api/Innersloth/RegionInfo.cs
new file mode 100644
index 0000000..c78978b
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Innersloth/RegionInfo.cs
@@ -0,0 +1,48 @@
+using System.Collections.Generic;
+using System.IO;
+
+namespace Impostor.Api.Innersloth
+{
+ public class RegionInfo
+ {
+ public RegionInfo(string name, string ping, IReadOnlyList<ServerInfo> servers)
+ {
+ Name = name;
+ Ping = ping;
+ Servers = servers;
+ }
+
+ public string Name { get; }
+ public string Ping { get; }
+ public IReadOnlyList<ServerInfo> Servers { get; }
+
+ public void Serialize(BinaryWriter writer)
+ {
+ writer.Write(0);
+ writer.Write(Name);
+ writer.Write(Ping);
+ writer.Write(Servers.Count);
+
+ foreach (var server in Servers)
+ {
+ server.Serialize(writer);
+ }
+ }
+
+ public static RegionInfo Deserialize(BinaryReader reader)
+ {
+ var unknown = reader.ReadInt32();
+ var name = reader.ReadString();
+ var ping = reader.ReadString();
+ var servers = new List<ServerInfo>();
+ var serverCount = reader.ReadInt32();
+
+ for (var i = 0; i < serverCount; i++)
+ {
+ servers.Add(ServerInfo.Deserialize(reader));
+ }
+
+ return new RegionInfo(name, ping, servers);
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Innersloth/ServerInfo.cs b/Impostor-dev/src/Impostor.Api/Innersloth/ServerInfo.cs
new file mode 100644
index 0000000..7785823
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Innersloth/ServerInfo.cs
@@ -0,0 +1,37 @@
+using System.IO;
+using System.Net;
+
+namespace Impostor.Api.Innersloth
+{
+ public class ServerInfo
+ {
+ public string Name { get; }
+ public string Ip { get; }
+ public ushort Port { get; }
+
+ public ServerInfo(string name, string ip, ushort port)
+ {
+ Name = name;
+ Ip = ip;
+ Port = port;
+ }
+
+ public void Serialize(BinaryWriter writer)
+ {
+ writer.Write(Name);
+ writer.Write(IPAddress.Parse(Ip).GetAddressBytes());
+ writer.Write(Port);
+ writer.Write(0);
+ }
+
+ public static ServerInfo Deserialize(BinaryReader reader)
+ {
+ var name = reader.ReadString();
+ var ip = new IPAddress(reader.ReadBytes(4)).ToString();
+ var port = reader.ReadUInt16();
+ var unknown = reader.ReadInt32();
+
+ return new ServerInfo(name, ip, port);
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Innersloth/SystemTypeHelpers.cs b/Impostor-dev/src/Impostor.Api/Innersloth/SystemTypeHelpers.cs
new file mode 100644
index 0000000..ad88c28
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Innersloth/SystemTypeHelpers.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Linq;
+
+namespace Impostor.Api.Innersloth
+{
+ internal class SystemTypeHelpers
+ {
+ public static readonly SystemTypes[] AllTypes;
+ public static readonly string[] Names;
+
+ static SystemTypeHelpers()
+ {
+ AllTypes = Enum.GetValues(typeof(SystemTypes)).Cast<SystemTypes>().ToArray();
+ Names = AllTypes.Select(x =>
+ {
+ return x switch
+ {
+ SystemTypes.UpperEngine => "Upper Engine",
+ SystemTypes.Nav => "Navigations",
+ SystemTypes.LifeSupp => "O2",
+ SystemTypes.LowerEngine => "Lower Engine",
+ SystemTypes.LockerRoom => "Locker Room",
+ _ => x.ToString()
+ };
+ }).ToArray();
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Innersloth/SystemTypes.cs b/Impostor-dev/src/Impostor.Api/Innersloth/SystemTypes.cs
new file mode 100644
index 0000000..7f91718
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Innersloth/SystemTypes.cs
@@ -0,0 +1,42 @@
+namespace Impostor.Api.Innersloth
+{
+ public enum SystemTypes : byte
+ {
+ Hallway = 0,
+ Storage = 1,
+ Cafeteria = 2,
+ Reactor = 3,
+ UpperEngine = 4,
+ Nav = 5,
+ Admin = 6,
+ Electrical = 7,
+ LifeSupp = 8,
+ Shields = 9,
+ MedBay = 10,
+ Security = 11,
+ Weapons = 12,
+ LowerEngine = 13,
+ Comms = 14,
+ ShipTasks = 15,
+ Doors = 16,
+ Sabotage = 17,
+ /// <summary>
+ /// Decontam on Mira and bottom decontam on Polus
+ /// </summary>
+ Decontamination = 18,
+ Launchpad = 19,
+ LockerRoom = 20,
+ Laboratory = 21,
+ Balcony = 22,
+ Office = 23,
+ Greenhouse = 24,
+ Dropship = 25,
+ /// <summary>
+ /// Top decontam on Polus
+ /// </summary>
+ Decontamination2 = 26,
+ Outside = 27,
+ Specimens = 28,
+ BoilerRoom = 29
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Innersloth/TaskBarUpdate.cs b/Impostor-dev/src/Impostor.Api/Innersloth/TaskBarUpdate.cs
new file mode 100644
index 0000000..f4d7c1f
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Innersloth/TaskBarUpdate.cs
@@ -0,0 +1,9 @@
+namespace Impostor.Api.Innersloth
+{
+ public enum TaskBarUpdate : byte
+ {
+ Always = 0,
+ Meetings = 1,
+ Never = 2
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Innersloth/TaskTypes.cs b/Impostor-dev/src/Impostor.Api/Innersloth/TaskTypes.cs
new file mode 100644
index 0000000..8b15354
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Innersloth/TaskTypes.cs
@@ -0,0 +1,49 @@
+namespace Impostor.Api.Innersloth
+{
+ public enum TaskTypes : uint
+ {
+ SubmitScan = 0,
+ PrimeShields = 1,
+ FuelEngines = 2,
+ ChartCourse = 3,
+ StartReactor = 4,
+ SwipeCard = 5,
+ ClearAsteroids = 6,
+ UploadData = 7,
+ InspectSample = 8,
+ EmptyChute = 9,
+ EmptyGarbage = 10,
+ AlignEngineOutput = 11,
+ FixWiring = 12,
+ CalibrateDistributor = 13,
+ DivertPower = 14,
+ UnlockManifolds = 15,
+ ResetReactor = 16,
+ FixLights = 17,
+ Filter = 18,
+ FixComms = 19,
+ RestoreOxy = 20,
+ StabilizeSteering = 21,
+ AssembleArtifact = 22,
+ SortSamples = 23,
+ MeasureWeather = 24,
+ EnterIdCode = 25,
+ BuyBeverage = 26,
+ ProcessData = 27,
+ RunDiagnostics = 28,
+ WaterPlants = 29,
+ MonitorOxygen = 30,
+ StoreArtifact = 31,
+ FillCanisters = 32,
+ ActivateWeatherNodes = 33,
+ InsertKeys = 34,
+ ResetSeismic = 35,
+ ScanBoardingPass = 36,
+ OpenWaterways = 37,
+ ReplaceWaterJug = 38,
+ RepairDrill = 39,
+ AlignTelescope = 40,
+ RecordTemperature = 41,
+ RebootWifi = 42,
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Innersloth/TextBox.cs b/Impostor-dev/src/Impostor.Api/Innersloth/TextBox.cs
new file mode 100644
index 0000000..9533d83
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Innersloth/TextBox.cs
@@ -0,0 +1,10 @@
+namespace Impostor.Api.Innersloth
+{
+ public static class TextBox
+ {
+ public static bool IsCharAllowed(char i)
+ {
+ return i == ' ' || (i >= 'A' && i <= 'Z') || (i >= 'a' && i <= 'z') || (i >= '0' && i <= '9') || (i >= 'À' && i <= 'ÿ') || (i >= 'Ѐ' && i <= 'џ') || (i >= 'ㄱ' && i <= 'ㆎ') || (i >= '가' && i <= '힣');
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Innersloth/VentLocation.cs b/Impostor-dev/src/Impostor.Api/Innersloth/VentLocation.cs
new file mode 100644
index 0000000..f9b8567
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Innersloth/VentLocation.cs
@@ -0,0 +1,48 @@
+namespace Impostor.Api.Innersloth
+{
+ public enum VentLocation : uint
+ {
+ // Skeld
+ SkeldAdmin = 0,
+ SkeldRightHallway = 1,
+ SkeldCafeteria = 2,
+ SkeldElectrical = 3,
+ SkeldUpperEngine = 4,
+ SkeldSecurity = 5,
+ SkeldMedbay = 6,
+ SkeldWeapons = 7,
+ SkeldLowerReactor = 8,
+ SkeldLowerEngine = 9,
+ SkeldShields = 10,
+ SkeldUpperReactor = 11,
+ SkeldUpperNavigation = 12,
+ SkeldLowerNavigation = 13,
+
+ // Mira HQ
+ MiraBalcony = 1,
+ MiraCafeteria = 2,
+ MiraReactor = 3,
+ MiraLaboratory = 4,
+ MiraOffice = 5,
+ MiraAdmin = 6,
+ MiraGreenhouse = 7,
+ MiraMedbay = 8,
+ MiraDecontamination = 9,
+ MiraLockerRoom = 10,
+ MiraLaunchpad = 11,
+
+ // Polus
+ PolusSecurity = 0,
+ PolusElectrical = 1,
+ PolusO2 = 2,
+ PolusCommunications = 3,
+ PolusOffice = 4,
+ PolusAdmin = 5,
+ PolusLaboratory = 6,
+ PolusLava = 7,
+ PolusStorage = 8,
+ PolusRightStabilizer = 9,
+ PolusLeftStabilizer = 10,
+ PolusOutsideAdmin = 11,
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Api/Net/IClient.cs b/Impostor-dev/src/Impostor.Api/Net/IClient.cs
new file mode 100644
index 0000000..48efeda
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Net/IClient.cs
@@ -0,0 +1,76 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Impostor.Api.Innersloth;
+using Impostor.Api.Net.Messages;
+
+namespace Impostor.Api.Net
+{
+ /// <summary>
+ /// Represents a connected game client.
+ /// </summary>
+ public interface IClient
+ {
+ /// <summary>
+ /// Gets or sets the unique ID of the client.
+ /// </summary>
+ /// <remarks>
+ /// This ID is generated when the client is registered in the client manager and should not be used
+ /// to store persisted data.
+ /// </remarks>
+ int Id { get; set; }
+
+ /// <summary>
+ /// Gets the name that was provided by the player in the client.
+ /// </summary>
+ /// <remarks>
+ /// The name is provided by the player and should not be used to store persisted data.
+ /// </remarks>
+ string Name { get; }
+
+ /// <summary>
+ /// Gets the connection of the client.
+ /// </summary>
+ /// <remarks>
+ /// Null when the client was not registered by the matchmaker.
+ /// </remarks>
+ IHazelConnection? Connection { get; }
+
+ /// <summary>
+ /// Gets a key/value collection that can be used to share data between messages.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// The stored data will not be saved.
+ /// After the connection has been closed all data will be lost.
+ /// </para>
+ /// <para>
+ /// Note that the values will not be disposed after the connection has been closed.
+ /// This has to be implemented by the plugin.
+ /// </para>
+ /// </remarks>
+ IDictionary<object, object> Items { get; }
+
+ /// <summary>
+ /// Gets or sets the current game data of the <see cref="IClient"/>.
+ /// </summary>
+ IClientPlayer? Player { get; }
+
+ ValueTask HandleMessageAsync(IMessageReader message, MessageType messageType);
+
+ ValueTask HandleDisconnectAsync(string reason);
+
+ /// <summary>
+ /// Disconnect the client with a <see cref="DisconnectReason"/>.
+ /// </summary>
+ /// <param name="reason">
+ /// The message to show to the player.
+ /// </param>
+ /// <param name="message">
+ /// Only used when <see cref="reason"/> is set to <see cref="DisconnectReason.Custom"/>.
+ /// </param>
+ /// <returns>
+ /// A <see cref="ValueTask"/> representing the asynchronous operation.
+ /// </returns>
+ ValueTask DisconnectAsync(DisconnectReason reason, string? message = null);
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Api/Net/IClientPlayer.cs b/Impostor-dev/src/Impostor.Api/Net/IClientPlayer.cs
new file mode 100644
index 0000000..6070210
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Net/IClientPlayer.cs
@@ -0,0 +1,43 @@
+using System.Threading.Tasks;
+using Impostor.Api.Games;
+using Impostor.Api.Net.Inner;
+using Impostor.Api.Net.Inner.Objects;
+
+namespace Impostor.Api.Net
+{
+ /// <summary>
+ /// Represents a player in <see cref="IGame"/>.
+ /// </summary>
+ public interface IClientPlayer
+ {
+ /// <summary>
+ /// Gets the client that belongs to the player.
+ /// </summary>
+ IClient Client { get; }
+
+ /// <summary>
+ /// Gets the game where the <see cref="IClientPlayer"/> belongs to.
+ /// </summary>
+ IGame Game { get; }
+
+ /// <summary>
+ /// Gets or sets the current limbo state of the player.
+ /// </summary>
+ LimboStates Limbo { get; set; }
+
+ IInnerPlayerControl? Character { get; }
+
+ public bool IsHost { get; }
+
+ /// <summary>
+ /// Checks if the specified <see cref="IInnerNetObject"/> is owned by <see cref="IClientPlayer"/>.
+ /// </summary>
+ /// <param name="netObject">The <see cref="IInnerNetObject"/>.</param>
+ /// <returns>Returns true if owned by <see cref="IClientPlayer"/>.</returns>
+ bool IsOwner(IInnerNetObject netObject);
+
+ ValueTask KickAsync();
+
+ ValueTask BanAsync();
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Api/Net/IConnection.cs b/Impostor-dev/src/Impostor.Api/Net/IConnection.cs
new file mode 100644
index 0000000..94f9b8b
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Net/IConnection.cs
@@ -0,0 +1,7 @@
+namespace Impostor.Api.Net
+{
+ public interface IConnection
+ {
+
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Net/IHazelConnection.cs b/Impostor-dev/src/Impostor.Api/Net/IHazelConnection.cs
new file mode 100644
index 0000000..4e6c4b3
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Net/IHazelConnection.cs
@@ -0,0 +1,41 @@
+using System.Net;
+using System.Threading.Tasks;
+using Impostor.Api.Net.Messages;
+
+namespace Impostor.Api.Net
+{
+ /// <summary>
+ /// Represents the connection of the client.
+ /// </summary>
+ public interface IHazelConnection
+ {
+ /// <summary>
+ /// Gets the IP endpoint of the client.
+ /// </summary>
+ IPEndPoint EndPoint { get; }
+
+ /// <summary>
+ /// Gets a value indicating whether the client is connected to the server.
+ /// </summary>
+ bool IsConnected { get; }
+
+ /// <summary>
+ /// Gets the client of the connection.
+ /// </summary>
+ IClient? Client { get; set; }
+
+ /// <summary>
+ /// Sends a message writer to the connection.
+ /// </summary>
+ /// <param name="writer">The message.</param>
+ /// <returns></returns>
+ ValueTask SendAsync(IMessageWriter writer);
+
+ /// <summary>
+ /// Disconnects the client and invokes the disconnect handler.
+ /// </summary>
+ /// <param name="reason">A reason.</param>
+ /// <returns></returns>
+ ValueTask DisconnectAsync(string? reason);
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Net/Inner/IGameNet.cs b/Impostor-dev/src/Impostor.Api/Net/Inner/IGameNet.cs
new file mode 100644
index 0000000..933a4de
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Net/Inner/IGameNet.cs
@@ -0,0 +1,18 @@
+using Impostor.Api.Net.Inner.Objects;
+
+namespace Impostor.Api.Net.Inner
+{
+ /// <summary>
+ /// Holds all data that is serialized over the network through GameData packets.
+ /// </summary>
+ public interface IGameNet
+ {
+ IInnerLobbyBehaviour LobbyBehaviour { get; }
+
+ IInnerGameData GameData { get; }
+
+ IInnerVoteBanSystem VoteBan { get; }
+
+ IInnerShipStatus ShipStatus { get; }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Net/Inner/IInnerNetObject.cs b/Impostor-dev/src/Impostor.Api/Net/Inner/IInnerNetObject.cs
new file mode 100644
index 0000000..c171377
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Net/Inner/IInnerNetObject.cs
@@ -0,0 +1,9 @@
+namespace Impostor.Api.Net.Inner
+{
+ public interface IInnerNetObject
+ {
+ public uint NetId { get; }
+
+ public int OwnerId { get; }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Net/Inner/Objects/Components/IInnerCustomNetworkTransform.cs b/Impostor-dev/src/Impostor.Api/Net/Inner/Objects/Components/IInnerCustomNetworkTransform.cs
new file mode 100644
index 0000000..6d867e7
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Net/Inner/Objects/Components/IInnerCustomNetworkTransform.cs
@@ -0,0 +1,15 @@
+using System.Numerics;
+using System.Threading.Tasks;
+
+namespace Impostor.Api.Net.Inner.Objects.Components
+{
+ public interface IInnerCustomNetworkTransform : IInnerNetObject
+ {
+ /// <summary>
+ /// Snaps the current to the given position <see cref="IInnerPlayerControl"/>.
+ /// </summary>
+ /// <param name="position">The target position.</param>
+ /// <returns>Task that must be awaited.</returns>
+ ValueTask SnapToAsync(Vector2 position);
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Api/Net/Inner/Objects/Components/IInnerPlayerPhysics.cs b/Impostor-dev/src/Impostor.Api/Net/Inner/Objects/Components/IInnerPlayerPhysics.cs
new file mode 100644
index 0000000..9378c5b
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Net/Inner/Objects/Components/IInnerPlayerPhysics.cs
@@ -0,0 +1,6 @@
+namespace Impostor.Api.Net.Inner.Objects.Components
+{
+ public interface IInnerPlayerPhysics : IInnerNetObject
+ {
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Api/Net/Inner/Objects/IInnerGameData.cs b/Impostor-dev/src/Impostor.Api/Net/Inner/Objects/IInnerGameData.cs
new file mode 100644
index 0000000..6e41020
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Net/Inner/Objects/IInnerGameData.cs
@@ -0,0 +1,6 @@
+namespace Impostor.Api.Net.Inner.Objects
+{
+ public interface IInnerGameData : IInnerNetObject
+ {
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Api/Net/Inner/Objects/IInnerLobbyBehaviour.cs b/Impostor-dev/src/Impostor.Api/Net/Inner/Objects/IInnerLobbyBehaviour.cs
new file mode 100644
index 0000000..f05f4cf
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Net/Inner/Objects/IInnerLobbyBehaviour.cs
@@ -0,0 +1,6 @@
+namespace Impostor.Api.Net.Inner.Objects
+{
+ public interface IInnerLobbyBehaviour : IInnerNetObject
+ {
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Api/Net/Inner/Objects/IInnerMeetingHud.cs b/Impostor-dev/src/Impostor.Api/Net/Inner/Objects/IInnerMeetingHud.cs
new file mode 100644
index 0000000..9c89d05
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Net/Inner/Objects/IInnerMeetingHud.cs
@@ -0,0 +1,6 @@
+namespace Impostor.Api.Net.Inner.Objects
+{
+ public interface IInnerMeetingHud
+ {
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Api/Net/Inner/Objects/IInnerPlayerControl.cs b/Impostor-dev/src/Impostor.Api/Net/Inner/Objects/IInnerPlayerControl.cs
new file mode 100644
index 0000000..04558b9
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Net/Inner/Objects/IInnerPlayerControl.cs
@@ -0,0 +1,116 @@
+using System.Threading.Tasks;
+using Impostor.Api.Innersloth.Customization;
+using Impostor.Api.Net.Inner.Objects.Components;
+
+namespace Impostor.Api.Net.Inner.Objects
+{
+ public interface IInnerPlayerControl : IInnerNetObject
+ {
+ /// <summary>
+ /// Gets the <see cref="PlayerId"/> assigned by the client of the host of the game.
+ /// </summary>
+ byte PlayerId { get; }
+
+ /// <summary>
+ /// Gets the <see cref="IInnerPlayerPhysics"/> of the <see cref="IInnerPlayerControl"/>.
+ /// Contains vent logic.
+ /// </summary>
+ IInnerPlayerPhysics Physics { get; }
+
+ /// <summary>
+ /// Gets the <see cref="IInnerCustomNetworkTransform"/> of the <see cref="IInnerPlayerControl"/>.
+ /// Contains position data about the player.
+ /// </summary>
+ IInnerCustomNetworkTransform NetworkTransform { get; }
+
+ /// <summary>
+ /// Gets the <see cref="IInnerPlayerInfo"/> of the <see cref="IInnerPlayerControl"/>.
+ /// Contains metadata about the player.
+ /// </summary>
+ IInnerPlayerInfo PlayerInfo { get; }
+
+ /// <summary>
+ /// Sets the name of the current <see cref="IInnerPlayerControl"/>.
+ /// Visible to all players.
+ /// </summary>
+ /// <param name="name">A name for the player.</param>
+ /// <returns>Task that must be awaited.</returns>
+ ValueTask SetNameAsync(string name);
+
+ /// <summary>
+ /// Sets the color of the current <see cref="IInnerPlayerControl"/>.
+ /// Visible to all players.
+ /// </summary>
+ /// <param name="colorId">A color for the player.</param>
+ /// <returns>Task that must be awaited.</returns>
+ ValueTask SetColorAsync(byte colorId);
+
+ /// <param name="colorType">A color for the player.</param>
+ /// <inheritdoc cref="SetColorAsync(byte)" />
+ ValueTask SetColorAsync(ColorType colorType);
+
+ /// <summary>
+ /// Sets the hat of the current <see cref="IInnerPlayerControl"/>.
+ /// Visible to all players.
+ /// </summary>
+ /// <param name="hatId">An hat for the player.</param>
+ /// <returns>Task that must be awaited.</returns>
+ ValueTask SetHatAsync(uint hatId);
+
+ /// <param name="hatType">An hat for the player.</param>
+ /// <inheritdoc cref="SetHatAsync(uint)" />
+ ValueTask SetHatAsync(HatType hatType);
+
+ /// <summary>
+ /// Sets the pet of the current <see cref="IInnerPlayerControl"/>.
+ /// Visible to all players.
+ /// </summary>
+ /// <param name="petId">A pet for the player.</param>
+ /// <returns>Task that must be awaited.</returns>
+ ValueTask SetPetAsync(uint petId);
+
+ /// <param name="petType">A pet for the player.</param>
+ /// <inheritdoc cref="SetPetAsync(uint)" />
+ ValueTask SetPetAsync(PetType petType);
+
+ /// <summary>
+ /// Sets the skin of the current <see cref="IInnerPlayerControl"/>.
+ /// Visible to all players.
+ /// </summary>
+ /// <param name="skinId">A skin for the player.</param>
+ /// <returns>Task that must be awaited.</returns>
+ ValueTask SetSkinAsync(uint skinId);
+
+ /// <param name="skinType">A skin for the player.</param>
+ /// <inheritdoc cref="SetSkinAsync(uint)" />
+ ValueTask SetSkinAsync(SkinType skinType);
+
+ /// <summary>
+ /// Send a chat message as the current <see cref="IInnerPlayerControl"/>.
+ /// Visible to all players.
+ /// </summary>
+ /// <param name="text">The message to send.</param>
+ /// <returns>Task that must be awaited.</returns>
+ ValueTask SendChatAsync(string text);
+
+ /// <summary>
+ /// Send a chat message as the current <see cref="IInnerPlayerControl"/>.
+ /// Visible to only the current.
+ /// </summary>
+ /// <param name="text">The message to send.</param>
+ /// <param name="player">
+ /// The player that should receive this chat message.
+ /// When left as null, will send message to self.
+ /// </param>
+ /// <returns>Task that must be awaited.</returns>
+ ValueTask SendChatToPlayerAsync(string text, IInnerPlayerControl? player = null);
+
+ /// <summary>
+ /// Sets the current to be murdered by an impostor <see cref="IInnerPlayerControl"/>.
+ /// Visible to all players.
+ /// </summary>
+ /// /// <param name="impostor">The Impostor who kill.</param>
+ /// <returns>Task that must be awaited.</returns>
+ ValueTask SetMurderedByAsync(IClientPlayer impostor);
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Api/Net/Inner/Objects/IInnerPlayerInfo.cs b/Impostor-dev/src/Impostor.Api/Net/Inner/Objects/IInnerPlayerInfo.cs
new file mode 100644
index 0000000..6cb3302
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Net/Inner/Objects/IInnerPlayerInfo.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Collections.Generic;
+using Impostor.Api.Innersloth;
+
+namespace Impostor.Api.Net.Inner.Objects
+{
+ public interface IInnerPlayerInfo
+ {
+ /// <summary>
+ /// Gets the name of the player as decided by the host.
+ /// </summary>
+ string PlayerName { get; }
+
+ /// <summary>
+ /// Gets the color of the player.
+ /// </summary>
+ byte ColorId { get; }
+
+ /// <summary>
+ /// Gets the hat of the player.
+ /// </summary>
+ uint HatId { get; }
+
+ /// <summary>
+ /// Gets the pet of the player.
+ /// </summary>
+ uint PetId { get; }
+
+ /// <summary>
+ /// Gets the skin of the player.
+ /// </summary>
+ uint SkinId { get; }
+
+ /// <summary>
+ /// Gets a value indicating whether the player is an impostor.
+ /// </summary>
+ bool IsImpostor { get; }
+
+ /// <summary>
+ /// Gets a value indicating whether the player is a dead in the current game.
+ /// </summary>
+ bool IsDead { get; }
+
+ /// <summary>
+ /// Gets the reason why the player is dead in the current game.
+ /// </summary>
+ DeathReason LastDeathReason { get; }
+
+ IEnumerable<ITaskInfo> Tasks { get; }
+
+ DateTimeOffset LastMurder { get; }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Api/Net/Inner/Objects/IInnerShipStatus.cs b/Impostor-dev/src/Impostor.Api/Net/Inner/Objects/IInnerShipStatus.cs
new file mode 100644
index 0000000..c0a05ae
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Net/Inner/Objects/IInnerShipStatus.cs
@@ -0,0 +1,7 @@
+namespace Impostor.Api.Net.Inner.Objects
+{
+ public interface IInnerShipStatus : IInnerNetObject
+ {
+
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Net/Inner/Objects/IInnerVoteBanSystem.cs b/Impostor-dev/src/Impostor.Api/Net/Inner/Objects/IInnerVoteBanSystem.cs
new file mode 100644
index 0000000..d0a816d
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Net/Inner/Objects/IInnerVoteBanSystem.cs
@@ -0,0 +1,7 @@
+namespace Impostor.Api.Net.Inner.Objects
+{
+ public interface IInnerVoteBanSystem : IInnerNetObject
+ {
+
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Net/Inner/Objects/ITaskInfo.cs b/Impostor-dev/src/Impostor.Api/Net/Inner/Objects/ITaskInfo.cs
new file mode 100644
index 0000000..2b6dd86
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Net/Inner/Objects/ITaskInfo.cs
@@ -0,0 +1,14 @@
+using Impostor.Api.Innersloth;
+using Impostor.Api.Net.Messages;
+
+namespace Impostor.Api.Net.Inner.Objects
+{
+ public interface ITaskInfo
+ {
+ uint Id { get; }
+
+ TaskTypes Type { get; }
+
+ bool Complete { get; }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Api/Net/LimboStates.cs b/Impostor-dev/src/Impostor.Api/Net/LimboStates.cs
new file mode 100644
index 0000000..44c493e
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Net/LimboStates.cs
@@ -0,0 +1,13 @@
+using System;
+
+namespace Impostor.Api.Net
+{
+ [Flags]
+ public enum LimboStates
+ {
+ PreSpawn = 1,
+ NotLimbo = 2,
+ WaitingForHost = 4,
+ All = PreSpawn | NotLimbo | WaitingForHost,
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Net/Manager/IClientManager.cs b/Impostor-dev/src/Impostor.Api/Net/Manager/IClientManager.cs
new file mode 100644
index 0000000..92bf89f
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Net/Manager/IClientManager.cs
@@ -0,0 +1,9 @@
+using System.Collections.Generic;
+
+namespace Impostor.Api.Net.Manager
+{
+ public interface IClientManager
+ {
+ IEnumerable<IClient> Clients { get; }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Net/Messages/C2S/Message00HostGameC2S.cs b/Impostor-dev/src/Impostor.Api/Net/Messages/C2S/Message00HostGameC2S.cs
new file mode 100644
index 0000000..4f5b39c
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Net/Messages/C2S/Message00HostGameC2S.cs
@@ -0,0 +1,27 @@
+using System.IO;
+using Impostor.Api.Innersloth;
+
+namespace Impostor.Api.Net.Messages.C2S
+{
+ public static class Message00HostGameC2S
+ {
+ public static void Serialize(IMessageWriter writer, GameOptionsData gameOptionsData)
+ {
+ writer.StartMessage(MessageFlags.HostGame);
+
+ using (var memory = new MemoryStream())
+ using (var writerBin = new BinaryWriter(memory))
+ {
+ gameOptionsData.Serialize(writerBin, GameOptionsData.LatestVersion);
+ writer.WriteBytesAndSize(memory.ToArray());
+ }
+
+ writer.EndMessage();
+ }
+
+ public static GameOptionsData Deserialize(IMessageReader reader)
+ {
+ return GameOptionsData.DeserializeCreate(reader);
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Net/Messages/C2S/Message01JoinGameC2S.cs b/Impostor-dev/src/Impostor.Api/Net/Messages/C2S/Message01JoinGameC2S.cs
new file mode 100644
index 0000000..f121b97
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Net/Messages/C2S/Message01JoinGameC2S.cs
@@ -0,0 +1,20 @@
+using System;
+
+namespace Impostor.Api.Net.Messages.C2S
+{
+ public static class Message01JoinGameC2S
+ {
+ public static void Serialize(IMessageWriter writer)
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public static void Deserialize(IMessageReader reader, out int gameCode, out byte unknown)
+ {
+ var slice = reader.ReadBytes(sizeof(Int32) + sizeof(byte)).Span;
+
+ gameCode = slice.ReadInt32();
+ unknown = slice.ReadByte();
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Net/Messages/C2S/Message04RemovePlayerC2S.cs b/Impostor-dev/src/Impostor.Api/Net/Messages/C2S/Message04RemovePlayerC2S.cs
new file mode 100644
index 0000000..99cdcfa
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Net/Messages/C2S/Message04RemovePlayerC2S.cs
@@ -0,0 +1,16 @@
+namespace Impostor.Api.Net.Messages.C2S
+{
+ public class Message04RemovePlayerC2S
+ {
+ public static void Serialize(IMessageWriter writer)
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public static void Deserialize(IMessageReader reader, out int playerId, out byte reason)
+ {
+ playerId = reader.ReadPackedInt32();
+ reason = reader.ReadByte();
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Net/Messages/C2S/Message08EndGameC2S.cs b/Impostor-dev/src/Impostor.Api/Net/Messages/C2S/Message08EndGameC2S.cs
new file mode 100644
index 0000000..7ca5e3a
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Net/Messages/C2S/Message08EndGameC2S.cs
@@ -0,0 +1,18 @@
+using Impostor.Api.Innersloth;
+
+namespace Impostor.Api.Net.Messages.C2S
+{
+ public class Message08EndGameC2S
+ {
+ public static void Serialize(IMessageWriter writer)
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public static void Deserialize(IMessageReader reader, out GameOverReason gameOverReason)
+ {
+ gameOverReason = (GameOverReason)reader.ReadByte();
+ reader.ReadBoolean(); // showAd
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Net/Messages/C2S/Message10AlterGameC2S.cs b/Impostor-dev/src/Impostor.Api/Net/Messages/C2S/Message10AlterGameC2S.cs
new file mode 100644
index 0000000..330f3b5
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Net/Messages/C2S/Message10AlterGameC2S.cs
@@ -0,0 +1,20 @@
+using Impostor.Api.Innersloth;
+
+namespace Impostor.Api.Net.Messages.C2S
+{
+ public class Message10AlterGameC2S
+ {
+ public static void Serialize(IMessageWriter writer)
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public static void Deserialize(IMessageReader reader, out AlterGameTags gameTag, out bool isPublic)
+ {
+ var slice = reader.ReadBytes(sizeof(byte) + sizeof(byte)).Span;
+
+ gameTag = (AlterGameTags)slice.ReadByte();
+ isPublic = slice.ReadBoolean();
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Net/Messages/C2S/Message11KickPlayerC2S.cs b/Impostor-dev/src/Impostor.Api/Net/Messages/C2S/Message11KickPlayerC2S.cs
new file mode 100644
index 0000000..7c5b8b9
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Net/Messages/C2S/Message11KickPlayerC2S.cs
@@ -0,0 +1,16 @@
+namespace Impostor.Api.Net.Messages.C2S
+{
+ public class Message11KickPlayerC2S
+ {
+ public static void Serialize(IMessageWriter writer)
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public static void Deserialize(IMessageReader reader, out int playerId, out bool isBan)
+ {
+ playerId = reader.ReadPackedInt32();
+ isBan = reader.ReadBoolean();
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Net/Messages/C2S/Message16GetGameListC2S.cs b/Impostor-dev/src/Impostor.Api/Net/Messages/C2S/Message16GetGameListC2S.cs
new file mode 100644
index 0000000..2b7e12a
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Net/Messages/C2S/Message16GetGameListC2S.cs
@@ -0,0 +1,18 @@
+using Impostor.Api.Innersloth;
+
+namespace Impostor.Api.Net.Messages.C2S
+{
+ public class Message16GetGameListC2S
+ {
+ public static void Serialize(IMessageWriter writer)
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public static void Deserialize(IMessageReader reader, out GameOptionsData options)
+ {
+ reader.ReadPackedInt32(); // Hardcoded 0.
+ options = GameOptionsData.DeserializeCreate(reader);
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Net/Messages/IMessageReader.cs b/Impostor-dev/src/Impostor.Api/Net/Messages/IMessageReader.cs
new file mode 100644
index 0000000..87c06c4
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Net/Messages/IMessageReader.cs
@@ -0,0 +1,68 @@
+using System;
+
+namespace Impostor.Api.Net.Messages
+{
+ public interface IMessageReader : IDisposable
+ {
+ /// <summary>
+ /// Gets the tag of the message.
+ /// </summary>
+ byte Tag { get; }
+
+ /// <summary>
+ /// Gets the buffer of the message.
+ /// </summary>
+ byte[] Buffer { get; }
+
+ /// <summary>
+ /// Gets the offset of our current <see cref="IMessageReader"/> in the entire <see cref="Buffer"/>.
+ /// </summary>
+ int Offset { get; }
+
+ /// <summary>
+ /// Gets the current position of the reader.
+ /// </summary>
+ int Position { get; }
+
+ /// <summary>
+ /// Gets the length of the buffer.
+ /// </summary>
+ int Length { get; }
+
+ IMessageReader ReadMessage();
+
+ bool ReadBoolean();
+
+ sbyte ReadSByte();
+
+ byte ReadByte();
+
+ ushort ReadUInt16();
+
+ short ReadInt16();
+
+ uint ReadUInt32();
+
+ int ReadInt32();
+
+ float ReadSingle();
+
+ string ReadString();
+
+ ReadOnlyMemory<byte> ReadBytesAndSize();
+
+ ReadOnlyMemory<byte> ReadBytes(int length);
+
+ int ReadPackedInt32();
+
+ uint ReadPackedUInt32();
+
+ void CopyTo(IMessageWriter writer);
+
+ void Seek(int position);
+
+ void RemoveMessage(IMessageReader message);
+
+ IMessageReader Copy(int offset = 0);
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Api/Net/Messages/IMessageWriter.cs b/Impostor-dev/src/Impostor.Api/Net/Messages/IMessageWriter.cs
new file mode 100644
index 0000000..4f6765b
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Net/Messages/IMessageWriter.cs
@@ -0,0 +1,127 @@
+using System;
+using System.Net;
+using Impostor.Api.Games;
+
+namespace Impostor.Api.Net.Messages
+{
+ /// <summary>
+ /// Base message writer.
+ /// </summary>
+ public interface IMessageWriter : IDisposable
+ {
+ public byte[] Buffer { get; }
+
+ public int Length { get; set; }
+
+ public int Position { get; set; }
+
+ public MessageType SendOption { get; }
+
+ /// <summary>
+ /// Writes a boolean to the message.
+ /// </summary>
+ /// <param name="value">Value to write.</param>
+ void Write(bool value);
+
+ /// <summary>
+ /// Writes a sbyte to the message.
+ /// </summary>
+ /// <param name="value">Value to write.</param>
+ void Write(sbyte value);
+
+ /// <summary>
+ /// Writes a byte to the message.
+ /// </summary>
+ /// <param name="value">Value to write.</param>
+ void Write(byte value);
+
+ /// <summary>
+ /// Writes a short to the message.
+ /// </summary>
+ /// <param name="value">Value to write.</param>
+ void Write(short value);
+
+ /// <summary>
+ /// Writes an ushort to the message.
+ /// </summary>
+ /// <param name="value">Value to write.</param>
+ void Write(ushort value);
+
+ /// <summary>
+ /// Writes an uint to the message.
+ /// </summary>
+ /// <param name="value">Value to write.</param>
+ void Write(uint value);
+
+ /// <summary>
+ /// Writes an int to the message.
+ /// </summary>
+ /// <param name="value">Value to write.</param>
+ void Write(int value);
+
+ /// <summary>
+ /// Writes a float to the message.
+ /// </summary>
+ /// <param name="value">Value to write.</param>
+ void Write(float value);
+
+ /// <summary>
+ /// Writes a string to the message.
+ /// </summary>
+ /// <param name="value">Value to write.</param>
+ void Write(string value);
+
+ /// <summary>
+ /// Writes a <see cref="IPAddress"/> to the message.
+ /// </summary>
+ /// <param name="value">Value to write.</param>
+ void Write(IPAddress value);
+
+ /// <summary>
+ /// Writes an packed int to the message.
+ /// </summary>
+ /// <param name="value">Value to write.</param>
+ void WritePacked(int value);
+
+ /// <summary>
+ /// Writes an packed uint to the message.
+ /// </summary>
+ /// <param name="value">Value to write.</param>
+ void WritePacked(uint value);
+
+ /// <summary>
+ /// Writes raw bytes to the message.
+ /// </summary>
+ /// <param name="data">Bytes to write.</param>
+ void Write(ReadOnlyMemory<byte> data);
+
+ /// <summary>
+ /// Writes a game code to the message.
+ /// </summary>
+ /// <param name="value">Value to write.</param>
+ void Write(GameCode value);
+
+ void WriteBytesAndSize(byte[] bytes);
+
+ void WriteBytesAndSize(byte[] bytes, int length);
+
+ void WriteBytesAndSize(byte[] bytes, int offset, int length);
+
+ /// <summary>
+ /// Starts a new message.
+ /// </summary>
+ /// <param name="typeFlag">Message flag header.</param>
+ void StartMessage(byte typeFlag);
+
+ /// <summary>
+ /// Mark the end of the message.
+ /// </summary>
+ void EndMessage();
+
+ /// <summary>
+ /// Clear the message writer.
+ /// </summary>
+ /// <param name="type">New type of the message.</param>
+ void Clear(MessageType type);
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Net/Messages/IMessageWriterProvider.cs b/Impostor-dev/src/Impostor.Api/Net/Messages/IMessageWriterProvider.cs
new file mode 100644
index 0000000..f398939
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Net/Messages/IMessageWriterProvider.cs
@@ -0,0 +1,16 @@
+namespace Impostor.Api.Net.Messages
+{
+ public interface IMessageWriterProvider
+ {
+ /// <summary>
+ /// Retrieves a <see cref="IMessageWriter"/> from the internal pool.
+ /// Make sure to call <see cref="IMessageWriter.Dispose"/> when you are done!
+ /// </summary>
+ /// <param name="sendOption">
+ /// Whether to send the message as <see cref="MessageType.Reliable"/> or <see cref="MessageType.Unreliable"/>.
+ /// Reliable packets will ensure delivery while unreliable packets may be lost.
+ /// </param>
+ /// <returns>A <see cref="IMessageWriter"/> from the pool.</returns>
+ IMessageWriter Get(MessageType sendOption = MessageType.Unreliable);
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Api/Net/Messages/MessageFlags.cs b/Impostor-dev/src/Impostor.Api/Net/Messages/MessageFlags.cs
new file mode 100644
index 0000000..aea0c60
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Net/Messages/MessageFlags.cs
@@ -0,0 +1,22 @@
+namespace Impostor.Api.Net.Messages
+{
+ public static class MessageFlags
+ {
+ public const byte HostGame = 0;
+ public const byte JoinGame = 1;
+ public const byte StartGame = 2;
+ public const byte RemoveGame = 3;
+ public const byte RemovePlayer = 4;
+ public const byte GameData = 5;
+ public const byte GameDataTo = 6;
+ public const byte JoinedGame = 7;
+ public const byte EndGame = 8;
+ public const byte AlterGame = 10;
+ public const byte KickPlayer = 11;
+ public const byte WaitForHost = 12;
+ public const byte Redirect = 13;
+ public const byte ReselectServer = 14;
+ public const byte GetGameList = 9;
+ public const byte GetGameListV2 = 16;
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Net/Messages/MessageType.cs b/Impostor-dev/src/Impostor.Api/Net/Messages/MessageType.cs
new file mode 100644
index 0000000..1604358
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Net/Messages/MessageType.cs
@@ -0,0 +1,32 @@
+using System;
+
+namespace Impostor.Api.Net.Messages
+{
+ /// <summary>
+ /// Specifies how a message should be sent between connections.
+ /// </summary>
+ [Flags]
+ public enum MessageType : byte
+ {
+ /// <summary>
+ /// Requests unreliable delivery with no fragmentation.
+ /// </summary>
+ /// <remarks>
+ /// Sending data using unreliable delivery means that data is not guaranteed to arrive at it's destination nor is
+ /// it guaranteed to arrive only once. However, unreliable delivery can be faster than other methods and it
+ /// typically requires a smaller number of protocol bytes than other methods. There is also typically less
+ /// processing involved and less memory needed as packets are not stored once sent.
+ /// </remarks>
+ Unreliable,
+
+ /// <summary>
+ /// Requests data be sent reliably but with no fragmentation.
+ /// </summary>
+ /// <remarks>
+ /// Sending data reliably means that data is guaranteed to arrive and to arrive only once. Reliable delivery
+ /// typically requires more processing, more memory (as packets need to be stored in case they need resending),
+ /// a larger number of protocol bytes and can be slower than unreliable delivery.
+ /// </remarks>
+ Reliable,
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Net/Messages/S2C/Message00HostGameS2C.cs b/Impostor-dev/src/Impostor.Api/Net/Messages/S2C/Message00HostGameS2C.cs
new file mode 100644
index 0000000..8402d10
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Net/Messages/S2C/Message00HostGameS2C.cs
@@ -0,0 +1,20 @@
+using System;
+using Impostor.Api.Innersloth;
+
+namespace Impostor.Api.Net.Messages.S2C
+{
+ public static class Message00HostGameS2C
+ {
+ public static void Serialize(IMessageWriter writer, int gameCode)
+ {
+ writer.StartMessage(MessageFlags.HostGame);
+ writer.Write(gameCode);
+ writer.EndMessage();
+ }
+
+ public static GameOptionsData Deserialize(IMessageReader reader)
+ {
+ throw new NotImplementedException();
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Net/Messages/S2C/Message01JoinGameS2C.cs b/Impostor-dev/src/Impostor.Api/Net/Messages/S2C/Message01JoinGameS2C.cs
new file mode 100644
index 0000000..c455201
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Net/Messages/S2C/Message01JoinGameS2C.cs
@@ -0,0 +1,50 @@
+using System;
+using Impostor.Api.Innersloth;
+
+namespace Impostor.Api.Net.Messages.S2C
+{
+ public class Message01JoinGameS2C
+ {
+ public static void SerializeJoin(IMessageWriter writer, bool clear, int gameCode, int playerId, int hostId)
+ {
+ if (clear)
+ {
+ writer.Clear(MessageType.Reliable);
+ }
+
+ writer.StartMessage(MessageFlags.JoinGame);
+ writer.Write(gameCode);
+ writer.Write(playerId);
+ writer.Write(hostId);
+ writer.EndMessage();
+ }
+
+ public static void SerializeError(IMessageWriter writer, bool clear, DisconnectReason reason, string? message = null)
+ {
+ if (clear)
+ {
+ writer.Clear(MessageType.Reliable);
+ }
+
+ writer.StartMessage(MessageFlags.JoinGame);
+ writer.Write((int)reason);
+
+ if (reason == DisconnectReason.Custom)
+ {
+ if (message == null)
+ {
+ throw new ArgumentNullException(nameof(message));
+ }
+
+ writer.Write(message);
+ }
+
+ writer.EndMessage();
+ }
+
+ public static void Deserialize(IMessageReader reader)
+ {
+ throw new System.NotImplementedException();
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Net/Messages/S2C/Message04RemovePlayerS2C.cs b/Impostor-dev/src/Impostor.Api/Net/Messages/S2C/Message04RemovePlayerS2C.cs
new file mode 100644
index 0000000..77b447d
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Net/Messages/S2C/Message04RemovePlayerS2C.cs
@@ -0,0 +1,29 @@
+using Impostor.Api.Innersloth;
+
+namespace Impostor.Api.Net.Messages.S2C
+{
+ public class Message04RemovePlayerS2C
+ {
+ public static void Serialize(IMessageWriter writer, bool clear, int gameCode, int playerId, int hostId, DisconnectReason reason)
+ {
+ // Only a subset of DisconnectReason shows an unique message.
+ // ExitGame, Banned and Kicked.
+ if (clear)
+ {
+ writer.Clear(MessageType.Reliable);
+ }
+
+ writer.StartMessage(MessageFlags.RemovePlayer);
+ writer.Write(gameCode);
+ writer.Write(playerId);
+ writer.Write(hostId);
+ writer.Write((byte)reason);
+ writer.EndMessage();
+ }
+
+ public static void Deserialize(IMessageReader reader)
+ {
+ throw new System.NotImplementedException();
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Net/Messages/S2C/Message07JoinedGameS2C.cs b/Impostor-dev/src/Impostor.Api/Net/Messages/S2C/Message07JoinedGameS2C.cs
new file mode 100644
index 0000000..da6eb40
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Net/Messages/S2C/Message07JoinedGameS2C.cs
@@ -0,0 +1,31 @@
+namespace Impostor.Api.Net.Messages.S2C
+{
+ public static class Message07JoinedGameS2C
+ {
+ public static void Serialize(IMessageWriter writer, bool clear, int gameCode, int playerId, int hostId, int[] otherPlayerIds)
+ {
+ if (clear)
+ {
+ writer.Clear(MessageType.Reliable);
+ }
+
+ writer.StartMessage(MessageFlags.JoinedGame);
+ writer.Write(gameCode);
+ writer.Write(playerId);
+ writer.Write(hostId);
+ writer.WritePacked(otherPlayerIds.Length);
+
+ foreach (var id in otherPlayerIds)
+ {
+ writer.WritePacked(id);
+ }
+
+ writer.EndMessage();
+ }
+
+ public static void Deserialize(IMessageReader reader)
+ {
+ throw new System.NotImplementedException();
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Net/Messages/S2C/Message10AlterGameS2C.cs b/Impostor-dev/src/Impostor.Api/Net/Messages/S2C/Message10AlterGameS2C.cs
new file mode 100644
index 0000000..fa155df
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Net/Messages/S2C/Message10AlterGameS2C.cs
@@ -0,0 +1,26 @@
+using Impostor.Api.Innersloth;
+
+namespace Impostor.Api.Net.Messages.S2C
+{
+ public static class Message10AlterGameS2C
+ {
+ public static void Serialize(IMessageWriter writer, bool clear, int gameCode, bool isPublic)
+ {
+ if (clear)
+ {
+ writer.Clear(MessageType.Reliable);
+ }
+
+ writer.StartMessage(MessageFlags.AlterGame);
+ writer.Write(gameCode);
+ writer.Write((byte)AlterGameTags.ChangePrivacy);
+ writer.Write(isPublic);
+ writer.EndMessage();
+ }
+
+ public static void Deserialize(IMessageReader reader)
+ {
+ throw new System.NotImplementedException();
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Net/Messages/S2C/Message11KickPlayerS2C.cs b/Impostor-dev/src/Impostor.Api/Net/Messages/S2C/Message11KickPlayerS2C.cs
new file mode 100644
index 0000000..1e2b6ef
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Net/Messages/S2C/Message11KickPlayerS2C.cs
@@ -0,0 +1,24 @@
+namespace Impostor.Api.Net.Messages.S2C
+{
+ public class Message11KickPlayerS2C
+ {
+ public static void Serialize(IMessageWriter writer, bool clear, int gameCode, int playerId, bool isBan)
+ {
+ if (clear)
+ {
+ writer.Clear(MessageType.Reliable);
+ }
+
+ writer.StartMessage(MessageFlags.KickPlayer);
+ writer.Write(gameCode);
+ writer.WritePacked(playerId);
+ writer.Write(isBan);
+ writer.EndMessage();
+ }
+
+ public static void Deserialize(IMessageReader reader)
+ {
+ throw new System.NotImplementedException();
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Net/Messages/S2C/Message12WaitForHostS2C.cs b/Impostor-dev/src/Impostor.Api/Net/Messages/S2C/Message12WaitForHostS2C.cs
new file mode 100644
index 0000000..5964b1c
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Net/Messages/S2C/Message12WaitForHostS2C.cs
@@ -0,0 +1,23 @@
+namespace Impostor.Api.Net.Messages.S2C
+{
+ public class Message12WaitForHostS2C
+ {
+ public static void Serialize(IMessageWriter writer, bool clear, int gameCode, int playerId)
+ {
+ if (clear)
+ {
+ writer.Clear(MessageType.Reliable);
+ }
+
+ writer.StartMessage(MessageFlags.WaitForHost);
+ writer.Write(gameCode);
+ writer.Write(playerId);
+ writer.EndMessage();
+ }
+
+ public static void Deserialize(IMessageReader reader)
+ {
+ throw new System.NotImplementedException();
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Net/Messages/S2C/Message13RedirectS2C.cs b/Impostor-dev/src/Impostor.Api/Net/Messages/S2C/Message13RedirectS2C.cs
new file mode 100644
index 0000000..4b93b0e
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Net/Messages/S2C/Message13RedirectS2C.cs
@@ -0,0 +1,25 @@
+using System.Net;
+
+namespace Impostor.Api.Net.Messages.S2C
+{
+ public class Message13RedirectS2C
+ {
+ public static void Serialize(IMessageWriter writer, bool clear, IPEndPoint ipEndPoint)
+ {
+ if (clear)
+ {
+ writer.Clear(MessageType.Reliable);
+ }
+
+ writer.StartMessage(MessageFlags.Redirect);
+ writer.Write(ipEndPoint.Address);
+ writer.Write((ushort)ipEndPoint.Port);
+ writer.EndMessage();
+ }
+
+ public static void Deserialize(IMessageReader reader)
+ {
+ throw new System.NotImplementedException();
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Net/Messages/S2C/Message16GetGameListS2C.cs b/Impostor-dev/src/Impostor.Api/Net/Messages/S2C/Message16GetGameListS2C.cs
new file mode 100644
index 0000000..93386d7
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Net/Messages/S2C/Message16GetGameListS2C.cs
@@ -0,0 +1,46 @@
+using System.Collections.Generic;
+using Impostor.Api.Games;
+
+namespace Impostor.Api.Net.Messages.S2C
+{
+ public class Message16GetGameListS2C
+ {
+ public static void Serialize(IMessageWriter writer, int skeldGameCount, int miraHqGameCount, int polusGameCount, IEnumerable<IGame> games)
+ {
+ writer.StartMessage(MessageFlags.GetGameListV2);
+
+ // Count
+ writer.StartMessage(1);
+ writer.Write(skeldGameCount); // The Skeld
+ writer.Write(miraHqGameCount); // Mira HQ
+ writer.Write(polusGameCount); // Polus
+ writer.EndMessage();
+
+ // Listing
+ writer.StartMessage(0);
+
+ foreach (var game in games)
+ {
+ writer.StartMessage(0);
+ writer.Write(game.PublicIp.Address);
+ writer.Write((ushort)game.PublicIp.Port);
+ writer.Write(game.Code);
+ writer.Write(game.Host.Client.Name);
+ writer.Write((byte)game.PlayerCount);
+ writer.WritePacked(1); // TODO: What does Age do?
+ writer.Write((byte)game.Options.MapId);
+ writer.Write((byte)game.Options.NumImpostors);
+ writer.Write((byte)game.Options.MaxPlayers);
+ writer.EndMessage();
+ }
+
+ writer.EndMessage();
+ writer.EndMessage();
+ }
+
+ public static void Deserialize(IMessageReader reader)
+ {
+ throw new System.NotImplementedException();
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Plugins/IPlugin.cs b/Impostor-dev/src/Impostor.Api/Plugins/IPlugin.cs
new file mode 100644
index 0000000..fd0f38f
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Plugins/IPlugin.cs
@@ -0,0 +1,14 @@
+using System.Threading.Tasks;
+using Impostor.Api.Events;
+
+namespace Impostor.Api.Plugins
+{
+ public interface IPlugin : IEventListener
+ {
+ ValueTask EnableAsync();
+
+ ValueTask DisableAsync();
+
+ ValueTask ReloadAsync();
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Plugins/IPluginStartup.cs b/Impostor-dev/src/Impostor.Api/Plugins/IPluginStartup.cs
new file mode 100644
index 0000000..aa6a35f
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Plugins/IPluginStartup.cs
@@ -0,0 +1,12 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+
+namespace Impostor.Api.Plugins
+{
+ public interface IPluginStartup
+ {
+ void ConfigureHost(IHostBuilder host);
+
+ void ConfigureServices(IServiceCollection services);
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Plugins/ImpostorPluginAttribute.cs b/Impostor-dev/src/Impostor.Api/Plugins/ImpostorPluginAttribute.cs
new file mode 100644
index 0000000..b31bd47
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Plugins/ImpostorPluginAttribute.cs
@@ -0,0 +1,24 @@
+using System;
+
+namespace Impostor.Api.Plugins
+{
+ [AttributeUsage(AttributeTargets.Class)]
+ public class ImpostorPluginAttribute : Attribute
+ {
+ public ImpostorPluginAttribute(string package, string name, string author, string version)
+ {
+ Package = package;
+ Name = name;
+ Author = author;
+ Version = version;
+ }
+
+ public string Package { get; }
+
+ public string Name { get; }
+
+ public string Author { get; }
+
+ public string Version { get; }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Plugins/PluginBase.cs b/Impostor-dev/src/Impostor.Api/Plugins/PluginBase.cs
new file mode 100644
index 0000000..0384363
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Plugins/PluginBase.cs
@@ -0,0 +1,22 @@
+using System.Threading.Tasks;
+
+namespace Impostor.Api.Plugins
+{
+ public class PluginBase : IPlugin
+ {
+ public virtual ValueTask EnableAsync()
+ {
+ return default;
+ }
+
+ public virtual ValueTask DisableAsync()
+ {
+ return default;
+ }
+
+ public virtual ValueTask ReloadAsync()
+ {
+ return default;
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/ProjectRules.ruleset b/Impostor-dev/src/Impostor.Api/ProjectRules.ruleset
new file mode 100644
index 0000000..4ba23c2
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/ProjectRules.ruleset
@@ -0,0 +1,17 @@
+<RuleSet Name="Rules for Hello World project" Description="These rules focus on critical issues for the Hello World app." ToolsVersion="10.0">
+ <Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.CSharp.OrderingRules">
+ <Rule Id="SA1200" Action="None" />
+ </Rules>
+ <Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.CSharp.DocumentationRules">
+ <Rule Id="SA1600" Action="None" />
+ <Rule Id="SA1601" Action="None" />
+ <Rule Id="SA1602" Action="None" />
+ <Rule Id="SA1633" Action="None" />
+ </Rules>
+ <Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.CSharp.ReadabilityRules">
+ <Rule Id="SA1101" Action="None" />
+ </Rules>
+ <Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.CSharp.NamingRules">
+ <Rule Id="SA1309" Action="None" />
+ </Rules>
+</RuleSet> \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Properties/AssemblyInfo.cs b/Impostor-dev/src/Impostor.Api/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..c02e44a
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Properties/AssemblyInfo.cs
@@ -0,0 +1,3 @@
+using System.Runtime.CompilerServices;
+
+[assembly:InternalsVisibleTo("Impostor.Server")] \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Api/Unity/Mathf.cs b/Impostor-dev/src/Impostor.Api/Unity/Mathf.cs
new file mode 100644
index 0000000..4b03417
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Api/Unity/Mathf.cs
@@ -0,0 +1,54 @@
+namespace Impostor.Api.Unity
+{
+ public static class Mathf
+ {
+ /// <summary>
+ /// <para>Clamps the given value between the given minimum float and maximum float values. Returns the given value if it is within the min and max range.</para>
+ /// </summary>
+ /// <param name="value">The floating point value to restrict inside the range defined by the min and max values.</param>
+ /// <param name="min">The minimum floating point value to compare against.</param>
+ /// <param name="max">The maximum floating point value to compare against.</param>
+ /// <returns>
+ /// <para>The float result between the min and max values.</para>
+ /// </returns>
+ public static float Clamp(float value, float min, float max)
+ {
+ if (value < (double)min)
+ {
+ value = min;
+ }
+ else if (value > (double)max)
+ {
+ value = max;
+ }
+
+ return value;
+ }
+
+ /// <summary>
+ /// <para>Clamps value between 0 and 1 and returns value.</para>
+ /// </summary>
+ /// <param name="value">Value.</param>
+ /// <returns>Clamped value.</returns>
+ public static float Clamp01(float value)
+ {
+ if (value < 0.0)
+ {
+ return 0.0f;
+ }
+
+ return (double)value > 1.0 ? 1f : value;
+ }
+
+ /// <summary>
+ /// <para>Linearly interpolates between a and b by t.</para>
+ /// </summary>
+ /// <param name="a">The start value.</param>
+ /// <param name="b">The end value.</param>
+ /// <param name="t">The interpolation value between the two floats.</param>
+ /// <returns>
+ /// <para>The interpolated float result between the two float values.</para>
+ /// </returns>
+ public static float Lerp(float a, float b, float t) => a + ((b - a) * Clamp01(t));
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Benchmarks/.gitignore b/Impostor-dev/src/Impostor.Benchmarks/.gitignore
new file mode 100644
index 0000000..1c2dac6
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Benchmarks/.gitignore
@@ -0,0 +1 @@
+BenchmarkDotNet.Artifacts \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Benchmarks/Data/MessageReader_Bytes.cs b/Impostor-dev/src/Impostor.Benchmarks/Data/MessageReader_Bytes.cs
new file mode 100644
index 0000000..2aba724
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Benchmarks/Data/MessageReader_Bytes.cs
@@ -0,0 +1,172 @@
+using System;
+using System.IO;
+using System.Runtime.CompilerServices;
+using System.Text;
+
+namespace Impostor.Benchmarks.Data
+{
+ public class MessageReader_Bytes
+ {
+ public byte Tag { get; }
+ public byte[] Buffer { get; }
+ public int Position { get; set; }
+ public int Length { get; set; }
+ public int BytesRemaining => this.Length - this.Position;
+
+ public MessageReader_Bytes(byte[] buffer, int position = 0, int length = 0)
+ {
+ Tag = byte.MaxValue;
+ Buffer = buffer;
+ Position = position;
+ Length = length;
+ }
+
+ public MessageReader_Bytes(byte tag, byte[] buffer, int position = 0, int length = 0)
+ {
+ Tag = tag;
+ Buffer = buffer;
+ Position = position;
+ Length = length;
+ }
+
+ public MessageReader_Bytes ReadMessage()
+ {
+ var length = ReadUInt16();
+ var tag = FastByte();
+ var pos = Position;
+
+ Position += length;
+ return new MessageReader_Bytes(tag, Buffer, pos, length);
+ }
+
+ public bool ReadBoolean()
+ {
+ byte val = FastByte();
+ return val != 0;
+ }
+
+ public sbyte ReadSByte()
+ {
+ return (sbyte)FastByte();
+ }
+
+ public byte ReadByte()
+ {
+ return FastByte();
+ }
+
+ public ushort ReadUInt16()
+ {
+ return (ushort)(this.FastByte() |
+ this.FastByte() << 8);
+ }
+
+ public short ReadInt16()
+ {
+ return (short)(this.FastByte() |
+ this.FastByte() << 8);
+ }
+
+ public uint ReadUInt32()
+ {
+ return this.FastByte()
+ | (uint)this.FastByte() << 8
+ | (uint)this.FastByte() << 16
+ | (uint)this.FastByte() << 24;
+ }
+
+ public int ReadInt32()
+ {
+ return this.FastByte()
+ | this.FastByte() << 8
+ | this.FastByte() << 16
+ | this.FastByte() << 24;
+ }
+
+ public unsafe float ReadSingle()
+ {
+ float output = 0;
+ fixed (byte* bufPtr = &this.Buffer[Position])
+ {
+ byte* outPtr = (byte*)&output;
+
+ *outPtr = *bufPtr;
+ *(outPtr + 1) = *(bufPtr + 1);
+ *(outPtr + 2) = *(bufPtr + 2);
+ *(outPtr + 3) = *(bufPtr + 3);
+ }
+
+ this.Position += 4;
+ return output;
+ }
+
+ public string ReadString()
+ {
+ var len = this.ReadPackedInt32();
+
+ if (this.BytesRemaining < len)
+ {
+ throw new InvalidDataException($"Read length is longer than message length: {len} of {this.BytesRemaining}");
+ }
+
+ var output = Encoding.UTF8.GetString(this.Buffer, Position, len);
+ this.Position += len;
+ return output;
+ }
+
+ public Span<byte> ReadBytesAndSize()
+ {
+ var len = ReadPackedInt32();
+ return ReadBytes(len);
+ }
+
+ public Span<byte> ReadBytes(int length)
+ {
+ var output = Buffer.AsSpan(Position, length);
+ Position += length;
+ return output;
+ }
+
+ public int ReadPackedInt32()
+ {
+ return (int)ReadPackedUInt32();
+ }
+
+ public uint ReadPackedUInt32()
+ {
+ bool readMore = true;
+ int shift = 0;
+ uint output = 0;
+
+ while (readMore)
+ {
+ byte b = FastByte();
+ if (b >= 0x80)
+ {
+ readMore = true;
+ b ^= 0x80;
+ }
+ else
+ {
+ readMore = false;
+ }
+
+ output |= (uint)(b << shift);
+ shift += 7;
+ }
+
+ return output;
+ }
+
+ public MessageReader_Bytes Slice(int start, int length)
+ {
+ return new MessageReader_Bytes(Tag, Buffer, start, length);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private byte FastByte()
+ {
+ return Buffer[Position++];
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Benchmarks/Data/MessageReader_Bytes_Pooled.cs b/Impostor-dev/src/Impostor.Benchmarks/Data/MessageReader_Bytes_Pooled.cs
new file mode 100644
index 0000000..1103336
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Benchmarks/Data/MessageReader_Bytes_Pooled.cs
@@ -0,0 +1,220 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.IO;
+using System.Runtime.CompilerServices;
+using System.Text;
+
+namespace Impostor.Benchmarks.Data
+{
+ public class MessageReader_Bytes_Pooled
+ {
+ private static ConcurrentQueue<MessageReader_Bytes_Pooled> _readers;
+
+ static MessageReader_Bytes_Pooled()
+ {
+ var instances = new List<MessageReader_Bytes_Pooled>();
+
+ for (var i = 0; i < 10000; i++)
+ {
+ instances.Add(new MessageReader_Bytes_Pooled());
+ }
+
+ _readers = new ConcurrentQueue<MessageReader_Bytes_Pooled>(instances);
+ }
+
+ public byte Tag { get; set; }
+ public byte[] Buffer { get; set; }
+ public int Position { get; set; }
+ public int Length { get; set; }
+ public int BytesRemaining => this.Length - this.Position;
+
+ public void Update(byte[] buffer, int position = 0, int length = 0)
+ {
+ Tag = byte.MaxValue;
+ Buffer = buffer;
+ Position = position;
+ Length = length;
+ }
+
+ public void Update(byte tag, byte[] buffer, int position = 0, int length = 0)
+ {
+ Tag = tag;
+ Buffer = buffer;
+ Position = position;
+ Length = length;
+ }
+
+ public MessageReader_Bytes_Pooled ReadMessage()
+ {
+ var length = ReadUInt16();
+ var tag = FastByte();
+ var pos = Position;
+
+ Position += length;
+
+ if (!_readers.TryDequeue(out var result))
+ {
+ throw new Exception("Failed to get pooled instance");
+ }
+
+ result.Update(tag, Buffer, pos, length);
+
+ return result;
+ }
+
+ public bool ReadBoolean()
+ {
+ byte val = FastByte();
+ return val != 0;
+ }
+
+ public sbyte ReadSByte()
+ {
+ return (sbyte)FastByte();
+ }
+
+ public byte ReadByte()
+ {
+ return FastByte();
+ }
+
+ public ushort ReadUInt16()
+ {
+ return (ushort)(this.FastByte() |
+ this.FastByte() << 8);
+ }
+
+ public short ReadInt16()
+ {
+ return (short)(this.FastByte() |
+ this.FastByte() << 8);
+ }
+
+ public uint ReadUInt32()
+ {
+ return this.FastByte()
+ | (uint)this.FastByte() << 8
+ | (uint)this.FastByte() << 16
+ | (uint)this.FastByte() << 24;
+ }
+
+ public int ReadInt32()
+ {
+ return this.FastByte()
+ | this.FastByte() << 8
+ | this.FastByte() << 16
+ | this.FastByte() << 24;
+ }
+
+ public unsafe float ReadSingle()
+ {
+ float output = 0;
+ fixed (byte* bufPtr = &this.Buffer[Position])
+ {
+ byte* outPtr = (byte*)&output;
+
+ *outPtr = *bufPtr;
+ *(outPtr + 1) = *(bufPtr + 1);
+ *(outPtr + 2) = *(bufPtr + 2);
+ *(outPtr + 3) = *(bufPtr + 3);
+ }
+
+ this.Position += 4;
+ return output;
+ }
+
+ public string ReadString()
+ {
+ var len = this.ReadPackedInt32();
+
+ if (this.BytesRemaining < len)
+ {
+ throw new InvalidDataException($"Read length is longer than message length: {len} of {this.BytesRemaining}");
+ }
+
+ var output = Encoding.UTF8.GetString(this.Buffer, Position, len);
+ this.Position += len;
+ return output;
+ }
+
+ public Span<byte> ReadBytesAndSize()
+ {
+ var len = ReadPackedInt32();
+ return ReadBytes(len);
+ }
+
+ public Span<byte> ReadBytes(int length)
+ {
+ var output = Buffer.AsSpan(Position, length);
+ Position += length;
+ return output;
+ }
+
+ public int ReadPackedInt32()
+ {
+ return (int)ReadPackedUInt32();
+ }
+
+ public uint ReadPackedUInt32()
+ {
+ bool readMore = true;
+ int shift = 0;
+ uint output = 0;
+
+ while (readMore)
+ {
+ byte b = FastByte();
+ if (b >= 0x80)
+ {
+ readMore = true;
+ b ^= 0x80;
+ }
+ else
+ {
+ readMore = false;
+ }
+
+ output |= (uint)(b << shift);
+ shift += 7;
+ }
+
+ return output;
+ }
+
+ public MessageReader_Bytes_Pooled Slice(int start, int length)
+ {
+ if (!_readers.TryDequeue(out var result))
+ {
+ throw new Exception("Failed to get pooled instance");
+ }
+
+ result.Update(Tag, Buffer, start, length);
+
+ return result;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private byte FastByte()
+ {
+ return Buffer[Position++];
+ }
+
+ public static MessageReader_Bytes_Pooled Get(byte[] data)
+ {
+ if (!_readers.TryDequeue(out var result))
+ {
+ throw new Exception("Failed to get pooled instance");
+ }
+
+ result.Update(data);
+
+ return result;
+ }
+
+ public static void Return(MessageReader_Bytes_Pooled instance)
+ {
+ _readers.Enqueue(instance);
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Benchmarks/Data/MessageReader_Bytes_Pooled_Improved.cs b/Impostor-dev/src/Impostor.Benchmarks/Data/MessageReader_Bytes_Pooled_Improved.cs
new file mode 100644
index 0000000..0066847
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Benchmarks/Data/MessageReader_Bytes_Pooled_Improved.cs
@@ -0,0 +1,90 @@
+using System;
+using System.Buffers.Binary;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.IO;
+using System.Runtime.CompilerServices;
+using System.Text;
+using Microsoft.Extensions.ObjectPool;
+
+namespace Impostor.Benchmarks.Data
+{
+ public class MessageReader_Bytes_Pooled_Improved : IDisposable
+ {
+ private readonly ObjectPool<MessageReader_Bytes_Pooled_Improved> _pool;
+
+ public MessageReader_Bytes_Pooled_Improved(ObjectPool<MessageReader_Bytes_Pooled_Improved> pool)
+ {
+ _pool = pool;
+ }
+
+ public byte Tag { get; set; }
+ public byte[] Buffer { get; set; }
+ public int Position { get; set; }
+ public int Length { get; set; }
+ public int BytesRemaining => this.Length - this.Position;
+
+ public void Update(byte[] buffer, int position = 0, int length = 0)
+ {
+ Tag = byte.MaxValue;
+ Buffer = buffer;
+ Position = position;
+ Length = length;
+ }
+
+ public void Update(byte tag, byte[] buffer, int position = 0, int length = 0)
+ {
+ Tag = tag;
+ Buffer = buffer;
+ Position = position;
+ Length = length;
+ }
+
+ public MessageReader_Bytes_Pooled_Improved ReadMessage()
+ {
+ var length = ReadUInt16();
+ var tag = ReadByte();
+ var pos = Position;
+
+ Position += length;
+
+ var result = _pool.Get();
+ result.Update(tag, Buffer, pos, length);
+ return result;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public byte ReadByte()
+ {
+ return Buffer[Position++];
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ushort ReadUInt16()
+ {
+ var res = BinaryPrimitives.ReadUInt16LittleEndian(Buffer.AsSpan(Position));
+ Position += sizeof(ushort);
+ return res;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public int ReadInt32()
+ {
+ var res = BinaryPrimitives.ReadInt32LittleEndian(Buffer.AsSpan(Position));
+ Position += sizeof(int);
+ return res;
+ }
+
+ public MessageReader_Bytes_Pooled_Improved Slice(int start, int length)
+ {
+ var result = _pool.Get();
+ result.Update(Tag, Buffer, start, length);
+ return result;
+ }
+
+ public void Dispose()
+ {
+ _pool.Return(this);
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Benchmarks/Data/MessageWriter.cs b/Impostor-dev/src/Impostor.Benchmarks/Data/MessageWriter.cs
new file mode 100644
index 0000000..94ff441
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Benchmarks/Data/MessageWriter.cs
@@ -0,0 +1,311 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Text;
+using Impostor.Api.Games;
+using Impostor.Api.Net.Messages;
+
+namespace Impostor.Benchmarks.Data
+{
+ public class MessageWriter
+ {
+ private static int BufferSize = 64000;
+
+ public MessageType SendOption { get; private set; }
+
+ private Stack<int> messageStarts = new Stack<int>();
+
+ public MessageWriter(byte[] buffer)
+ {
+ this.Buffer = buffer;
+ this.Length = this.Buffer.Length;
+ }
+
+ public MessageWriter(int bufferSize)
+ {
+ this.Buffer = new byte[bufferSize];
+ }
+
+ public byte[] Buffer { get; }
+ public int Length { get; set; }
+ public int Position { get; set; }
+
+ public byte[] ToByteArray(bool includeHeader)
+ {
+ if (includeHeader)
+ {
+ byte[] output = new byte[this.Length];
+ System.Buffer.BlockCopy(this.Buffer, 0, output, 0, this.Length);
+ return output;
+ }
+ else
+ {
+ switch (this.SendOption)
+ {
+ case MessageType.Reliable:
+ {
+ byte[] output = new byte[this.Length - 3];
+ System.Buffer.BlockCopy(this.Buffer, 3, output, 0, this.Length - 3);
+ return output;
+ }
+ case MessageType.Unreliable:
+ {
+ byte[] output = new byte[this.Length - 1];
+ System.Buffer.BlockCopy(this.Buffer, 1, output, 0, this.Length - 1);
+ return output;
+ }
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ throw new NotImplementedException();
+ }
+
+ public bool HasBytes(int expected)
+ {
+ if (this.SendOption == MessageType.Unreliable)
+ {
+ return this.Length > 1 + expected;
+ }
+
+ return this.Length > 3 + expected;
+ }
+
+ public void Write(GameCode value)
+ {
+ this.Write(value.Value);
+ }
+
+ ///
+ public void StartMessage(byte typeFlag)
+ {
+ messageStarts.Push(this.Position);
+ this.Position += 2; // Skip for size
+ this.Write(typeFlag);
+ }
+
+ ///
+ public void EndMessage()
+ {
+ var lastMessageStart = messageStarts.Pop();
+ ushort length = (ushort)(this.Position - lastMessageStart - 3); // Minus length and type byte
+ this.Buffer[lastMessageStart] = (byte)length;
+ this.Buffer[lastMessageStart + 1] = (byte)(length >> 8);
+ }
+
+ ///
+ public void CancelMessage()
+ {
+ this.Position = this.messageStarts.Pop();
+ this.Length = this.Position;
+ }
+
+ public void Clear(MessageType sendOption)
+ {
+ this.messageStarts.Clear();
+ this.SendOption = sendOption;
+ this.Buffer[0] = (byte)sendOption;
+ switch (sendOption)
+ {
+ default:
+ case MessageType.Unreliable:
+ this.Length = this.Position = 1;
+ break;
+
+ case MessageType.Reliable:
+ this.Length = this.Position = 3;
+ break;
+ }
+ }
+
+ #region WriteMethods
+
+ public void Write(bool value)
+ {
+ this.Buffer[this.Position++] = (byte)(value ? 1 : 0);
+ if (this.Position > this.Length) this.Length = this.Position;
+ }
+
+ public void Write(sbyte value)
+ {
+ this.Buffer[this.Position++] = (byte)value;
+ if (this.Position > this.Length) this.Length = this.Position;
+ }
+
+ public void Write(byte value)
+ {
+ this.Buffer[this.Position++] = value;
+ if (this.Position > this.Length) this.Length = this.Position;
+ }
+
+ public void Write(short value)
+ {
+ this.Buffer[this.Position++] = (byte)value;
+ this.Buffer[this.Position++] = (byte)(value >> 8);
+ if (this.Position > this.Length) this.Length = this.Position;
+ }
+
+ public void Write(ushort value)
+ {
+ this.Buffer[this.Position++] = (byte)value;
+ this.Buffer[this.Position++] = (byte)(value >> 8);
+ if (this.Position > this.Length) this.Length = this.Position;
+ }
+
+ public void Write(uint value)
+ {
+ this.Buffer[this.Position++] = (byte)value;
+ this.Buffer[this.Position++] = (byte)(value >> 8);
+ this.Buffer[this.Position++] = (byte)(value >> 16);
+ this.Buffer[this.Position++] = (byte)(value >> 24);
+ if (this.Position > this.Length) this.Length = this.Position;
+ }
+
+ public void Write(int value)
+ {
+ this.Buffer[this.Position++] = (byte)value;
+ this.Buffer[this.Position++] = (byte)(value >> 8);
+ this.Buffer[this.Position++] = (byte)(value >> 16);
+ this.Buffer[this.Position++] = (byte)(value >> 24);
+ if (this.Position > this.Length) this.Length = this.Position;
+ }
+
+ public unsafe void Write(float value)
+ {
+ fixed (byte* ptr = &this.Buffer[this.Position])
+ {
+ byte* valuePtr = (byte*)&value;
+
+ *ptr = *valuePtr;
+ *(ptr + 1) = *(valuePtr + 1);
+ *(ptr + 2) = *(valuePtr + 2);
+ *(ptr + 3) = *(valuePtr + 3);
+ }
+
+ this.Position += 4;
+ if (this.Position > this.Length) this.Length = this.Position;
+ }
+
+ public void Write(string value)
+ {
+ var bytes = UTF8Encoding.UTF8.GetBytes(value);
+ this.WritePacked(bytes.Length);
+ this.Write(bytes);
+ }
+
+ public void Write(IPAddress value)
+ {
+ this.Write(value.GetAddressBytes());
+ }
+
+ public void WriteBytesAndSize(byte[] bytes)
+ {
+ this.WritePacked((uint)bytes.Length);
+ this.Write(bytes);
+ }
+
+ public void WriteBytesAndSize(byte[] bytes, int length)
+ {
+ this.WritePacked((uint)length);
+ this.Write(bytes, length);
+ }
+
+ public void WriteBytesAndSize(byte[] bytes, int offset, int length)
+ {
+ this.WritePacked((uint)length);
+ this.Write(bytes, offset, length);
+ }
+
+ public void Write(ReadOnlyMemory<byte> data)
+ {
+ Write(data.Span);
+ }
+
+ public void Write(ReadOnlySpan<byte> bytes)
+ {
+ bytes.CopyTo(this.Buffer.AsSpan(this.Position, bytes.Length));
+
+ this.Position += bytes.Length;
+ if (this.Position > this.Length) this.Length = this.Position;
+ }
+
+ public void Write(byte[] bytes)
+ {
+ Array.Copy(bytes, 0, this.Buffer, this.Position, bytes.Length);
+ this.Position += bytes.Length;
+ if (this.Position > this.Length) this.Length = this.Position;
+ }
+
+ public void Write(byte[] bytes, int offset, int length)
+ {
+ Array.Copy(bytes, offset, this.Buffer, this.Position, length);
+ this.Position += length;
+ if (this.Position > this.Length) this.Length = this.Position;
+ }
+
+ public void Write(byte[] bytes, int length)
+ {
+ Array.Copy(bytes, 0, this.Buffer, this.Position, length);
+ this.Position += length;
+ if (this.Position > this.Length) this.Length = this.Position;
+ }
+
+ ///
+ public void WritePacked(int value)
+ {
+ this.WritePacked((uint)value);
+ }
+
+ ///
+ public void WritePacked(uint value)
+ {
+ do
+ {
+ byte b = (byte)(value & 0xFF);
+ if (value >= 0x80)
+ {
+ b |= 0x80;
+ }
+
+ this.Write(b);
+ value >>= 7;
+ } while (value > 0);
+ }
+
+ #endregion WriteMethods
+
+ public void Write(MessageWriter msg, bool includeHeader)
+ {
+ int offset = 0;
+ if (!includeHeader)
+ {
+ switch (msg.SendOption)
+ {
+ case MessageType.Unreliable:
+ offset = 1;
+ break;
+
+ case MessageType.Reliable:
+ offset = 3;
+ break;
+ }
+ }
+
+ this.Write(msg.Buffer, offset, msg.Length - offset);
+ }
+
+ public unsafe static bool IsLittleEndian()
+ {
+ byte b;
+ unsafe
+ {
+ int i = 1;
+ byte* bp = (byte*)&i;
+ b = *bp;
+ }
+
+ return b == 1;
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Benchmarks/Data/Pool/MessageReader_Bytes_Pooled_ImprovedPolicy.cs b/Impostor-dev/src/Impostor.Benchmarks/Data/Pool/MessageReader_Bytes_Pooled_ImprovedPolicy.cs
new file mode 100644
index 0000000..802fcaa
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Benchmarks/Data/Pool/MessageReader_Bytes_Pooled_ImprovedPolicy.cs
@@ -0,0 +1,26 @@
+using System;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.ObjectPool;
+
+namespace Impostor.Benchmarks.Data.Pool
+{
+ public class MessageReader_Bytes_Pooled_ImprovedPolicy : IPooledObjectPolicy<MessageReader_Bytes_Pooled_Improved>
+ {
+ private readonly IServiceProvider _serviceProvider;
+
+ public MessageReader_Bytes_Pooled_ImprovedPolicy(IServiceProvider serviceProvider)
+ {
+ _serviceProvider = serviceProvider;
+ }
+
+ public MessageReader_Bytes_Pooled_Improved Create()
+ {
+ return new MessageReader_Bytes_Pooled_Improved(_serviceProvider.GetRequiredService<ObjectPool<MessageReader_Bytes_Pooled_Improved>>());
+ }
+
+ public bool Return(MessageReader_Bytes_Pooled_Improved obj)
+ {
+ return true;
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Benchmarks/Data/Span/MessageReaderOwner.cs b/Impostor-dev/src/Impostor.Benchmarks/Data/Span/MessageReaderOwner.cs
new file mode 100644
index 0000000..402fbaf
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Benchmarks/Data/Span/MessageReaderOwner.cs
@@ -0,0 +1,22 @@
+using System;
+
+namespace Impostor.Benchmarks.Data.Span
+{
+ public class MessageReaderOwner
+ {
+ private readonly Memory<byte> _data;
+
+ public MessageReaderOwner(Memory<byte> data)
+ {
+ _data = data;
+ }
+
+ public int Position { get; internal set; }
+ public int Length => _data.Length;
+
+ public MessageReader_Span CreateReader()
+ {
+ return new MessageReader_Span(this, byte.MaxValue, _data.Span.Slice(Position));
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Benchmarks/Data/Span/MessageReader_Span.cs b/Impostor-dev/src/Impostor.Benchmarks/Data/Span/MessageReader_Span.cs
new file mode 100644
index 0000000..5636dbe
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Benchmarks/Data/Span/MessageReader_Span.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Buffers.Binary;
+using Impostor.Hazel;
+
+namespace Impostor.Benchmarks.Data.Span
+{
+ public ref struct MessageReader_Span
+ {
+ private readonly MessageReaderOwner _owner;
+ private readonly byte _tag;
+ private readonly Span<byte> _data;
+
+ public MessageReader_Span(MessageReaderOwner owner, byte tag, Span<byte> data)
+ {
+ _owner = owner;
+ _tag = tag;
+ _data = data;
+ }
+
+ public MessageReader_Span ReadMessage()
+ {
+ var length = ReadUInt16();
+ var tag = ReadByte();
+ var pos = _owner.Position;
+
+ _owner.Position += length;
+
+ return new MessageReader_Span(_owner, tag, _data.Slice(3, length));
+ }
+
+ public byte ReadByte()
+ {
+ return _data[_owner.Position++];
+ }
+
+ public ushort ReadUInt16()
+ {
+ var output = BinaryPrimitives.ReadUInt16LittleEndian(_data.Slice(_owner.Position));
+ _owner.Position += sizeof(ushort);
+ return output;
+ }
+
+ public int ReadInt32()
+ {
+ var output = BinaryPrimitives.ReadInt32LittleEndian(_data.Slice(_owner.Position));
+ _owner.Position += sizeof(int);
+ return output;
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Benchmarks/Extensions/SpanExtensions.cs b/Impostor-dev/src/Impostor.Benchmarks/Extensions/SpanExtensions.cs
new file mode 100644
index 0000000..8fbceeb
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Benchmarks/Extensions/SpanExtensions.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Buffers.Binary;
+using System.Runtime.CompilerServices;
+
+namespace Impostor.Benchmarks.Extensions
+{
+ public static class SpanExtensions
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Span<byte> ReadMessage(this Span<byte> input)
+ {
+ var length = BinaryPrimitives.ReadUInt16LittleEndian(input);
+ var tag = input[2];
+
+ return input.Slice(3, length);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int ReadUInt16(this ref ReadOnlySpan<byte> input)
+ {
+ return BinaryPrimitives.ReadUInt16LittleEndian(input);
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Benchmarks/Impostor.Benchmarks.csproj b/Impostor-dev/src/Impostor.Benchmarks/Impostor.Benchmarks.csproj
new file mode 100644
index 0000000..6f19bda
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Benchmarks/Impostor.Benchmarks.csproj
@@ -0,0 +1,17 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net5.0</TargetFramework>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\Impostor.Server\Impostor.Server.csproj" />
+ </ItemGroup>
+
+</Project>
diff --git a/Impostor-dev/src/Impostor.Benchmarks/Program.cs b/Impostor-dev/src/Impostor.Benchmarks/Program.cs
new file mode 100644
index 0000000..3ce7383
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Benchmarks/Program.cs
@@ -0,0 +1,23 @@
+using BenchmarkDotNet.Configs;
+using BenchmarkDotNet.Diagnosers;
+using BenchmarkDotNet.Running;
+using Impostor.Benchmarks.Tests;
+
+namespace Impostor.Benchmarks
+{
+ internal static class Program
+ {
+ private static void Main(string[] args)
+ {
+ // BenchmarkRunner.Run<EventManagerBenchmark>(
+ // DefaultConfig.Instance
+ // .AddDiagnoser(MemoryDiagnoser.Default)
+ // );
+
+ BenchmarkRunner.Run<MessageReaderBenchmark>(
+ DefaultConfig.Instance
+ .AddDiagnoser(MemoryDiagnoser.Default)
+ );
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Benchmarks/Tests/EventManagerBenchmark.cs b/Impostor-dev/src/Impostor.Benchmarks/Tests/EventManagerBenchmark.cs
new file mode 100644
index 0000000..0675080
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Benchmarks/Tests/EventManagerBenchmark.cs
@@ -0,0 +1,77 @@
+// using System.Threading.Tasks;
+// using BenchmarkDotNet.Attributes;
+// using Impostor.Api.Events;
+// using Impostor.Api.Events.Managers;
+// using Impostor.Server.Events;
+// using Microsoft.Extensions.DependencyInjection;
+//
+// namespace Impostor.Benchmarks.Tests
+// {
+// public class EventManagerBenchmark
+// {
+// private IEventManager _eventManager;
+// private IGameEvent _event;
+//
+// [GlobalSetup]
+// public void Setup()
+// {
+// var services = new ServiceCollection();
+//
+// services.AddLogging();
+// services.AddSingleton<IEventManager, EventManager>();
+//
+// _event = new GameStartedEvent(null);
+// _eventManager = services.BuildServiceProvider().GetRequiredService<IEventManager>();
+// _eventManager.RegisterListener(new EventListener());
+// _eventManager.RegisterListener(new EventListener());
+// _eventManager.RegisterListener(new EventListener());
+// _eventManager.RegisterListener(new EventListener());
+// _eventManager.RegisterListener(new EventListener());
+// }
+//
+// [Benchmark]
+// public async Task Run_1()
+// {
+// for (var i = 0; i < 1; i++)
+// {
+// await _eventManager.CallAsync(_event);
+// }
+// }
+//
+// [Benchmark]
+// public async Task Run_1000()
+// {
+// for (var i = 0; i < 1000; i++)
+// {
+// await _eventManager.CallAsync(_event);
+// }
+// }
+//
+// [Benchmark]
+// public async Task Run_10000()
+// {
+// for (var i = 0; i < 10000; i++)
+// {
+// await _eventManager.CallAsync(_event);
+// }
+// }
+//
+// [Benchmark]
+// public async Task Run_100000()
+// {
+// for (var i = 0; i < 100000; i++)
+// {
+// await _eventManager.CallAsync(_event);
+// }
+// }
+//
+// private class EventListener : IEventListener
+// {
+// [EventListener]
+// public void OnGameStarted(IGameStartedEvent e)
+// {
+//
+// }
+// }
+// }
+// }
diff --git a/Impostor-dev/src/Impostor.Benchmarks/Tests/MessageReaderBenchmark.cs b/Impostor-dev/src/Impostor.Benchmarks/Tests/MessageReaderBenchmark.cs
new file mode 100644
index 0000000..abf1642
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Benchmarks/Tests/MessageReaderBenchmark.cs
@@ -0,0 +1,132 @@
+using System;
+using System.Buffers.Binary;
+using BenchmarkDotNet.Attributes;
+using Impostor.Benchmarks.Data;
+using Impostor.Benchmarks.Data.Pool;
+using Impostor.Benchmarks.Extensions;
+using Impostor.Hazel;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.ObjectPool;
+using MessageWriter = Impostor.Benchmarks.Data.MessageWriter;
+
+namespace Impostor.Benchmarks.Tests
+{
+ public class MessageReaderBenchmark
+ {
+ private byte[] _data;
+ private ObjectPool<MessageReader_Bytes_Pooled_Improved> _pool;
+
+ [GlobalSetup]
+ public void Setup()
+ {
+ var message = new MessageWriter(1024);
+
+ message.StartMessage(1);
+ message.Write((ushort)3100);
+ message.Write((byte)100);
+ message.Write((int) int.MaxValue);
+ message.WritePacked(int.MaxValue);
+ message.EndMessage();
+
+ _data = message.ToByteArray(true);
+
+ MessageReader_Bytes_Pooled.Return(MessageReader_Bytes_Pooled.Get(_data));
+
+ // Services
+ var services = new ServiceCollection();
+
+ services.AddSingleton<ObjectPoolProvider>(new DefaultObjectPoolProvider());
+ services.AddSingleton(serviceProvider =>
+ {
+ var provider = serviceProvider.GetRequiredService<ObjectPoolProvider>();
+ var policy = new MessageReader_Bytes_Pooled_ImprovedPolicy(serviceProvider);
+ return provider.Create(policy);
+ });
+
+ _pool = services
+ .BuildServiceProvider()
+ .GetRequiredService<ObjectPool<MessageReader_Bytes_Pooled_Improved>>();
+ }
+
+ [Benchmark]
+ public void Span_Run_1_000_000()
+ {
+ for (var i = 0; i < 1_000_000; i++)
+ {
+ var span = _data.AsSpan();
+ var inner = span.ReadMessage();
+
+ _ = BinaryPrimitives.ReadUInt16LittleEndian(inner);
+ _ = inner[2];
+ _ = BinaryPrimitives.ReadInt32LittleEndian(inner.Slice(3));
+ }
+ }
+
+ // [Benchmark]
+ // public void Normal_Run_1_000_000()
+ // {
+ // for (var i = 0; i < 1_000_000; i++)
+ // {
+ // var reader = new MessageReader(_data);
+ // var inner = reader.ReadMessage();
+ //
+ // _ = inner.ReadUInt16();
+ // _ = inner.ReadByte();
+ // _ = inner.ReadInt32();
+ // // inner.ReadPackedInt32();
+ // }
+ // }
+
+ [Benchmark]
+ public void Bytes_Run_1_000_000()
+ {
+ for (var i = 0; i < 1_000_000; i++)
+ {
+ var reader = new MessageReader_Bytes(_data);
+ var inner = reader.ReadMessage();
+
+ _ = inner.ReadUInt16();
+ _ = inner.ReadByte();
+ _ = inner.ReadInt32();
+ // inner.ReadPackedInt32();
+ }
+ }
+
+ [Benchmark]
+ public void Pooled_Bytes_Run_1_000_000()
+ {
+ for (var i = 0; i < 1_000_000; i++)
+ {
+ var reader = MessageReader_Bytes_Pooled.Get(_data);
+ var inner = reader.ReadMessage();
+
+ _ = inner.ReadUInt16();
+ _ = inner.ReadByte();
+ _ = inner.ReadInt32();
+ // inner.ReadPackedInt32();
+
+ MessageReader_Bytes_Pooled.Return(inner);
+ MessageReader_Bytes_Pooled.Return(reader);
+ }
+ }
+
+ [Benchmark]
+ public void Improved_Pooled_Bytes_Run_1_000_000()
+ {
+ using (var reader = _pool.Get())
+ {
+ for (var i = 0; i < 1_000_000; i++)
+ {
+ reader.Update(_data);
+
+ using (var inner = reader.ReadMessage())
+ {
+ _ = inner.ReadUInt16();
+ _ = inner.ReadByte();
+ _ = inner.ReadInt32();
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Client.App/Impostor.Client.App.csproj b/Impostor-dev/src/Impostor.Client.App/Impostor.Client.App.csproj
new file mode 100644
index 0000000..886e26e
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Client.App/Impostor.Client.App.csproj
@@ -0,0 +1,16 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net5.0</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\Impostor.Client\Impostor.Client.csproj" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
+ </ItemGroup>
+
+</Project>
diff --git a/Impostor-dev/src/Impostor.Client.App/Program.cs b/Impostor-dev/src/Impostor.Client.App/Program.cs
new file mode 100644
index 0000000..aa8866a
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Client.App/Program.cs
@@ -0,0 +1,75 @@
+using System;
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+using Impostor.Api.Innersloth;
+using Impostor.Api.Net.Messages;
+using Impostor.Api.Net.Messages.C2S;
+using Impostor.Hazel;
+using Impostor.Hazel.Udp;
+using Serilog;
+
+namespace Impostor.Client.App
+{
+ internal static class Program
+ {
+ private static readonly ManualResetEvent QuitEvent = new ManualResetEvent(false);
+
+ private static async Task Main(string[] args)
+ {
+ Log.Logger = new LoggerConfiguration()
+ .WriteTo.Console()
+ .CreateLogger();
+
+ var writeHandshake = MessageWriter.Get(MessageType.Reliable);
+
+ writeHandshake.Write(50516550);
+ writeHandshake.Write("AeonLucid");
+
+ var writeGameCreate = MessageWriter.Get(MessageType.Reliable);
+
+ Message00HostGameC2S.Serialize(writeGameCreate, new GameOptionsData
+ {
+ MaxPlayers = 4,
+ NumImpostors = 2
+ });
+
+ // TODO: ObjectPool for MessageReaders
+ using (var connection = new UdpClientConnection(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 22023), null))
+ {
+ var e = new ManualResetEvent(false);
+
+ // Register events.
+ connection.DataReceived = DataReceived;
+ connection.Disconnected = Disconnected;
+
+ // Connect and send handshake.
+ await connection.ConnectAsync(writeHandshake.ToByteArray(false));
+ Log.Information("Connected.");
+
+ // Create a game.
+ await connection.SendAsync(writeGameCreate);
+ Log.Information("Requested game creation.");
+
+ // Recycle.
+ writeHandshake.Recycle();
+ writeGameCreate.Recycle();
+
+ e.WaitOne();
+ }
+ }
+
+ private static ValueTask DataReceived(DataReceivedEventArgs e)
+ {
+ Log.Information("Received data.");
+ return default;
+ }
+
+ private static ValueTask Disconnected(DisconnectedEventArgs e)
+ {
+ Log.Information("Disconnected: " + e.Reason);
+ QuitEvent.Set();
+ return default;
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Client/Impostor.Client.csproj b/Impostor-dev/src/Impostor.Client/Impostor.Client.csproj
new file mode 100644
index 0000000..28b6ed3
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Client/Impostor.Client.csproj
@@ -0,0 +1,12 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>net5.0</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\Impostor.Api\Impostor.Api.csproj" />
+ <ProjectReference Include="..\Impostor.Hazel\Impostor.Hazel.csproj" />
+ </ItemGroup>
+
+</Project>
diff --git a/Impostor-dev/src/Impostor.Hazel/Connection.cs b/Impostor-dev/src/Impostor.Hazel/Connection.cs
new file mode 100644
index 0000000..dec8cfe
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Hazel/Connection.cs
@@ -0,0 +1,249 @@
+using System;
+using System.Net;
+using System.Threading.Tasks;
+using Impostor.Api.Net.Messages;
+using Serilog;
+
+namespace Impostor.Hazel
+{
+ /// <summary>
+ /// Base class for all connections.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// Connection is the base class for all connections that Hazel can make. It provides common functionality and a
+ /// standard interface to allow connections to be swapped easily.
+ /// </para>
+ /// <para>
+ /// Any class inheriting from Connection should provide the 3 standard guarantees that Hazel provides:
+ /// <list type="bullet">
+ /// <item>
+ /// <description>Thread Safe</description>
+ /// </item>
+ /// <item>
+ /// <description>Connection Orientated</description>
+ /// </item>
+ /// <item>
+ /// <description>Packet/Message Based</description>
+ /// </item>
+ /// </list>
+ /// </para>
+ /// </remarks>
+ /// <threadsafety static="true" instance="true"/>
+ public abstract class Connection : IDisposable
+ {
+ private static readonly ILogger Logger = Log.ForContext<Connection>();
+
+ /// <summary>
+ /// Called when a message has been received.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// DataReceived is invoked everytime a message is received from the end point of this connection, the message
+ /// that was received can be found in the <see cref="DataReceivedEventArgs"/> alongside other information from the
+ /// event.
+ /// </para>
+ /// <include file="DocInclude/common.xml" path="docs/item[@name='Event_Thread_Safety_Warning']/*" />
+ /// </remarks>
+ /// <example>
+ /// <code language="C#" source="DocInclude/TcpClientExample.cs"/>
+ /// </example>
+ public Func<DataReceivedEventArgs, ValueTask> DataReceived;
+
+ public int TestLagMs = -1;
+ public int TestDropRate = 0;
+ protected int testDropCount = 0;
+
+ /// <summary>
+ /// Called when the end point disconnects or an error occurs.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// Disconnected is invoked when the connection is closed due to an exception occuring or because the remote
+ /// end point disconnected. If it was invoked due to an exception occuring then the exception is available
+ /// in the <see cref="DisconnectedEventArgs"/> passed with the event.
+ /// </para>
+ /// <include file="DocInclude/common.xml" path="docs/item[@name='Event_Thread_Safety_Warning']/*" />
+ /// </remarks>
+ /// <example>
+ /// <code language="C#" source="DocInclude/TcpClientExample.cs"/>
+ /// </example>
+ public Func<DisconnectedEventArgs, ValueTask> Disconnected;
+
+ /// <summary>
+ /// The remote end point of this Connection.
+ /// </summary>
+ /// <remarks>
+ /// This is the end point that this connection is connected to (i.e. the other device). This returns an abstract
+ /// <see cref="ConnectionEndPoint"/> which can then be cast to an appropriate end point depending on the
+ /// connection type.
+ /// </remarks>
+ public IPEndPoint EndPoint { get; protected set; }
+
+ public IPMode IPMode { get; protected set; }
+
+ /// <summary>
+ /// The traffic statistics about this Connection.
+ /// </summary>
+ /// <remarks>
+ /// Contains statistics about the number of messages and bytes sent and received by this connection.
+ /// </remarks>
+ public ConnectionStatistics Statistics { get; protected set; }
+
+ /// <summary>
+ /// The state of this connection.
+ /// </summary>
+ /// <remarks>
+ /// All implementers should be aware that when this is set to ConnectionState.Connected it will
+ /// release all threads that are blocked on <see cref="WaitOnConnect"/>.
+ /// </remarks>
+ public ConnectionState State
+ {
+ get
+ {
+ return this._state;
+ }
+
+ protected set
+ {
+ this._state = value;
+ this.SetState(value);
+ }
+ }
+
+ protected ConnectionState _state;
+ protected virtual void SetState(ConnectionState state) { }
+
+ /// <summary>
+ /// Constructor that initializes the ConnecitonStatistics object.
+ /// </summary>
+ /// <remarks>
+ /// This constructor initialises <see cref="Statistics"/> with empty statistics and sets <see cref="State"/> to
+ /// <see cref="ConnectionState.NotConnected"/>.
+ /// </remarks>
+ protected Connection()
+ {
+ this.Statistics = new ConnectionStatistics();
+ this.State = ConnectionState.NotConnected;
+ }
+
+ /// <summary>
+ /// Sends a number of bytes to the end point of the connection using the specified <see cref="MessageType"/>.
+ /// </summary>
+ /// <param name="msg">The message to send.</param>
+ /// <remarks>
+ /// <include file="DocInclude/common.xml" path="docs/item[@name='Connection_SendBytes_General']/*" />
+ /// <para>
+ /// The messageType parameter is only a request to use those options and the actual method used to send the
+ /// data is up to the implementation. There are circumstances where this parameter may be ignored but in
+ /// general any implementer should aim to always follow the user's request.
+ /// </para>
+ /// </remarks>
+ public abstract ValueTask SendAsync(IMessageWriter msg);
+
+ /// <summary>
+ /// Sends a number of bytes to the end point of the connection using the specified <see cref="MessageType"/>.
+ /// </summary>
+ /// <param name="bytes">The bytes of the message to send.</param>
+ /// <param name="messageType">The option specifying how the message should be sent.</param>
+ /// <remarks>
+ /// <include file="DocInclude/common.xml" path="docs/item[@name='Connection_SendBytes_General']/*" />
+ /// <para>
+ /// The messageType parameter is only a request to use those options and the actual method used to send the
+ /// data is up to the implementation. There are circumstances where this parameter may be ignored but in
+ /// general any implementer should aim to always follow the user's request.
+ /// </para>
+ /// </remarks>
+ public abstract ValueTask SendBytes(byte[] bytes, MessageType messageType = MessageType.Unreliable);
+
+ /// <summary>
+ /// Connects the connection to a server and begins listening.
+ /// This method does not block.
+ /// </summary>
+ /// <param name="bytes">The bytes of data to send in the handshake.</param>
+ public abstract ValueTask ConnectAsync(byte[] bytes = null);
+
+ /// <summary>
+ /// Invokes the DataReceived event.
+ /// </summary>
+ /// <param name="msg">The bytes received.</param>
+ /// <param name="messageType">The <see cref="MessageType"/> the message was received with.</param>
+ /// <remarks>
+ /// Invokes the <see cref="DataReceived"/> event on this connection to alert subscribers a new message has been
+ /// received. The bytes and the send option that the message was sent with should be passed in to give to the
+ /// subscribers.
+ /// </remarks>
+ protected async ValueTask InvokeDataReceived(IMessageReader msg, MessageType messageType)
+ {
+ // Make a copy to avoid race condition between null check and invocation
+ var handler = DataReceived;
+ if (handler != null)
+ {
+ try
+ {
+ await handler(new DataReceivedEventArgs(this, msg, messageType));
+ }
+ catch (Exception e)
+ {
+ Logger.Error(e, "Invoking data received failed");
+ await Disconnect("Invoking data received failed");
+ }
+ }
+ }
+
+ /// <summary>
+ /// Invokes the Disconnected event.
+ /// </summary>
+ /// <param name="e">The exception, if any, that occurred to cause this.</param>
+ /// <param name="reader">Extra disconnect data</param>
+ /// <remarks>
+ /// Invokes the <see cref="Disconnected"/> event to alert subscribres this connection has been disconnected either
+ /// by the end point or because an error occurred. If an error occurred the error should be passed in in order to
+ /// pass to the subscribers, otherwise null can be passed in.
+ /// </remarks>
+ protected async ValueTask InvokeDisconnected(string e, IMessageReader reader)
+ {
+ // Make a copy to avoid race condition between null check and invocation
+ var handler = Disconnected;
+ if (handler != null)
+ {
+ try
+ {
+ await handler(new DisconnectedEventArgs(e, reader));
+ }
+ catch (Exception ex)
+ {
+ Logger.Error(ex, "Error in InvokeDisconnected");
+ }
+ }
+ }
+
+ /// <summary>
+ /// For times when you want to force the disconnect handler to fire as well as close it.
+ /// If you only want to close it, just use Dispose.
+ /// </summary>
+ public abstract ValueTask Disconnect(string reason, MessageWriter writer = null);
+
+ /// <summary>
+ /// Disposes of this NetworkConnection.
+ /// </summary>
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ /// <summary>
+ /// Disposes of this NetworkConnection.
+ /// </summary>
+ /// <param name="disposing">Are we currently disposing?</param>
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ this.DataReceived = null;
+ this.Disconnected = null;
+ }
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Hazel/ConnectionListener.cs b/Impostor-dev/src/Impostor.Hazel/ConnectionListener.cs
new file mode 100644
index 0000000..116f657
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Hazel/ConnectionListener.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Threading.Tasks;
+using Impostor.Api.Net.Messages;
+using Serilog;
+
+namespace Impostor.Hazel
+{
+ /// <summary>
+ /// Base class for all connection listeners.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// ConnectionListeners are server side objects that listen for clients and create matching server side connections
+ /// for each client in a similar way to TCP does. These connections should be ready for communication immediately.
+ /// </para>
+ /// <para>
+ /// Each time a client connects the <see cref="NewConnection"/> event will be invoked to alert all subscribers to
+ /// the new connection. A disconnected event is then present on the <see cref="Connection"/> that is passed to the
+ /// subscribers.
+ /// </para>
+ /// </remarks>
+ /// <threadsafety static="true" instance="true"/>
+ public abstract class ConnectionListener : IAsyncDisposable
+ {
+ private static readonly ILogger Logger = Log.ForContext<ConnectionListener>();
+
+ /// <summary>
+ /// Invoked when a new client connects.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// NewConnection is invoked each time a client connects to the listener. The
+ /// <see cref="NewConnectionEventArgs"/> contains the new <see cref="Connection"/> for communication with this
+ /// client.
+ /// </para>
+ /// <para>
+ /// Hazel may or may not store connections so it is your responsibility to keep track and properly Dispose of
+ /// connections to your server.
+ /// </para>
+ /// <include file="DocInclude/common.xml" path="docs/item[@name='Event_Thread_Safety_Warning']/*" />
+ /// </remarks>
+ /// <example>
+ /// <code language="C#" source="DocInclude/TcpListenerExample.cs"/>
+ /// </example>
+ public Func<NewConnectionEventArgs, ValueTask> NewConnection;
+
+ /// <summary>
+ /// Makes this connection listener begin listening for connections.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// This instructs the listener to begin listening for new clients connecting to the server. When a new client
+ /// connects the <see cref="NewConnection"/> event will be invoked containing the connection to the new client.
+ /// </para>
+ /// <para>
+ /// To stop listening you should call <see cref="DisposeAsync()"/>.
+ /// </para>
+ /// </remarks>
+ /// <example>
+ /// <code language="C#" source="DocInclude/TcpListenerExample.cs"/>
+ /// </example>
+ public abstract Task StartAsync();
+
+ /// <summary>
+ /// Invokes the NewConnection event with the supplied connection.
+ /// </summary>
+ /// <param name="msg">The user sent bytes that were received as part of the handshake.</param>
+ /// <param name="connection">The connection to pass in the arguments.</param>
+ /// <remarks>
+ /// Implementers should call this to invoke the <see cref="NewConnection"/> event before data is received so that
+ /// subscribers do not miss any data that may have been sent immediately after connecting.
+ /// </remarks>
+ internal async Task InvokeNewConnection(IMessageReader msg, Connection connection)
+ {
+ // Make a copy to avoid race condition between null check and invocation
+ var handler = NewConnection;
+ if (handler != null)
+ {
+ try
+ {
+ await handler(new NewConnectionEventArgs(msg, connection));
+ }
+ catch (Exception e)
+ {
+ Logger.Error(e, "Accepting connection failed");
+ await connection.Disconnect("Accepting connection failed");
+ }
+ }
+ }
+
+ /// <summary>
+ /// Call to dispose of the connection listener.
+ /// </summary>
+ public virtual ValueTask DisposeAsync()
+ {
+ this.NewConnection = null;
+ return ValueTask.CompletedTask;
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Hazel/ConnectionState.cs b/Impostor-dev/src/Impostor.Hazel/ConnectionState.cs
new file mode 100644
index 0000000..5dd7c6a
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Hazel/ConnectionState.cs
@@ -0,0 +1,23 @@
+namespace Impostor.Hazel
+{
+ /// <summary>
+ /// Represents the state a <see cref="Connection"/> is currently in.
+ /// </summary>
+ public enum ConnectionState
+ {
+ /// <summary>
+ /// The Connection has either not been established yet or has been disconnected.
+ /// </summary>
+ NotConnected,
+
+ /// <summary>
+ /// The Connection is currently connecting to an endpoint.
+ /// </summary>
+ Connecting,
+
+ /// <summary>
+ /// The Connection is connected and data can be transfered.
+ /// </summary>
+ Connected,
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Hazel/ConnectionStatistics.cs b/Impostor-dev/src/Impostor.Hazel/ConnectionStatistics.cs
new file mode 100644
index 0000000..4802620
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Hazel/ConnectionStatistics.cs
@@ -0,0 +1,566 @@
+using System.Runtime.CompilerServices;
+using System.Threading;
+
+[assembly: InternalsVisibleTo("Hazel.Tests")]
+namespace Impostor.Hazel
+{
+ /// <summary>
+ /// Holds statistics about the traffic through a <see cref="Connection"/>.
+ /// </summary>
+ /// <threadsafety static="true" instance="true"/>
+ public class ConnectionStatistics
+ {
+ private const int ExpectedMTU = 1200;
+
+ /// <summary>
+ /// The total number of messages sent.
+ /// </summary>
+ public int MessagesSent
+ {
+ get
+ {
+ return UnreliableMessagesSent + ReliableMessagesSent + FragmentedMessagesSent + AcknowledgementMessagesSent + HelloMessagesSent;
+ }
+ }
+
+ /// <summary>
+ /// The number of messages sent larger than 576 bytes. This is smaller than most default MTUs.
+ /// </summary>
+ /// <remarks>
+ /// This is the number of unreliable messages that were sent from the <see cref="Connection"/>, incremented
+ /// each time that LogUnreliableSend is called by the Connection. Messages that caused an error are not
+ /// counted and messages are only counted once all other operations in the send are complete.
+ /// </remarks>
+ public int FragmentableMessagesSent
+ {
+ get
+ {
+ return fragmentableMessagesSent;
+ }
+ }
+
+ /// <summary>
+ /// The number of messages sent larger than 576 bytes.
+ /// </summary>
+ int fragmentableMessagesSent;
+
+ /// <summary>
+ /// The number of unreliable messages sent.
+ /// </summary>
+ /// <remarks>
+ /// This is the number of unreliable messages that were sent from the <see cref="Connection"/>, incremented
+ /// each time that LogUnreliableSend is called by the Connection. Messages that caused an error are not
+ /// counted and messages are only counted once all other operations in the send are complete.
+ /// </remarks>
+ public int UnreliableMessagesSent
+ {
+ get
+ {
+ return unreliableMessagesSent;
+ }
+ }
+
+ /// <summary>
+ /// The number of unreliable messages sent.
+ /// </summary>
+ int unreliableMessagesSent;
+
+ /// <summary>
+ /// The number of reliable messages sent.
+ /// </summary>
+ /// <remarks>
+ /// This is the number of reliable messages that were sent from the <see cref="Connection"/>, incremented
+ /// each time that LogReliableSend is called by the Connection. Messages that caused an error are not
+ /// counted and messages are only counted once all other operations in the send are complete.
+ /// </remarks>
+ public int ReliableMessagesSent
+ {
+ get
+ {
+ return reliableMessagesSent;
+ }
+ }
+
+ /// <summary>
+ /// The number of unreliable messages sent.
+ /// </summary>
+ int reliableMessagesSent;
+
+ /// <summary>
+ /// The number of fragmented messages sent.
+ /// </summary>
+ /// <remarks>
+ /// This is the number of fragmented messages that were sent from the <see cref="Connection"/>, incremented
+ /// each time that LogFragmentedSend is called by the Connection. Messages that caused an error are not
+ /// counted and messages are only counted once all other operations in the send are complete.
+ /// </remarks>
+ public int FragmentedMessagesSent
+ {
+ get
+ {
+ return fragmentedMessagesSent;
+ }
+ }
+
+ /// <summary>
+ /// The number of fragmented messages sent.
+ /// </summary>
+ int fragmentedMessagesSent;
+
+ /// <summary>
+ /// The number of acknowledgement messages sent.
+ /// </summary>
+ /// <remarks>
+ /// This is the number of acknowledgements that were sent from the <see cref="Connection"/>, incremented
+ /// each time that LogAcknowledgementSend is called by the Connection. Messages that caused an error are not
+ /// counted and messages are only counted once all other operations in the send are complete.
+ /// </remarks>
+ public int AcknowledgementMessagesSent
+ {
+ get
+ {
+ return acknowledgementMessagesSent;
+ }
+ }
+
+ /// <summary>
+ /// The number of acknowledgement messages sent.
+ /// </summary>
+ int acknowledgementMessagesSent;
+
+ /// <summary>
+ /// The number of hello messages sent.
+ /// </summary>
+ /// <remarks>
+ /// This is the number of hello messages that were sent from the <see cref="Connection"/>, incremented
+ /// each time that LogHelloSend is called by the Connection. Messages that caused an error are not
+ /// counted and messages are only counted once all other operations in the send are complete.
+ /// </remarks>
+ public int HelloMessagesSent
+ {
+ get
+ {
+ return helloMessagesSent;
+ }
+ }
+
+ /// <summary>
+ /// The number of hello messages sent.
+ /// </summary>
+ int helloMessagesSent;
+
+ /// <summary>
+ /// The number of bytes of data sent.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// This is the number of bytes of data (i.e. user bytes) that were sent from the <see cref="Connection"/>,
+ /// accumulated each time that LogSend is called by the Connection. Messages that caused an error are not
+ /// counted and messages are only counted once all other operations in the send are complete.
+ /// </para>
+ /// <para>
+ /// For the number of bytes including protocol bytes see <see cref="TotalBytesSent"/>.
+ /// </para>
+ /// </remarks>
+ public long DataBytesSent
+ {
+ get
+ {
+ return Interlocked.Read(ref dataBytesSent);
+ }
+ }
+
+ /// <summary>
+ /// The number of bytes of data sent.
+ /// </summary>
+ long dataBytesSent;
+
+ /// <summary>
+ /// The number of bytes sent in total.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// This is the total number of bytes (the data bytes plus protocol bytes) that were sent from the
+ /// <see cref="Connection"/>, accumulated each time that LogSend is called by the Connection. Messages that
+ /// caused an error are not counted and messages are only counted once all other operations in the send are
+ /// complete.
+ /// </para>
+ /// <para>
+ /// For the number of data bytes excluding protocol bytes see <see cref="DataBytesSent"/>.
+ /// </para>
+ /// </remarks>
+ public long TotalBytesSent
+ {
+ get
+ {
+ return Interlocked.Read(ref totalBytesSent);
+ }
+ }
+
+ /// <summary>
+ /// The number of bytes sent in total.
+ /// </summary>
+ long totalBytesSent;
+
+ /// <summary>
+ /// The total number of messages received.
+ /// </summary>
+ public int MessagesReceived
+ {
+ get
+ {
+ return UnreliableMessagesReceived + ReliableMessagesReceived + FragmentedMessagesReceived + AcknowledgementMessagesReceived + helloMessagesReceived;
+ }
+ }
+
+ /// <summary>
+ /// The number of unreliable messages received.
+ /// </summary>
+ /// <remarks>
+ /// This is the number of unreliable messages that were received by the <see cref="Connection"/>, incremented
+ /// each time that LogUnreliableReceive is called by the Connection. Messages are counted before the receive event is invoked.
+ /// </remarks>
+ public int UnreliableMessagesReceived
+ {
+ get
+ {
+ return unreliableMessagesReceived;
+ }
+ }
+
+ /// <summary>
+ /// The number of unreliable messages received.
+ /// </summary>
+ int unreliableMessagesReceived;
+
+ /// <summary>
+ /// The number of reliable messages received.
+ /// </summary>
+ /// <remarks>
+ /// This is the number of reliable messages that were received by the <see cref="Connection"/>, incremented
+ /// each time that LogReliableReceive is called by the Connection. Messages are counted before the receive event is invoked.
+ /// </remarks>
+ public int ReliableMessagesReceived
+ {
+ get
+ {
+ return reliableMessagesReceived;
+ }
+ }
+
+ /// <summary>
+ /// The number of reliable messages received.
+ /// </summary>
+ int reliableMessagesReceived;
+
+ /// <summary>
+ /// The number of fragmented messages received.
+ /// </summary>
+ /// <remarks>
+ /// This is the number of fragmented messages that were received by the <see cref="Connection"/>, incremented
+ /// each time that LogFragmentedReceive is called by the Connection. Messages are counted before the receive event is invoked.
+ /// </remarks>
+ public int FragmentedMessagesReceived
+ {
+ get
+ {
+ return fragmentedMessagesReceived;
+ }
+ }
+
+ /// <summary>
+ /// The number of fragmented messages received.
+ /// </summary>
+ int fragmentedMessagesReceived;
+
+ /// <summary>
+ /// The number of acknowledgement messages received.
+ /// </summary>
+ /// <remarks>
+ /// This is the number of acknowledgement messages that were received by the <see cref="Connection"/>, incremented
+ /// each time that LogAcknowledgemntReceive is called by the Connection. Messages are counted before the receive event is invoked.
+ /// </remarks>
+ public int AcknowledgementMessagesReceived
+ {
+ get
+ {
+ return acknowledgementMessagesReceived;
+ }
+ }
+
+ /// <summary>
+ /// The number of acknowledgement messages received.
+ /// </summary>
+ int acknowledgementMessagesReceived;
+
+ /// <summary>
+ /// The number of ping messages received.
+ /// </summary>
+ /// <remarks>
+ /// This is the number of hello messages that were received by the <see cref="Connection"/>, incremented
+ /// each time that LogHelloReceive is called by the Connection. Messages are counted before the receive event is invoked.
+ /// </remarks>
+ public int PingMessagesReceived
+ {
+ get
+ {
+ return pingMessagesReceived;
+ }
+ }
+
+ /// <summary>
+ /// The number of hello messages received.
+ /// </summary>
+ int pingMessagesReceived;
+
+ /// <summary>
+ /// The number of hello messages received.
+ /// </summary>
+ /// <remarks>
+ /// This is the number of hello messages that were received by the <see cref="Connection"/>, incremented
+ /// each time that LogHelloReceive is called by the Connection. Messages are counted before the receive event is invoked.
+ /// </remarks>
+ public int HelloMessagesReceived
+ {
+ get
+ {
+ return helloMessagesReceived;
+ }
+ }
+
+ /// <summary>
+ /// The number of hello messages received.
+ /// </summary>
+ int helloMessagesReceived;
+
+ /// <summary>
+ /// The number of bytes of data received.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// This is the number of bytes of data (i.e. user bytes) that were received by the <see cref="Connection"/>,
+ /// accumulated each time that LogReceive is called by the Connection. Messages are counted before the receive
+ /// event is invoked.
+ /// </para>
+ /// <para>
+ /// For the number of bytes including protocol bytes see <see cref="TotalBytesReceived"/>.
+ /// </para>
+ /// </remarks>
+ public long DataBytesReceived
+ {
+ get
+ {
+ return Interlocked.Read(ref dataBytesReceived);
+ }
+ }
+
+ /// <summary>
+ /// The number of bytes of data received.
+ /// </summary>
+ long dataBytesReceived;
+
+ /// <summary>
+ /// The number of bytes received in total.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// This is the total number of bytes (the data bytes plus protocol bytes) that were received by the
+ /// <see cref="Connection"/>, accumulated each time that LogReceive is called by the Connection. Messages are
+ /// counted before the receive event is invoked.
+ /// </para>
+ /// <para>
+ /// For the number of data bytes excluding protocol bytes see <see cref="DataBytesReceived"/>.
+ /// </para>
+ /// </remarks>
+ public long TotalBytesReceived
+ {
+ get
+ {
+ return Interlocked.Read(ref totalBytesReceived);
+ }
+ }
+
+ /// <summary>
+ /// The number of bytes received in total.
+ /// </summary>
+ long totalBytesReceived;
+
+ public int MessagesResent { get { return messagesResent; } }
+ int messagesResent;
+
+ /// <summary>
+ /// Logs the sending of an unreliable data packet in the statistics.
+ /// </summary>
+ /// <param name="dataLength">The number of bytes of data sent.</param>
+ /// <param name="totalLength">The total number of bytes sent.</param>
+ /// <remarks>
+ /// This should be called after the data has been sent and should only be called for data that is sent sucessfully.
+ /// </remarks>
+ internal void LogUnreliableSend(int dataLength, int totalLength)
+ {
+ Interlocked.Increment(ref unreliableMessagesSent);
+ Interlocked.Add(ref dataBytesSent, dataLength);
+ Interlocked.Add(ref totalBytesSent, totalLength);
+
+ if (totalLength > ExpectedMTU)
+ {
+ Interlocked.Increment(ref fragmentableMessagesSent);
+ }
+ }
+
+ /// <summary>
+ /// Logs the sending of a reliable data packet in the statistics.
+ /// </summary>
+ /// <param name="dataLength">The number of bytes of data sent.</param>
+ /// <param name="totalLength">The total number of bytes sent.</param>
+ /// <remarks>
+ /// This should be called after the data has been sent and should only be called for data that is sent sucessfully.
+ /// </remarks>
+ internal void LogReliableSend(int dataLength, int totalLength)
+ {
+ Interlocked.Increment(ref reliableMessagesSent);
+ Interlocked.Add(ref dataBytesSent, dataLength);
+ Interlocked.Add(ref totalBytesSent, totalLength);
+
+ if (totalLength > ExpectedMTU)
+ {
+ Interlocked.Increment(ref fragmentableMessagesSent);
+ }
+ }
+
+ /// <summary>
+ /// Logs the sending of a fragmented data packet in the statistics.
+ /// </summary>
+ /// <param name="dataLength">The number of bytes of data sent.</param>
+ /// <param name="totalLength">The total number of bytes sent.</param>
+ /// <remarks>
+ /// This should be called after the data has been sent and should only be called for data that is sent sucessfully.
+ /// </remarks>
+ internal void LogFragmentedSend(int dataLength, int totalLength)
+ {
+ Interlocked.Increment(ref fragmentedMessagesSent);
+ Interlocked.Add(ref dataBytesSent, dataLength);
+ Interlocked.Add(ref totalBytesSent, totalLength);
+
+ if (totalLength > ExpectedMTU)
+ {
+ Interlocked.Increment(ref fragmentableMessagesSent);
+ }
+ }
+
+ /// <summary>
+ /// Logs the sending of a acknowledgement data packet in the statistics.
+ /// </summary>
+ /// <param name="totalLength">The total number of bytes sent.</param>
+ /// <remarks>
+ /// This should be called after the data has been sent and should only be called for data that is sent sucessfully.
+ /// </remarks>
+ internal void LogAcknowledgementSend(int totalLength)
+ {
+ Interlocked.Increment(ref acknowledgementMessagesSent);
+ Interlocked.Add(ref totalBytesSent, totalLength);
+ }
+
+ /// <summary>
+ /// Logs the sending of a hellp data packet in the statistics.
+ /// </summary>
+ /// <param name="totalLength">The total number of bytes sent.</param>
+ /// <remarks>
+ /// This should be called after the data has been sent and should only be called for data that is sent sucessfully.
+ /// </remarks>
+ internal void LogHelloSend(int totalLength)
+ {
+ Interlocked.Increment(ref helloMessagesSent);
+ Interlocked.Add(ref totalBytesSent, totalLength);
+ }
+
+ /// <summary>
+ /// Logs the receiving of an unreliable data packet in the statistics.
+ /// </summary>
+ /// <param name="dataLength">The number of bytes of data received.</param>
+ /// <param name="totalLength">The total number of bytes received.</param>
+ /// <remarks>
+ /// This should be called before the received event is invoked so it is up to date for subscribers to that event.
+ /// </remarks>
+ internal void LogUnreliableReceive(int dataLength, int totalLength)
+ {
+ Interlocked.Increment(ref unreliableMessagesReceived);
+ Interlocked.Add(ref dataBytesReceived, dataLength);
+ Interlocked.Add(ref totalBytesReceived, totalLength);
+ }
+
+ /// <summary>
+ /// Logs the receiving of a reliable data packet in the statistics.
+ /// </summary>
+ /// <param name="dataLength">The number of bytes of data received.</param>
+ /// <param name="totalLength">The total number of bytes received.</param>
+ /// <remarks>
+ /// This should be called before the received event is invoked so it is up to date for subscribers to that event.
+ /// </remarks>
+ internal void LogReliableReceive(int dataLength, int totalLength)
+ {
+ Interlocked.Increment(ref reliableMessagesReceived);
+ Interlocked.Add(ref dataBytesReceived, dataLength);
+ Interlocked.Add(ref totalBytesReceived, totalLength);
+ }
+
+ /// <summary>
+ /// Logs the receiving of a fragmented data packet in the statistics.
+ /// </summary>
+ /// <param name="dataLength">The number of bytes of data received.</param>
+ /// <param name="totalLength">The total number of bytes received.</param>
+ /// <remarks>
+ /// This should be called before the received event is invoked so it is up to date for subscribers to that event.
+ /// </remarks>
+ internal void LogFragmentedReceive(int dataLength, int totalLength)
+ {
+ Interlocked.Increment(ref fragmentedMessagesReceived);
+ Interlocked.Add(ref dataBytesReceived, dataLength);
+ Interlocked.Add(ref totalBytesReceived, totalLength);
+ }
+
+ /// <summary>
+ /// Logs the receiving of an acknowledgement data packet in the statistics.
+ /// </summary>
+ /// <param name="totalLength">The total number of bytes received.</param>
+ /// <remarks>
+ /// This should be called before the received event is invoked so it is up to date for subscribers to that event.
+ /// </remarks>
+ internal void LogAcknowledgementReceive(int totalLength)
+ {
+ Interlocked.Increment(ref acknowledgementMessagesReceived);
+ Interlocked.Add(ref totalBytesReceived, totalLength);
+ }
+
+ /// <summary>
+ /// Logs the receiving of a hello data packet in the statistics.
+ /// </summary>
+ /// <param name="totalLength">The total number of bytes received.</param>
+ /// <remarks>
+ /// This should be called before the received event is invoked so it is up to date for subscribers to that event.
+ /// </remarks>
+ internal void LogPingReceive(int totalLength)
+ {
+ Interlocked.Increment(ref pingMessagesReceived);
+ Interlocked.Add(ref totalBytesReceived, totalLength);
+ }
+
+ /// <summary>
+ /// Logs the receiving of a hello data packet in the statistics.
+ /// </summary>
+ /// <param name="totalLength">The total number of bytes received.</param>
+ /// <remarks>
+ /// This should be called before the received event is invoked so it is up to date for subscribers to that event.
+ /// </remarks>
+ internal void LogHelloReceive(int totalLength)
+ {
+ Interlocked.Increment(ref helloMessagesReceived);
+ Interlocked.Add(ref totalBytesReceived, totalLength);
+ }
+
+ internal void LogMessageResent()
+ {
+ Interlocked.Increment(ref messagesResent);
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Hazel/DataReceivedEventArgs.cs b/Impostor-dev/src/Impostor.Hazel/DataReceivedEventArgs.cs
new file mode 100644
index 0000000..9176d8d
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Hazel/DataReceivedEventArgs.cs
@@ -0,0 +1,26 @@
+using Impostor.Api.Net.Messages;
+
+namespace Impostor.Hazel
+{
+ public struct DataReceivedEventArgs
+ {
+ public readonly Connection Sender;
+
+ /// <summary>
+ /// The bytes received from the client.
+ /// </summary>
+ public readonly IMessageReader Message;
+
+ /// <summary>
+ /// The <see cref="Type"/> the data was sent with.
+ /// </summary>
+ public readonly MessageType Type;
+
+ public DataReceivedEventArgs(Connection sender, IMessageReader msg, MessageType type)
+ {
+ this.Sender = sender;
+ this.Message = msg;
+ this.Type = type;
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Hazel/DisconnectedEventArgs.cs b/Impostor-dev/src/Impostor.Hazel/DisconnectedEventArgs.cs
new file mode 100644
index 0000000..d46df4b
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Hazel/DisconnectedEventArgs.cs
@@ -0,0 +1,25 @@
+using System;
+using Impostor.Api.Net.Messages;
+
+namespace Impostor.Hazel
+{
+ public class DisconnectedEventArgs : EventArgs
+ {
+ /// <summary>
+ /// Optional disconnect reason. May be null.
+ /// </summary>
+ public readonly string Reason;
+
+ /// <summary>
+ /// Optional data sent with a disconnect message. May be null.
+ /// You must not recycle this. If you need the message outside of a callback, you should copy it.
+ /// </summary>
+ public readonly IMessageReader Message;
+
+ public DisconnectedEventArgs(string reason, IMessageReader message)
+ {
+ this.Reason = reason;
+ this.Message = message;
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Hazel/Extensions/ServiceProviderExtensions.cs b/Impostor-dev/src/Impostor.Hazel/Extensions/ServiceProviderExtensions.cs
new file mode 100644
index 0000000..56c7380
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Hazel/Extensions/ServiceProviderExtensions.cs
@@ -0,0 +1,21 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.ObjectPool;
+
+namespace Impostor.Hazel.Extensions
+{
+ public static class ServiceProviderExtensions
+ {
+ public static void AddHazel(this IServiceCollection services)
+ {
+ services.TryAddSingleton<ObjectPoolProvider>(new DefaultObjectPoolProvider());
+
+ services.AddSingleton(serviceProvider =>
+ {
+ var provider = serviceProvider.GetRequiredService<ObjectPoolProvider>();
+ var policy = ActivatorUtilities.CreateInstance<MessageReaderPolicy>(serviceProvider);
+ return provider.Create(policy);
+ });
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Hazel/HazelException.cs b/Impostor-dev/src/Impostor.Hazel/HazelException.cs
new file mode 100644
index 0000000..8c6fc3c
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Hazel/HazelException.cs
@@ -0,0 +1,21 @@
+using System;
+
+namespace Impostor.Hazel
+{
+ /// <summary>
+ /// Wrapper for exceptions thrown from Hazel.
+ /// </summary>
+ [Serializable]
+ public class HazelException : Exception
+ {
+ internal HazelException(string msg) : base (msg)
+ {
+
+ }
+
+ internal HazelException(string msg, Exception e) : base (msg, e)
+ {
+
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Hazel/IPMode.cs b/Impostor-dev/src/Impostor.Hazel/IPMode.cs
new file mode 100644
index 0000000..5eb6679
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Hazel/IPMode.cs
@@ -0,0 +1,24 @@
+namespace Impostor.Hazel
+{
+ /// <summary>
+ /// Represents the IP version that a connection or listener will use.
+ /// </summary>
+ /// <remarks>
+ /// If you wand a client to connect or be able to connect using IPv6 then you should use <see cref="IPv4AndIPv6"/>,
+ /// this sets the underlying sockets to use IPv6 but still allow IPv4 sockets to connect for backwards compatability
+ /// and hence it is the default IPMode in most cases.
+ /// </remarks>
+ public enum IPMode
+ {
+ /// <summary>
+ /// Instruction to use IPv4 only, IPv6 connections will not be able to connect.
+ /// </summary>
+ IPv4,
+
+ /// <summary>
+ /// Instruction to use IPv6 only, IPv4 connections will not be able to connect. IPv4 addresses can be connected
+ /// by converting to IPv6 addresses.
+ /// </summary>
+ IPv6
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Hazel/IRecyclable.cs b/Impostor-dev/src/Impostor.Hazel/IRecyclable.cs
new file mode 100644
index 0000000..69be122
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Hazel/IRecyclable.cs
@@ -0,0 +1,24 @@
+namespace Impostor.Hazel
+{
+ /// <summary>
+ /// Interface for all items that can be returned to an object pool.
+ /// </summary>
+ /// <threadsafety static="true" instance="true"/>
+ public interface IRecyclable
+ {
+ /// <summary>
+ /// Returns this object back to the object pool.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// Calling this when you are done with the object returns the object back to a pool in order to be reused.
+ /// This can reduce the amount of work the GC has to do dramatically but it is optional to call this.
+ /// </para>
+ /// <para>
+ /// Calling this indicates to Hazel that this can be reused and thus you should only call this when you are
+ /// completely finished with the object as the contents can be overwritten at any point after.
+ /// </para>
+ /// </remarks>
+ void Recycle();
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Hazel/Impostor.Hazel.csproj b/Impostor-dev/src/Impostor.Hazel/Impostor.Hazel.csproj
new file mode 100644
index 0000000..3e035fb
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Hazel/Impostor.Hazel.csproj
@@ -0,0 +1,19 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ <TargetFramework>net5.0</TargetFramework>
+ <DefineConstants>HAZEL_BAG</DefineConstants>
+ <Version>1.0.0</Version>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="Microsoft.Extensions.ObjectPool" Version="5.0.0" />
+ <PackageReference Include="Serilog" Version="2.10.0" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\Impostor.Api\Impostor.Api.csproj" />
+ </ItemGroup>
+
+</Project>
diff --git a/Impostor-dev/src/Impostor.Hazel/MessageReader.cs b/Impostor-dev/src/Impostor.Hazel/MessageReader.cs
new file mode 100644
index 0000000..986d0b0
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Hazel/MessageReader.cs
@@ -0,0 +1,256 @@
+using System;
+using System.Buffers;
+using System.Buffers.Binary;
+using System.Runtime.CompilerServices;
+using System.Text;
+using Impostor.Api;
+using Impostor.Api.Net.Messages;
+using Microsoft.Extensions.ObjectPool;
+
+namespace Impostor.Hazel
+{
+ public class MessageReader : IMessageReader
+ {
+ private static readonly ArrayPool<byte> ArrayPool = ArrayPool<byte>.Shared;
+
+ private readonly ObjectPool<MessageReader> _pool;
+ private bool _inUse;
+
+ internal MessageReader(ObjectPool<MessageReader> pool)
+ {
+ _pool = pool;
+ }
+
+ public byte[] Buffer { get; private set; }
+
+ public int Offset { get; internal set; }
+
+ public int Position { get; internal set; }
+
+ public int Length { get; internal set; }
+
+ public byte Tag { get; private set; }
+
+ public MessageReader Parent { get; private set; }
+
+ private int ReadPosition => Offset + Position;
+
+ public void Update(byte[] buffer, int offset = 0, int position = 0, int? length = null, byte tag = byte.MaxValue, MessageReader parent = null)
+ {
+ _inUse = true;
+
+ Buffer = buffer;
+ Offset = offset;
+ Position = position;
+ Length = length ?? buffer.Length;
+ Tag = tag;
+ Parent = parent;
+ }
+
+ internal void Reset()
+ {
+ _inUse = false;
+
+ Tag = byte.MaxValue;
+ Buffer = null;
+ Offset = 0;
+ Position = 0;
+ Length = 0;
+ Parent = null;
+ }
+
+ public IMessageReader ReadMessage()
+ {
+ var length = ReadUInt16();
+ var tag = FastByte();
+ var pos = ReadPosition;
+
+ Position += length;
+
+ var reader = _pool.Get();
+ reader.Update(Buffer, pos, 0, length, tag, this);
+ return reader;
+ }
+
+ public bool ReadBoolean()
+ {
+ byte val = FastByte();
+ return val != 0;
+ }
+
+ public sbyte ReadSByte()
+ {
+ return (sbyte)FastByte();
+ }
+
+ public byte ReadByte()
+ {
+ return FastByte();
+ }
+
+ public ushort ReadUInt16()
+ {
+ var output = BinaryPrimitives.ReadUInt16LittleEndian(Buffer.AsSpan(ReadPosition));
+ Position += sizeof(ushort);
+ return output;
+ }
+
+ public short ReadInt16()
+ {
+ var output = BinaryPrimitives.ReadInt16LittleEndian(Buffer.AsSpan(ReadPosition));
+ Position += sizeof(short);
+ return output;
+ }
+
+ public uint ReadUInt32()
+ {
+ var output = BinaryPrimitives.ReadUInt32LittleEndian(Buffer.AsSpan(ReadPosition));
+ Position += sizeof(uint);
+ return output;
+ }
+
+ public int ReadInt32()
+ {
+ var output = BinaryPrimitives.ReadInt32LittleEndian(Buffer.AsSpan(ReadPosition));
+ Position += sizeof(int);
+ return output;
+ }
+
+ public unsafe float ReadSingle()
+ {
+ var output = BinaryPrimitives.ReadSingleLittleEndian(Buffer.AsSpan(ReadPosition));
+ Position += sizeof(float);
+ return output;
+ }
+
+ public string ReadString()
+ {
+ var len = ReadPackedInt32();
+ var output = Encoding.UTF8.GetString(Buffer.AsSpan(ReadPosition, len));
+ Position += len;
+ return output;
+ }
+
+ public ReadOnlyMemory<byte> ReadBytesAndSize()
+ {
+ var len = ReadPackedInt32();
+ return ReadBytes(len);
+ }
+
+ public ReadOnlyMemory<byte> ReadBytes(int length)
+ {
+ var output = Buffer.AsMemory(ReadPosition, length);
+ Position += length;
+ return output;
+ }
+
+ public int ReadPackedInt32()
+ {
+ return (int)ReadPackedUInt32();
+ }
+
+ public uint ReadPackedUInt32()
+ {
+ bool readMore = true;
+ int shift = 0;
+ uint output = 0;
+
+ while (readMore)
+ {
+ byte b = FastByte();
+ if (b >= 0x80)
+ {
+ readMore = true;
+ b ^= 0x80;
+ }
+ else
+ {
+ readMore = false;
+ }
+
+ output |= (uint)(b << shift);
+ shift += 7;
+ }
+
+ return output;
+ }
+
+ public void CopyTo(IMessageWriter writer)
+ {
+ writer.Write((ushort) Length);
+ writer.Write((byte) Tag);
+ writer.Write(Buffer.AsMemory(Offset, Length));
+ }
+
+ public void Seek(int position)
+ {
+ Position = position;
+ }
+
+ public void RemoveMessage(IMessageReader message)
+ {
+ if (message.Buffer != Buffer)
+ {
+ throw new ImpostorProtocolException("Tried to remove message from a message that does not have the same buffer.");
+ }
+
+ // Offset of where to start removing.
+ var offsetStart = message.Offset - 3;
+
+ // Offset of where to end removing.
+ var offsetEnd = message.Offset + message.Length;
+
+ // The amount of bytes to copy over ourselves.
+ var lengthToCopy = message.Buffer.Length - offsetEnd;
+
+ System.Buffer.BlockCopy(Buffer, offsetEnd, Buffer, offsetStart, lengthToCopy);
+
+ ((MessageReader) message).Parent.AdjustLength(message.Offset, message.Length + 3);
+ }
+
+ private void AdjustLength(int offset, int amount)
+ {
+ this.Length -= amount;
+
+ if (this.ReadPosition > offset)
+ {
+ this.Position -= amount;
+ }
+
+ if (Parent != null)
+ {
+ var lengthOffset = this.Offset - 3;
+ var curLen = this.Buffer[lengthOffset] |
+ (this.Buffer[lengthOffset + 1] << 8);
+
+ curLen -= amount;
+
+ this.Buffer[lengthOffset] = (byte)curLen;
+ this.Buffer[lengthOffset + 1] = (byte)(this.Buffer[lengthOffset + 1] >> 8);
+
+ Parent.AdjustLength(offset, amount);
+ }
+ }
+
+ public IMessageReader Copy(int offset = 0)
+ {
+ var reader = _pool.Get();
+ reader.Update(Buffer, Offset + offset, Position, Length - offset, Tag, Parent);
+ return reader;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private byte FastByte()
+ {
+ return Buffer[Offset + Position++];
+ }
+
+ public void Dispose()
+ {
+ if (_inUse)
+ {
+ _pool.Return(this);
+ }
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Hazel/MessageReaderPolicy.cs b/Impostor-dev/src/Impostor.Hazel/MessageReaderPolicy.cs
new file mode 100644
index 0000000..ef3939a
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Hazel/MessageReaderPolicy.cs
@@ -0,0 +1,27 @@
+using System;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.ObjectPool;
+
+namespace Impostor.Hazel
+{
+ public class MessageReaderPolicy : IPooledObjectPolicy<MessageReader>
+ {
+ private readonly IServiceProvider _serviceProvider;
+
+ public MessageReaderPolicy(IServiceProvider serviceProvider)
+ {
+ _serviceProvider = serviceProvider;
+ }
+
+ public MessageReader Create()
+ {
+ return new MessageReader(_serviceProvider.GetRequiredService<ObjectPool<MessageReader>>());
+ }
+
+ public bool Return(MessageReader obj)
+ {
+ obj.Reset();
+ return true;
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Hazel/MessageWriter.cs b/Impostor-dev/src/Impostor.Hazel/MessageWriter.cs
new file mode 100644
index 0000000..5b7342a
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Hazel/MessageWriter.cs
@@ -0,0 +1,335 @@
+using Impostor.Api.Games;
+using Impostor.Api.Net.Messages;
+
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Text;
+
+namespace Impostor.Hazel
+{
+ public class MessageWriter : IMessageWriter, IRecyclable, IDisposable
+ {
+ private static int BufferSize = 64000;
+ private static readonly ObjectPoolCustom<MessageWriter> WriterPool = new ObjectPoolCustom<MessageWriter>(() => new MessageWriter(BufferSize));
+
+ public MessageType SendOption { get; private set; }
+
+ private Stack<int> messageStarts = new Stack<int>();
+
+ public MessageWriter(byte[] buffer)
+ {
+ this.Buffer = buffer;
+ this.Length = this.Buffer.Length;
+ }
+
+ public MessageWriter(int bufferSize)
+ {
+ this.Buffer = new byte[bufferSize];
+ }
+
+ public byte[] Buffer { get; }
+ public int Length { get; set; }
+ public int Position { get; set; }
+
+ public byte[] ToByteArray(bool includeHeader)
+ {
+ if (includeHeader)
+ {
+ byte[] output = new byte[this.Length];
+ System.Buffer.BlockCopy(this.Buffer, 0, output, 0, this.Length);
+ return output;
+ }
+ else
+ {
+ switch (this.SendOption)
+ {
+ case MessageType.Reliable:
+ {
+ byte[] output = new byte[this.Length - 3];
+ System.Buffer.BlockCopy(this.Buffer, 3, output, 0, this.Length - 3);
+ return output;
+ }
+ case MessageType.Unreliable:
+ {
+ byte[] output = new byte[this.Length - 1];
+ System.Buffer.BlockCopy(this.Buffer, 1, output, 0, this.Length - 1);
+ return output;
+ }
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// <param name="sendOption">The option specifying how the message should be sent.</param>
+ public static MessageWriter Get(MessageType sendOption = MessageType.Unreliable)
+ {
+ var output = WriterPool.GetObject();
+ output.Clear(sendOption);
+
+ return output;
+ }
+
+ public bool HasBytes(int expected)
+ {
+ if (this.SendOption == MessageType.Unreliable)
+ {
+ return this.Length > 1 + expected;
+ }
+
+ return this.Length > 3 + expected;
+ }
+
+ public void Write(GameCode value)
+ {
+ this.Write(value.Value);
+ }
+
+ ///
+ public void StartMessage(byte typeFlag)
+ {
+ messageStarts.Push(this.Position);
+ this.Position += 2; // Skip for size
+ this.Write(typeFlag);
+ }
+
+ ///
+ public void EndMessage()
+ {
+ var lastMessageStart = messageStarts.Pop();
+ ushort length = (ushort)(this.Position - lastMessageStart - 3); // Minus length and type byte
+ this.Buffer[lastMessageStart] = (byte)length;
+ this.Buffer[lastMessageStart + 1] = (byte)(length >> 8);
+ }
+
+ ///
+ public void CancelMessage()
+ {
+ this.Position = this.messageStarts.Pop();
+ this.Length = this.Position;
+ }
+
+ public void Clear(MessageType sendOption)
+ {
+ this.messageStarts.Clear();
+ this.SendOption = sendOption;
+ this.Buffer[0] = (byte)sendOption;
+ switch (sendOption)
+ {
+ default:
+ case MessageType.Unreliable:
+ this.Length = this.Position = 1;
+ break;
+
+ case MessageType.Reliable:
+ this.Length = this.Position = 3;
+ break;
+ }
+ }
+
+ ///
+ public void Recycle()
+ {
+ this.Position = this.Length = 0;
+ WriterPool.PutObject(this);
+ }
+
+ #region WriteMethods
+
+ public void Write(bool value)
+ {
+ this.Buffer[this.Position++] = (byte)(value ? 1 : 0);
+ if (this.Position > this.Length) this.Length = this.Position;
+ }
+
+ public void Write(sbyte value)
+ {
+ this.Buffer[this.Position++] = (byte)value;
+ if (this.Position > this.Length) this.Length = this.Position;
+ }
+
+ public void Write(byte value)
+ {
+ this.Buffer[this.Position++] = value;
+ if (this.Position > this.Length) this.Length = this.Position;
+ }
+
+ public void Write(short value)
+ {
+ this.Buffer[this.Position++] = (byte)value;
+ this.Buffer[this.Position++] = (byte)(value >> 8);
+ if (this.Position > this.Length) this.Length = this.Position;
+ }
+
+ public void Write(ushort value)
+ {
+ this.Buffer[this.Position++] = (byte)value;
+ this.Buffer[this.Position++] = (byte)(value >> 8);
+ if (this.Position > this.Length) this.Length = this.Position;
+ }
+
+ public void Write(uint value)
+ {
+ this.Buffer[this.Position++] = (byte)value;
+ this.Buffer[this.Position++] = (byte)(value >> 8);
+ this.Buffer[this.Position++] = (byte)(value >> 16);
+ this.Buffer[this.Position++] = (byte)(value >> 24);
+ if (this.Position > this.Length) this.Length = this.Position;
+ }
+
+ public void Write(int value)
+ {
+ this.Buffer[this.Position++] = (byte)value;
+ this.Buffer[this.Position++] = (byte)(value >> 8);
+ this.Buffer[this.Position++] = (byte)(value >> 16);
+ this.Buffer[this.Position++] = (byte)(value >> 24);
+ if (this.Position > this.Length) this.Length = this.Position;
+ }
+
+ public unsafe void Write(float value)
+ {
+ fixed (byte* ptr = &this.Buffer[this.Position])
+ {
+ byte* valuePtr = (byte*)&value;
+
+ *ptr = *valuePtr;
+ *(ptr + 1) = *(valuePtr + 1);
+ *(ptr + 2) = *(valuePtr + 2);
+ *(ptr + 3) = *(valuePtr + 3);
+ }
+
+ this.Position += 4;
+ if (this.Position > this.Length) this.Length = this.Position;
+ }
+
+ public void Write(string value)
+ {
+ var bytes = UTF8Encoding.UTF8.GetBytes(value);
+ this.WritePacked(bytes.Length);
+ this.Write(bytes);
+ }
+
+ public void Write(IPAddress value)
+ {
+ this.Write(value.GetAddressBytes());
+ }
+
+ public void WriteBytesAndSize(byte[] bytes)
+ {
+ this.WritePacked((uint)bytes.Length);
+ this.Write(bytes);
+ }
+
+ public void WriteBytesAndSize(byte[] bytes, int length)
+ {
+ this.WritePacked((uint)length);
+ this.Write(bytes, length);
+ }
+
+ public void WriteBytesAndSize(byte[] bytes, int offset, int length)
+ {
+ this.WritePacked((uint)length);
+ this.Write(bytes, offset, length);
+ }
+
+ public void Write(ReadOnlyMemory<byte> data)
+ {
+ Write(data.Span);
+ }
+
+ public void Write(ReadOnlySpan<byte> bytes)
+ {
+ bytes.CopyTo(this.Buffer.AsSpan(this.Position, bytes.Length));
+
+ this.Position += bytes.Length;
+ if (this.Position > this.Length) this.Length = this.Position;
+ }
+
+ public void Write(byte[] bytes)
+ {
+ Array.Copy(bytes, 0, this.Buffer, this.Position, bytes.Length);
+ this.Position += bytes.Length;
+ if (this.Position > this.Length) this.Length = this.Position;
+ }
+
+ public void Write(byte[] bytes, int offset, int length)
+ {
+ Array.Copy(bytes, offset, this.Buffer, this.Position, length);
+ this.Position += length;
+ if (this.Position > this.Length) this.Length = this.Position;
+ }
+
+ public void Write(byte[] bytes, int length)
+ {
+ Array.Copy(bytes, 0, this.Buffer, this.Position, length);
+ this.Position += length;
+ if (this.Position > this.Length) this.Length = this.Position;
+ }
+
+ ///
+ public void WritePacked(int value)
+ {
+ this.WritePacked((uint)value);
+ }
+
+ ///
+ public void WritePacked(uint value)
+ {
+ do
+ {
+ byte b = (byte)(value & 0xFF);
+ if (value >= 0x80)
+ {
+ b |= 0x80;
+ }
+
+ this.Write(b);
+ value >>= 7;
+ } while (value > 0);
+ }
+
+ #endregion WriteMethods
+
+ public void Write(MessageWriter msg, bool includeHeader)
+ {
+ int offset = 0;
+ if (!includeHeader)
+ {
+ switch (msg.SendOption)
+ {
+ case MessageType.Unreliable:
+ offset = 1;
+ break;
+
+ case MessageType.Reliable:
+ offset = 3;
+ break;
+ }
+ }
+
+ this.Write(msg.Buffer, offset, msg.Length - offset);
+ }
+
+ public unsafe static bool IsLittleEndian()
+ {
+ byte b;
+ unsafe
+ {
+ int i = 1;
+ byte* bp = (byte*)&i;
+ b = *bp;
+ }
+
+ return b == 1;
+ }
+
+ public void Dispose()
+ {
+ Recycle();
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Hazel/NetworkConnection.cs b/Impostor-dev/src/Impostor.Hazel/NetworkConnection.cs
new file mode 100644
index 0000000..282fe10
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Hazel/NetworkConnection.cs
@@ -0,0 +1,121 @@
+using System;
+using System.Net;
+using System.Threading.Tasks;
+using Impostor.Api.Net.Messages;
+
+namespace Impostor.Hazel
+{
+ public enum HazelInternalErrors
+ {
+ SocketExceptionSend,
+ SocketExceptionReceive,
+ ReceivedZeroBytes,
+ PingsWithoutResponse,
+ ReliablePacketWithoutResponse,
+ ConnectionDisconnected
+ }
+
+ /// <summary>
+ /// Abstract base class for a <see cref="Connection"/> to a remote end point via a network protocol like TCP or UDP.
+ /// </summary>
+ /// <threadsafety static="true" instance="true"/>
+ public abstract class NetworkConnection : Connection
+ {
+ /// <summary>
+ /// An event that gives us a chance to send well-formed disconnect messages to clients when an internal disconnect happens.
+ /// </summary>
+ public Func<HazelInternalErrors, MessageWriter> OnInternalDisconnect;
+
+ /// <summary>
+ /// The remote end point of this connection.
+ /// </summary>
+ /// <remarks>
+ /// This is the end point of the other device given as an <see cref="System.Net.EndPoint"/> rather than a generic
+ /// <see cref="ConnectionEndPoint"/> as the base <see cref="Connection"/> does.
+ /// </remarks>
+ public IPEndPoint RemoteEndPoint { get; protected set; }
+
+ public long GetIP4Address()
+ {
+ if (IPMode == IPMode.IPv4)
+ {
+ return ((IPEndPoint)this.RemoteEndPoint).Address.Address;
+ }
+ else
+ {
+ var bytes = ((IPEndPoint)this.RemoteEndPoint).Address.GetAddressBytes();
+ return BitConverter.ToInt64(bytes, bytes.Length - 8);
+ }
+ }
+
+ /// <summary>
+ /// Sends a disconnect message to the end point.
+ /// </summary>
+ protected abstract ValueTask<bool> SendDisconnect(MessageWriter writer);
+
+ /// <summary>
+ /// Called when the socket has been disconnected at the remote host.
+ /// </summary>
+ protected async ValueTask DisconnectRemote(string reason, IMessageReader reader)
+ {
+ if (await SendDisconnect(null))
+ {
+ try
+ {
+ await InvokeDisconnected(reason, reader);
+ }
+ catch { }
+ }
+
+ this.Dispose();
+ }
+
+ /// <summary>
+ /// Called when socket is disconnected internally
+ /// </summary>
+ internal async ValueTask DisconnectInternal(HazelInternalErrors error, string reason)
+ {
+ var handler = this.OnInternalDisconnect;
+ if (handler != null)
+ {
+ MessageWriter messageToRemote = handler(error);
+ if (messageToRemote != null)
+ {
+ try
+ {
+ await Disconnect(reason, messageToRemote);
+ }
+ finally
+ {
+ messageToRemote.Recycle();
+ }
+ }
+ else
+ {
+ await Disconnect(reason);
+ }
+ }
+ else
+ {
+ await Disconnect(reason);
+ }
+ }
+
+ /// <summary>
+ /// Called when the socket has been disconnected locally.
+ /// </summary>
+ public override async ValueTask Disconnect(string reason, MessageWriter writer = null)
+ {
+ if (await SendDisconnect(writer))
+ {
+ try
+ {
+ await InvokeDisconnected(reason, null);
+ }
+ catch { }
+ }
+
+ this.Dispose();
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Hazel/NetworkConnectionListener.cs b/Impostor-dev/src/Impostor.Hazel/NetworkConnectionListener.cs
new file mode 100644
index 0000000..e1d7ffa
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Hazel/NetworkConnectionListener.cs
@@ -0,0 +1,21 @@
+using System.Net;
+
+namespace Impostor.Hazel
+{
+ /// <summary>
+ /// Abstract base class for a <see cref="ConnectionListener"/> for network based connections.
+ /// </summary>
+ /// <threadsafety static="true" instance="true"/>
+ public abstract class NetworkConnectionListener : ConnectionListener
+ {
+ /// <summary>
+ /// The local end point the listener is listening for new clients on.
+ /// </summary>
+ public IPEndPoint EndPoint { get; protected set; }
+
+ /// <summary>
+ /// The <see cref="IPMode">IPMode</see> the listener is listening for new clients on.
+ /// </summary>
+ public IPMode IPMode { get; protected set; }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Hazel/NewConnectionEventArgs.cs b/Impostor-dev/src/Impostor.Hazel/NewConnectionEventArgs.cs
new file mode 100644
index 0000000..be9e7a2
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Hazel/NewConnectionEventArgs.cs
@@ -0,0 +1,24 @@
+using Impostor.Api.Net.Messages;
+
+namespace Impostor.Hazel
+{
+ public struct NewConnectionEventArgs
+ {
+ /// <summary>
+ /// The data received from the client in the handshake.
+ /// This data is yours. Remember to recycle it.
+ /// </summary>
+ public readonly IMessageReader HandshakeData;
+
+ /// <summary>
+ /// The <see cref="Connection"/> to the new client.
+ /// </summary>
+ public readonly Connection Connection;
+
+ public NewConnectionEventArgs(IMessageReader handshakeData, Connection connection)
+ {
+ this.HandshakeData = handshakeData;
+ this.Connection = connection;
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Hazel/ObjectPoolCustom.cs b/Impostor-dev/src/Impostor.Hazel/ObjectPoolCustom.cs
new file mode 100644
index 0000000..5c9ef9b
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Hazel/ObjectPoolCustom.cs
@@ -0,0 +1,107 @@
+using System;
+using System.Collections.Concurrent;
+using System.Threading;
+
+namespace Impostor.Hazel
+{
+ /// <summary>
+ /// A fairly simple object pool for items that will be created a lot.
+ /// </summary>
+ /// <typeparam name="T">The type that is pooled.</typeparam>
+ /// <threadsafety static="true" instance="true"/>
+ public sealed class ObjectPoolCustom<T> where T : IRecyclable
+ {
+ private int numberCreated;
+ public int NumberCreated { get { return numberCreated; } }
+
+ public int NumberInUse { get { return this.inuse.Count; } }
+ public int NumberNotInUse { get { return this.pool.Count; } }
+ public int Size { get { return this.NumberInUse + this.NumberNotInUse; } }
+
+#if HAZEL_BAG
+ private readonly ConcurrentBag<T> pool = new ConcurrentBag<T>();
+#else
+ private readonly List<T> pool = new List<T>();
+#endif
+
+ // Unavailable objects
+ private readonly ConcurrentDictionary<T, bool> inuse = new ConcurrentDictionary<T, bool>();
+
+ /// <summary>
+ /// The generator for creating new objects.
+ /// </summary>
+ /// <returns></returns>
+ private readonly Func<T> objectFactory;
+
+ /// <summary>
+ /// Internal constructor for our ObjectPool.
+ /// </summary>
+ internal ObjectPoolCustom(Func<T> objectFactory)
+ {
+ this.objectFactory = objectFactory;
+ }
+
+ /// <summary>
+ /// Returns a pooled object of type T, if none are available another is created.
+ /// </summary>
+ /// <returns>An instance of T.</returns>
+ internal T GetObject()
+ {
+#if HAZEL_BAG
+ if (!pool.TryTake(out T item))
+ {
+ Interlocked.Increment(ref numberCreated);
+ item = objectFactory.Invoke();
+ }
+#else
+ T item;
+ lock (this.pool)
+ {
+ if (this.pool.Count > 0)
+ {
+ var idx = this.pool.Count - 1;
+ item = this.pool[idx];
+ this.pool.RemoveAt(idx);
+ }
+ else
+ {
+ Interlocked.Increment(ref numberCreated);
+ item = objectFactory.Invoke();
+ }
+ }
+#endif
+
+ if (!inuse.TryAdd(item, true))
+ {
+ throw new Exception("Duplicate pull " + typeof(T).Name);
+ }
+
+ return item;
+ }
+
+ /// <summary>
+ /// Returns an object to the pool.
+ /// </summary>
+ /// <param name="item">The item to return.</param>
+ internal void PutObject(T item)
+ {
+ if (inuse.TryRemove(item, out bool b))
+ {
+#if HAZEL_BAG
+ pool.Add(item);
+#else
+ lock (this.pool)
+ {
+ pool.Add(item);
+ }
+#endif
+ }
+ else
+ {
+#if DEBUG
+ throw new Exception("Duplicate add " + typeof(T).Name);
+#endif
+ }
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Hazel/Udp/SendOptionInternal.cs b/Impostor-dev/src/Impostor.Hazel/Udp/SendOptionInternal.cs
new file mode 100644
index 0000000..c0c4e21
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Hazel/Udp/SendOptionInternal.cs
@@ -0,0 +1,33 @@
+namespace Impostor.Hazel.Udp
+{
+ /// <summary>
+ /// Extra internal states for SendOption enumeration when using UDP.
+ /// </summary>
+ public enum UdpSendOption : byte
+ {
+ /// <summary>
+ /// Hello message for initiating communication.
+ /// </summary>
+ Hello = 8,
+
+ /// <summary>
+ /// A single byte of continued existence
+ /// </summary>
+ Ping = 12,
+
+ /// <summary>
+ /// Message for discontinuing communication.
+ /// </summary>
+ Disconnect = 9,
+
+ /// <summary>
+ /// Message acknowledging the receipt of a message.
+ /// </summary>
+ Acknowledgement = 10,
+
+ /// <summary>
+ /// Message that is part of a larger, fragmented message.
+ /// </summary>
+ Fragment = 11,
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Hazel/Udp/UdpBroadcastListener.cs b/Impostor-dev/src/Impostor.Hazel/Udp/UdpBroadcastListener.cs
new file mode 100644
index 0000000..ed7b68d
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Hazel/Udp/UdpBroadcastListener.cs
@@ -0,0 +1,156 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+
+namespace Impostor.Hazel.Udp
+{
+ public class BroadcastPacket
+ {
+ public string Data;
+ public DateTime ReceiveTime;
+ public IPEndPoint Sender;
+
+ public BroadcastPacket(string data, IPEndPoint sender)
+ {
+ this.Data = data;
+ this.Sender = sender;
+ this.ReceiveTime = DateTime.Now;
+ }
+
+ public string GetAddress()
+ {
+ return this.Sender.Address.ToString();
+ }
+ }
+
+ public class UdpBroadcastListener : IDisposable
+ {
+ private Socket socket;
+ private EndPoint endpoint;
+ private Action<string> logger;
+
+ private byte[] buffer = new byte[1024];
+
+ private List<BroadcastPacket> packets = new List<BroadcastPacket>();
+
+ public bool Running { get; private set; }
+
+ ///
+ public UdpBroadcastListener(int port, Action<string> logger = null)
+ {
+ this.logger = logger;
+ this.socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
+ this.socket.EnableBroadcast = true;
+ this.socket.MulticastLoopback = false;
+ this.endpoint = new IPEndPoint(IPAddress.Any, port);
+ this.socket.Bind(this.endpoint);
+ }
+
+ ///
+ public void StartListen()
+ {
+ if (this.Running) return;
+ this.Running = true;
+
+ try
+ {
+ EndPoint endpt = new IPEndPoint(IPAddress.Any, 0);
+ this.socket.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref endpt, this.HandleData, null);
+ }
+ catch (NullReferenceException) { }
+ catch (Exception e)
+ {
+ this.logger?.Invoke("BroadcastListener: " + e);
+ this.Dispose();
+ }
+ }
+
+ private void HandleData(IAsyncResult result)
+ {
+ this.Running = false;
+
+ int numBytes;
+ EndPoint endpt = new IPEndPoint(IPAddress.Any, 0);
+ try
+ {
+ numBytes = this.socket.EndReceiveFrom(result, ref endpt);
+ }
+ catch (NullReferenceException)
+ {
+ // Already disposed
+ return;
+ }
+ catch (Exception e)
+ {
+ this.logger?.Invoke("BroadcastListener: " + e);
+ this.Dispose();
+ return;
+ }
+
+ if (numBytes < 3
+ || buffer[0] != 4 || buffer[1] != 2)
+ {
+ this.StartListen();
+ return;
+ }
+
+ IPEndPoint ipEnd = (IPEndPoint)endpt;
+ string data = UTF8Encoding.UTF8.GetString(buffer, 2, numBytes - 2);
+ int dataHash = data.GetHashCode();
+
+ lock (packets)
+ {
+ bool found = false;
+ for (int i = 0; i < this.packets.Count; ++i)
+ {
+ var pkt = this.packets[i];
+ if (pkt == null || pkt.Data == null)
+ {
+ this.packets.RemoveAt(i);
+ i--;
+ continue;
+ }
+
+ if (pkt.Data.GetHashCode() == dataHash
+ && pkt.Sender.Equals(ipEnd))
+ {
+ this.packets[i].ReceiveTime = DateTime.Now;
+ break;
+ }
+ }
+
+ if (!found)
+ {
+ this.packets.Add(new BroadcastPacket(data, ipEnd));
+ }
+ }
+
+ this.StartListen();
+ }
+
+ ///
+ public BroadcastPacket[] GetPackets()
+ {
+ lock (this.packets)
+ {
+ var output = this.packets.ToArray();
+ this.packets.Clear();
+ return output;
+ }
+ }
+
+ ///
+ public void Dispose()
+ {
+ if (this.socket != null)
+ {
+ try { this.socket.Shutdown(SocketShutdown.Both); } catch { }
+ try { this.socket.Close(); } catch { }
+ try { this.socket.Dispose(); } catch { }
+ this.socket = null;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Hazel/Udp/UdpBroadcaster.cs b/Impostor-dev/src/Impostor.Hazel/Udp/UdpBroadcaster.cs
new file mode 100644
index 0000000..5fa1cca
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Hazel/Udp/UdpBroadcaster.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+
+namespace Impostor.Hazel.Udp
+{
+ ///
+ public class UdpBroadcaster : IDisposable
+ {
+ private Socket socket;
+ private byte[] data;
+ private EndPoint endpoint;
+ private Action<string> logger;
+
+ ///
+ public UdpBroadcaster(int port, Action<string> logger = null)
+ {
+ this.logger = logger;
+ this.socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
+ this.socket.EnableBroadcast = true;
+ this.socket.MulticastLoopback = false;
+ this.endpoint = new IPEndPoint(IPAddress.Broadcast, port);
+ }
+
+ ///
+ public void SetData(string data)
+ {
+ int len = UTF8Encoding.UTF8.GetByteCount(data);
+ this.data = new byte[len + 2];
+ this.data[0] = 4;
+ this.data[1] = 2;
+
+ UTF8Encoding.UTF8.GetBytes(data, 0, data.Length, this.data, 2);
+ }
+
+ ///
+ public void Broadcast()
+ {
+ if (this.data == null)
+ {
+ return;
+ }
+
+ try
+ {
+ this.socket.BeginSendTo(data, 0, data.Length, SocketFlags.None, this.endpoint, this.FinishSendTo, null);
+ }
+ catch (Exception e)
+ {
+ this.logger?.Invoke("BroadcastListener: " + e);
+ }
+ }
+
+ private void FinishSendTo(IAsyncResult evt)
+ {
+ try
+ {
+ this.socket.EndSendTo(evt);
+ }
+ catch (Exception e)
+ {
+ this.logger?.Invoke("BroadcastListener: " + e);
+ }
+ }
+
+ ///
+ public void Dispose()
+ {
+ if (this.socket != null)
+ {
+ try { this.socket.Shutdown(SocketShutdown.Both); } catch { }
+ try { this.socket.Close(); } catch { }
+ try { this.socket.Dispose(); } catch { }
+ this.socket = null;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Hazel/Udp/UdpClientConnection.cs b/Impostor-dev/src/Impostor.Hazel/Udp/UdpClientConnection.cs
new file mode 100644
index 0000000..5125ebe
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Hazel/Udp/UdpClientConnection.cs
@@ -0,0 +1,225 @@
+using System;
+using System.Buffers;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+using System.Threading.Channels;
+using System.Threading.Tasks;
+using Impostor.Api.Net.Messages;
+using Microsoft.Extensions.ObjectPool;
+using Serilog;
+
+namespace Impostor.Hazel.Udp
+{
+ /// <summary>
+ /// Represents a client's connection to a server that uses the UDP protocol.
+ /// </summary>
+ /// <inheritdoc/>
+ public sealed class UdpClientConnection : UdpConnection
+ {
+ private static readonly ILogger Logger = Log.ForContext<UdpClientConnection>();
+
+ /// <summary>
+ /// The socket we're connected via.
+ /// </summary>
+ private readonly UdpClient _socket;
+
+ private readonly Timer _reliablePacketTimer;
+ private readonly SemaphoreSlim _connectWaitLock;
+ private Task _listenTask;
+
+ /// <summary>
+ /// Creates a new UdpClientConnection.
+ /// </summary>
+ /// <param name="remoteEndPoint">A <see cref="NetworkEndPoint"/> to connect to.</param>
+ public UdpClientConnection(IPEndPoint remoteEndPoint, ObjectPool<MessageReader> readerPool, IPMode ipMode = IPMode.IPv4) : base(null, readerPool)
+ {
+ EndPoint = remoteEndPoint;
+ RemoteEndPoint = remoteEndPoint;
+ IPMode = ipMode;
+
+ _socket = new UdpClient
+ {
+ DontFragment = false
+ };
+
+ _reliablePacketTimer = new Timer(ManageReliablePacketsInternal, null, 100, Timeout.Infinite);
+ _connectWaitLock = new SemaphoreSlim(1, 1);
+ }
+
+ ~UdpClientConnection()
+ {
+ Dispose(false);
+ }
+
+ private async void ManageReliablePacketsInternal(object state)
+ {
+ await ManageReliablePackets();
+
+ try
+ {
+ _reliablePacketTimer.Change(100, Timeout.Infinite);
+ }
+ catch
+ {
+ // ignored
+ }
+ }
+
+ /// <inheritdoc />
+ protected override ValueTask WriteBytesToConnection(byte[] bytes, int length)
+ {
+ return WriteBytesToConnectionReal(bytes, length);
+ }
+
+ private async ValueTask WriteBytesToConnectionReal(byte[] bytes, int length)
+ {
+ try
+ {
+ await _socket.SendAsync(bytes, length);
+ }
+ catch (NullReferenceException) { }
+ catch (ObjectDisposedException)
+ {
+ // Already disposed and disconnected...
+ }
+ catch (SocketException ex)
+ {
+ await DisconnectInternal(HazelInternalErrors.SocketExceptionSend, "Could not send data as a SocketException occurred: " + ex.Message);
+ }
+ }
+
+ /// <inheritdoc />
+ public override async ValueTask ConnectAsync(byte[] bytes = null)
+ {
+ State = ConnectionState.Connecting;
+
+ try
+ {
+ _socket.Connect(RemoteEndPoint);
+ }
+ catch (SocketException e)
+ {
+ State = ConnectionState.NotConnected;
+ throw new HazelException("A SocketException occurred while binding to the port.", e);
+ }
+
+ try
+ {
+ _listenTask = Task.Factory.StartNew(ListenAsync, TaskCreationOptions.LongRunning);
+ }
+ catch (ObjectDisposedException)
+ {
+ // If the socket's been disposed then we can just end there but make sure we're in NotConnected state.
+ // If we end up here I'm really lost...
+ State = ConnectionState.NotConnected;
+ return;
+ }
+ catch (SocketException e)
+ {
+ Dispose();
+ throw new HazelException("A SocketException occurred while initiating a receive operation.", e);
+ }
+
+ // Write bytes to the server to tell it hi (and to punch a hole in our NAT, if present)
+ // When acknowledged set the state to connected
+ await SendHello(bytes, () =>
+ {
+ State = ConnectionState.Connected;
+ InitializeKeepAliveTimer();
+ });
+
+ await _connectWaitLock.WaitAsync(TimeSpan.FromSeconds(10));
+ }
+
+ private async Task ListenAsync()
+ {
+ // Start packet handler.
+ await StartAsync();
+
+ // Listen.
+ while (State != ConnectionState.NotConnected)
+ {
+ UdpReceiveResult data;
+
+ try
+ {
+ data = await _socket.ReceiveAsync();
+ }
+ catch (SocketException e)
+ {
+ await DisconnectInternal(HazelInternalErrors.SocketExceptionReceive, "Socket exception while reading data: " + e.Message);
+ return;
+ }
+ catch (Exception)
+ {
+ return;
+ }
+
+ if (data.Buffer.Length == 0)
+ {
+ await DisconnectInternal(HazelInternalErrors.ReceivedZeroBytes, "Received 0 bytes");
+ return;
+ }
+
+ // Write to client.
+ await Pipeline.Writer.WriteAsync(data.Buffer);
+ }
+ }
+
+ protected override void SetState(ConnectionState state)
+ {
+ if (state == ConnectionState.Connected)
+ {
+ _connectWaitLock.Release();
+ }
+ }
+
+ /// <summary>
+ /// Sends a disconnect message to the end point.
+ /// You may include optional disconnect data. The SendOption must be unreliable.
+ /// </summary>
+ protected override async ValueTask<bool> SendDisconnect(MessageWriter data = null)
+ {
+ lock (this)
+ {
+ if (_state == ConnectionState.NotConnected) return false;
+ _state = ConnectionState.NotConnected;
+ }
+
+ var bytes = EmptyDisconnectBytes;
+ if (data != null && data.Length > 0)
+ {
+ if (data.SendOption != MessageType.Unreliable)
+ {
+ throw new ArgumentException("Disconnect messages can only be unreliable.");
+ }
+
+ bytes = data.ToByteArray(true);
+ bytes[0] = (byte)UdpSendOption.Disconnect;
+ }
+
+ try
+ {
+ await _socket.SendAsync(bytes, bytes.Length, RemoteEndPoint);
+ }
+ catch { }
+
+ return true;
+ }
+
+ /// <inheritdoc />
+ protected override void Dispose(bool disposing)
+ {
+ State = ConnectionState.NotConnected;
+
+ try { _socket.Close(); } catch { }
+ try { _socket.Dispose(); } catch { }
+
+ _reliablePacketTimer.Dispose();
+ _connectWaitLock.Dispose();
+
+ base.Dispose(disposing);
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Hazel/Udp/UdpConnection.KeepAlive.cs b/Impostor-dev/src/Impostor.Hazel/Udp/UdpConnection.KeepAlive.cs
new file mode 100644
index 0000000..a73291b
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Hazel/Udp/UdpConnection.KeepAlive.cs
@@ -0,0 +1,167 @@
+using System;
+using System.Collections.Concurrent;
+using System.Diagnostics;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Impostor.Hazel.Udp
+{
+ partial class UdpConnection
+ {
+
+ /// <summary>
+ /// Class to hold packet data
+ /// </summary>
+ public class PingPacket : IRecyclable
+ {
+ private static readonly ObjectPoolCustom<PingPacket> PacketPool = new ObjectPoolCustom<PingPacket>(() => new PingPacket());
+
+ public readonly Stopwatch Stopwatch = new Stopwatch();
+
+ internal static PingPacket GetObject()
+ {
+ return PacketPool.GetObject();
+ }
+
+ public void Recycle()
+ {
+ Stopwatch.Stop();
+ PacketPool.PutObject(this);
+ }
+ }
+
+ internal ConcurrentDictionary<ushort, PingPacket> activePingPackets = new ConcurrentDictionary<ushort, PingPacket>();
+
+ /// <summary>
+ /// The interval from data being received or transmitted to a keepalive packet being sent in milliseconds.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// Keepalive packets serve to close connections when an endpoint abruptly disconnects and to ensure than any
+ /// NAT devices do not close their translation for our argument. By ensuring there is regular contact the
+ /// connection can detect and prevent these issues.
+ /// </para>
+ /// <para>
+ /// The default value is 10 seconds, set to System.Threading.Timeout.Infinite to disable keepalive packets.
+ /// </para>
+ /// </remarks>
+ public int KeepAliveInterval
+ {
+ get
+ {
+ return keepAliveInterval;
+ }
+
+ set
+ {
+ keepAliveInterval = value;
+ ResetKeepAliveTimer();
+ }
+ }
+ private int keepAliveInterval = 1500;
+
+ public int MissingPingsUntilDisconnect { get; set; } = 6;
+ private volatile int pingsSinceAck = 0;
+
+ /// <summary>
+ /// The timer creating keepalive pulses.
+ /// </summary>
+ private Timer keepAliveTimer;
+
+ /// <summary>
+ /// Starts the keepalive timer.
+ /// </summary>
+ protected void InitializeKeepAliveTimer()
+ {
+ keepAliveTimer = new Timer(
+ HandleKeepAlive,
+ null,
+ keepAliveInterval,
+ keepAliveInterval
+ );
+ }
+
+ private async void HandleKeepAlive(object state)
+ {
+ if (this.State != ConnectionState.Connected) return;
+
+ if (this.pingsSinceAck >= this.MissingPingsUntilDisconnect)
+ {
+ this.DisposeKeepAliveTimer();
+ await this.DisconnectInternal(HazelInternalErrors.PingsWithoutResponse, $"Sent {this.pingsSinceAck} pings that remote has not responded to.");
+ return;
+ }
+
+ try
+ {
+ Interlocked.Increment(ref pingsSinceAck);
+ await SendPing();
+ }
+ catch
+ {
+ }
+ }
+
+ // Pings are special, quasi-reliable packets.
+ // We send them to trigger responses that validate our connection is alive
+ // An unacked ping should never be the sole cause of a disconnect.
+ // Rather, the responses will reset our pingsSinceAck, enough unacked
+ // pings should cause a disconnect.
+ private async ValueTask SendPing()
+ {
+ ushort id = (ushort)Interlocked.Increment(ref lastIDAllocated);
+
+ byte[] bytes = new byte[3];
+ bytes[0] = (byte)UdpSendOption.Ping;
+ bytes[1] = (byte)(id >> 8);
+ bytes[2] = (byte)id;
+
+ PingPacket pkt;
+ if (!this.activePingPackets.TryGetValue(id, out pkt))
+ {
+ pkt = PingPacket.GetObject();
+ if (!this.activePingPackets.TryAdd(id, pkt))
+ {
+ throw new Exception("This shouldn't be possible");
+ }
+ }
+
+ pkt.Stopwatch.Restart();
+
+ await WriteBytesToConnection(bytes, bytes.Length);
+
+ Statistics.LogReliableSend(0, bytes.Length);
+ }
+
+ /// <summary>
+ /// Resets the keepalive timer to zero.
+ /// </summary>
+ private void ResetKeepAliveTimer()
+ {
+ try
+ {
+ keepAliveTimer.Change(keepAliveInterval, keepAliveInterval);
+ }
+ catch { }
+ }
+
+ /// <summary>
+ /// Disposes of the keep alive timer.
+ /// </summary>
+ private void DisposeKeepAliveTimer()
+ {
+ if (this.keepAliveTimer != null)
+ {
+ this.keepAliveTimer.Dispose();
+ }
+
+ foreach (var kvp in activePingPackets)
+ {
+ if (this.activePingPackets.TryRemove(kvp.Key, out var pkt))
+ {
+ pkt.Recycle();
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Hazel/Udp/UdpConnection.Reliable.cs b/Impostor-dev/src/Impostor.Hazel/Udp/UdpConnection.Reliable.cs
new file mode 100644
index 0000000..a7a4309
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Hazel/Udp/UdpConnection.Reliable.cs
@@ -0,0 +1,491 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Threading;
+using System.Threading.Tasks;
+using Impostor.Api.Net.Messages;
+
+namespace Impostor.Hazel.Udp
+{
+ partial class UdpConnection
+ {
+ /// <summary>
+ /// The starting timeout, in miliseconds, at which data will be resent.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// For reliable delivery data is resent at specified intervals unless an acknowledgement is received from the
+ /// receiving device. The ResendTimeout specifies the interval between the packets being resent, each time a packet
+ /// is resent the interval is increased for that packet until the duration exceeds the <see cref="DisconnectTimeout"/> value.
+ /// </para>
+ /// <para>
+ /// Setting this to its default of 0 will mean the timeout is 2 times the value of the average ping, usually
+ /// resulting in a more dynamic resend that responds to endpoints on slower or faster connections.
+ /// </para>
+ /// </remarks>
+ public volatile int ResendTimeout = 0;
+
+ /// <summary>
+ /// Max number of times to resend. 0 == no limit
+ /// </summary>
+ public volatile int ResendLimit = 0;
+
+ /// <summary>
+ /// A compounding multiplier to back off resend timeout.
+ /// Applied to ping before first timeout when ResendTimeout == 0.
+ /// </summary>
+ public volatile float ResendPingMultiplier = 2;
+
+ /// <summary>
+ /// Holds the last ID allocated.
+ /// </summary>
+ private int lastIDAllocated = 0;
+
+ /// <summary>
+ /// The packets of data that have been transmitted reliably and not acknowledged.
+ /// </summary>
+ internal ConcurrentDictionary<ushort, Packet> reliableDataPacketsSent = new ConcurrentDictionary<ushort, Packet>();
+
+ /// <summary>
+ /// Packet ids that have not been received, but are expected.
+ /// </summary>
+ private HashSet<ushort> reliableDataPacketsMissing = new HashSet<ushort>();
+
+ /// <summary>
+ /// The packet id that was received last.
+ /// </summary>
+ private volatile ushort reliableReceiveLast = ushort.MaxValue;
+
+ private object PingLock = new object();
+
+ /// <summary>
+ /// Returns the average ping to this endpoint.
+ /// </summary>
+ /// <remarks>
+ /// This returns the average ping for a one-way trip as calculated from the reliable packets that have been sent
+ /// and acknowledged by the endpoint.
+ /// </remarks>
+ public float AveragePingMs = 500;
+
+ /// <summary>
+ /// The maximum times a message should be resent before marking the endpoint as disconnected.
+ /// </summary>
+ /// <remarks>
+ /// Reliable packets will be resent at an interval defined in <see cref="ResendTimeout"/> for the number of times
+ /// specified here. Once a packet has been retransmitted this number of times and has not been acknowledged the
+ /// connection will be marked as disconnected and the <see cref="Connection.Disconnected">Disconnected</see> event
+ /// will be invoked.
+ /// </remarks>
+ public volatile int DisconnectTimeout = 5000;
+
+ /// <summary>
+ /// Class to hold packet data
+ /// </summary>
+ public class Packet : IRecyclable
+ {
+ /// <summary>
+ /// Object pool for this event.
+ /// </summary>
+ public static readonly ObjectPoolCustom<Packet> PacketPool = new ObjectPoolCustom<Packet>(() => new Packet());
+
+ /// <summary>
+ /// Returns an instance of this object from the pool.
+ /// </summary>
+ /// <returns></returns>
+ internal static Packet GetObject()
+ {
+ return PacketPool.GetObject();
+ }
+
+ public ushort Id;
+ private byte[] Data;
+ private UdpConnection Connection;
+ private int Length;
+
+ public int NextTimeout;
+ public volatile bool Acknowledged;
+
+ public Action AckCallback;
+
+ public int Retransmissions;
+ public Stopwatch Stopwatch = new Stopwatch();
+
+ Packet()
+ {
+ }
+
+ internal void Set(ushort id, UdpConnection connection, byte[] data, int length, int timeout, Action ackCallback)
+ {
+ this.Id = id;
+ this.Data = data;
+ this.Connection = connection;
+ this.Length = length;
+
+ this.Acknowledged = false;
+ this.NextTimeout = timeout;
+ this.AckCallback = ackCallback;
+ this.Retransmissions = 0;
+
+ this.Stopwatch.Restart();
+ }
+
+ // Packets resent
+ public async ValueTask<int> Resend()
+ {
+ var connection = this.Connection;
+ if (!this.Acknowledged && connection != null)
+ {
+ long lifetime = this.Stopwatch.ElapsedMilliseconds;
+ if (lifetime >= connection.DisconnectTimeout)
+ {
+ if (connection.reliableDataPacketsSent.TryRemove(this.Id, out Packet self))
+ {
+ await connection.DisconnectInternal(HazelInternalErrors.ReliablePacketWithoutResponse, $"Reliable packet {self.Id} (size={this.Length}) was not ack'd after {lifetime}ms ({self.Retransmissions} resends)");
+
+ self.Recycle();
+ }
+
+ return 0;
+ }
+
+ if (lifetime >= this.NextTimeout)
+ {
+ ++this.Retransmissions;
+ if (connection.ResendLimit != 0
+ && this.Retransmissions > connection.ResendLimit)
+ {
+ if (connection.reliableDataPacketsSent.TryRemove(this.Id, out Packet self))
+ {
+ await connection.DisconnectInternal(HazelInternalErrors.ReliablePacketWithoutResponse, $"Reliable packet {self.Id} (size={this.Length}) was not ack'd after {self.Retransmissions} resends ({lifetime}ms)");
+
+ self.Recycle();
+ }
+
+ return 0;
+ }
+
+ this.NextTimeout += (int)Math.Min(this.NextTimeout * connection.ResendPingMultiplier, 1000);
+ try
+ {
+ await connection.WriteBytesToConnection(this.Data, this.Length);
+ connection.Statistics.LogMessageResent();
+ return 1;
+ }
+ catch (InvalidOperationException)
+ {
+ await connection.DisconnectInternal(HazelInternalErrors.ConnectionDisconnected, "Could not resend data as connection is no longer connected");
+ }
+ }
+ }
+
+ return 0;
+ }
+
+ /// <summary>
+ /// Returns this object back to the object pool from whence it came.
+ /// </summary>
+ public void Recycle()
+ {
+ this.Acknowledged = true;
+ this.Connection = null;
+
+ PacketPool.PutObject(this);
+ }
+ }
+
+ internal async ValueTask<int> ManageReliablePackets()
+ {
+ int output = 0;
+ if (this.reliableDataPacketsSent.Count > 0)
+ {
+ foreach (var kvp in this.reliableDataPacketsSent)
+ {
+ Packet pkt = kvp.Value;
+
+ try
+ {
+ output += await pkt.Resend();
+ }
+ catch { }
+ }
+ }
+
+ return output;
+ }
+
+ /// <summary>
+ /// Adds a 2 byte ID to the packet at offset and stores the packet reference for retransmission.
+ /// </summary>
+ /// <param name="buffer">The buffer to attach to.</param>
+ /// <param name="offset">The offset to attach at.</param>
+ /// <param name="ackCallback">The callback to make once the packet has been acknowledged.</param>
+ protected void AttachReliableID(byte[] buffer, int offset, int sendLength, Action ackCallback = null)
+ {
+ ushort id = (ushort)Interlocked.Increment(ref lastIDAllocated);
+
+ buffer[offset] = (byte)(id >> 8);
+ buffer[offset + 1] = (byte)id;
+
+ Packet packet = Packet.GetObject();
+ packet.Set(
+ id,
+ this,
+ buffer,
+ sendLength,
+ ResendTimeout > 0 ? ResendTimeout : (int)Math.Min(AveragePingMs * this.ResendPingMultiplier, 300),
+ ackCallback);
+
+ if (!reliableDataPacketsSent.TryAdd(id, packet))
+ {
+ throw new Exception("That shouldn't be possible");
+ }
+ }
+
+ public static int ClampToInt(float value, int min, int max)
+ {
+ if (value < min) return min;
+ if (value > max) return max;
+ return (int)value;
+ }
+
+ /// <summary>
+ /// Sends the bytes reliably and stores the send.
+ /// </summary>
+ /// <param name="sendOption"></param>
+ /// <param name="data">The byte array to write to.</param>
+ /// <param name="ackCallback">The callback to make once the packet has been acknowledged.</param>
+ private async ValueTask ReliableSend(byte sendOption, byte[] data, Action ackCallback = null)
+ {
+ //Inform keepalive not to send for a while
+ ResetKeepAliveTimer();
+
+ byte[] bytes = new byte[data.Length + 3];
+
+ //Add message type
+ bytes[0] = sendOption;
+
+ //Add reliable ID
+ AttachReliableID(bytes, 1, bytes.Length, ackCallback);
+
+ //Copy data into new array
+ Buffer.BlockCopy(data, 0, bytes, bytes.Length - data.Length, data.Length);
+
+ //Write to connection
+ await WriteBytesToConnection(bytes, bytes.Length);
+
+ Statistics.LogReliableSend(data.Length, bytes.Length);
+ }
+
+ /// <summary>
+ /// Handles a reliable message being received and invokes the data event.
+ /// </summary>
+ /// <param name="message">The buffer received.</param>
+ private async ValueTask ReliableMessageReceive(MessageReader message)
+ {
+ if (await ProcessReliableReceive(message.Buffer, 1))
+ {
+ message.Offset += 3;
+ message.Length -= 3;
+ message.Position = 0;
+
+ await InvokeDataReceived(message, MessageType.Reliable);
+ }
+
+ Statistics.LogReliableReceive(message.Length - 3, message.Length);
+ }
+
+ /// <summary>
+ /// Handles receives from reliable packets.
+ /// </summary>
+ /// <param name="bytes">The buffer containing the data.</param>
+ /// <param name="offset">The offset of the reliable header.</param>
+ /// <returns>Whether the packet was a new packet or not.</returns>
+ private async ValueTask<bool> ProcessReliableReceive(ReadOnlyMemory<byte> bytes, int offset)
+ {
+ var b1 = bytes.Span[offset];
+ var b2 = bytes.Span[offset + 1];
+
+ //Get the ID form the packet
+ var id = (ushort)((b1 << 8) + b2);
+
+ //Send an acknowledgement
+ await SendAck(id);
+
+ /*
+ * It gets a little complicated here (note the fact I'm actually using a multiline comment for once...)
+ *
+ * In a simple world if our data is greater than the last reliable packet received (reliableReceiveLast)
+ * then it is guaranteed to be a new packet, if it's not we can see if we are missing that packet (lookup
+ * in reliableDataPacketsMissing).
+ *
+ * --------rrl############# (1)
+ *
+ * (where --- are packets received already and #### are packets that will be counted as new)
+ *
+ * Unfortunately if id becomes greater than 65535 it will loop back to zero so we will add a pointer that
+ * specifies any packets with an id behind it are also new (overwritePointer).
+ *
+ * ####op----------rrl##### (2)
+ *
+ * ------rll#########op---- (3)
+ *
+ * Anything behind than the reliableReceiveLast pointer (but greater than the overwritePointer is either a
+ * missing packet or something we've already received so when we change the pointers we need to make sure
+ * we keep note of what hasn't been received yet (reliableDataPacketsMissing).
+ *
+ * So...
+ */
+
+ lock (reliableDataPacketsMissing)
+ {
+ //Calculate overwritePointer
+ ushort overwritePointer = (ushort)(reliableReceiveLast - 32768);
+
+ //Calculate if it is a new packet by examining if it is within the range
+ bool isNew;
+ if (overwritePointer < reliableReceiveLast)
+ isNew = id > reliableReceiveLast || id <= overwritePointer; //Figure (2)
+ else
+ isNew = id > reliableReceiveLast && id <= overwritePointer; //Figure (3)
+
+ //If it's new or we've not received anything yet
+ if (isNew)
+ {
+ // Mark items between the most recent receive and the id received as missing
+ if (id > reliableReceiveLast)
+ {
+ for (ushort i = (ushort)(reliableReceiveLast + 1); i < id; i++)
+ {
+ reliableDataPacketsMissing.Add(i);
+ }
+ }
+ else
+ {
+ int cnt = (ushort.MaxValue - reliableReceiveLast) + id;
+ for (ushort i = 1; i < cnt; ++i)
+ {
+ reliableDataPacketsMissing.Add((ushort)(i + reliableReceiveLast));
+ }
+ }
+
+ //Update the most recently received
+ reliableReceiveLast = id;
+ }
+
+ //Else it could be a missing packet
+ else
+ {
+ //See if we're missing it, else this packet is a duplicate as so we return false
+ if (!reliableDataPacketsMissing.Remove(id))
+ {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// Handles acknowledgement packets to us.
+ /// </summary>
+ /// <param name="bytes">The buffer containing the data.</param>
+ private void AcknowledgementMessageReceive(ReadOnlySpan<byte> bytes)
+ {
+ this.pingsSinceAck = 0;
+
+ ushort id = (ushort)((bytes[1] << 8) + bytes[2]);
+ AcknowledgeMessageId(id);
+
+ if (bytes.Length == 4)
+ {
+ byte recentPackets = bytes[3];
+ for (int i = 1; i <= 8; ++i)
+ {
+ if ((recentPackets & 1) != 0)
+ {
+ AcknowledgeMessageId((ushort)(id - i));
+ }
+
+ recentPackets >>= 1;
+ }
+ }
+
+ Statistics.LogReliableReceive(0, bytes.Length);
+ }
+
+ private void AcknowledgeMessageId(ushort id)
+ {
+ // Dispose of timer and remove from dictionary
+ if (reliableDataPacketsSent.TryRemove(id, out Packet packet))
+ {
+ float rt = packet.Stopwatch.ElapsedMilliseconds;
+
+ packet.AckCallback?.Invoke();
+ packet.Recycle();
+
+ lock (PingLock)
+ {
+ this.AveragePingMs = Math.Max(50, this.AveragePingMs * .7f + rt * .3f);
+ }
+ }
+ else if (this.activePingPackets.TryRemove(id, out PingPacket pingPkt))
+ {
+ float rt = pingPkt.Stopwatch.ElapsedMilliseconds;
+
+ pingPkt.Recycle();
+
+ lock (PingLock)
+ {
+ this.AveragePingMs = Math.Max(50, this.AveragePingMs * .7f + rt * .3f);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Sends an acknowledgement for a packet given its identification bytes.
+ /// </summary>
+ /// <param name="byte1">The first identification byte.</param>
+ /// <param name="byte2">The second identification byte.</param>
+ private async ValueTask SendAck(ushort id)
+ {
+ byte recentPackets = 0;
+ lock (this.reliableDataPacketsMissing)
+ {
+ for (int i = 1; i <= 8; ++i)
+ {
+ if (!this.reliableDataPacketsMissing.Contains((ushort)(id - i)))
+ {
+ recentPackets |= (byte)(1 << (i - 1));
+ }
+ }
+ }
+
+ byte[] bytes = new byte[]
+ {
+ (byte)UdpSendOption.Acknowledgement,
+ (byte)(id >> 8),
+ (byte)(id >> 0),
+ recentPackets
+ };
+
+ try
+ {
+ await WriteBytesToConnection(bytes, bytes.Length);
+ }
+ catch (InvalidOperationException) { }
+ }
+
+ private void DisposeReliablePackets()
+ {
+ foreach (var kvp in reliableDataPacketsSent)
+ {
+ if (this.reliableDataPacketsSent.TryRemove(kvp.Key, out var pkt))
+ {
+ pkt.Recycle();
+ }
+ }
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Hazel/Udp/UdpConnection.cs b/Impostor-dev/src/Impostor.Hazel/Udp/UdpConnection.cs
new file mode 100644
index 0000000..5288d3c
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Hazel/Udp/UdpConnection.cs
@@ -0,0 +1,312 @@
+using System;
+using System.Threading;
+using System.Threading.Channels;
+using System.Threading.Tasks;
+using Impostor.Api.Net.Messages;
+using Microsoft.Extensions.ObjectPool;
+using Serilog;
+
+namespace Impostor.Hazel.Udp
+{
+ /// <summary>
+ /// Represents a connection that uses the UDP protocol.
+ /// </summary>
+ /// <inheritdoc />
+ public abstract partial class UdpConnection : NetworkConnection
+ {
+ protected static readonly byte[] EmptyDisconnectBytes = { (byte)UdpSendOption.Disconnect };
+
+ private static readonly ILogger Logger = Log.ForContext<UdpConnection>();
+ private readonly ConnectionListener _listener;
+ private readonly ObjectPool<MessageReader> _readerPool;
+ private readonly CancellationTokenSource _stoppingCts;
+
+ private bool _isDisposing;
+ private bool _isFirst = true;
+ private Task _executingTask;
+
+ protected UdpConnection(ConnectionListener listener, ObjectPool<MessageReader> readerPool)
+ {
+ _listener = listener;
+ _readerPool = readerPool;
+ _stoppingCts = new CancellationTokenSource();
+
+ Pipeline = Channel.CreateUnbounded<byte[]>(new UnboundedChannelOptions
+ {
+ SingleReader = true,
+ SingleWriter = true
+ });
+ }
+
+ internal Channel<byte[]> Pipeline { get; }
+
+ public Task StartAsync()
+ {
+ // Store the task we're executing
+ _executingTask = Task.Factory.StartNew(ReadAsync, TaskCreationOptions.LongRunning);
+
+ // If the task is completed then return it, this will bubble cancellation and failure to the caller
+ if (_executingTask.IsCompleted)
+ {
+ return _executingTask;
+ }
+
+ // Otherwise it's running
+ return Task.CompletedTask;
+ }
+
+ public void Stop()
+ {
+ // Stop called without start
+ if (_executingTask == null)
+ {
+ return;
+ }
+
+ // Signal cancellation to methods.
+ _stoppingCts.Cancel();
+
+ try
+ {
+ // Cancel reader.
+ Pipeline.Writer.Complete();
+ }
+ catch (ChannelClosedException)
+ {
+ // Already done.
+ }
+
+ // Remove references.
+ if (!_isDisposing)
+ {
+ Dispose(true);
+ }
+ }
+
+ private async Task ReadAsync()
+ {
+ var reader = new MessageReader(_readerPool);
+
+ while (!_stoppingCts.IsCancellationRequested)
+ {
+ var result = await Pipeline.Reader.ReadAsync(_stoppingCts.Token);
+
+ try
+ {
+ reader.Update(result);
+
+ await HandleReceive(reader);
+ }
+ catch (Exception e)
+ {
+ Logger.Error(e, "Exception during ReadAsync");
+ Dispose(true);
+ break;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Writes the given bytes to the connection.
+ /// </summary>
+ /// <param name="bytes">The bytes to write.</param>
+ /// <param name="length"></param>
+ protected abstract ValueTask WriteBytesToConnection(byte[] bytes, int length);
+
+ /// <inheritdoc/>
+ public override async ValueTask SendAsync(IMessageWriter msg)
+ {
+ if (this._state != ConnectionState.Connected)
+ throw new InvalidOperationException("Could not send data as this Connection is not connected. Did you disconnect?");
+
+ byte[] buffer = new byte[msg.Length];
+ Buffer.BlockCopy(msg.Buffer, 0, buffer, 0, msg.Length);
+
+ switch (msg.SendOption)
+ {
+ case MessageType.Reliable:
+ ResetKeepAliveTimer();
+
+ AttachReliableID(buffer, 1, buffer.Length);
+ await WriteBytesToConnection(buffer, buffer.Length);
+ Statistics.LogReliableSend(buffer.Length - 3, buffer.Length);
+ break;
+
+ default:
+ await WriteBytesToConnection(buffer, buffer.Length);
+ Statistics.LogUnreliableSend(buffer.Length - 1, buffer.Length);
+ break;
+ }
+ }
+
+ /// <inheritdoc/>
+ /// <remarks>
+ /// <include file="DocInclude/common.xml" path="docs/item[@name='Connection_SendBytes_General']/*" />
+ /// <para>
+ /// Udp connections can currently send messages using <see cref="SendOption.None"/> and
+ /// <see cref="SendOption.Reliable"/>. Fragmented messages are not currently supported and will default to
+ /// <see cref="SendOption.None"/> until implemented.
+ /// </para>
+ /// </remarks>
+ public override async ValueTask SendBytes(byte[] bytes, MessageType sendOption = MessageType.Unreliable)
+ {
+ //Add header information and send
+ await HandleSend(bytes, (byte)sendOption);
+ }
+
+ /// <summary>
+ /// Handles the reliable/fragmented sending from this connection.
+ /// </summary>
+ /// <param name="data">The data being sent.</param>
+ /// <param name="sendOption">The <see cref="SendOption"/> specified as its byte value.</param>
+ /// <param name="ackCallback">The callback to invoke when this packet is acknowledged.</param>
+ /// <returns>The bytes that should actually be sent.</returns>
+ protected async ValueTask HandleSend(byte[] data, byte sendOption, Action ackCallback = null)
+ {
+ switch (sendOption)
+ {
+ case (byte)UdpSendOption.Ping:
+ case (byte)MessageType.Reliable:
+ case (byte)UdpSendOption.Hello:
+ await ReliableSend(sendOption, data, ackCallback);
+ break;
+
+ //Treat all else as unreliable
+ default:
+ await UnreliableSend(sendOption, data);
+ break;
+ }
+ }
+
+ /// <summary>
+ /// Handles the receiving of data.
+ /// </summary>
+ /// <param name="message">The buffer containing the bytes received.</param>
+ protected async ValueTask HandleReceive(MessageReader message)
+ {
+ // Check if the first message received is the hello packet.
+ if (_isFirst)
+ {
+ _isFirst = false;
+
+ // Slice 4 bytes to get handshake data.
+ if (_listener != null)
+ {
+ using (var handshake = message.Copy(4))
+ {
+ await _listener.InvokeNewConnection(handshake, this);
+ }
+ }
+ }
+
+ switch (message.Buffer[0])
+ {
+ //Handle reliable receives
+ case (byte)MessageType.Reliable:
+ await ReliableMessageReceive(message);
+ break;
+
+ //Handle acknowledgments
+ case (byte)UdpSendOption.Acknowledgement:
+ AcknowledgementMessageReceive(message.Buffer);
+ break;
+
+ //We need to acknowledge hello and ping messages but dont want to invoke any events!
+ case (byte)UdpSendOption.Ping:
+ await ProcessReliableReceive(message.Buffer, 1);
+ Statistics.LogHelloReceive(message.Length);
+ break;
+ case (byte)UdpSendOption.Hello:
+ await ProcessReliableReceive(message.Buffer, 1);
+ Statistics.LogHelloReceive(message.Length);
+ break;
+
+ case (byte)UdpSendOption.Disconnect:
+ using (var reader = message.Copy(1))
+ {
+ await DisconnectRemote("The remote sent a disconnect request", reader);
+ }
+ break;
+
+ //Treat everything else as unreliable
+ default:
+ using (var reader = message.Copy(1))
+ {
+ await InvokeDataReceived(reader, MessageType.Unreliable);
+ }
+ Statistics.LogUnreliableReceive(message.Length - 1, message.Length);
+ break;
+ }
+ }
+
+ /// <summary>
+ /// Sends bytes using the unreliable UDP protocol.
+ /// </summary>
+ /// <param name="sendOption">The SendOption to attach.</param>
+ /// <param name="data">The data.</param>
+ ValueTask UnreliableSend(byte sendOption, byte[] data)
+ {
+ return UnreliableSend(sendOption, data, 0, data.Length);
+ }
+
+ /// <summary>
+ /// Sends bytes using the unreliable UDP protocol.
+ /// </summary>
+ /// <param name="data">The data.</param>
+ /// <param name="sendOption">The SendOption to attach.</param>
+ /// <param name="offset"></param>
+ /// <param name="length"></param>
+ async ValueTask UnreliableSend(byte sendOption, byte[] data, int offset, int length)
+ {
+ byte[] bytes = new byte[length + 1];
+
+ //Add message type
+ bytes[0] = sendOption;
+
+ //Copy data into new array
+ Buffer.BlockCopy(data, offset, bytes, bytes.Length - length, length);
+
+ //Write to connection
+ await WriteBytesToConnection(bytes, bytes.Length);
+
+ Statistics.LogUnreliableSend(length, bytes.Length);
+ }
+
+ /// <summary>
+ /// Sends a hello packet to the remote endpoint.
+ /// </summary>
+ /// <param name="bytes"></param>
+ /// <param name="acknowledgeCallback">The callback to invoke when the hello packet is acknowledged.</param>
+ protected ValueTask SendHello(byte[] bytes, Action acknowledgeCallback)
+ {
+ //First byte of handshake is version indicator so add data after
+ byte[] actualBytes;
+ if (bytes == null)
+ {
+ actualBytes = new byte[1];
+ }
+ else
+ {
+ actualBytes = new byte[bytes.Length + 1];
+ Buffer.BlockCopy(bytes, 0, actualBytes, 1, bytes.Length);
+ }
+
+ return HandleSend(actualBytes, (byte)UdpSendOption.Hello, acknowledgeCallback);
+ }
+
+ /// <inheritdoc/>
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _isDisposing = true;
+
+ Stop();
+ DisposeKeepAliveTimer();
+ DisposeReliablePackets();
+ }
+
+ base.Dispose(disposing);
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Hazel/Udp/UdpConnectionListener.cs b/Impostor-dev/src/Impostor.Hazel/Udp/UdpConnectionListener.cs
new file mode 100644
index 0000000..573a00c
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Hazel/Udp/UdpConnectionListener.cs
@@ -0,0 +1,281 @@
+using System;
+using System.Buffers;
+using System.Collections.Concurrent;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+using System.Threading.Channels;
+using System.Threading.Tasks;
+using Microsoft.Extensions.ObjectPool;
+using Serilog;
+
+namespace Impostor.Hazel.Udp
+{
+ /// <summary>
+ /// Listens for new UDP connections and creates UdpConnections for them.
+ /// </summary>
+ /// <inheritdoc />
+ public class UdpConnectionListener : NetworkConnectionListener
+ {
+ private static readonly ILogger Logger = Log.ForContext<UdpConnectionListener>();
+
+ /// <summary>
+ /// A callback for early connection rejection.
+ /// * Return false to reject connection.
+ /// * A null response is ok, we just won't send anything.
+ /// </summary>
+ public AcceptConnectionCheck AcceptConnection;
+ public delegate bool AcceptConnectionCheck(IPEndPoint endPoint, byte[] input, out byte[] response);
+
+ private readonly UdpClient _socket;
+ private readonly ObjectPool<MessageReader> _readerPool;
+ private readonly MemoryPool<byte> _pool;
+ private readonly Timer _reliablePacketTimer;
+ private readonly ConcurrentDictionary<EndPoint, UdpServerConnection> _allConnections;
+ private readonly CancellationTokenSource _stoppingCts;
+ private readonly UdpConnectionRateLimit _connectionRateLimit;
+ private Task _executingTask;
+
+ /// <summary>
+ /// Creates a new UdpConnectionListener for the given <see cref="IPAddress"/>, port and <see cref="IPMode"/>.
+ /// </summary>
+ /// <param name="endPoint">The endpoint to listen on.</param>
+ /// <param name="ipMode"></param>
+ public UdpConnectionListener(IPEndPoint endPoint, ObjectPool<MessageReader> readerPool, IPMode ipMode = IPMode.IPv4)
+ {
+ EndPoint = endPoint;
+ IPMode = ipMode;
+
+ _readerPool = readerPool;
+ _pool = MemoryPool<byte>.Shared;
+ _socket = new UdpClient(endPoint);
+
+ try
+ {
+ _socket.DontFragment = false;
+ }
+ catch (SocketException)
+ {
+ }
+
+ _reliablePacketTimer = new Timer(ManageReliablePackets, null, 100, Timeout.Infinite);
+
+ _allConnections = new ConcurrentDictionary<EndPoint, UdpServerConnection>();
+
+ _stoppingCts = new CancellationTokenSource();
+ _stoppingCts.Token.Register(() =>
+ {
+ _socket.Dispose();
+ });
+
+ _connectionRateLimit = new UdpConnectionRateLimit();
+ }
+
+ public int ConnectionCount => this._allConnections.Count;
+
+ private async void ManageReliablePackets(object state)
+ {
+ foreach (var kvp in _allConnections)
+ {
+ var sock = kvp.Value;
+ await sock.ManageReliablePackets();
+ }
+
+ try
+ {
+ this._reliablePacketTimer.Change(100, Timeout.Infinite);
+ }
+ catch { }
+ }
+
+ /// <inheritdoc />
+ public override Task StartAsync()
+ {
+ // Store the task we're executing
+ _executingTask = Task.Factory.StartNew(ListenAsync, TaskCreationOptions.LongRunning);
+
+ // If the task is completed then return it, this will bubble cancellation and failure to the caller
+ if (_executingTask.IsCompleted)
+ {
+ return _executingTask;
+ }
+
+ // Otherwise it's running
+ return Task.CompletedTask;
+ }
+
+ private async Task StopAsync()
+ {
+ // Stop called without start
+ if (_executingTask == null)
+ {
+ return;
+ }
+
+ try
+ {
+ // Signal cancellation to the executing method
+ _stoppingCts.Cancel();
+ }
+ finally
+ {
+ // Wait until the task completes or the timeout triggers
+ await Task.WhenAny(_executingTask, Task.Delay(TimeSpan.FromSeconds(5)));
+ }
+ }
+
+ /// <summary>
+ /// Instructs the listener to begin listening.
+ /// </summary>
+ private async Task ListenAsync()
+ {
+ try
+ {
+ while (!_stoppingCts.IsCancellationRequested)
+ {
+ UdpReceiveResult data;
+
+ try
+ {
+ data = await _socket.ReceiveAsync();
+
+ if (data.Buffer.Length == 0)
+ {
+ Logger.Fatal("Hazel read 0 bytes from UDP server socket.");
+ continue;
+ }
+ }
+ catch (SocketException)
+ {
+ // Client no longer reachable, pretend it didn't happen
+ continue;
+ }
+ catch (ObjectDisposedException)
+ {
+ // Socket was disposed, don't care.
+ return;
+ }
+
+ // Get client from active clients
+ if (!_allConnections.TryGetValue(data.RemoteEndPoint, out var client))
+ {
+ // Check for malformed connection attempts
+ if (data.Buffer[0] != (byte)UdpSendOption.Hello)
+ {
+ continue;
+ }
+
+ // Check rateLimit.
+ if (!_connectionRateLimit.IsAllowed(data.RemoteEndPoint.Address))
+ {
+ Logger.Warning("Ratelimited connection attempt from {0}.", data.RemoteEndPoint);
+ continue;
+ }
+
+ // Create new client
+ client = new UdpServerConnection(this, data.RemoteEndPoint, IPMode, _readerPool);
+
+ // Store the client
+ if (!_allConnections.TryAdd(data.RemoteEndPoint, client))
+ {
+ throw new HazelException("Failed to add a connection. This should never happen.");
+ }
+
+ // Activate the reader loop of the client
+ await client.StartAsync();
+ }
+
+ // Write to client.
+ await client.Pipeline.Writer.WriteAsync(data.Buffer);
+ }
+ }
+ catch (Exception e)
+ {
+ Logger.Error(e, "Listen loop error");
+ }
+ }
+
+#if DEBUG
+ public int TestDropRate = -1;
+ private int dropCounter = 0;
+#endif
+
+ /// <summary>
+ /// Sends data from the listener socket.
+ /// </summary>
+ /// <param name="bytes">The bytes to send.</param>
+ /// <param name="endPoint">The endpoint to send to.</param>
+ internal async ValueTask SendData(byte[] bytes, int length, IPEndPoint endPoint)
+ {
+ if (length > bytes.Length) return;
+
+#if DEBUG
+ if (TestDropRate > 0)
+ {
+ if (Interlocked.Increment(ref dropCounter) % TestDropRate == 0)
+ {
+ return;
+ }
+ }
+#endif
+
+ try
+ {
+ await _socket.SendAsync(bytes, length, endPoint);
+ }
+ catch (SocketException e)
+ {
+ Logger.Error(e, "Could not send data as a SocketException occurred");
+ }
+ catch (ObjectDisposedException)
+ {
+ //Keep alive timer probably ran, ignore
+ return;
+ }
+ }
+
+ /// <summary>
+ /// Sends data from the listener socket.
+ /// </summary>
+ /// <param name="bytes">The bytes to send.</param>
+ /// <param name="length"></param>
+ /// <param name="endPoint">The endpoint to send to.</param>
+ internal void SendDataSync(byte[] bytes, int length, IPEndPoint endPoint)
+ {
+ try
+ {
+ _socket.Send(bytes, length, endPoint);
+ }
+ catch (SocketException e)
+ {
+ Logger.Error(e, "Could not send data sync as a SocketException occurred");
+ }
+ }
+
+ /// <summary>
+ /// Removes a virtual connection from the list.
+ /// </summary>
+ /// <param name="endPoint">The endpoint of the virtual connection.</param>
+ internal void RemoveConnectionTo(EndPoint endPoint)
+ {
+ this._allConnections.TryRemove(endPoint, out var conn);
+ }
+
+ /// <inheritdoc />
+ public override async ValueTask DisposeAsync()
+ {
+ foreach (var kvp in _allConnections)
+ {
+ kvp.Value.Dispose();
+ }
+
+ await StopAsync();
+
+ await _reliablePacketTimer.DisposeAsync();
+
+ _connectionRateLimit.Dispose();
+
+ await base.DisposeAsync();
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Hazel/Udp/UdpConnectionRateLimit.cs b/Impostor-dev/src/Impostor.Hazel/Udp/UdpConnectionRateLimit.cs
new file mode 100644
index 0000000..64881d3
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Hazel/Udp/UdpConnectionRateLimit.cs
@@ -0,0 +1,75 @@
+using System;
+using System.Collections.Concurrent;
+using System.Net;
+using System.Threading;
+using Serilog;
+
+namespace Impostor.Hazel.Udp
+{
+ public class UdpConnectionRateLimit : IDisposable
+ {
+ private static readonly ILogger Logger = Log.ForContext<UdpConnectionRateLimit>();
+
+ // Allow burst to 5 connections.
+ // Decrease by 1 every second.
+ private const int MaxConnections = 5;
+ private const int FalloffMs = 1000;
+
+ private readonly ConcurrentDictionary<IPAddress, int> _connectionCount;
+ private readonly Timer _timer;
+ private bool _isDisposed;
+
+ public UdpConnectionRateLimit()
+ {
+ _connectionCount = new ConcurrentDictionary<IPAddress, int>();
+ _timer = new Timer(UpdateRateLimit, null, FalloffMs, Timeout.Infinite);
+ }
+
+ private void UpdateRateLimit(object state)
+ {
+ try
+ {
+ foreach (var pair in _connectionCount)
+ {
+ var count = pair.Value - 1;
+ if (count > 0)
+ {
+ _connectionCount.TryUpdate(pair.Key, count, pair.Value);
+ }
+ else
+ {
+ _connectionCount.TryRemove(pair);
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ Logger.Error(e, "Exception caught in UpdateRateLimit.");
+ }
+ finally
+ {
+ if (!_isDisposed)
+ {
+ _timer.Change(FalloffMs, Timeout.Infinite);
+ }
+ }
+ }
+
+ public bool IsAllowed(IPAddress key)
+ {
+ if (_connectionCount.TryGetValue(key, out var value) && value >= MaxConnections)
+ {
+ return false;
+ }
+
+ _connectionCount.AddOrUpdate(key, _ => 1, (_, i) => i + 1);
+ return true;
+ }
+
+ public void Dispose()
+ {
+ _isDisposed = true;
+ _timer.Dispose();
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Hazel/Udp/UdpServerConnection.cs b/Impostor-dev/src/Impostor.Hazel/Udp/UdpServerConnection.cs
new file mode 100644
index 0000000..22eed98
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Hazel/Udp/UdpServerConnection.cs
@@ -0,0 +1,97 @@
+using System;
+using System.Net;
+using System.Threading.Tasks;
+using Impostor.Api.Net.Messages;
+using Microsoft.Extensions.ObjectPool;
+
+namespace Impostor.Hazel.Udp
+{
+ /// <summary>
+ /// Represents a servers's connection to a client that uses the UDP protocol.
+ /// </summary>
+ /// <inheritdoc/>
+ internal sealed class UdpServerConnection : UdpConnection
+ {
+ /// <summary>
+ /// The connection listener that we use the socket of.
+ /// </summary>
+ /// <remarks>
+ /// Udp server connections utilize the same socket in the listener for sends/receives, this is the listener that
+ /// created this connection and is hence the listener this conenction sends and receives via.
+ /// </remarks>
+ public UdpConnectionListener Listener { get; private set; }
+
+ /// <summary>
+ /// Creates a UdpConnection for the virtual connection to the endpoint.
+ /// </summary>
+ /// <param name="listener">The listener that created this connection.</param>
+ /// <param name="endPoint">The endpoint that we are connected to.</param>
+ /// <param name="IPMode">The IPMode we are connected using.</param>
+ internal UdpServerConnection(UdpConnectionListener listener, IPEndPoint endPoint, IPMode IPMode, ObjectPool<MessageReader> readerPool) : base(listener, readerPool)
+ {
+ this.Listener = listener;
+ this.RemoteEndPoint = endPoint;
+ this.EndPoint = endPoint;
+ this.IPMode = IPMode;
+
+ State = ConnectionState.Connected;
+ this.InitializeKeepAliveTimer();
+ }
+
+ /// <inheritdoc />
+ protected override async ValueTask WriteBytesToConnection(byte[] bytes, int length)
+ {
+ await Listener.SendData(bytes, length, RemoteEndPoint);
+ }
+
+ /// <inheritdoc />
+ /// <remarks>
+ /// This will always throw a HazelException.
+ /// </remarks>
+ public override ValueTask ConnectAsync(byte[] bytes = null)
+ {
+ throw new InvalidOperationException("Cannot manually connect a UdpServerConnection, did you mean to use UdpClientConnection?");
+ }
+
+ /// <summary>
+ /// Sends a disconnect message to the end point.
+ /// </summary>
+ protected override async ValueTask<bool> SendDisconnect(MessageWriter data = null)
+ {
+ lock (this)
+ {
+ if (this._state != ConnectionState.Connected) return false;
+ this._state = ConnectionState.NotConnected;
+ }
+
+ var bytes = EmptyDisconnectBytes;
+ if (data != null && data.Length > 0)
+ {
+ if (data.SendOption != MessageType.Unreliable) throw new ArgumentException("Disconnect messages can only be unreliable.");
+
+ bytes = data.ToByteArray(true);
+ bytes[0] = (byte)UdpSendOption.Disconnect;
+ }
+
+ try
+ {
+ await Listener.SendData(bytes, bytes.Length, RemoteEndPoint);
+ }
+ catch { }
+
+ return true;
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ Listener.RemoveConnectionTo(RemoteEndPoint);
+
+ if (disposing)
+ {
+ SendDisconnect();
+ }
+
+ base.Dispose(disposing);
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Patcher/Directory.Build.props b/Impostor-dev/src/Impostor.Patcher/Directory.Build.props
new file mode 100644
index 0000000..5302edd
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Patcher/Directory.Build.props
@@ -0,0 +1,8 @@
+<Project>
+ <PropertyGroup>
+ <AssemblyTitle>Impostor</AssemblyTitle>
+ <Product>Impostor</Product>
+ <Copyright>Copyright © AeonLucid 2020</Copyright>
+ <Version>1.0.0</Version>
+ </PropertyGroup>
+</Project> \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.Cli/Impostor.Patcher.Cli.csproj b/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.Cli/Impostor.Patcher.Cli.csproj
new file mode 100644
index 0000000..c59fa87
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.Cli/Impostor.Patcher.Cli.csproj
@@ -0,0 +1,19 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <AssemblyName>Impostor.Cli</AssemblyName>
+ <TargetFramework>net5.0</TargetFramework>
+ <RuntimeIdentifiers>win-x64;linux-x64;linux-arm;linux-arm64;osx-x64</RuntimeIdentifiers>
+ <OutputType>Exe</OutputType>
+ <IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\Impostor.Patcher.Shared\Impostor.Patcher.Shared.csproj" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <PackageReference Include="System.CommandLine" Version="2.0.0-beta1.20478.1" />
+ </ItemGroup>
+
+</Project>
diff --git a/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.Cli/Program.cs b/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.Cli/Program.cs
new file mode 100644
index 0000000..76653a1
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.Cli/Program.cs
@@ -0,0 +1,88 @@
+using System;
+using System.CommandLine;
+using System.CommandLine.Invocation;
+using System.Threading.Tasks;
+using Impostor.Patcher.Shared;
+using Impostor.Patcher.Shared.Events;
+
+namespace Impostor.Patcher.Cli
+{
+ internal static class Program
+ {
+ private static readonly AmongUsModifier Modifier = new AmongUsModifier();
+
+ internal static Task<int> Main(string[] args)
+ {
+ var rootCommand = new RootCommand
+ {
+ new Option<string>(
+ "--address",
+ "IP Address of the server, will prompt if not specified"
+ ),
+ new Option<string>(
+ "--name",
+ () => AmongUsModifier.DefaultRegionName,
+ "Name for server region"
+ )
+ };
+
+ rootCommand.Handler = CommandHandler.Create<string, string>((address, name) =>
+ {
+ Modifier.RegionName = name;
+ Modifier.Error += ModifierOnError;
+ Modifier.Saved += ModifierOnSaved;
+
+ Console.WriteLine("Welcome to Impostor");
+
+ if (Modifier.TryLoadRegionInfo(out var regionInfo))
+ {
+ Console.WriteLine($"Currently selected region: {regionInfo.Name} ({regionInfo.Ping}, {regionInfo.Servers.Count} server(s))");
+ }
+
+ if (address != null)
+ {
+ return Modifier.SaveIpAsync(address);
+ }
+
+ return PromptAsync();
+ });
+
+ return rootCommand.InvokeAsync(args);
+ }
+
+ private static void ModifierOnSaved(object sender, SavedEventArgs e)
+ {
+ Console.ForegroundColor = ConsoleColor.Green;
+ Console.WriteLine("The IP Address was saved, please (re)start Among Us.");
+ Console.ResetColor();
+ }
+
+ private static void WriteError(string message)
+ {
+ Console.ForegroundColor = ConsoleColor.Red;
+ Console.WriteLine(message);
+ Console.ResetColor();
+ }
+
+ private static void ModifierOnError(object sender, ErrorEventArgs e)
+ {
+ WriteError(e.Message);
+ }
+
+ private static async Task PromptAsync()
+ {
+ Console.WriteLine("Please enter in the IP Address of the server you would like to use for Among Us");
+ Console.WriteLine("If you want to stop playing on the server, simply select another region");
+
+ while (true)
+ {
+ Console.Write("> ");
+
+ if (await Modifier.SaveIpAsync(Console.ReadLine()))
+ {
+ return;
+ }
+ }
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.Shared/AmongUsModifier.cs b/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.Shared/AmongUsModifier.cs
new file mode 100644
index 0000000..95f5524
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.Shared/AmongUsModifier.cs
@@ -0,0 +1,247 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Runtime.InteropServices;
+using System.Threading.Tasks;
+using Gameloop.Vdf;
+using Gameloop.Vdf.Linq;
+using Impostor.Patcher.Shared.Events;
+using Impostor.Patcher.Shared.Innersloth;
+using ErrorEventArgs = Impostor.Patcher.Shared.Events.ErrorEventArgs;
+
+namespace Impostor.Patcher.Shared
+{
+ public class AmongUsModifier
+ {
+ private const uint AppId = 945360;
+ public const string DefaultRegionName = "Impostor";
+ public const ushort DefaultPort = 22023;
+
+ private readonly string _amongUsDir;
+ private readonly string _regionFile;
+
+ public string RegionName { get; set; } = DefaultRegionName;
+
+ public AmongUsModifier()
+ {
+ var appData = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "..", "LocalLow");
+
+ if (!Directory.Exists(appData))
+ {
+ appData = FindProtonAppData();
+ }
+
+ if (appData == null)
+ return;
+
+ _amongUsDir = Path.Combine(appData, "Innersloth", "Among Us");
+ _regionFile = Path.Combine(_amongUsDir, "regionInfo.dat");
+ }
+
+ private string FindProtonAppData()
+ {
+ string steamApps;
+
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ steamApps = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".steam", "steam", "steamapps");
+ }
+ else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ {
+ steamApps = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Application Support", "Steam", "steamapps");
+ }
+ else
+ {
+ return null;
+ }
+
+ if (!Directory.Exists(steamApps))
+ return null;
+
+ var libraries = new List<string>
+ {
+ steamApps
+ };
+
+ var vdf = Path.Combine(steamApps, "libraryfolders.vdf");
+ if (File.Exists(vdf))
+ {
+ var libraryFolders = VdfConvert.Deserialize(File.ReadAllText(vdf));
+
+ foreach (var libraryFolder in libraryFolders.Value.Children<VProperty>())
+ {
+ if (!int.TryParse(libraryFolder.Key, out _))
+ continue;
+
+ libraries.Add(Path.Combine(libraryFolder.Value.Value<string>(), "steamapps"));
+ }
+ }
+
+ foreach (var library in libraries)
+ {
+ var path = Path.Combine(library, "compatdata", AppId.ToString(), "pfx", "drive_c", "users", "steamuser", "AppData", "LocalLow");
+ if (Directory.Exists(path))
+ {
+ return path;
+ }
+ }
+
+ return null;
+ }
+
+ public async Task<bool> SaveIpAsync(string input)
+ {
+ // Filter out whitespace.
+ input = input.Trim();
+
+ // Split port from ip.
+ // Only IPv4 is supported so just do it simple.
+ var ip = string.Empty;
+ var port = DefaultPort;
+
+ var parts = input.Split(':');
+ if (parts.Length >= 1)
+ {
+ ip = parts[0];
+ }
+
+ if (parts.Length >= 2)
+ {
+ ushort.TryParse(parts[1], out port);
+ }
+
+ // Check if a valid IP address was entered.
+ if (!IPAddress.TryParse(ip, out var ipAddress))
+ {
+ // Attempt to resolve DNS.
+ try
+ {
+ var hostAddresses = await Dns.GetHostAddressesAsync(ip);
+ if (hostAddresses.Length == 0)
+ {
+ OnError("Invalid IP Address entered");
+ return false;
+ }
+
+ // Use first IPv4 result.
+ ipAddress = hostAddresses.First(x => x.AddressFamily == AddressFamily.InterNetwork);
+ }
+ catch (SocketException)
+ {
+ OnError("Failed to resolve hostname.");
+ return false;
+ }
+ }
+
+ // Only IPv4.
+ if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6)
+ {
+ OnError("Invalid IP Address entered, only IPv4 is allowed.");
+ return false;
+ }
+
+ return WriteIp(ipAddress, port);
+ }
+
+ /// <summary>
+ /// Writes an IP Address to the Among Us region file.
+ /// </summary>
+ /// <param name="ipAddress">The IPv4 address to write.</param>
+ /// <param name="port"></param>
+ private bool WriteIp(IPAddress ipAddress, ushort port)
+ {
+ if (ipAddress == null ||
+ ipAddress.AddressFamily != AddressFamily.InterNetwork)
+ {
+ throw new ArgumentException(nameof(ipAddress));
+ }
+
+ if (!Directory.Exists(_amongUsDir))
+ {
+ OnError("Among Us directory was not found, is it installed? Try running it once.");
+ return false;
+ }
+
+ using (var file = File.Open(_regionFile, FileMode.Create, FileAccess.Write))
+ using (var writer = new BinaryWriter(file))
+ {
+ var ip = ipAddress.ToString();
+ var region = new RegionInfo(RegionName, ip, new[]
+ {
+ new ServerInfo($"{RegionName}-Master-1", ip, port)
+ });
+
+ region.Serialize(writer);
+
+ OnSaved(ip, port);
+ return true;
+ }
+ }
+
+ /// <summary>
+ /// Loads the existing region info from the Among Us.
+ /// </summary>
+ public bool TryLoadRegionInfo(out RegionInfo regionInfo)
+ {
+ regionInfo = null;
+
+ if (!File.Exists(_regionFile))
+ {
+ return false;
+ }
+
+ using (var file = File.Open(_regionFile, FileMode.Open, FileAccess.Read))
+ using (var reader = new BinaryReader(file))
+ {
+ try
+ {
+ regionInfo = RegionInfo.Deserialize(reader);
+ return true;
+ }
+ catch (Exception exception)
+ {
+ OnError("Couldn't parse region info\n" + exception);
+ return false;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Loads the existing IP Address from the Among Us region file
+ /// if it was set by Impostor before.
+ /// </summary>
+ public bool TryLoadIp(out string ipAddress)
+ {
+ ipAddress = null;
+
+ if (!TryLoadRegionInfo(out var regionInfo))
+ {
+ return false;
+ }
+
+ if ((regionInfo.Name == RegionName || regionInfo.Name == DefaultRegionName) && regionInfo.Servers.Count >= 1)
+ {
+ ipAddress = regionInfo.Servers.ElementAt(0).Ip;
+ return true;
+ }
+
+ return false;
+ }
+
+ private void OnError(string message)
+ {
+ Error?.Invoke(this, new ErrorEventArgs(message));
+ }
+
+ private void OnSaved(string ipAddress, ushort port)
+ {
+ Saved?.Invoke(this, new SavedEventArgs(ipAddress, port));
+ }
+
+ public event EventHandler<ErrorEventArgs> Error;
+ public event EventHandler<SavedEventArgs> Saved;
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.Shared/Configuration.cs b/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.Shared/Configuration.cs
new file mode 100644
index 0000000..b256156
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.Shared/Configuration.cs
@@ -0,0 +1,65 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace Impostor.Patcher.Shared
+{
+ public class Configuration
+ {
+ private const string FileRecentIps = @"recent_ips.txt";
+ private const int MaxRecentIps = 5;
+
+ private readonly string _baseDir;
+ private readonly string _recentIpsPath;
+ private readonly List<string> _recentIps;
+
+ public Configuration()
+ {
+ var appData = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData));
+
+ _baseDir = Path.Combine(appData, "Impostor");
+ _recentIpsPath = Path.Combine(_baseDir, FileRecentIps);
+ _recentIps = new List<string>();
+ }
+
+ public IReadOnlyList<string> RecentIps => _recentIps;
+
+ public void Load()
+ {
+ if (File.Exists(_recentIpsPath))
+ {
+ _recentIps.AddRange(File.ReadAllLines(_recentIpsPath));
+ }
+ }
+
+ public void Save()
+ {
+ Directory.CreateDirectory(_baseDir);
+
+ if (!Directory.Exists(_baseDir))
+ {
+ return;
+ }
+
+ if (_recentIps.Count > 0)
+ {
+ File.WriteAllLines(_recentIpsPath, _recentIps);
+ }
+ }
+
+ public void AddIp(string ip)
+ {
+ if (_recentIps.Contains(ip))
+ {
+ _recentIps.Remove(ip);
+ }
+
+ _recentIps.Insert(0, ip);
+
+ if (_recentIps.Count > MaxRecentIps)
+ {
+ _recentIps.RemoveAt(MaxRecentIps);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.Shared/Events/ErrorEventArgs.cs b/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.Shared/Events/ErrorEventArgs.cs
new file mode 100644
index 0000000..7211d5d
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.Shared/Events/ErrorEventArgs.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace Impostor.Patcher.Shared.Events
+{
+ public class ErrorEventArgs : EventArgs
+ {
+ public ErrorEventArgs(string message)
+ {
+ Message = message;
+ }
+
+ public string Message { get; }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.Shared/Events/SavedEventArgs.cs b/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.Shared/Events/SavedEventArgs.cs
new file mode 100644
index 0000000..c91d071
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.Shared/Events/SavedEventArgs.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace Impostor.Patcher.Shared.Events
+{
+ public class SavedEventArgs : EventArgs
+ {
+ public SavedEventArgs(string ipAddress, ushort port)
+ {
+ IpAddress = ipAddress;
+ Port = port;
+ }
+
+ public string IpAddress { get; }
+ public ushort Port { get; }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.Shared/Impostor.Patcher.Shared.csproj b/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.Shared/Impostor.Patcher.Shared.csproj
new file mode 100644
index 0000000..e480870
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.Shared/Impostor.Patcher.Shared.csproj
@@ -0,0 +1,12 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
+ <Version>1.0.0</Version>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="Gameloop.Vdf" Version="0.6.1" />
+ </ItemGroup>
+
+</Project>
diff --git a/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.Shared/Innersloth/RegionInfo.cs b/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.Shared/Innersloth/RegionInfo.cs
new file mode 100644
index 0000000..01b74d1
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.Shared/Innersloth/RegionInfo.cs
@@ -0,0 +1,48 @@
+using System.Collections.Generic;
+using System.IO;
+
+namespace Impostor.Patcher.Shared.Innersloth
+{
+ public class RegionInfo
+ {
+ public RegionInfo(string name, string ping, IReadOnlyList<ServerInfo> servers)
+ {
+ Name = name;
+ Ping = ping;
+ Servers = servers;
+ }
+
+ public string Name { get; }
+ public string Ping { get; }
+ public IReadOnlyList<ServerInfo> Servers { get; }
+
+ public void Serialize(BinaryWriter writer)
+ {
+ writer.Write(0);
+ writer.Write(Name);
+ writer.Write(Ping);
+ writer.Write(Servers.Count);
+
+ foreach (var server in Servers)
+ {
+ server.Serialize(writer);
+ }
+ }
+
+ public static RegionInfo Deserialize(BinaryReader reader)
+ {
+ var unknown = reader.ReadInt32();
+ var name = reader.ReadString();
+ var ping = reader.ReadString();
+ var servers = new List<ServerInfo>();
+ var serverCount = reader.ReadInt32();
+
+ for (var i = 0; i < serverCount; i++)
+ {
+ servers.Add(ServerInfo.Deserialize(reader));
+ }
+
+ return new RegionInfo(name, ping, servers);
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.Shared/Innersloth/ServerInfo.cs b/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.Shared/Innersloth/ServerInfo.cs
new file mode 100644
index 0000000..7203c84
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.Shared/Innersloth/ServerInfo.cs
@@ -0,0 +1,37 @@
+using System.IO;
+using System.Net;
+
+namespace Impostor.Patcher.Shared.Innersloth
+{
+ public class ServerInfo
+ {
+ public string Name { get; }
+ public string Ip { get; }
+ public ushort Port { get; }
+
+ public ServerInfo(string name, string ip, ushort port)
+ {
+ Name = name;
+ Ip = ip;
+ Port = port;
+ }
+
+ public void Serialize(BinaryWriter writer)
+ {
+ writer.Write(Name);
+ writer.Write(IPAddress.Parse(Ip).GetAddressBytes());
+ writer.Write(Port);
+ writer.Write(0);
+ }
+
+ public static ServerInfo Deserialize(BinaryReader reader)
+ {
+ var name = reader.ReadString();
+ var ip = new IPAddress(reader.ReadBytes(4)).ToString();
+ var port = reader.ReadUInt16();
+ var unknown = reader.ReadInt32();
+
+ return new ServerInfo(name, ip, port);
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.WinForms/App.config b/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.WinForms/App.config
new file mode 100644
index 0000000..2a83c36
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.WinForms/App.config
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<configuration>
+ <startup>
+ <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
+ </startup>
+</configuration> \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.WinForms/Forms/FrmMain.Designer.cs b/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.WinForms/Forms/FrmMain.Designer.cs
new file mode 100644
index 0000000..0f6320b
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.WinForms/Forms/FrmMain.Designer.cs
@@ -0,0 +1,141 @@
+namespace Impostor.Patcher.WinForms.Forms
+{
+ partial class FrmMain
+ {
+ /// <summary>
+ /// Required designer variable.
+ /// </summary>
+ private System.ComponentModel.IContainer components = null;
+
+ /// <summary>
+ /// Clean up any resources being used.
+ /// </summary>
+ /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form Designer generated code
+
+ /// <summary>
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ /// </summary>
+ private void InitializeComponent()
+ {
+ System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FrmMain));
+ this.label1 = new System.Windows.Forms.Label();
+ this.label2 = new System.Windows.Forms.Label();
+ this.buttonLaunch = new System.Windows.Forms.Button();
+ this.lblUrl = new System.Windows.Forms.Label();
+ this.label3 = new System.Windows.Forms.Label();
+ this.comboIp = new System.Windows.Forms.ComboBox();
+ this.SuspendLayout();
+ //
+ // label1
+ //
+ this.label1.AutoSize = true;
+ this.label1.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+ this.label1.Location = new System.Drawing.Point(28, 139);
+ this.label1.Name = "label1";
+ this.label1.Size = new System.Drawing.Size(60, 13);
+ this.label1.TabIndex = 1;
+ this.label1.Text = "IP Address";
+ //
+ // label2
+ //
+ this.label2.AutoSize = true;
+ this.label2.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+ this.label2.Location = new System.Drawing.Point(28, 23);
+ this.label2.Name = "label2";
+ this.label2.Size = new System.Drawing.Size(225, 91);
+ this.label2.TabIndex = 0;
+ this.label2.Text = "Welcome to Impostor\r\n\r\nPlease enter in the IP Address of the \r\nserver you would l" +
+ "ike to use for Among Us\r\n\r\nIf you want to stop playing on the server, \r\nsimply s" +
+ "elect another region";
+ //
+ // buttonLaunch
+ //
+ this.buttonLaunch.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+ this.buttonLaunch.Location = new System.Drawing.Point(179, 155);
+ this.buttonLaunch.Name = "buttonLaunch";
+ this.buttonLaunch.Size = new System.Drawing.Size(74, 22);
+ this.buttonLaunch.TabIndex = 3;
+ this.buttonLaunch.Text = "Save";
+ this.buttonLaunch.UseVisualStyleBackColor = true;
+ this.buttonLaunch.Click += new System.EventHandler(this.buttonLaunch_Click);
+ //
+ // lblUrl
+ //
+ this.lblUrl.AutoSize = true;
+ this.lblUrl.Cursor = System.Windows.Forms.Cursors.Hand;
+ this.lblUrl.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Underline, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+ this.lblUrl.ForeColor = System.Drawing.SystemColors.Highlight;
+ this.lblUrl.Location = new System.Drawing.Point(39, 232);
+ this.lblUrl.Name = "lblUrl";
+ this.lblUrl.Size = new System.Drawing.Size(212, 13);
+ this.lblUrl.TabIndex = 5;
+ this.lblUrl.Text = "https://github.com/AeonLucid/Impostor";
+ this.lblUrl.Click += new System.EventHandler(this.lblUrl_Click);
+ //
+ // label3
+ //
+ this.label3.AutoSize = true;
+ this.label3.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+ this.label3.Location = new System.Drawing.Point(54, 216);
+ this.label3.Name = "label3";
+ this.label3.Size = new System.Drawing.Size(182, 13);
+ this.label3.TabIndex = 6;
+ this.label3.Text = "Source code and latest versions at\r\n";
+ //
+ // comboIp
+ //
+ this.comboIp.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+ this.comboIp.FormattingEnabled = true;
+ this.comboIp.Location = new System.Drawing.Point(31, 155);
+ this.comboIp.Name = "comboIp";
+ this.comboIp.Size = new System.Drawing.Size(141, 21);
+ this.comboIp.TabIndex = 2;
+ this.comboIp.KeyDown += new System.Windows.Forms.KeyEventHandler(this.textIp_KeyDown);
+ //
+ // FrmMain
+ //
+ this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.ClientSize = new System.Drawing.Size(283, 262);
+ this.Controls.Add(this.comboIp);
+ this.Controls.Add(this.label3);
+ this.Controls.Add(this.lblUrl);
+ this.Controls.Add(this.buttonLaunch);
+ this.Controls.Add(this.label2);
+ this.Controls.Add(this.label1);
+ this.ForeColor = System.Drawing.SystemColors.ControlText;
+ this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
+ this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
+ this.MaximizeBox = false;
+ this.Name = "FrmMain";
+ this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
+ this.Text = "Impostor";
+ this.Load += new System.EventHandler(this.FrmMain_Load);
+ this.Shown += new System.EventHandler(this.FrmMain_Shown);
+ this.ResumeLayout(false);
+ this.PerformLayout();
+
+ }
+
+ #endregion
+
+ private System.Windows.Forms.Label label1;
+ private System.Windows.Forms.Label label2;
+ private System.Windows.Forms.Button buttonLaunch;
+ private System.Windows.Forms.Label lblUrl;
+ private System.Windows.Forms.Label label3;
+ private System.Windows.Forms.ComboBox comboIp;
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.WinForms/Forms/FrmMain.cs b/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.WinForms/Forms/FrmMain.cs
new file mode 100644
index 0000000..5c06669
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.WinForms/Forms/FrmMain.cs
@@ -0,0 +1,114 @@
+using System;
+using System.Diagnostics;
+using System.Windows.Forms;
+using Impostor.Patcher.Shared;
+using Impostor.Patcher.Shared.Events;
+
+namespace Impostor.Patcher.WinForms.Forms
+{
+ public partial class FrmMain : Form
+ {
+ private readonly Configuration _config;
+ private readonly AmongUsModifier _modifier;
+
+ public FrmMain()
+ {
+ InitializeComponent();
+
+ AcceptButton = buttonLaunch;
+
+ _config = new Configuration();
+ _modifier = new AmongUsModifier();
+ _modifier.Error += ModifierOnError;
+ _modifier.Saved += ModifierOnSaved;
+ }
+
+ private void ModifierOnError(object sender, ErrorEventArgs e)
+ {
+ MessageBox.Show(e.Message, "Error",
+ MessageBoxButtons.OK,
+ MessageBoxIcon.Error);
+
+ comboIp.Text = string.Empty;
+ comboIp.Focus();
+
+ comboIp.Enabled = true;
+ buttonLaunch.Enabled = true;
+ }
+
+ private void ModifierOnSaved(object sender, SavedEventArgs e)
+ {
+ MessageBox.Show("The IP Address was saved, please (re)start Among Us.", "Success",
+ MessageBoxButtons.OK,
+ MessageBoxIcon.Information);
+
+ var ipText = e.Port == AmongUsModifier.DefaultPort
+ ? e.IpAddress
+ : $"{e.IpAddress}:{e.Port}";
+
+ comboIp.Text = ipText;
+ comboIp.Enabled = true;
+ buttonLaunch.Enabled = true;
+
+ _config.AddIp(ipText);
+ _config.Save();
+
+ RefreshComboIps();
+ }
+
+ private void FrmMain_Load(object sender, EventArgs e)
+ {
+ _config.Load();
+
+ RefreshComboIps();
+
+ if (_modifier.TryLoadIp(out var ipAddress))
+ {
+ comboIp.Text = ipAddress;
+ }
+ }
+
+ private void FrmMain_Shown(object sender, EventArgs e)
+ {
+ comboIp.Focus();
+ }
+
+ private void textIp_KeyDown(object sender, KeyEventArgs e)
+ {
+ if (e.KeyCode != Keys.Enter)
+ {
+ return;
+ }
+
+ e.Handled = true;
+
+ buttonLaunch_Click(this, EventArgs.Empty);
+ }
+
+ private async void buttonLaunch_Click(object sender, EventArgs e)
+ {
+ comboIp.Enabled = false;
+ buttonLaunch.Enabled = false;
+
+ await _modifier.SaveIpAsync(comboIp.Text);
+ }
+
+ private void lblUrl_Click(object sender, EventArgs e)
+ {
+ Process.Start("https://github.com/AeonLucid/Impostor");
+ }
+
+ private void RefreshComboIps()
+ {
+ comboIp.Items.Clear();
+
+ if (_config.RecentIps.Count > 0)
+ {
+ foreach (var ip in _config.RecentIps)
+ {
+ comboIp.Items.Add(ip);
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.WinForms/Forms/FrmMain.resx b/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.WinForms/Forms/FrmMain.resx
new file mode 100644
index 0000000..839a9c4
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.WinForms/Forms/FrmMain.resx
@@ -0,0 +1,2338 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
+ <data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>
+ 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
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABIaXAQeMocbEwpxBBAA
+ ZwILAEcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAALXSbAShnjgUbQWcJDRc8CwoPNAsaPGIZH01yLRc1WkUUK1FNEB9FTwYAJVIGACVSBgAlUgYA
+ JVIGACVSBgAlUgYAJVIGACVSBgAlUgYAJVIGACVSDRRHUA8cU04WL3Q5FSxuIwkKNQsJCzgLEB9ZChcw
+ dQIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAACdOpAcgMZEdFhB6BxIAcAISAHAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAKGeNBiBSeCYaPGNFFS9VWREkSW8QHUKAEB9EpwwRN70LDjTEDBM52wsRNvUIBiv/BwQp/wcD
+ KP8HAif/BwEm/wYBJv8GASb/BgAl/wYAJf8GACX/BgAl/wYAJf8GACX/BgAl/wYAJf8GACX/BgAl/wYA
+ Jf8GASb/BgEm/wYBJ/8GAij/BwIp/wcDKf8JCDP7CAgyzAkJNMEKCzi/Cw8+ug4XTJ8NFkp8DxtSaRQp
+ aU0XMXgSAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACRC
+ nAEjPpkfFxR9CBIAcAMSAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAKGmQBxc1WxQXNFopDRg9WwkILV4KCzCeCQkuswsPNOAHBSruBwQp+QcDKP8GASb/BgAl/wYA
+ Jf8GACX/BgAl/wYAJf8GACX/BgAl/wYAJf8GACb/BgAn/wYAJ/8HACj/BwAp/wcAKf8HACr/BwAq/wcA
+ Kv8HACr/BwAq/wcAKv8HACr/BwAq/wcAKv8HACr/BwAq/wcAKf8HACj/BgAn/wYAJ/8GACX/BgAl/wYA
+ Jf8GACX/BgAl/wYAJf8GACX/BgAl/wYAJf8GACX/BwIo/wYBJvEHAijsBwIpuAYAJaUJCTOjCAcwYgsQ
+ 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
+ xv8AAMb/AADG/wAAxv8AAMb/BABe/wYAJf8JADf/DgCB/wEAw/8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADF/w8Afv8SAHD/EABi/wYAJ/8GACb/DABL/w0A
+ Uf8NAFH/DQBR/w0AUf8PAF7/EgBu/wsAQ/8GACX/BgAn/wMAdP8AAMT/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxf8GAKb/EQB1/xIA
+ cP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8QAGf/DQBS/w0A
+ Uf8NAFH/DQBR/w0AUf8JADf/BgAl/w0VOtQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAGACXNBgAl/wkAO/8NAFH/DQBR/w0AUf8NAFH/DQBR/w0AUf8NAFP/EQBr/xIA
+ cP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcf8MAIr/AwC5/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAwP8FAD7/BgAl/wsA
+ Sf8GAKf/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/DACL/xIAcP8QAGL/BgAn/wYAJv8MAEn/DQBR/w0AUf8NAFH/DQBR/w0AUv8QAGT/EQBr/wkA
+ Ov8GACX/BgAp/wMAef8AAMP/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8FAK//EAB5/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIA
+ cP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAb/8OAFn/DQBR/w0AUf8NAFH/DQBR/wkAN/8GACX/DRU61AAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAJX0GACX/CQA7/w0A
+ Uf8NAFH/DQBR/w0AUf8NAFH/DQBR/w8AXv8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIA
+ cP8SAHD/EgBw/xIAcP8RAHb/BwCj/wEAw/8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AACx/wYAKf8GACb/DABq/wEAwf8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8MAI7/EgBw/w8AX/8GACb/BgAl/wYA
+ KP8JADf/DABJ/w0AUf8NAFH/DQBR/w0AUv8QAGf/EQBp/wgANP8GACX/BgAo/wMAef8AAMT/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8DALT/EAB6/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xAA
+ Zf8NAFH/DQBR/w0AUf8NAFH/CQA3/wYAJf8NFTrUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAABgAlVwYAJf8JADv/DQBR/w0AUf8NAFH/DQBR/w0AUf8NAFL/EQBq/xIA
+ cP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/DgCA/wQAtP8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8CAJH/BgAl/wcA
+ K/8HAJf/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/w0Aiv8SAHD/DwBc/wYAJv8GACX/BgAl/wYAJf8GACf/CQA3/wwATv8NAFH/DQBR/w0A
+ U/8QAGj/EABl/wgAM/8GACX/BgAp/wMAe/8AAMX/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8DALX/EAB5/xIAcP8SAHD/EgBw/xIA
+ cP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBu/w4AVv8NAFH/DQBR/w0AUf8JADf/BgAl/w0V
+ OtQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGACVhBgAl/wkA
+ O/8NAFH/DQBR/w0AUf8NAFH/DQBR/w4AWf8SAG//EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIA
+ cP8SAHD/EgBw/wwAi/8CAL7/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wMAbf8GACX/CAA7/wIAu/8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMX/DwB9/xIAcP8NAFP/BgAl/wYA
+ Jv8HACv/BgAn/wYAJf8GACX/CAAw/wwATP8NAFH/DQBR/w0AVP8RAGr/EABl/wgAMf8GACX/BgAt/wEA
+ r/8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8FAK7/EQB1/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIA
+ cP8SAHD/DwBg/w0AUf8NAFH/DQBR/wkAN/8GACX/DRU61AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYBJ1YGACX/CQA4/w0AUf8NAFH/DQBR/w0AUf8NAFL/EABn/xIA
+ cP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcf8KAJT/AQDD/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMT/BABK/wYA
+ Jf8EAGj/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wIAu/8SAHL/EgBw/woAQP8GACX/CAAy/w0AUP8MAEr/CAA1/wYAJv8GACX/BwAt/wwA
+ S/8NAFH/DQBR/w4AVf8RAGv/DwBg/wcAKf8GACX/AgCB/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8HAKP/EgBx/xIA
+ cP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8RAGr/DQBS/w0AUf8NAFH/CQA3/wYA
+ Jf8NFTrUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgIoXwYA
+ Jf8JADf/DQBR/w0AUf8NAFH/DQBR/w4AWP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIA
+ cP8SAHH/CQCZ/wEAw/8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAtv8FADL/BgAl/wIAk/8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/BwCl/xIAcP8RAGv/BwAu/wYA
+ Jf8KAED/DQBR/w0AUf8NAFD/CgBA/wYAKf8GACX/CAAv/wwATv8NAFH/DQBR/w4AV/8RAGz/CQA2/wYA
+ Jf8DAHH/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxP8MAIz/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIA
+ cP8SAHD/EgBw/xIAb/8OAFb/DQBR/w0AUf8JADf/BgAl/w0WO9EAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAihYBgAl/wkAN/8NAFH/DQBR/w0AUf8NAFH/EABj/xIA
+ cP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/woAlv8AAMT/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AQCc/wYA
+ Jv8GAC7/AQCw/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxf8MAI7/EgBw/w0AU/8GACX/BwAp/wwATf8NAFH/DQBR/w0AUf8NAFH/DABK/wcA
+ K/8GACX/CQA6/w0AUf8NAFH/DQBR/w4AWv8JADj/BgAl/wMAcf8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wIA
+ vP8QAHr/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/w8AXv8NAFH/DQBR/wgA
+ NP8GACX/EBxCsgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgF
+ K2IGACX/CAA0/w0AUf8NAFH/DQBR/w0AVP8RAGz/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIA
+ cP8MAIz/AQDC/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8DAHf/BgAl/wUARP8AAMH/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AQDC/w8Aff8RAGz/CAAy/wYA
+ Jf8IADb/DQBR/w0AUf8OAFX/DwBb/w4AWP8NAFL/CgA+/wYAJf8HACn/DABM/w0AUf8NAFH/DQBR/wgA
+ M/8GACX/AwBx/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wYAp/8SAHL/EgBw/xIAcP8SAHD/EgBw/xIA
+ cP8SAHD/EgBw/xIAcP8SAHD/EABm/w0AUf8NAFH/CAA0/wYAJf8QHEKuAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQgtZgYAJf8IADT/DQBR/w0AUf8NAFH/DwBc/xIA
+ cP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/DwB8/wIAvP8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wQA
+ U/8GACX/BABm/wAAxf8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8DALb/EQBz/w0AVP8GACb/BgAm/wsAR/8NAFH/DgBY/xIAbv8SAHD/EgBv/xAA
+ aP8NAE//BwAq/wYAJf8JADv/DQBR/w0AUf8NAFH/CQA3/wYAJf8DAGf/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADE/wwAiv8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8RAGz/DQBT/w0A
+ Uf8IADL/BgAl/w0XPZoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAKDDFtBgAl/wgAMf8NAFH/DQBR/w0AUf8QAGf/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIA
+ cv8GAKr/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AALz/BQA4/wYAJf8CAIv/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wgAn/8RAG3/CAAx/wYA
+ Jf8HACv/DABM/w0ATv8LAEP/EQBt/w8Af/8MAIr/EgBw/xAAZf8JADv/BgAl/wcAKv8MAE7/DQBR/w0A
+ Uf8KADz/BgAl/wQAWP8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AwC5/xAAd/8SAHD/EgBw/xIA
+ cP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAb/8OAFb/DQBR/wcAMP8GACX/CxE5kAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsQNXwGACX/CAAw/w0AUf8NAFH/DQBU/xIA
+ bv8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/DQCJ/wAAxf8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wEA
+ qP8GACn/BgAp/wEAqf8AAMb/AADG/wAAxv8AAMb/AADG/wUArv8EALH/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMP/DgCB/w8AXv8GACb/BgAl/wYAJf8GACj/BwAq/wYAKf8PAGH/DACN/wEA
+ wf8OAIP/EgBw/xAAYv8HACr/BgAl/woAQf8NAFH/DQBR/wsAQv8GACX/BQBG/wAAw/8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/CACd/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/w8A
+ W/8NAFH/BwAt/wYAJf8LDz6OAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAADBQ5fwYAJf8HAC3/DQBR/w0AUf8OAFr/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIA
+ cv8FALD/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AgCG/wYAJv8FADP/AAC9/wAAxv8AAMb/AADG/wAA
+ xv8AAMT/DgCD/w0AiP8AAMX/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wEAwv8QAHz/EQBo/wgA
+ Mf8HACn/BgAn/wYAJ/8HACn/CAAy/xAAZv8HAKL/AADG/wYAqP8SAHD/EgBw/wsAQv8GACX/CAAv/w0A
+ UP8NAFH/CwBI/wYAJv8FADT/AAC4/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8BAMH/DgCB/xIA
+ cP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/DwBh/w0AUP8HACv/BgAl/w4ZToQAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMEzhrBgAl/wcALP8NAFH/DQBR/w8A
+ Yf8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/DQCF/wEAw/8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8DAGP/BgAl/wUARv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxP8OAIH/DwB7/wEAwP8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wMAtv8MAIv/EABz/xAAaP8QAGP/EABi/xAAZf8RAGv/DwB+/wEA
+ v/8AAMb/AgC8/xEAdP8SAHD/DwBg/wYAJ/8GACb/CwBG/w0AUf8MAEv/BgAo/wYAJ/8BAKH/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8FAK3/EgBy/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIA
+ cP8QAGX/DABO/wcAKv8GACX/EB5WZwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAABMpTmgGACX/BwAr/w0AT/8NAFH/EABn/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIA
+ cP8GAKf/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wUASf8GACX/BABb/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADE/w4Agf8SAHH/BwCk/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8BAL//AwC3/wQAsv8FAK7/BgCp/wUAq/8CALz/AADG/wAAxv8AAMX/DwB8/xIAcP8MAIf/CQA6/wYA
+ Jf8IADT/DQBR/w0AUP8HAC3/BgAl/wMAeP8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xf8MAIv/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xEAaf8MAEz/BgAo/wYAJf8QHlhGAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFzRaXQYAJf8HACr/DABO/w0A
+ U/8RAGz/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EAB3/wEAv/8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/BQBH/wYAJf8EAEb/AADD/wAAxv8AAMb/AADG/wAAxv8AAMT/DgCE/xIAcP8QAHr/AwC2/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxP8QAHv/EgBw/wUArv8MAGT/BgAm/wYAKP8MAEz/DgBV/wsAQ/8GACX/BABL/wAA
+ xP8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wMAtf8RAHT/EgBw/xIAcP8SAHD/EgBw/xIA
+ cP8SAHD/EQBr/wwATP8GACf/BgAl/xUsbz0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAZOmBKBgAl/wYAKP8MAEz/DQBU/xIAbv8SAHD/EgBw/xIAcP8SAHD/EgBw/xIA
+ cP8MAI3/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8DAHD/BgAl/wYAJ/8DAHT/AADA/wAA
+ xv8AAMb/AADG/wAAxv8DALX/CwCQ/xEAdP8PAH3/BACw/wAAxf8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/BACy/xEAc/8SAHD/AgC8/wkA
+ lP8IADL/BgAl/woAQP8OAFn/DwBc/wYAJv8GAC3/AQCu/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADF/wsAkP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8RAG3/DABK/wYAJv8HAyr/Gz2LKAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABxGbDoGACX/BgAm/wwA
+ Sv8OAFX/EgBv/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/wYAqf8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAuP8FAEr/BgAl/wYAJv8EAFD/AQCr/wAAxv8AAMb/AADG/wAAxv8AAMb/AwC5/woA
+ lP8QAHn/CgCT/wMAuP8AAMX/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wMAtf8PAH3/EgBw/xAAef8BAMH/BACy/wwAUP8GACX/CQA4/w8AX/8RAGz/BwAs/wYA
+ Jf8DAH//AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AwC2/xEAdP8SAHD/EgBw/xIA
+ cP8SAHD/EgBw/xIAb/8LAEj/BgAl/wkJNPkdQZMJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAI1mAKAcDKP8GACb/CwBH/w4AVv8SAHD/EgBw/xIAcP8SAHD/EgBw/xIA
+ cP8RAHf/AgC9/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wEAr/8FADD/BgAl/wYA
+ Jf8FADT/AgCE/wAAwv8AAMb/AADG/wAAxv8AAMb/AADF/wIAuv8JAJn/DwB9/wwAjf8EALL/AADE/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wEAwf8IAJ7/EAB4/w8Af/8KAJX/BAC0/wAA
+ xv8BAMT/BwCF/wYAJ/8IADT/EQBo/xIAcP8JADf/BgAl/wQAUv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMX/DACK/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/wsARP8GACX/Cgs36Bk4
+ gwIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoZ44OCgsw+wYA
+ Jf8LAEP/DgBW/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/w0AiP8AAMX/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADB/wUAQ/8GACX/BgAl/wYAJf8GACb/BABY/wEAsP8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8BAL7/CQCc/w8Aff8NAIj/BQCs/wEAw/8AAMb/AADG/wAAxv8AAMb/AADG/wEA
+ wP8HAKX/DwB//w4AgP8GAKf/AQDD/wAAxv8AAMb/AADG/wAAw/8DAGj/BgAl/wsARv8SAG//EgBv/wgA
+ Mv8GACX/BABM/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8FAK3/EgBx/xIA
+ cP8SAHD/EgBw/xIAcP8SAHD/CgBA/wYAJf8HAyq7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAB9OdAILDzXqBgAl/woAP/8OAFb/EgBw/xIAcP8SAHD/EgBw/xIA
+ cP8SAHD/CACe/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AwBm/wYA
+ Jf8GACb/BwAu/wYAJf8GACX/BQA3/wIAi/8AAMT/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AQC//wcA
+ o/8OAID/DgCD/wQAr/8AAMb/AADG/wIAvf8KAJT/DwB//w0Ah/8HAKP/AQDB/wAAxv8AAMb/AADG/wAA
+ xv8AAMX/AgB+/wYAKP8IADP/EQBp/xIAcP8PAF3/BgAn/wYAJf8DAHH/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wEAw/8PAH7/EgBw/xIAcP8SAHD/EgBw/xIAb/8KAD//BgAl/woM
+ ObMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJmCHAAcE
+ KcIGACX/CQA6/w4AVf8SAG//EgBw/xIAcP8SAHD/EgBw/xIAcf8EALL/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8CAIn/BgAl/wYAJf8LAET/CgA9/wcAKf8GACX/BgAo/wQA
+ Yf8BALT/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wEAwv8FAK3/BQCv/wAAxv8AAMb/BQCv/wYA
+ p/8CALr/AADF/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wEAn/8GAC7/BwAs/xAAYv8SAHD/EAB3/wgA
+ PP8GACX/BQAz/wEArv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wkA
+ mv8SAHD/EgBw/xIAcP8SAHD/EgBv/wkANf8GACX/BwQsfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgwxsQYAJf8IADX/DgBV/xIAbv8SAHD/EgBw/xIA
+ cP8SAHD/EAB5/wEAv/8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wEA
+ pP8GACX/BgAl/woAPf8NAFH/DABK/wgAMP8GACX/BgAl/wUAOv8CAJX/AADE/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8BALP/BQA7/wYAKP8OAFn/EgBw/wwAif8DAIz/BgAn/wYAJv8CAH7/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AwC1/xEAcv8SAHD/EgBw/xIAcP8RAGz/BwAu/wYA
+ Jf8JCzdyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAGACV0BgAl/wgAMP8NAFT/EgBu/xIAcP8SAHD/EgBw/xIAcP8NAIb/AADF/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AAC2/wYALP8GACX/CQA3/w0AUf8NAFH/DQBP/woA
+ Pf8GACf/BgAl/wYAKv8DAGj/AAC5/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AAC7/wQATf8GACb/DABJ/xIAb/8OAIT/AQC+/wQA
+ Tf8GACX/BQBB/wAAvP8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8BAMP/DwB//xIAcP8SAHD/EgBw/xAAZ/8HACn/BgEm/woLODcAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcEKXQGACX/BwAs/w0AU/8RAG3/EgBw/xIA
+ cP8SAHD/EgBw/woAmP8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAL7/BQA5/wYAJf8IADL/DQBU/w8AYf8QAGP/EABj/w4AV/8JADb/BgAl/wYAJf8FAD//AQCc/wAA
+ xP8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ wP8EAF3/BgAl/woAPP8SAG7/EAB5/wMAt/8BAJ3/BgAp/wYAJf8CAIj/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8KAJb/EgBw/xIAcP8SAHD/DwBg/wYA
+ Jf8GAij/Cg47LAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAADx1CWAYBJv8HACn/DQBQ/xEAbf8SAHD/EgBw/xIAcP8SAHD/BgCo/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAw/8FAEH/BgAl/wcALv8QAGf/EgBw/xIA
+ cP8SAHD/EgBw/xEAbP8MAEr/BgAo/wYAJf8GACz/AwBu/wAAvf8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMP/AwBr/wYAJv8HAC7/EABn/xEAc/8FAKz/AADE/wQA
+ Xv8GACX/BQA3/wAAvP8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wUArf8SAHH/EgBw/xIAcP8OAFb/BgAl/wgGMPIWL3MXAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDDEtBgEm/wYAJ/8MAE3/EQBs/xIA
+ cP8SAHD/EgBw/xEAc/8DALj/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wQAS/8GACX/BwAu/xEAbf8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAb/8PAF3/CAAy/wYA
+ Jf8GACX/BQBG/wEAof8AAMX/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADB/wMA
+ dP8GACj/BgAl/w0AUP8RAHT/BwCk/wAAxv8BALH/BQAw/wYAJf8DAHL/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AgC+/xEAdv8SAHD/EgBw/wwA
+ Sv8GACX/CQgz4gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAA4aQCwHAyj+BgAl/wsASf8RAGz/EgBw/xIAcP8SAHD/EAB4/wEAwP8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/BABV/wYAJf8HACv/EQBp/xIA
+ cf8MAIv/CgCT/w4AhP8RAHX/EgBw/xIAcP8RAGr/CgA8/wYAJf8GACX/BgAt/wMAeP8AAL//AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAv/8DAGj/BgAn/wYAJf8HACn/EABo/wcApP8AAMX/AADG/wIA
+ gv8GACX/BgAt/wEAqv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/DgCC/xIAcP8SAHD/CgA//wYAJf8MEkLHAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIE51FgkKL/EGACX/CgBC/xEA
+ a/8SAHD/EgBw/xIAcP8OAID/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8EAGH/BgAl/wcAKf8QAGT/DACN/wAAw/8AAMb/AADE/wMAuf8LAJH/EgBx/xIA
+ bv8JADj/BgAl/wYAJf8GACX/BgAm/wQATf8BAKj/AADG/wAAxv8AAMb/AADG/wAAxv8BAK7/BABS/wYA
+ Jv8GACX/BgAl/wgAM/8IAJn/AADF/wAAxv8AAMT/BABL/wYAJf8EAE3/AADD/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8KAJP/EgBw/xIA
+ cP8IADT/BgAl/wwTRJgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAACg0y4QYAJf8JADz/EQBq/xIAcP8SAHD/EgBw/wwAjP8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wMAa/8GACX/BgAn/w8A
+ ZP8EALT/AADG/wAAxv8AAMb/AADG/wAAxf8JAJn/EABp/wYAJv8GACX/BgAt/wQATP8GACf/BgAl/wYA
+ Lv8CAIL/AAC//wAAxv8AAMP/AgCS/wUAMv8GACX/BQBG/wQASf8GACX/BQBI/wAAw/8AAMb/AADG/wEA
+ qv8GACz/BgAl/wIAgf8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wcApP8SAHD/EQBq/wcALf8GACX/DRZKYgAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOFz3LBgAl/wgA
+ Nf8QAGj/EgBw/xIAcP8SAHD/CgCV/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AwB2/wYAJf8GACX/DQBo/wEAw/8AAMb/AADG/wAAxv8AAMb/AADG/wEA
+ w/8KAHP/BgAl/wYAJf8EAFz/AAC+/wIAff8GAC7/BgAl/wYAJ/8EAFT/AgCM/wQAYv8GACr/BgAo/wMA
+ Z/8AALr/AwBk/wYAJf8FAD3/AAC+/wAAxv8AAMb/AwB8/wYAJf8GACv/AQCw/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/BACx/xIA
+ cP8QAGL/BgAn/wYAJf8LDj0YAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4YPZQGACX/BwAv/xAAZv8SAHD/EgBw/xIAcP8IAJ7/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8CAID/BgAl/wYA
+ Jf8KAGr/AADF/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wMAg/8GACX/BgAm/wIAkf8AAMb/AADG/wEA
+ q/8EAFv/BgAq/wYAJf8GACX/BgAl/wUAOP8BAJD/AADE/wAAxv8CAID/BgAl/wYAKv8BAKL/AADG/wAA
+ v/8FAEL/BgAl/wQAVv8AAMT/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8CALr/EQB0/w0AUv8GACX/BwIo5AwQPggAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEB9EcgYA
+ Jf8HACr/DwBh/xIAcP8SAHD/EgBw/wcApf8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wIAi/8GACb/BgAl/wgAa/8AAMX/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AwB7/wYAJf8GACv/AAC0/wAAxv8AAMb/AADG/wAAw/8BAJ7/BABb/wQAS/8DAGr/AQCy/wAA
+ xv8AAMb/AADG/wEAsf8FADL/BgAl/wUAPP8DAG3/BABX/wYAJv8GACj/AQCY/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wEA
+ wP8QAHn/CwBF/wYAJf8HAijGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASJEovBgAl/wYAJ/8OAFv/EgBw/xIAcP8SAHD/BgCp/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AgCV/wYA
+ J/8GACX/BgBt/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8DAHv/BgAl/wYALf8AAML/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMX/AADD/wAAxf8AAMb/AADG/wAAxv8AAMb/AADF/wEAjP8FAC7/BgAl/wYA
+ Jf8GACX/BgAm/wQAYv8AAML/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADE/w8Aff8JADj/BgAl/wgGLo4AAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsQ
+ NQ4HBCn+BgAl/w0AUf8SAHD/EgBw/xIAcP8FAK3/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8BAJ//BgAo/wYAJf8DAGf/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wMAe/8GACX/BgAt/wAAw/8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wEAmv8EAE//BQBB/wQASv8DAHb/AADA/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMX/DQCA/wcALf8GACX/CQo2fQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHklwBwkJLuMGACX/CwBG/xIAcP8SAHD/EgBw/wUA
+ rf8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wEA
+ qP8GACn/BgAl/wQAXf8AAMX/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AwB7/wYAJf8GACz/AAC9/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ w/8AAL//AADC/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxf8LAHz/BgAn/wYBJ/8NFkpBAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAACAcwwwYAJf8KAD7/EgBw/xIAcP8SAHD/BQCt/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AQCy/wYAKv8GACX/BABS/wAAxf8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8DAHP/BgAl/wYAKv8BALH/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADF/wgAcv8GACX/CAUu+hYwdycAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIBjCGBgAl/wgAMv8SAG7/EgBw/xIA
+ cP8FAK3/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8BALb/BgAw/wYAJf8EAFH/AADF/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wMAbf8GACX/BgAp/wEA
+ p/8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMX/BQBg/wYAJf8KDDjgIU+rAgAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAwTRWoGASb/BwAq/xEAaP8SAHD/EgBw/wYAp/8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAuP8FADT/BgAl/wQAUf8AAMX/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/BABh/wYAJf8GACf/AQCf/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAw/8FAEX/BgAl/woMOZQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEB1VNAcCKf4GACX/DwBd/xIA
+ cP8SAHD/BwCk/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AAC8/wUAOv8GACX/BABR/wAAxf8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8EAFz/BgAl/wYA
+ J/8HAIP/CACg/wgAoP8IAKD/CQCa/wkAmP8KAJP/CwCR/wsAkf8LAJH/CgCY/wkAm/8IAKD/BwCl/wYA
+ qv8GAKj/BgCo/wYAqP8GAKr/BACy/wIAuv8CALv/AgC7/wIAvv8BAML/AQDD/wAAxf8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AQCy/wYAMP8GACX/Cw8+WAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAbPYoPCQgz8QYAJf8NAE7/EgBw/xIAcP8IAJ7/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMT/AQDD/wEAwf8DALP/BQA8/wYAJf8EAFH/AADF/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wQAVP8GACX/BgAn/xAAYf8SAHD/EgBw/xIAcP8SAHD/EgBw/xIA
+ cP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHL/EQBz/xEA
+ c/8RAHP/EQB0/xAAd/8PAH3/DQCI/wsAkv8JAJz/BgCq/wMAuP8BAML/AADF/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8CAJT/BgAl/wcCKP4JCTUZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALDz7OBgAl/woA
+ Pf8SAHD/EgBw/wkAmP8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADE/wEAwf8CALv/BACx/wYApv8JAJj/DACN/w4A
+ hP8PAHz/EQB0/xEAcP8IADP/BgAl/wQAR/8AAMT/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/BABU/wYA
+ Jf8GACf/EABk/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIA
+ cP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIA
+ cP8SAHD/EgBy/xAAef8NAIf/CQCa/wUArP8CALv/AQDD/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wMAbP8GACX/CAUu3Bcy
+ eQYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgFLWsGACX/BwAu/xEAbP8SAHD/CQCY/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADF/wEAv/8FAKv/CQCc/wsA
+ j/8OAIX/EAB7/xEAdf8SAHL/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBv/wkANf8GACX/BQBE/wAA
+ xP8AAMb/AADG/wAAxv8AAMb/AADG/wAAxP8EAEz/BgAl/wYAKP8QAGj/EgBw/xIAcP8SAHD/EgBw/xIA
+ cP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIA
+ cP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBx/xEA
+ df8OAIL/CgCU/wQAsP8AAMT/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMT/BQBE/wYAJf8LDz6uAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBNEPgYA
+ Jf8GACf/DwBh/xIAcP8KAJX/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8BAMP/AwC1/wkAm/8OAIP/EQBz/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIA
+ cP8SAHD/EgBw/xIAcP8SAHD/CQA2/wYAJf8FAET/AADE/wAAxv8AAMb/AADG/wAAxv8AAMb/AADD/wUA
+ Sf8GACX/BgAo/xIBZ/8TAWv/EwFr/xMBa/8TAWv/EwFr/xMBa/8TAWv/EwFr/xMBa/8TAWz/EgFt/xIB
+ bf8SAW3/EgBu/xIAb/8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIA
+ cP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBx/w4Agf8HAKX/AQDB/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wEAsf8GAC3/BgAl/w8c
+ UW0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOGU8VCAUt+AYAJf8NAFH/EgBw/wsAkf8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8BAMP/BwCj/w4Agv8RAHP/EgBw/xIAcP8SAHD/EgBw/xIA
+ cP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAb/8JADf/BgAl/wUA
+ RP8AAMT/AADG/wAAxv8AAMb/AADG/wAAxv8AAMD/BQBA/wYAJf8IAST/IBYa/yAWGv8gFhr/IBYa/yAW
+ Gv8gFhr/IBYa/yAWGv8gFhr/IBYa/yAWHf8fFSD/HxUh/x8UI/8eEyf/HhIp/x0RLv8cDzb/Gw44/xoN
+ P/8ZCkf/GAhQ/xYGWf8UBGL/EwJp/xIBbv8SAG//EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIA
+ cP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcf8OAID/BQCr/wAAxf8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AgCO/wYAJv8HBCn8IE93JgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAICDLOBgAl/woAP/8SAHD/DACL/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AwC5/w0A
+ hv8SAHH/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAW7/EwJq/xUF
+ Xv8XCFP/GQpJ/xoNPv8cEDP/HhMp/w4HI/8GACX/BQBE/wAAxP8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ vv8FADr/BgAl/wkDIv8jGg3/IxoN/yMaDf8jGg3/IxoN/yMaDf8jGg3/IxoN/yMaDf8jGg3/IxoN/yMa
+ Df8jGg3/IxoN/yMaDf8jGg3/IxoN/yMaDf8jGg3/IxoN/yMaDf8jGg3/IxoP/yIZEf8iGBP/IBYb/x4T
+ J/8cEDT/GgxC/xcIUf8UBGL/EwFs/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIA
+ cP8SAHH/CwCP/wIAvP8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxf8EAF//BgAl/wgH
+ LMIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoOPIwGACX/CAAv/xEAbf8OAIT/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wIAuv8PAH3/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIA
+ cP8SAHD/EwFs/xUEYP8YCUz/HA81/x8UIv8iGBT/IhkQ/yMaDv8jGg3/IxoN/yMaDf8jGg3/Dwgd/wYA
+ Jf8FAET/AADE/wAAxv8AAMb/AADG/wAAxv8AAMb/AAC7/wUAMf8GACX/FQ0n/1VELP9WRCz/VkQs/1ZE
+ LP9WRCz/VkQs/1ZELP9WRCz/VkQs/1ZELP9TQir/Tj4n/04+J/9LOyX/Rjci/0I0IP89MB3/OCwa/zYq
+ Gf8xJhb/LCIT/yceEP8mHA//JRsO/yMaDf8jGg3/IxoN/yMaDf8jGg3/IxoO/yIZEf8gFhz/HBAy/xgK
+ Sv8UBGH/EgFu/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/DgCA/wIAu/8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AAC8/wUAOv8GACX/CxA1jAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAESNfUwYBJv8GACf/EABh/w8AfP8AAMX/AADG/wAAxv8AAMb/AADG/wAAxv8AAMT/DACL/xIA
+ cP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8TAWv/FwhS/xwPNP8gFR3/IhkR/yMaDf8jGg3/IxoN/yMa
+ Df8kGg3/JBsO/ygeEP8xJRX/Oi0b/0M1If8bEyX/BgAl/wUARv8AAMT/AADG/wAAxv8AAMb/AADG/wAA
+ xv8BALL/BgAr/wYAJf8jGS3/fWVF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35m
+ Rf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf98ZET/e2RD/3pjQv94YUH/dl9A/2xXOv9mUjb/XUsx/1JB
+ Kv9FNiL/OSwa/yshEv8kGw7/IxoN/yMaDf8jGg3/IxoN/yIZEv8fFCP/Gg0//xUFXf8SAG//EgBw/xIA
+ cP8SAHD/EgBw/xIAcP8SAHD/DgCD/wEAvv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8BAKD/BgAo/wYB
+ Jv8OGT5DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaPIgMCAYv7wYAJf8MAE3/EAB4/wEA
+ wP8AAMb/AADG/wAAxv8AAMb/AADG/wUArf8SAHH/EgBw/xIAcP8SAHD/EgBw/xMCaf8XB1X/HBA0/yIY
+ Ff8jGg7/IxoN/yMaDf8kGw7/KB4Q/zQpGP9FNiL/VUQs/2VSNv9yXD7/emJC/3tkQ/99ZUT/fmZF/ysg
+ L/8GACX/BABW/wAAxf8AAMb/AADG/wAAxv8AAMb/AADG/wEAo/8GACn/BgAl/zQqNv+Re1b/kXxW/5F8
+ Vv+RfFb/kXxW/5F8Vv+RfFb/kXxW/5F8Vv+RfFb/kXxW/5F8Vv+PeVT/jHZS/4x2Uv+MdlH/iHJO/4Rt
+ Sv+Cakj/gWlI/4BoR/9/Z0b/fmZF/35mRf9+ZkX/fmZF/35mRf99ZUT/emND/29aPP9cSjD/SDkk/zYq
+ GP8oHhD/IxoN/yMaDf8jGg3/IxkP/x8UJP8ZC0b/FANj/xIAb/8SAHD/EgBw/xIAcP8SAHD/DACO/wAA
+ xf8AAMb/AADG/wAAxv8AAMb/AADG/wMAdP8GACX/CQku7hQuVBMAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAIBzGuBgAl/wkAOf8RAHP/AwC6/wAAxv8AAMb/AADG/wAAxv8AAMb/DACO/xIA
+ cP8SAHD/EgBw/xMBav8aDED/IBYb/yMaD/8jGg3/IxoN/yQbDf8wJRX/Sjol/2BNM/9yXD7/e2ND/35m
+ Rf9+ZkX/f2dF/4FqSP+Jck//kHpV/5mFXf+ij2X/MSg3/wYAJf8DAGb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AgCT/wYAJ/8GACX/ZFxg/+DVrP/e1Kr/18uc/8/Cjv/OwYz/0sWT/9vQo//d0qf/49mx/+Ta
+ sv/g1az/2s+j/9bKm//OwY7/zL+L/8y/i//LvYr/ybyI/8a3hf/BsoH/vK18/7eneP+yoXP/qphs/6OP
+ Zf+ZhV3/kHpV/4hxTv+AaEf/fmZF/35mRf9+ZkX/e2ND/3FcPf9bSTD/QTMf/ykfEP8jGg3/IxoN/yMa
+ Df8iGBX/HA82/xQDZf8SAHD/EgBw/xIAcP8RAHX/AgC7/wAAxv8AAMb/AADG/wAAxv8AAMT/BQBG/wYA
+ Jf8NFju6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkKNmgGACX/BwAq/xAA
+ aP8EALL/AADG/wAAxv8AAMb/AADG/wEAwf8PAH3/EgBw/xIAcP8VBVz/IBYc/yMaDf8jGg3/JRwO/zMn
+ F/9OPij/bFc6/3tjQ/9+ZkX/gGhH/4hxTv+WgVr/p5Rp/7OidP/BsoH/y72K/87Bjv/QxJD/1ciW/9XI
+ l/81Lj//BgAl/wMAbP8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8CAID/BgAl/wYAJf98c2n/28+h/9rP
+ of/Zzp//1MiV/9HEj//bz6H/3tOn/97Tp//Zzp//1sqa/9PHlP/TxpP/0sWS/9DEj//Qw47/0MOO/9DD
+ jv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/PwY3/zb+L/8q8iP++r37/tKR1/6iV
+ av+Wglv/hW9M/39nRv99ZUX/dmBA/19MMv9AMh//KiAR/yMaDf8jGg3/IRYa/xYGWP8SAHD/EgBw/xIA
+ cf8FAK3/AADG/wAAxv8AAMb/AADG/wEAqv8GACz/BgAl/xMpT2oAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAADRdLKAcDKv0GACX/DgBX/wYAp/8AAMb/AADG/wAAxv8AAMb/AgC9/xEA
+ df8SAHD/FANk/yEWGv8jGg3/JRwO/0EzH/9pVTj/emND/35mRf+AaEb/kXxW/6yabv++rn7/y76K/8/C
+ jf/SxZL/4NWq/+feuP/p4Lz/6+G+/+jeuf/l27T/3dKp/zIrQP8GACX/AwB0/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wMAav8GACX/BgAl/5GGbf/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DD
+ jv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DD
+ jv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0sWR/9PGk//KvIj/tKN1/5N9V/+AaEf/fmZF/4hy
+ Uv+FclX/TT0n/ycdD/8jGg3/IRcY/xQDYv8SAHD/EgBx/wYAqf8AAMb/AADG/wAAxv8AAMb/AwB7/wYA
+ Jf8IBiv8FSxSDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdRZkECgw52QYA
+ Jf8KAEH/CQCa/wAAxv8AAMb/AADG/wAAxv8CALz/EQBz/xIAcP8bDjv/IxoN/yYdD/9bSS//gWpJ/4Bo
+ SP9+ZkX/loFa/72tff/SxpT/3dKl/9TIlv/p4Lv/18ua/9LFkf/VyJb/08aT/9HFkf/Qw47/0MOO/9DD
+ jv/JvIr/KCE3/wYAJf8DAHr/AADG/wAAxv8AAMb/AADG/wAAxv8AAMX/BABT/wYAJf8NBij/p5x5/9DD
+ jv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DD
+ jv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DD
+ jv/d0qX/8+zN/+Xbs//Qw4//0MOR/7urfP+Re1b/gGhH/7Cggv+qmHv/YE0z/ycdD/8jGg3/Gw44/xIA
+ cP8SAHH/BQCu/wAAxv8AAMb/AADG/wAAw/8EAEz/BgAl/woNMsIiWYABAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANFkuLBgAl/wcALf8LAIn/AADF/wAAxv8AAMb/AADG/wIA
+ vf8RAHX/EgBu/yAVIf8jGg3/QjQg/4BpSP+ciWr/f2dG/6CMY//OwY3/4des//rz2P/y6sv/1MeV/9fL
+ m//RxJD/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/8K2h/8eFzL/BgAl/wIAhf8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAwP8FAED/BgAl/xYPLf+9sYT/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DD
+ jv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DD
+ jv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/SxZL/08eU/9HFkf/z7M7/8OjH/8/C
+ kP+gjGP/gGhH/4p0VP+EbEz/RTYi/yMaDf8gFSD/EgFu/xEAc/8CALr/AADG/wAAxv8AAMb/AQCt/wYA
+ Lf8GACX/DBQ5dAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwT
+ RBMGASf/BgAm/wsAa/8AAMX/AADG/wAAxv8AAMb/AQDA/xAAev8TAWv/IRYa/yMaDf9UQyv/gWlI/4Zv
+ Tv+HcE3/ybuI/+HXrP/999//6uG9/9PGk//Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DD
+ jv/Qw47/u6+D/xIMK/8GACX/AgCR/wAAxv8AAMb/AADG/wAAxv8AAMb/AAC6/wUAMv8GACX/LSY5/8m8
+ iv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DD
+ jv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DD
+ jv/Qw47/0MOO/9DDjv/Qw47/0MOO/+DVq//++OD/5Nqy/8i6h/+EbUr/hm9P/72ukf9UQyz/IxoN/yEW
+ Gv8TAWv/DwB9/wEAwv8AAMb/AADG/wAAxv8CAH7/BgAl/wgHLPoYOmArAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBFBAAcELL0GACX/CQBK/wEAv/8AAMb/AADG/wAA
+ xv8AAMX/DgCE/xIBbv8gFSD/IxoN/08/KP+yoYT/gmtK/5WAWv/SxZL/+vPY/+nhvP/RxI//0MOO/9DD
+ jv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv+uo33/CwUo/wYAJf8BAJ//AADG/wAA
+ xv8AAMb/AADG/wAAxv8BAKv/BgAp/wYAJf8/NT3/rJtu/62cb/+tnG//rZxv/62cb/+tnG//rZxv/62c
+ b/+tnG//rZxv/62cb/+tnG//rZxv/62cb/+yoXP/tKN1/7Sjdf+0o3X/t6d4/72tff/Cs4H/x7iF/8y/
+ i//OwYz/z8KN/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0cSP//Pr
+ zP/17tD/z8KO/454U/9+Zkb/g2xM/04+J/8jGg3/IBUh/xIAbv8LAI7/AADG/wAAxv8AAMb/AADD/wQA
+ Sv8GACX/DRQ6yCltlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAACxA/eAYAJv8HADH/AwCy/wAAxv8AAMb/AADG/wAAxv8KAJX/EgBw/x0RL/8jGg3/PzEe/7yt
+ kf+Qe1v/loFa/9jMnf/89dz/1cmY/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Pwo3/zL+K/8S2
+ g/+9rn3/taV2/31vWv8IAib/BgAr/wEAq/8AAMb/AADG/wAAxv8AAMb/AADG/wIAjf8GACb/BgAl/0c4
+ Nv9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35m
+ Rf9+ZkX/fmZF/35mRf9+ZkX/f2dG/4FqSP+DbEr/h3BN/496VP+ZhV3/p5Rp/7emeP/DtIL/zcCM/9DD
+ jv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/5964//v02v/Qw5D/jHZR/5J9Xv+Bakr/PTAd/yMa
+ Df8dEDH/EgBw/wcApf8AAMb/AADG/wAAxv8BAKf/BgAq/wYAJf8OGj9sAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATJmUlCAUu+gYAJv8CAJT/AADG/wAA
+ xv8AAMb/AADG/wUArv8SAHH/GQpI/yMaDf8tIhP/jHhb/52Jav+MdVH/08aW//v02v/VyZf/0MOO/9DD
+ jv/Qw47/zsGN/8O0gv+xoHL/oY1k/5J9V/+Gb0z/gmpI/35nRf9+ZkX/Szs3/wYAJf8FADL/AQC2/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AwBk/wYAJf8IAib/Y08+/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35m
+ Rf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35m
+ Rf9+ZkX/fmZF/35mRf9+ZkX/fmZF/4FpSP+IcU7/mIRc/6iVav+6qnr/y76K/9DDjv/Qw47/0MOO/9DD
+ jv/m3bb/+fLX/8q9i/+EbEr/o5By/3ljRP8rIRL/IxoO/xgKSv8RAHT/AgC7/wAAxv8AAMb/AADG/wMA
+ dP8GACX/BwUq/BUwVhwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAACFOqAEKDDq/BgAl/wQAY/8AAMX/AADG/wAAxv8AAMb/AQDB/w8AfP8UBGD/IhkR/yQb
+ Dv9kUTb/jnhY/4FpR//BsoH/6+K//9bKmf/Qw47/yLmH/6WSZ/+Nd1L/gmpI/35mRf9+ZkX/fmZF/35m
+ Rf9+ZkX/fmZF/35mRf84KzL/BgAl/wUAN/8AAL7/AADG/wAAxv8AAMb/AADG/wAAuf8FADf/BgAl/xgP
+ Kv97ZET/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35m
+ Rf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35m
+ Rf9+ZkX/fmZF/39nRv+KdFD/p5Rp/8e5hv/Qw47/0MOO/+rhvP/w6Mf/tqZ3/39nRv+EbUz/YU4z/yQb
+ Df8iGBP/FANj/w0Ahv8AAMX/AADG/wAAxv8AAML/BQBB/wYAJf8LDzXCIE92AQAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgHMUgGACX/BQAz/wEA
+ tv8AAMb/AADG/wAAxv8AAMb/CQCZ/xIBbv8fFCP/IxoN/0Y3I/9+ZkX/fmZF/6KPZf/RxI//0MOO/8W3
+ hf+MdlL/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fWVF/yMZLf8GACX/BQBC/wAA
+ wv8AAMb/AADG/wAAxv8AAMb/AwB5/wYAJf8GACX/RDU1/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35m
+ Rf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35m
+ Rf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/iHJO/7en
+ eP/Qw47/3tOn/9rOof+Xglv/fmZF/35mRf9DNSH/IxoN/x8TJv8SAG//BgCo/wAAxv8AAMb/AADG/wEA
+ n/8GACj/BgEm/xEkSWcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAADBNEDwcELPYGACX/AgCF/wAAxv8AAMb/AADG/wAAxv8DALn/EQB0/xoM
+ Qv8jGg3/KyES/3dgQf9+ZkX/hm9M/8q8if/Pwo7/nYph/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35m
+ Rf9+ZkX/fmZF/35mRf93YEP/Ewso/wYAJf8FAD//AADC/wAAxv8AAMb/AADG/wEAnP8FADH/BgAl/xcP
+ Kf9xW0L/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35m
+ Rf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35m
+ Rf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/g2tJ/7qre//Qw47/u6x8/4BpR/9+ZkX/dF4//ykf
+ Ef8jGg3/GQtG/w4Agf8BAMP/AADG/wAAxv8AAMX/AwBl/wYAJf8JCS7rIVR7FQAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQo1vgYA
+ Jf8EAEv/AADE/wAAxv8AAMb/AADG/wAAxf8MAI3/FANi/yIZEP8jGg3/W0kw/35mRf9+ZkX/qZdr/8W2
+ hP+Ca0n/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/3ReQv8OByf/BgAl/wYA
+ L/8CAIT/AgCV/wIAlP8DAHD/BgAw/wYAJf8PCCf/ZFA+/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35m
+ Rf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35m
+ Rf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35m
+ Rf9+ZkX/jXdS/8u+iv+Uf1n/fmZF/35mRf9XRS3/IxoN/yIZEv8UA2b/BwCj/wAAxv8AAMb/AADG/wAA
+ uP8FADb/BgAl/woLMJIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARIFpeBgEn/wYAKf8BAKX/AADG/wAAxv8AAMb/AADG/wQA
+ tP8RAHP/HhIr/yMaDf85LBr/fGRE/35mRf+AaEf/jHZR/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35m
+ Rf9+ZkX/fmZF/35mRf9+ZkX/fWVF/zgrMv8HASX/BgAl/wYAJf8GACX/BgAl/wYAJf8GACX/EAkn/15L
+ PP9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35m
+ Rf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35m
+ Rf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/jnhT/39nRv9+ZkX/e2ND/zUp
+ GP8jGg3/HRAx/w8Ae/8BAL//AADG/wAAxv8AAMb/AgCJ/wYAJv8HAif/ECBGPwAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABk7
+ hw8IBi/dBgAl/wMAZ/8AAMX/AADG/wAAxv8AAMb/AADF/wwAjv8XB1P/IxoP/yYcD/9oVDj/fmZF/35m
+ Rf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/emJE/0s7
+ N/8ZECr/DQYn/woEJv8KBCb/Egoo/zIlMP9oVD//fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35m
+ Rf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35m
+ Rf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35m
+ Rf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9lUTb/JRwO/yIZEP8WBlf/BwCh/wAAxv8AAMb/AADG/wAA
+ xf8EAE3/BgAl/wsRNtgcRWsHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkKMHQGACX/BQAz/wEAtP8AAMb/AADG/wAA
+ xv8AAMb/AwC4/xIBcP8fFCH/IxoN/0I0IP9+ZkX/f2dG/39nRv9/Z0b/f2dG/39nRv9/Z0b/f2dG/39n
+ Rv9/Z0b/f2dG/39nRv9/Z0b/f2dG/39nRv9/Z0b/f2dG/3tkRf9uWEH/aVRA/2pVQP92X0P/f2dG/39n
+ Rv9/Z0b/f2dG/39nRv9/Z0b/f2dG/39nRv9/Z0b/f2dG/39nRv9/Z0b/f2dG/39nRv9/Z0b/f2dG/39n
+ Rv9/Z0b/f2dG/39nRv9/Z0b/f2dG/39nRv9/Z0b/f2dG/39nRv9/Z0b/f2dG/39nRv9/Z0b/f2dG/39n
+ Rv9/Z0b/f2dG/39nRv9/Z0b/f2dG/39nRv9/Z0b/f2dG/39nRv9/Z0b/f2dG/39nRv9/Z0b/fWVF/z4w
+ Hf8jGg3/HxMl/w8BfP8BAMH/AADG/wAAxv8AAMb/AQCl/wYAKv8GACX/EiRJdQAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAESFHHggHLPgGACX/AgCA/wAAxv8AAMb/AADG/wAAxv8AAMb/CACd/xgKSv8jGg7/Jx4Q/29b
+ Pf+Ca0n/gmtJ/4JrSf+Ca0n/gmtJ/4JrSf+Ca0n/gmtJ/4JrSf+Ca0n/gmtJ/4JrSf+Ca0n/gmtJ/4Jr
+ Sf+Ca0n/gmtJ/4JrSf+Ca0n/gmtJ/4JrSf+Ca0n/gmtJ/4JrSf+Ca0n/gmtJ/4JrSf+Ca0n/gmtJ/4Jr
+ Sf+Ca0n/gmtJ/4JrSf+Ca0n/gmtJ/4JrSf+Ca0n/gmtJ/4JrSf+Ca0n/gmtJ/4JrSf+Ca0n/gmtJ/4Jr
+ Sf+Ca0n/gmtJ/4JrSf+Ca0n/gmtJ/4JrSf+Ca0n/gmtJ/4JrSf+Ca0n/gmtJ/4JrSf+Ca0n/gmtJ/4Jr
+ Sf+Ca0n/gmtJ/4JrSf+Ca0n/gmtJ/4JrSf9sWDv/Jh0P/yMaDv8XCVD/BgCp/wAAxv8AAMb/AADG/wAA
+ xf8DAGf/BgAl/wgHLPQWM1kFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADRU6tgYAJf8FAD//AADA/wAA
+ xv8AAMb/AADG/wAAxv8BAMH/DwF8/yAWHv8jGg3/Rjgj/4VuS/+Gb0z/hm9M/4ZvTP+Gb0z/hm9M/4Zv
+ TP+Gb0z/hm9M/4ZvTP+Gb0z/hm9M/4ZvTP+Gb0z/hm9M/4ZvTP+Gb0z/hm9M/4ZvTP+Gb0z/hm9M/4Zv
+ TP+Gb0z/hm9M/4ZvTP+Gb0z/hm9M/4ZvTP+Gb0z/hm9M/4ZvTP+Gb0z/hm9M/4ZvTP+Gb0z/hm9M/4Zv
+ TP+Gb0z/hm9M/4ZvTP+Gb0z/hm9M/4ZvTP+Gb0z/hm9M/4ZvTP+Gb0z/hm9M/4ZvTP+Gb0z/hm9M/4Zv
+ TP+Gb0z/hm9M/4ZvTP+Gb0z/hm9M/4ZvTP+Gb0z/hm9M/4ZvTP+Gb0z/hm9M/4ZvTP+Gb0z/hW5L/0I0
+ IP8jGg3/HxQi/w4Bg/8BAMP/AADG/wAAxv8AAMb/AAC2/wUANf8GACX/CQgukQAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAXNltABgEm/wYAJ/8CAI3/AADG/wAAxv8AAMb/AADG/wAAxv8EALD/GApM/yMa
+ Dv8mHA//b1w+/4pzT/+Kc0//inNP/4pzT/+Kc0//inNP/4pzT/+Kc0//inNP/4pzT/+Kc0//inNP/4pz
+ T/+Kc0//inNP/4pzT/+Kc0//inNP/4pzT/+Kc0//inNP/4pzT/+Kc0//inNP/4pzT/+Kc0//inNP/4pz
+ T/+Kc0//inNP/4pzT/+Kc0//inNP/4pzT/+Kc0//inNP/4pzT/+Kc0//inNP/4pzT/+Kc0//inNP/4pz
+ T/+Kc0//inNP/4pzT/+Kc0//inNP/4pzT/+Kc0//inNP/4pzT/+Kc0//inNP/4pzT/+Kc0//inNP/4pz
+ T/+Kc0//inNP/4pzT/+Kc0//inNP/4pzT/9pVzr/JRsO/yMaDv8XCVD/BACz/wAAxv8AAMb/AADG/wAA
+ xv8CAIH/BgAl/wgFKvwUKk84AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJCC2/BgAl/wUA
+ S/8AAMH/AADG/wAAxv8AAMb/AADG/wAAxf8MAoz/IBYb/yMaDf8+Mh//inVR/454U/+OeFP/jnhT/454
+ U/+OeFP/jnhT/454U/+OeFP/jnhT/454U/+OeFP/jnhT/454U/+OeFP/jnhT/454U/+OeFP/jnhT/454
+ U/+OeFP/jnhT/454U/+OeFP/jnhT/454U/+OeFP/jnhT/454U/+OeFP/jnhT/454U/+OeFP/jnhT/454
+ U/+OeFP/jnhT/454U/+OeFP/jnhT/454U/+OeFP/jnhT/454U/+OeFP/jnhT/454U/+OeFP/jnhT/454
+ U/+OeFP/jnhT/454U/+OeFP/jnhT/454U/+OeFP/jnhT/454U/+OeFP/lH9Y/495VP+OeFP/iHNP/zkt
+ G/8jGg3/IBYc/wsCjf8AAMX/AADG/wAAxv8AAMb/AADA/wUAQ/8GACX/DBM4wSdjiQMAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8cQVgHAif/BgAo/wIAmP8AAMb/AADG/wAAxv8AAMb/AADG/wEA
+ vv8WClb/IxoO/yUcDv9tXD7/kXxW/6KPZf+hjmX/lH9Z/5F8Vv+RfFb/kXxW/5F8Vv+RfFb/kXxW/5F8
+ Vv+RfFb/kXxW/5F8Vv+RfFb/kXxW/5F8Vv+RfFb/kXxW/5F8Vv+RfFb/kXxW/5F8Vv+RfFb/kXxW/5F8
+ Vv+RfFb/kXxW/5F8Vv+RfFb/kXxW/5F8Vv+RfFb/kXxW/5F8Vv+RfFb/kXxW/5F8Vv+RfFb/kXxW/5F8
+ Vv+RfFb/kXxW/5F8Vv+RfFb/kXxW/5F8Vv+RfFb/kXxW/5F8Vv+RfFb/kXxW/5F8Vv+RfFb/kXxW/5F8
+ Vv+VgFr/ppNo/7ysfP+1pXb/kn1X/5F8Vv9lVDn/JBsO/yMaDv8WClT/AgC9/wAAxv8AAMb/AADG/wAA
+ xv8CAJH/BgAn/wYAJf8PG0BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHktxCgsQ
+ NtkGACX/BABS/wAAw/8AAMb/AADG/wAAxv8AAMb/AADG/wYBpv8fFCX/IxoN/zgsG/+Qe1b/moZd/8Kz
+ gf/GuIX/tKR1/6OQZv+ZhV3/loFa/5WBWv+VgFr/lYBa/5WAWv+VgFr/lYBa/5WAWv+VgFr/lYBa/5WA
+ Wv+VgFr/lYBa/5WAWv+VgFr/lYBa/5WAWv+VgFr/lYBa/5WAWv+VgFr/lYBa/5WAWv+VgFr/lYBa/5WA
+ Wv+VgFr/lYBa/5WAWv+VgFr/lYBa/5WAWv+VgFr/lYBa/5WAWv+VgFr/lYBa/5WAWv+VgFr/lYBa/5WA
+ Wv+VgFr/lYBa/5WAWv+VgFr/lYFa/5iDXP+ij2X/tqZ3/8i6h//Qw47/yryI/52JYP+VgFr/i3hT/zEm
+ Fv8jGg3/HxQk/wcBof8AAMb/AADG/wAAxv8AAMb/AADD/wQAT/8GACX/CQkw1hMmTAQAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBI3UQYAJf8GACj/AQCZ/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADF/xAHdP8jGg//IxoN/2JTOP+ZhV3/oo9l/8q8if/Qw47/0MOO/8q8iP+9rn3/rZxv/6CN
+ Y/+ahl7/mYVd/5mFXf+ZhV3/mYVd/5mFXf+ZhV3/mYVd/5mFXf+ZhV3/mYVd/5mFXf+ZhV3/mYVd/5mF
+ Xf+ZhV3/mYVd/5mFXf+ZhV3/mYVd/5mFXf+ZhV3/mYVd/5mFXf+ZhV3/mYVd/5mFXf+ZhV3/mYVd/5mF
+ Xf+ZhV3/mYVd/5mFXf+ZhV3/mYVd/5mFXf+ZhV3/mYVd/5mFXf+ZhV3/moVe/6GOZP+zonT/xreF/8/C
+ jf/Qw47/0sWS/8/Cjf+unHD/mYVd/5iEXf9YSTH/IxoN/yMaDv8RB27/AADE/wAAxv8AAMb/AADG/wAA
+ xv8BAKD/BgAp/wYBJv8OF0t4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAULFIFCgwx3gYAJf8EAFD/AADC/wAAxv8AAMb/AADG/wAAxv8AAMb/AgC7/xsROf8jGg3/LyUW/416
+ Vf+diWD/q5pt/83Ai//Qw47/0MOO/9DDjv/Qw47/zb+L/8S1g/+3p3j/rJpu/6ORZ/+fjGL/nYlh/52J
+ Yf+diWD/nYlg/52JYP+diWD/nYlg/52JYP+diWD/nYlg/52JYP+diWD/nYlg/52JYP+diWD/nYlg/52J
+ YP+diWD/nYlg/52JYP+diWD/nYlg/52JYP+diWD/nYlg/52JYP+diWD/nYlg/52JYP+diWD/nYlh/56L
+ Yv+ij2X/qJZr/7Khc//Cs4L/zcCL/9DDjv/Qw47/0MOO/9/Uqf/k2rH/wLB//56LYv+diWD/hHJP/yoh
+ Ev8jGg3/GxE1/wMAuP8AAMb/AADG/wAAxv8AAMb/AADD/wQAWv8GACX/CAQs1RYxdw8AAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARIkdqBgEm/wYAKP8CAJf/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/CQSX/yEYF/8jGg3/U0Yu/5+MY/+hjmT/tKN1/8/Cjv/bz6H/0cSQ/9DD
+ jv/Qw47/0MOO/9DDjv/Qw47/zsGM/8m7iP/Cs4L/u6t7/7ald/+xoHL/rZtv/6iWav+mk2j/pJFm/6KP
+ Zf+ij2X/oY5k/6GNZP+hjWT/oY1k/6GNZP+hjWT/oY1k/6GNZP+hjWT/oY1k/6GNZP+hjWT/oY1k/6GN
+ ZP+hjWT/oY5k/6OQZv+mlGj/q5lt/7Wkdf+/sH//x7mG/82/i//Qw47/0MOO/9DDjv/Qw47/0MOO/9DD
+ jv/az6H/+vPZ/9rOov+olWr/oY1k/52KYf9GOiX/IxoN/yEYGv8IBJv/AADG/wAAxv8AAMb/AADG/wAA
+ xv8BAKf/BgAs/wYAJf8KDjt3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAACNYfgkKCzDNBgAl/wUASP8AAL7/AADG/wAAxv8AAMb/AADG/wAAxv8AAMP/Egxl/yMa
+ Df8mHQ//gXFP/6SRZ/+kkmf/uqp7/93Spv/l27T/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DD
+ jv/Qw47/0MOO/9DDjv/Qw47/0MKO/87AjP/Lvor/ybyI/8m8iP/Iuof/x7mG/8a3hf/EtYP/w7SD/8Cx
+ gP/AsYD/wLGA/8CxgP/AsYD/wLGA/8CxgP/AsYD/wbKB/8S1g//Iuof/yr2J/87BjP/Qw47/0MOO/9DD
+ jv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/08aT//fv0//n3rj/t6d5/6SRZ/+kkWf/cWJD/yQb
+ Dv8jGg3/EQ1q/wAAxf8AAMb/AADG/wAAxv8AAMb/AADG/wQAYf8GACX/CAYv8g4ZUBEAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0VOk0HAif/BgAn/wIA
+ if8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8CAbz/HBUv/yMaDf8+MyD/oZBm/6iWav+pl2z/wbKB/+PZ
+ sf/k2rL/0cSQ/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DD
+ jv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DD
+ jv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9XI
+ l//d0qX/2Myc/8W2hP+qmGz/qJZq/5mIYP8zKBj/IxoN/xwVMP8CAbz/AADG/wAAxv8AAMb/AADG/wAA
+ xv8BAKv/BgAs/wYAJf8NFUiSAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAHERpBwwUOc0GACX/BQA7/wAAu/8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8KB5H/IhkT/yQbDf9hVDn/q5lt/6yabv+unG//xbaE/+XbtP/v58b/2Myd/9DDjv/Qw47/0MOO/9DD
+ jv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DD
+ jv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DD
+ jv/Qw47/0MOO/9DDjv/Qw47/0sWS/9bKmf/l27P/+vTa//fw1P/NwI3/sJ9y/6yabv+pmGz/VUgw/yMa
+ Df8iGRT/CQeU/wAAxv8AAMb/AADG/wAAxv8AAMb/AADD/wQAXv8GACX/BwMq6hImZQ8AAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFi9VOQcC
+ J/cGACb/AwB0/wAAxf8AAMb/AADG/wAAxv8AAMb/AADG/wEAw/8XEU3/IxoN/yogEv+LfFf/sJ5x/7Ce
+ cf+xoHP/v7B//9jMn//o37n/4das/9HEkP/h163/6uG8/9vPof/Qw47/0MOO/9PHlP/Xy5v/3NGj/9/U
+ qP/h163/49mx/93Spv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DD
+ jv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/RxI//3NCj/+HXrf/Wypj/0MOO/+DWq//38NX//Pbd//33
+ 3v/w6Mj/0MOV/7emd/+wnnH/sJ5x/4N0Uf8nHhD/IxoO/xQPWf8AAMP/AADG/wAAxv8AAMb/AADG/wAA
+ xv8BAKP/BgAs/wYAJv8JCzd8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACg0ymQYAJf8GADH/AQCp/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wQDsP8eFiX/IxoN/zkuHf+hkWf/tKN1/7Sjdf+0o3X/uKh4/8O0gv/KvIn/zcCM/9DE
+ kf/Tx5b/0cSS/87BjP/PwY3/1MeW/9bKmv/Wypr/1sqa/9bKmv/VyZr/0cSR/87BjP/OwYz/zsGM/87B
+ jP/OwYz/zsGM/87BjP/OwYz/zsGM/87BjP/OwYz/zsGM/87BjP/OwYz/zsGM/87BjP/OwYz/zsGM/8/C
+ jv/azqH/29Ck/9PGlf/OwYz/0cSS/9rOo//ZzaP/zsCU/7ytff+0o3X/tKN1/7OidP95a0v/MCYW/yMa
+ Df8dFS7/AwK3/wAAxv8AAMb/AADG/wAAxv8AAMb/AADE/wQAVv8GACX/CQkz7BIkYRMAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAZOmAjCgsw7gYAJf8EAFX/AADD/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wkHlf8hGRf/IxoN/zwx
+ H/+WiGD/t6d4/7eneP+3p3j/t6d4/7ioef+5qXr/uap6/7qre/+6q3v/uqt7/7qre/+6q3v/uqt7/7qr
+ e/+6q3v/uqt7/7qre/+6q3v/uqt7/7qre/+6q3v/uqt7/7qre/+6q3v/uqt7/7qre/+6q3v/uqt7/7qr
+ e/+6q3v/uqt7/7qre/+6q3v/uqt7/7qre/+6q3v/uqt7/7qre/+6q3v/uqt7/7qre/+6qnv/ual5/7io
+ ef+3p3j/t6d4/7eneP+2pnf/kYJc/y8lFf8jGg3/IBgc/wcFnv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8CAJT/BgAo/wYAJf8KDTtbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOGj9bBgAl/wYAJ/8CAIr/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADF/w0Kf/8iGRT/IxoN/ysiE/9mWj3/iHtW/5mLY/+ll2v/qptv/66f
+ cv+xonT/tKR2/7eoeP+3qHj/uqp6/7use/+7rHv/u6x7/7use/+7rHv/u6x7/7use/+7rHv/u6x7/7us
+ e/+7rHv/u6x7/7use/+7rHv/u6x7/7use/+7rHv/u6x7/7use/+7rHv/u6x7/7use/+7rHv/u6x7/7us
+ e/+7q3v/t6h5/7Okdf+zo3X/sqN1/6+fcv+tnnH/qptv/6WXa/+ajGP/jH5Z/19UOf8rIhP/IxoN/yEY
+ Gf8LCIz/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AAC+/wUARv8GACX/CAYv1woOPAEAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAABEkSQEKDTLBBgAl/wUANv8BALH/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADF/wwJ
+ h/8gFx//IxoN/yMaDf8jGg3/JBoN/yQbDv8kGw7/KyIT/zEnF/82LBv/PDIf/zwyH/9COCP/Rjwn/1FH
+ L/9SRy//Ukcv/1JHL/9SRy//Ukcv/1JHL/9SRy//VEkw/19UOP9gVTn/YFU5/2BVOf9fVDn/VEkx/1JH
+ L/9SRy//Ukcv/1JHL/9SRy//SD0o/0Q6Jf9EOiX/RDol/0Q6Jf89MyD/NSsa/zQqGv8zKhn/LCMU/ykg
+ Ef8kGw7/JBsO/yQbDf8jGg3/IxoN/yMaDf8eFib/CQaX/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8CAH7/BgAm/wcCKf0OG1JVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABc0Wi0ICC3wBgAl/wQA
+ Wv8AAMH/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wUEq/8VEFX/HhYn/yEYGv8iGRP/IxoP/yMa
+ Dv8jGg3/IxoN/yMaDf8jGg3/IxoN/yMaDf8jGg3/IxoN/yMaDf8jGg3/IxoN/yMaDf8jGg3/IxoN/yMa
+ Df8jGg3/IxoN/yMaDf8jGg3/IxoN/yMaDf8jGg3/IxoN/yMaDf8jGg3/IxoN/yMaDf8jGg3/IxoN/yMa
+ Df8jGg3/IxoN/yMaDf8jGg3/IxoN/yMaDf8jGg3/IxoN/yMaDv8jGg//IhkT/yEYGP8eFyX/Eg1l/wMC
+ tP8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AQCt/wUAM/8GACX/Cgs4sxo7iAMAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsQNVYHAif/BgAn/wIAg/8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AwK1/wYEp/8IBpv/CQeV/wwJhv8OC3n/Dwt2/xENaf8SDWj/FA9b/xUP
+ V/8XEUv/FxFK/xcRSv8XEUr/FxFK/xcRSv8XEUr/FxFK/xcRSv8ZEz//GhM9/xoTPf8aEz3/GhM+/xcR
+ SP8XEUr/FxFK/xcRSv8XEUr/FxFK/xUQVf8VD1j/FQ9Y/xUPWP8VD1n/Eg1n/w8Ld/8OC3j/Dgt4/wwJ
+ gv8LCIr/CQaY/wgGnP8GBKb/BAOz/wAAxP8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ w/8EAFr/BgAl/wcELPIKDDocAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHkhuAw4Z
+ PrMGACX/BQAw/wEAoP8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxP8AAMT/AADE/wAAxP8AAMT/AADE/wAA
+ xP8AAMT/AADE/wEAw/8BAMP/AQDD/wEAw/8BAMP/AADE/wAAxP8AAMT/AADE/wAAxP8AAMT/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AgCK/wYAKP8GACb/DRVJgwAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHkpwFwkJL98GACX/BQA9/wEAsv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wEA
+ r/8FADf/BgAl/wkIM8cYNX4IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAEihPSwcFLPoGACX/BABM/wAAvP8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMH/BABT/wYAJf8IBS33EB1VNwAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiVKkBDxtSfgYBJ/8GACX/BABc/wAA
+ v/8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADF/wMA
+ ev8GACb/BgAm/w4YTX4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAACw89lgYBJv8GACb/AwBo/wAAwf8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8BAJn/BgAs/wYAJf8JCjW2AAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQHlYECAcxmAYA
+ Jf8GACf/AwBv/wAAw/8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AQCq/wUA
+ OP8GACX/CQk05RMrbSMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIBi8BCgs4twYAJf8GACf/AwBt/wAAwf8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAvP8FAEf/BgAl/wcDKvEOFkk1AAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAATJ2cSCgw5xwYAJf8GACf/AwBo/wAAwP8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAL//AwBe/wYA
+ Jv8HAin6DhpPYwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASJWQSCw49xgYAJf8GACb/BABd/wAA
+ uv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADA/wMAYf8GACb/BwMp/g0URmwAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAQIFoDCAYumQYBJv8GACX/BABI/wEAq/8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAvf8DAF//BgAl/wYB
+ J/0LDj1wESJeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQHlYEDBJCkwYB
+ J/8GACX/BQA2/wIAkv8AAMX/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AALj/BABV/wYAJv8HAij+Cg48dAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBFCZgcELPcGACX/BgAp/wMAav8AAL3/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AQCn/wUARP8GACX/BwQr/AwT
+ RGoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAADxtSPAgHMN4GACX/BgAm/wQAS/8BAKP/AADF/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADD/wIAi/8FADT/BgAl/wcDKu8PGlBdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADhdLCwsQQKAHAyv9BgAl/wYA
+ Lv8DAGz/AQC1/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wEAtP8EAGH/BgAo/wYAJf8JCjXkDxtSNAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAFS1xAQ0WSjwIBS7SBgEm/wYAJf8FADb/AgB7/wAAt/8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ vv8CAIb/BQA2/wYAJf8HAij9CAcxnA4bUhcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABcy
+ ehEOGE2DCAcw9gYAJf8GACb/BQA4/wMAcf8BALD/AADF/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAwf8CAI3/BQBG/wYAJ/8GACX/CAcx8g4ZT2YZN4ECAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABxBkgANFkoOCw48mAgGL/cGACX/BgAl/wYA
+ K/8EAFb/AgCN/wAAtv8AAMP/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADD/wEAsf8CAID/BQBA/wYA
+ Jv8GACX/CAcs+QkLMYMSJmEaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAgTacBFSxvLAwRQYwIBS7nBgAm/wYAJf8GACb/BQA0/wQAU/8CAH//AQCm/wAA
+ wP8AAMT/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMT/AAC2/wIAj/8EAFb/BQAw/wYAJf8GACX/BwMo+wsPNKMWM1k0J2aMAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAOGE1YCw8+uwgGL/oGACb/BgAl/wYAJf8GACn/BQA0/wQATP8DAHD/AgCH/wIAm/8BAKj/AACy/wAA
+ tP8AALT/AAC0/wAAtP8BALD/AQCn/wIAl/8CAIL/AwBp/wUAR/8GAC7/BgAm/wYAJf8GASb/Cgwx7A8b
+ QZMRIkgdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYNn8AEiRhHQoNOl8JCDKsCAYv5wYB
+ JvEGACX/BgAl/wYAJf8GACX/BgAn/wYAKv8GAC3/BgAt/wYALf8GAC3/BgAt/wYALP8GACr/BgAm/wYA
+ Jf8GACX/BgAl/wYBJv8HAifvCQkutRAfRFwXNlsOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFzNZFBImTGsPHUKnDBE33wkLMPsHAyj/BwIn/wYB
+ Jv8GASb/BgEm/wYBJv8GASb/BwEm/wcDKP8HBCn/CxA27gsQNcINFTuHFS9UVhxDaAoAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAgUXcFEyhNCgoNMgsOGT4TFCtQTRQrUE4UK1BOFCtQThQrUE4YNlxDHENpHQsS
+ NwsbQmgIK22VAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//////////////////4P/////////wAAAAf/////B////////w
+ AAAAAAB////4P//////wAAAAAAAAAD///B//////AAAAAAAAAAAD//8P////wAAAAAAAAAAAAA//h///
+ /AAAAAAAAAAAAAAA/8P//+AAAAAAAAAAAAAAAB/h//4AAAAAAAAAAAAAAAAA8P/4AAAAAAAAAAAAAAAA
+ ADj/gAAAAAAAAAAAAAAAAAA4/gAAAAAAAAAAAAAAAAAAPPwAAAAAAAAAAAAAAAAAAD78AAAAAAAAAAAA
+ AAAAAAA//AAAAAAAAAAAAAAAAAAAP/wAAAAAAAAAAAAAAAAAAD/8AAAAAAAAAAAAAAAAAAA//AAAAAAA
+ AAAAAAAAAAAAP/wAAAAAAAAAAAAAAAAAAD/8AAAAAAAAAAAAAAAAAAA//AAAAAAAAAAAAAAAAAAAP/wA
+ AAAAAAAAAAAAAAAAAD/8AAAAAAAAAAAAAAAAAAA//AAAAAAAAAAAAAAAAAAAP/wAAAAAAAAAAAAAAAAA
+ AD/8AAAAAAAAAAAAAAAAAAA//AAAAAAAAAAAAAAAAAAAP/wAAAAAAAAAAAAAAAAAAD/8AAAAAAAAAAAA
+ AAAAAAA//AAAAAAAAAAAAAAAAAAAP/wAAAAAAAAAAAAAAAAAAD/8AAAAAAAAAAAAAAAAAAA//AAAAAAA
+ AAAAAAAAAAAAP/wAAAAAAAAAAAAAAAAAAD/8AAAAAAAAAAAAAAAAAAA//AAAAAAAAAAAAAAAAAAAP/wA
+ AAAAAAAAAAAAAAAAAD/8AAAAAAAAAAAAAAAAAAA//AAAAAAAAAAAAAAAAAAAP/wAAAAAAAAAAAAAAAAA
+ AD/8AAAAAAAAAAAAAAAAAAB//AAAAAAAAAAAAAAAAAAAf/wAAAAAAAAAAAAAAAAAAH/+AAAAAAAAAAAA
+ AAAAAAB//gAAAAAAAAAAAAAAAAAAf/4AAAAAAAAAAAAAAAAAAH/+AAAAAAAAAAAAAAAAAAB//gAAAAAA
+ AAAAAAAAAAAA//4AAAAAAAAAAAAAAAAAAP/+AAAAAAAAAAAAAAAAAAD//wAAAAAAAAAAAAAAAAAA//8A
+ AAAAAAAAAAAAAAAAAP//AAAAAAAAAAAAAAAAAAD//wAAAAAAAAAAAAAAAAAB//8AAAAAAAAAAAAAAAAA
+ Af//AAAAAAAAAAAAAAAAAAH//wAAAAAAAAAAAAAAAAAB//+AAAAAAAAAAAAAAAAAAf//gAAAAAAAAAAA
+ AAAAAAH//4AAAAAAAAAAAAAAAAAD//+AAAAAAAAAAAAAAAAAA///gAAAAAAAAAAAAAAAAAP//8AAAAAA
+ AAAAAAAAAAAD///AAAAAAAAAAAAAAAAAB///wAAAAAAAAAAAAAAAAAf//8AAAAAAAAAAAAAAAAAH///g
+ AAAAAAAAAAAAAAAAD///4AAAAAAAAAAAAAAAAA///+AAAAAAAAAAAAAAAAAP///gAAAAAAAAAAAAAAAA
+ D///8AAAAAAAAAAAAAAAAB////AAAAAAAAAAAAAAAAAf///wAAAAAAAAAAAAAAAAH///8AAAAAAAAAAA
+ AAAAAB////gAAAAAAAAAAAAAAAA////4AAAAAAAAAAAAAAAAP///+AAAAAAAAAAAAAAAAD////wAAAAA
+ AAAAAAAAAAB////8AAAAAAAAAAAAAAAAf////AAAAAAAAAAAAAAAAH////4AAAAAAAAAAAAAAAD////+
+ AAAAAAAAAAAAAAAA/////wAAAAAAAAAAAAAAAf////8AAAAAAAAAAAAAAAH/////AAAAAAAAAAAAAAAB
+ /////4AAAAAAAAAAAAAAA/////+AAAAAAAAAAAAAAAP/////wAAAAAAAAAAAAAAH/////8AAAAAAAAAA
+ AAAAB//////gAAAAAAAAAAAAAAf/////4AAAAAAAAAAAAAAP/////+AAAAAAAAAAAAAAD//////wAAAA
+ AAAAAAAAAB//////8AAAAAAAAAAAAAAf//////gAAAAAAAAAAAAAP//////4AAAAAAAAAAAAAD//////
+ /AAAAAAAAAAAAAB///////wAAAAAAAAAAAAAf//////+AAAAAAAAAAAAAP///////wAAAAAAAAAAAAD/
+ //////8AAAAAAAAAAAAB////////gAAAAAAAAAAAAf///////4AAAAAAAAAAAAP////////AAAAAAAAA
+ AAAD////////4AAAAAAAAAAAB////////+AAAAAAAAAAAA/////////wAAAAAAAAAAAP////////+AAA
+ AAAAAAAAH/////////gAAAAAAAAAAD/////////+AAAAAAAAAAB//////////gAAAAAAAAAAf///////
+ //8AAAAAAAAAAP//////////gAAAAAAAAAH//////////8AAAAAAAAAD///////////gAAAAAAAAA///
+ ////////8AAAAAAAAA////////////wAAAAAAAAf///////////+AAAAAAAAP////////////wAAAAAA
+ AH////////////+AAAAAAAD/////////////4AAAAAAB//////////////AAAAAAB//////////////8
+ AAAAAA///////////////8AAAAB////////////////gAAAB/////////////////wAAD///////////
+ ///////gAH//////////////////////////////KAAAAEAAAACAAAAAAQAgAAAAAAAAQAAAAAAAAAAA
+ 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
+ LV0HAC7/EABi/xIAcP8SAHD/CgCX/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8EAF7/CAAz/woAQP8GACn/BABd/wAAuf8AAMb/AADG/wAAxf8CALr/AADG/wMA
+ uf8BAMP/AADG/wAAxv8AAMH/BQBM/w4AVv8MAH//BwAs/wIAif8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/DACM/xIAcP8SAG//BwAr/wgHMTwAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAHAic6BwAq/w8AYf8SAHD/EgBw/wYAqv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AwB2/wcALf8OAFb/DQBV/wkANv8FADf/AgCW/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMT/BABi/wsARv8NAIn/AwB0/wUARf8AAMT/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wYAp/8SAHD/EQBq/wYB
+ J/8KDDkZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADRc9IQYAJ/8PAF3/EgBw/xIAcf8CALv/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wIAhf8HACn/EQBt/xIA
+ cP8SAG//DQBP/wYAKv8DAGb/AAC9/wAAxv8AAMb/AADG/wAAxv8AAMX/AwBy/wgAMv8OAHz/AgC6/wUA
+ Nv8CAIv/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8CAL7/EgBy/w8AYP8HBCv0Fi9zBgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQsURAHAyj7DgBY/xIA
+ cP8RAHb/AADF/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8CAJH/BgAn/xAAc/8GAKr/CACd/xAAef8NAFP/BgAl/wUAPP8BAJ7/AADG/wAAxv8AAMD/AwBo/wYA
+ Jv8KAFj/AgC9/wIAlv8FADH/AAC+/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/w8Aff8OAFX/CAcx1wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAACQgt6w0AUf8SAHD/DwCA/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AQCb/wYAJf8IAJH/AADG/wAAxv8CALr/CgBK/wUANf8DAGv/BgAq/wMA
+ b/8BAJ7/BQBG/wQAY/8FAD7/AwCB/wAAxv8EAF7/BABg/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8MAI3/DABI/wcELJ4AAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkJLsELAEj/EgBw/w0Aif8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wEApv8GACX/BQCY/wAAxv8AAMb/AADG/wQA
+ Uv8DAGX/AADG/wEApP8EAFL/BQBA/wIAkP8AAMb/BABi/wQAS/8CAJL/BgAt/wEAnv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/CQCa/wkA
+ OP8HAilsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIBSqOCgA+/xIAcP8MAI3/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8BALD/BgAm/wIA
+ mP8AAMb/AADG/wAAxv8EAFD/AwB4/wAAxv8AAMb/AADG/wAAxf8AAMb/AADG/wAAt/8EAE//BQA1/wMA
+ b/8AAMX/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wcAov8HACz/CQgyQwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQkwawgA
+ M/8SAHD/DACO/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AAC6/wYAJ/8CAI7/AADG/wAAxv8AAMb/BABO/wMAcf8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADF/wAAw/8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8FAJ7/BwIo/hEgWxoAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAoMOTwHACr/EgBu/wwAjf8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAv/8GACv/AgCL/wAAxv8AAMb/AADG/wUARv8EAGX/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AgCL/wgFLd0hT6sBAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASJGERBwMp+xAAY/8NAIn/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxf8BAL3/BgAw/wIAi/8AAMb/AADG/wAA
+ xv8FAD//CQBM/w0AiP8NAIf/DgCD/w4AgP8OAIL/DQCH/wwAjP8MAIz/DACN/woAlP8KAJf/CQCb/wcA
+ o/8FAK//AgC8/wAAxf8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wMA
+ Z/8HAyqcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcELM4NAFL/DgCE/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMT/AwC1/wYAqP8JAJv/DACO/w4Agf8QAHj/EgBx/wcA
+ LP8CAIX/AADG/wAAxv8AAMX/BQA6/wsAR/8SAHD/EgBw/xIAcP8SAHD/EgBw/xIAcP8SAHD/EgBw/xIA
+ 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
+ Of8AAMH/AADG/wQAtP8bDj3/PS8d/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf99ZUX/V0U7/zsu
+ M/8/MTT/ZlI//35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35m
+ Rf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf87Lhz/Gg1C/wIAu/8AAMb/AwB4/wkJ
+ LtMcRWsCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAABEhRwcIBivrAgCR/wAAxv8AAMb/DAOJ/yMaEv9vWz3/hG1K/4RtSv+EbUr/hG1K/4Rt
+ Sv+EbUr/hG1K/4RtSv+EbUr/hG1K/4RtSv+EbUr/hG1K/4RtSv+EbUr/hG1K/4RtSv+EbUr/hG1K/4Rt
+ Sv+EbUr/hG1K/4RtSv+EbUr/hG1K/4RtSv+EbUr/hG1K/4RtSv+EbUr/hG1K/4RtSv9tWTz/IxkT/wsC
+ kP8AAMb/AADC/wUAOv8ICC1jAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQswfwUASf8AAMX/AADG/wEAwP8aD0D/PTEe/4t1
+ Uf+MdlH/jHZR/4x2Uf+MdlH/jHZR/4x2Uf+MdlH/jHZR/4x2Uf+MdlH/jHZR/4x2Uf+MdlH/jHZR/4x2
+ Uf+MdlH/jHZR/4x2Uf+MdlH/jHZR/4x2Uf+MdlH/jHZR/4x2Uf+MdlH/jHZR/4x2Uf+MdlH/jHZR/414
+ U/+KdFD/Oi4c/xkPQv8BAMH/AADG/wIAkv8IBSrvFS1SDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAgRhkHBCr1AQCd/wAA
+ xv8AAMb/BwOg/yIZFP9yYEH/qJVq/6iWa/+Yg1z/k35Y/5N+WP+Tflj/k35Y/5N+WP+Tflj/k35Y/5N+
+ WP+Tflj/k35Y/5N+WP+Tflj/k35Y/5N+WP+Tflj/k35Y/5N+WP+Tflj/k35Y/5N+WP+Tflj/k35Y/5N+
+ WP+Xglv/qZdr/7+vf/+ei2L/bFs+/yIZE/8IA57/AADG/wAAxf8FAEv/CActhgAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAACQgtjQQATv8AAMX/AADG/wAAxv8UDF7/Nisa/5mGXv/FtoT/zsGN/8O0gv+yoXT/pZNo/52K
+ Yf+bh1//m4df/5uHX/+bh1//m4df/5uHX/+bh1//m4df/5uHX/+bh1//m4df/5uHX/+bh1//m4df/5uH
+ X/+bh1//n4xj/6qXbP+8rXz/zcCM/9nOn/+3pnf/lYFa/zIoF/8UDVr/AADG/wAAxv8BAKH/BgEo9A8a
+ UCIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAABImTB0HAyjzAgCZ/wAAxv8AAMb/AgG6/x4WJv9mWDz/p5Vq/9DD
+ lP/WyZj/0MOO/9DDjv/OwIz/x7mG/8Kzgf+9rn3/uKl5/7ald/+0o3X/s6J0/7Ggcv+wn3L/sJ9y/7Cf
+ cv+xn3L/s6N0/7ioef/AsX//yryI/8/Cjf/Qw47/0MOO/93Spf/d0qv/pJFn/15QNv8eFif/AgG7/wAA
+ xv8AAMb/BABW/wgGLp4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgwxiAUARP8AAMP/AADG/wAA
+ xv8KB5H/KiAT/5WFXf+xoHL/3NGn/9rPoP/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DD
+ jv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0sWS/+HWrP/e06j/s6J0/5CA
+ Wv8nHRH/CgeS/wAAxv8AAMb/AQCl/wYBKPoNFkooAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYv
+ VQ4HAyjjAgCF/wAAxv8AAMb/AADF/xcRTP9EOSX/rZxw/7ald//PwpT/0saU/9zRpP/SxpP/0cWR/9jM
+ nf/b0KL/2s6g/8/Cjf/Pwo3/z8KN/8/Cjf/Pwo3/z8KN/8/Cjf/Pwo3/z8KN/9XJmP/ZzqD/1MiW/+rg
+ vv/e067/vKx9/6OSaP8/NSH/FhBT/wAAxf8AAMb/AADF/wQAUv8IBzCfAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAADBM5WwYAMv8BALb/AADG/wAAxv8CArn/HRUu/0g9KP+XiWH/q5xw/7Kj
+ df+2p3j/ual6/7ure/+7q3v/u6t7/7ure/+7q3v/u6t7/7ure/+7q3v/u6t7/7ure/+7q3v/u6t7/7ur
+ e/+6qnr/t6d4/7Wmd/+yonT/q5xw/5aIYP9DOST/HBQz/wIBvP8AAMb/AADG/wIAl/8GASj1Cg07FwAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABEkSQAJCS63BABa/wAAxf8AAMb/AADG/wMC
+ tv8XEUv/IRkX/yMaD/8lHA//KyIT/zAmFv8zKhn/OjAe/zsxHv87MR7/OzEe/z40If9BNyP/QTcj/zsx
+ Hv87MR7/OzEe/zUrGv80Khn/MigY/ywiE/8pIBL/JRwO/yMaD/8hGRb/FhBT/wICuv8AAMb/AADG/wAA
+ wP8FAD//CQo1ggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBI3FggF
+ K+wCAIb/AADG/wAAxv8AAMb/AADG/wEBwv8DA7P/BQSq/wcFn/8JB5f/CgiQ/wwJiP8MCYf/DAmH/wwJ
+ h/8MCYT/DQqA/w0KgP8MCYf/DAmH/wwJh/8LCI7/CgiP/woHk/8HBZ//BwWi/wUErP8DA7P/AQHB/wAA
+ xv8AAMb/AADG/wAAxv8DAHT/BwQs3QoMOgcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAANFTtQBgEt/gEAoP8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8BAKL/BgEr/QsOPEIAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIlSpAAoLN4UFADP/AQCs/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AALr/BQA8/wkJNI0AAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQHlYBCAUulAUA
+ Of8BAK7/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMP/BABT/wgF
+ LsMTK20JAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAABMnZwUJCTSnBQA3/wEAp/8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMX/AwBp/wcELNkOGk8ZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAECBaAQgHMIwGAC7/AgCO/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMP/AwBm/wcDKtsLDz0cAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQs3ZgYC
+ KPcDAGb/AAC9/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8BALb/BABR/wgFLdIME0QbAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAALEUErBwQswwUAOf8CAIv/AADC/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxP8CAI7/BgA1/wgGL6UPG1INAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABcyegQKDTtiBwQs4wUAPf8DAH3/AQC0/wAA
+ xf8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADA/wIAkv8FAEj/BwQp3gsO
+ PFwZN4EBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABUt
+ cAsJCjVdCAYwxAYCLP4FAEf/AwBw/wIAkv8BAKz/AAC6/wAAvf8AAL3/AAC5/wEAqf8CAI//AwBm/wUA
+ Nf8JCC3fCQkvbxYzWQ0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAABg2fwAMEkQfCAcwZQkILpwJCi/hBwMp/gYBKf8GACn/BgAp/wYB
+ Kf8IBSr7CAcs0gkJL5MLEDZEFzZbBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ 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
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkJNWYDAHP/AADG/wgAn/8gFSb/gW5R/83A
+ mf/SxZH/vKx8/6WSaP+VgFr/iXJP/xMLLv8AAMD/AADG/wQAYP9LOzf/fmZF/35mRf9+ZkX/fmZF/35m
+ Rf9+ZkX/fmZF/35nRf+AaEf/h3BN/5aCW/+pl2v/xreF/9nNnv/HuZT/eWVI/yAUJ/8HAKX/AAC//wYC
+ NfYQIUcNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkJNAwFAT/4AADD/wIA
+ vv8ZC0f/U0Iq/5+MY/+tm2//fmZF/35mRf9+ZkX/cVtC/wgCL/8AAL3/AQCf/xEJLv90XkL/fmZF/35m
+ Rf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/gmpI/7urff+ahl//UUEp/xgL
+ Tf8BAMH/AgCR/wgHLaUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAIBS+tAgCa/wAAxv8MA4n/MCQa/31mRf+DbEn/fmZF/35mRf9+ZkX/emNE/y8jMP8SCjD/IBYu/2VR
+ Pv9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/35mRf9+ZkX/fmZF/4Rt
+ Sv98ZUT/LiMa/woCkv8AAMb/BABU/wsQNkUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAALDjNDBABX/wAAxv8CAL3/GxA7/2JPNP+Da0n/g2tJ/4NrSf+Da0n/g2tJ/4Nr
+ Sf+AaUn/gmtJ/4NrSf+Da0n/g2tJ/4NrSf+Da0n/g2tJ/4NrSf+Da0n/g2tJ/4NrSf+Da0n/g2tJ/4Nr
+ Sf+Da0n/g2tJ/4NrSf9gTjP/Gg8+/wEAwP8BAK//BwMs1BUuUwQAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYOF4DBwQtzQEAq/8AAMb/CQSW/zEmGv+KdVH/jnlU/413
+ Uv+Nd1L/jXdS/413Uv+Nd1L/jXdS/413Uv+Nd1L/jXdS/413Uv+Nd1L/jXdS/413Uv+Nd1L/jXdS/413
+ Uv+Nd1L/jXdS/413Uv+OeFP/lYBa/4lzUP8wJRn/CQSX/wAAxv8DAG7/CgswbQAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgwxWgQAYf8AAMb/AADF/xcP
+ T/9lVTn/taV2/7emd/+mlGn/moZe/5eDXP+Xg1v/l4Nb/5eDW/+Xg1v/l4Nb/5eDW/+Xg1v/l4Nb/5eD
+ W/+Xg1v/l4Nb/5eDW/+Xg1z/noph/7Cfcv/GuIb/rZxv/2BQNv8XD0z/AADE/wAAvP8GAjXuDRY/EAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEiVKBgcF
+ LtYBAKz/AADG/wQCsv8qISH/l4Ve/8q8jP/TxpT/zb+L/8S2hP+8rHz/t6d4/7Khc/+vnnH/rpxw/6yb
+ bv+smm7/rJpu/6yabv+unXD/tKN1/72uff/HuYb/zsGN/9fLm//Sxp7/kX9Z/ycfIf8EArL/AADG/wMA
+ fP8IBS2EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAsPNFQEAFj/AADF/wAAxv8NCn//UUUu/66dcP/Xy6D/18ub/9PHlP/RxI//0cSP/9LG
+ kv/SxZL/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9LFkf/Ux5T/4das/93Sqf+vnnH/TEAr/w0K
+ gP8AAMb/AAC8/wYBOPENFkoXAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAABo8YgEIBi29AgCX/wAAxv8BAMP/GRNF/3FkRf+1pHb/wrOD/8i6
+ i//GuIb/yLqK/8u9j//Ju4v/xbaE/8W2hP/FtoT/xbaE/8W2hP/FtoT/xbeF/8u+j//Iuov/yr2R/7am
+ eP9nWz7/GBJJ/wAAxP8AAMb/AwB1/wgFLoEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjMnBgI/9QAAvP8AAMb/AgK6/xcR
+ TP80KiD/Qjgk/0tAKv9SRy//WE00/1tPNv9bTzb/XFA2/2BUOf9fUzj/W082/1tPNv9WSzL/VUox/05D
+ LP9JPin/Qjgk/zQqIP8WEVL/AgG8/wAAxv8BALD/BgMz5AsPPw8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgwxawQA
+ X/4AAMX/AADG/wAAxv8BAcH/AwK0/wUEqv8HBaL/CAaa/wkGl/8JBpf/CQeW/woHkv8KB5P/CQaX/wkG
+ l/8IBpz/CAad/wYEp/8FBKz/AwK1/wEBwP8AAMb/AADG/wAAxf8EAFT+CQgzVwAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAFS9VAwgHMK8DAHr/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wIAgv8IBS6sDxtSAQAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsRQQwHAyzDAgCE/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AQCh/wcD
+ Md0MFUgUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAME0YUBwMtxgMAe/8AAMX/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8BAKr/BgI66AsOPCwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACw49DQgF
+ Lq8EAF3/AAC7/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wEAn/8GAjnnCQk0MAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAA8bUgMICDJvBQE98QIAjv8AAMT/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMD/AwB2/wcDMMwKDTsjAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADRZJIAgGL58FAUL3AwB+/wEAr/8AAMT/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AQC1/wMAf/8GAz3wCAcvexEgWwgAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALDjwbCQgzdwcC
+ LMIGAj/xBABZ/wMAbf8DAHL/AwBw/wQAY/8FAkr8BwMvzwoMMXgLEDYTAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAVLlMNDRY7OgkJLk4LDjRdCxA1WgsPNEIPHUIaGTthAgAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAD//wAAP+MAAP/AAAAA8QAA/gAAAAAIAADgAAAAAAAAAMAAAAAAAgAAwAAAAAADAADAAAAAAAMAAMAA
+ AAAAAwAAwAAAAAADAADAAAAAAAMAAMAAAAAAAwAAwAAAAAADAADAAAAAAAMAAMAAAAAAAwAAwAAAAAAD
+ AADAAAAAAAMAAMAAAAAAAwAAwAAAAAADAADAAAAAAAcAAOAAAAAABwAA4AAAAAAHAADgAAAAAAcAAOAA
+ AAAABwAA4AAAAAAHAADgAAAAAA8AAPAAAAAADwAA8AAAAAAPAADwAAAAAB8AAPAAAAAAHwAA+AAAAAAf
+ AAD4AAAAAD8AAPwAAAAAPwAA/AAAAAA/AAD8AAAAAH8AAP4AAAAAfwAA/gAAAAD/AAD/AAAAAP8AAP8A
+ AAAB/wAA/4AAAAH/AAD/wAAAA/8AAP/AAAAD/wAA/+AAAAf/AAD/8AAAD/8AAP/4AAAf/wAA//wAAD//
+ AAD//wAAf/8AAP//wAH//wAA///4B///AAAoAAAAIAAAAEAAAAABACAAAAAAAAAQAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEiZMCgoLMCkKDTJLCQovZQgH
+ LHsIBSuJBwMqlAYAJ5QGACeUBwMskgcDKoMIBS1wCQk0XQcCKTUJCzcXEyprAgAAAAAAAAAAAAAAABIa
+ XAAeL40JGBiAAQAAAAAAAAAAAAAAAAAAAAAAAAAAFS1vAQoNODIIBittCAQvpwoDPtcMAkf8DQBQ/w4A
+ Wv8LAEL/CgA8/woAQf8NAE7/DwBf/xIAbf8SAG7/EQBq/xAAZf8PAF7/DgBV/wwAS/8LA0TvCQI0vggH
+ LY0IByxNESJHEgAAAAAeLY4JFxB7AQAAAAAWMHUBCQgzOgcDLZIJAjfiCgA//w0AU/8RAGz/EgBw/xIA
+ cP8SAHD/EgBt/wkAOf8PAF7/DwBc/wsAR/8HAC7/CQA5/xEAa/8SAHD/EgBw/xIAcP8SAHD/EgBw/xIA
+ cP8SAG3/DgBV/wsARP8JATv6CQUzyA0VOygbIocIAAAAAAgFLm8KAD7/DQBO/w0AUf8PAFz/EgBw/xIA
+ cP8SAHD/EgBw/xIAcP8OAGT/DQBR/wsAkP8KAJf/EQBz/woAPf8MAEj/BABs/wIAvP8EALP/CACg/w8A
+ fv8SAHD/EgBw/xIAcP8SAG//DgBY/w0AUf8LAEb/Cg0yVxgVfwAAAAAABgAldgwATP8NAFH/DgBY/xIA
+ b/8SAHD/EgBw/xIAcv8LAJL/AwC2/wMAfP8LAHz/AADG/wAAxv8HAKT/DgBU/wsASP8KAED/AQCm/wAA
+ xv8AAMb/AADE/wkAnP8SAHL/EgBw/xIAcP8RAGz/DQBT/wwASf8JCS9nAAAAAAAAAAAGACVwDABM/w0A
+ Uv8RAGz/EgBw/xIAcP8OAIX/AwC4/wAAxv8AAMb/BQBf/wMAt/8AAMb/AADG/wAAxf8NAF7/CgBA/w4A
+ V/8IAEn/AQCy/wAAxv8AAMb/AADG/wUAsP8RAHb/EgBw/xIAcP8PAGD/DABK/wkKL3QAAAAAAAAAAAYA
+ JVcMAEv/DwBd/xIAcP8SAHD/CgCX/wAAxf8AAMb/AADG/wAAxf8EAGX/AADG/wAAxv8AAMb/AQDD/w0A
+ Vf8JADb/CQA6/w4AV/8GAFP/AADG/wAAxv8AAMb/AADG/wQAsv8SAHL/EgBw/xEAbf8MAEv/CQovdQAA
+ AAAAAAAABwInWQwASv8RAGn/EgBw/wsAk/8AAMb/AADG/wAAxv8AAMb/AQCu/wMAgf8AAMb/AADG/wAA
+ xv8FALD/CgA8/w4AWf8MAE7/CwBD/wgARv8AAMb/AADG/wAAxv8AAMb/AADG/wkAmv8SAHD/EgBw/w0A
+ UP8JCi9oAAAAAAAAAAAIByxdDABL/xIAcP8RAHX/AQDB/wAAxv8AAMb/AADG/wAAxv8CAIr/AQCi/wMA
+ t/8EALT/AADG/wUArP8IAFv/CQB5/woAlP8JAD3/CQA9/wAAvv8AAMb/AADG/wAAxv8AAMb/AQDD/w8A
+ fP8SAHD/DQBT/wgHMVwAAAAAAAAAAAoOM1AMAE3/EgBw/wsAkP8AAMb/AADG/wAAxv8AAMb/AADG/wIA
+ m/8DAGv/AQC8/wcAo/8DALb/AADG/wAAxv8BAMP/CwCT/wYAcv8MAEn/AgCT/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/BgCo/xIAcP8NAFP/CQo2RQAAAAAAAAAACgwyNgwASv8SAHD/BgCp/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wYAPf8GAEr/AQCo/wIAvv8EALH/AQC//wUAr/8AAMP/BgBk/wwAVv8CAJb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMT/EAB4/w0AT/8IBzEmAAAAAAAAAAAJCi8XCwBE/xIAcP8CALz/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/BQBU/xAAYv8LAEv/AwB7/wAAxP8AAMb/AADG/wQAc/8KAIH/BABe/wAA
+ xf8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8LAJL/DAFH/AwTRAgAAAAAAAAAABQsUQQLA0D5EQB2/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8EAF7/BwCd/wcApf8JAD7/BABc/wEApv8DAHT/BgBP/wIA
+ nv8CAIX/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wcApv8LAkHdAAAAAAAAAAAAAAAAAAAAAAoD
+ OtMPAH3/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wMAaP8CAK//AADG/wQAYP8AAL3/AgCH/wAA
+ uP8DAG3/BABZ/wAAvP8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/BACy/wgBMasAAAAAAAAAAAAA
+ AAAAAAAACAMwqQ8Afv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AwBz/wEAqf8AAMb/BABb/wAA
+ xv8AAMb/AADG/wAAxv8AAMX/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8CAK3/CAUtfQAA
+ AAAAAAAAAAAAAAAAAAAIBS12DgBw/wAAxv8AAMb/AADG/wAAxf8CALr/BQCt/wgAof8IAGP/AQCn/wAA
+ xv8IAEP/EAB8/xAAef8QAHr/DwB+/w8AgP8OAIT/DACM/wkAmv8FAK3/AQDB/wAAxv8AAMb/AADG/wIA
+ jP8IBi9AAAAAAAAAAAAAAAAAAAAAAAgHMDoMAFr/AADG/wAAxf8JAJv/EAB4/xIAb/8VBF//GAlP/xEG
+ N/8BAKX/AADE/xAILf8rHi//Kx4v/yocMP8mGTH/IRQ0/xwOO/8ZC0f/FgZY/xMBbf8RAHT/CQCc/wAA
+ xf8AAMb/BAFl9BMqWwkAAAAAAAAAAAAAAAAAAAAAEyZkBgoBRO8BAMP/BgCo/xUEYP8gEzb/PjAn/15O
+ NP97akn/UkZB/wEAq/8AALv/QzlE/66dc/+yoXn/sJ51/6mXbP+kkWf/mYZf/4VzUP9oVzr/RDUn/yMW
+ M/8VBV//CACh/wAAxP8HBT+2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAMyqQIAuP8JAJr/Nigl/497
+ WP/OwZf/1MeV/9bKmv9yaGD/AQC1/wEApv9nXVf/0MOO/9DDjv/Qw47/0MOO/9DDjv/Qw47/0MOO/9PH
+ lP/NwJX/l4Rh/zotKv8KAJf/AQCg/wgHLGYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIBi9RAgCW/wYA
+ qP9DNjT/uamE/8/CkP+3pnj/p5Rp/0Y7Qv8AAMD/AgCJ/15NQf+Kc0//inNP/4pzT/+LdVH/j3lU/5iD
+ XP+nlGn/u6t7/9bKmf+1pID/Oy0r/wUArP8EAWr7Dhk+FQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkJ
+ NAUEAmTwAQDC/x0RQP+Gck7/lYBa/35mRf9+ZkX/KR4y/wIAjv8aEkP/emJE/35mRf9+ZkX/fmZF/35m
+ Rf9+ZkX/fmZF/35mRf9+ZkX/nYpi/4NuTP8cEET/AQC//wcEPLMAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAcEM5IAALj/CwSQ/1NDLP+Bakj/gWpI/4FqSP93YUX/YU4//3tkRv+Bakj/gWpI/4Fq
+ SP+Bakj/gWpI/4FqSP+Bakj/gWpI/4FqSP+Bakj/UkIs/woElf8CAI7/CQkuTgAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAACg4zJgMBdv0AAMX/IBdE/4x4U/+WgVr/j3pV/496Vf+PelX/j3pV/496
+ Vf+PelX/j3pV/496Vf+PelX/j3pV/496Vf+Qe1X/oIxj/4l0Uf8fFkT/AADF/wUCUt0VLVIEAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwQ5pwAAuv8GA6n/VUg2/8O0hP/HuIf/uKh5/7Cf
+ cf+rmW3/qJZq/6aUaf+mk2j/ppNo/6iWa/+wn3H/uqp6/87Aj//Etor/UUQ0/wYDqP8BAKH/CAUtbQAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALDzUmAwFv+AAAxv8TDm3/jn5Z/8/C
+ lf/Tx5X/0cSQ/9XIl//SxpL/0MKO/9DCjv/Qwo7/0MKO/9THlf/YzJ3/1sqg/4l6Vv8SDW//AADG/wUB
+ WOYNFkoKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIBzKEAQCn/wEA
+ w/8gGVb/YlY+/25iRP92aUn/e25N/3tuTf99cE//fXBO/3tuTf94a0r/c2dH/21hQ/9hVj3/HhhZ/wAA
+ xP8CAJf/CAUtZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwS
+ NwYGBEnOAAC8/wAAxv8BAcD/AwK1/wUErf8GBKf/BgSn/wYFpP8GBaX/BgSn/wUEqv8EA6//AwK2/wEB
+ wP8AAMb/AAC9/wYDRMcKDDoCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAoLOCEFAVbkAADA/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxf8EAWHwCQs3JQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkKNSsFAVPiAAC4/wAAxv8AAMb/AADG/wAAxv8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMX/AwFy9ggHMUMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkLNxoGA0C5AgCS/wAAxf8AAMb/AADG/wAA
+ xv8AAMb/AADG/wAAxv8AAMb/AQC3/wQBX+gJCDI+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABcyegEJCDJUBgNNyAMA
+ e/8BAKT/AAC8/wAAwf8AALv/AQCg/wQCb/cHBDiWCw49FwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAYNn8ACQk1IQkJL18HBCuHCAYvkwgGLIYJCC1ZDBI3EgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+AAHHwAAAEgAAAAIAAAACAAAABgAAAAYAAAAGAAAABgAAAAYAA
+ AAGAAAABgAAAAYAAAAPAAAADwAAAA8AAAAPAAAADwAAAB+AAAAfgAAAH4AAAD/AAAA/wAAAP+AAAH/gA
+ AB/8AAA//AAAP/4AAH//AAD//4AB///AA///8A//KAAAABAAAAAgAAAAAQAgAAAAAAAABAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAACw46DQgFLUULA0N3DQJOnAoDOrcKAT3HDQBPyg4BVcUOAlOzDAFLkwoD
+ PWwIByw3FSZfBx0oigMIBi4cCgE/sgwASfgRAGj/EgBw/xEAbP8MAF7/DQBr/wkAO/8KAIH/DACN/xEA
+ dP8SAG//DgBY/wsBQvAMEToiBgAlOQ0ATv8RAGn/EQB1/wgAoP8DAJb/AwCw/wIAvf8MAE7/CABi/wAA
+ wf8CALv/DgCC/xIAb/8NAFL/CQovNwYBJiwOAFf/EAB5/wMAuv8AAMb/AgCW/wAAxv8BAMD/CwBI/wsA
+ SP8EAIn/AADG/wEAwf8QAHv/DwBe/wkKLzcJCi8rDwBe/wcAo/8AAMb/AADG/wIAjP8EALP/AgC7/wQA
+ l/8JAHb/BgB2/wAAxv8AAMb/BgCr/xAAYf8JCTMoCgwxEw8AW/8CALz/AADG/wAAxv8DAIf/CABo/wIA
+ q/8CAL7/BQCH/wQAhP8AAMb/AADG/wAAxv8NAGj+CQk1CxQsUQENAV3zAADG/wAAxv8AAMb/AgCV/wQA
+ rv8EAG7/AgCW/wQAbf8BALP/AADG/wAAxv8AAMb/BwF64gAAAAAAAAAADAFdxwAAxv8AAMb/AgC9/wUA
+ j/8BALf/BwB4/wgAoP8HAKL/BgCn/wQAtf8AAMX/AADG/wQBf68AAAAAAAAAAAsCTYwCAL7/EwZq/zEh
+ Sv89MET/AQC0/0s/Rf9tXlP/ZVVO/1VFTP81JUn/FAdp/wIAvP8FA1VtAAAAAAAAAAAIBDE/BQCk/3Bh
+ Tf/KvI3/jYFp/wEAqf+IeF3/rZtv/6+dcP+4p3j/zL+P/3BhTf8FAJP+CQovHwAAAAAAAAAACQk0AQMB
+ juBAMlP/hW5M/2hUQf8+MVX/fmdG/4BoRv+AaEb/gGhG/4dxTv8/MVT/AwKCwAAAAAAAAAAAAAAAAAAA
+ AAAFA1pyCgeb/459Wv+nlWr/noti/5uHX/+bhl7/noph/6qYbf+Pf1z/CgaU/wYERlMAAAAAAAAAAAAA
+ AAAAAAAACw81CQIBj98wKnj/nJBr/6WZb/+mmm//pplt/6SXbf+fk2//Lih4/wMBh9INFkoDAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAHBUY9AQCo+AEBwP8DArj/AwK2/wMCtv8CArn/AQHB/wEAq/sGBEI7AAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYDTEoCAZrtAADG/wAAxv8AAMb/AADG/wEAqvkFA1teAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQgzFQQCZHoDAoe5AwKLxgQCeJgHBjkrAAAAAAAA
+ AAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAgAEAAIABAACAAQAAgAMAAMAD
+ AADAAwAA4AcAAPAPAAD4HwAA
+</value>
+ </data>
+</root> \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.WinForms/Impostor.Patcher.WinForms.csproj b/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.WinForms/Impostor.Patcher.WinForms.csproj
new file mode 100644
index 0000000..7149436
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.WinForms/Impostor.Patcher.WinForms.csproj
@@ -0,0 +1,22 @@
+<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
+
+ <PropertyGroup>
+ <AssemblyName>Impostor</AssemblyName>
+ <ProjectGuid>{804CF172-0C87-4423-9688-BD97D549891E}</ProjectGuid>
+ <OutputType>WinExe</OutputType>
+ <TargetFramework>net472</TargetFramework>
+ <UseWindowsForms>true</UseWindowsForms>
+ <Copyright>Copyright © AeonLucid 2020</Copyright>
+ <ApplicationIcon>icon.ico</ApplicationIcon>
+ <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
+ <GenerateResourceUsePreserializedResources>true</GenerateResourceUsePreserializedResources>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\Impostor.Patcher.Shared\Impostor.Patcher.Shared.csproj" />
+ <PackageReference Include="System.Resources.Extensions" Version="5.0.0" />
+ <Reference Include="System.Runtime.InteropServices.RuntimeInformation" />
+ <Reference Include="System.Windows.Forms" />
+ </ItemGroup>
+
+</Project> \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.WinForms/Program.cs b/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.WinForms/Program.cs
new file mode 100644
index 0000000..7ca9035
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.WinForms/Program.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Windows.Forms;
+using Impostor.Patcher.WinForms.Forms;
+
+namespace Impostor.Patcher.WinForms
+{
+ internal static class Program
+ {
+ [STAThread]
+ private static void Main()
+ {
+ Application.EnableVisualStyles();
+ Application.SetCompatibleTextRenderingDefault(false);
+ Application.Run(new FrmMain());
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.WinForms/Properties/Resources.Designer.cs b/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.WinForms/Properties/Resources.Designer.cs
new file mode 100644
index 0000000..bb5c4cf
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.WinForms/Properties/Resources.Designer.cs
@@ -0,0 +1,69 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// 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.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace Impostor.Patcher.WinForms.Properties
+{
+ /// <summary>
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ /// </summary>
+ // 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()
+ {
+ }
+
+ /// <summary>
+ /// Returns the cached ResourceManager instance used by this class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState
+ .Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager
+ {
+ get
+ {
+ if ((resourceMan == null))
+ {
+ global::System.Resources.ResourceManager temp =
+ new global::System.Resources.ResourceManager("Impostor.Client.WinForms.Properties.Resources",
+ typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+
+ return resourceMan;
+ }
+ }
+
+ /// <summary>
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState
+ .Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture
+ {
+ get { return resourceCulture; }
+ set { resourceCulture = value; }
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.WinForms/Properties/Resources.resx b/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.WinForms/Properties/Resources.resx
new file mode 100644
index 0000000..af7dbeb
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.WinForms/Properties/Resources.resx
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+</root> \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.WinForms/Properties/Settings.Designer.cs b/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.WinForms/Properties/Settings.Designer.cs
new file mode 100644
index 0000000..42356db
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.WinForms/Properties/Settings.Designer.cs
@@ -0,0 +1,26 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// 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.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace Impostor.Patcher.Properties
+{
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute(
+ "Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
+ internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
+ {
+ private static Settings defaultInstance =
+ ((Settings) (global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+ public static Settings Default
+ {
+ get { return defaultInstance; }
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.WinForms/Properties/Settings.settings b/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.WinForms/Properties/Settings.settings
new file mode 100644
index 0000000..3964565
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.WinForms/Properties/Settings.settings
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='utf-8'?>
+<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
+ <Profiles>
+ <Profile Name="(Default)" />
+ </Profiles>
+ <Settings />
+</SettingsFile>
diff --git a/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.WinForms/icon.ico b/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.WinForms/icon.ico
new file mode 100644
index 0000000..8cc46f3
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.WinForms/icon.ico
Binary files differ
diff --git a/Impostor-dev/src/Impostor.Plugins.Debugger/App.razor b/Impostor-dev/src/Impostor.Plugins.Debugger/App.razor
new file mode 100644
index 0000000..38e8633
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Plugins.Debugger/App.razor
@@ -0,0 +1,10 @@
+<Router AppAssembly="@typeof(DebugPlugin).Assembly">
+ <Found Context="routeData">
+ <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/>
+ </Found>
+ <NotFound>
+ <LayoutView Layout="@typeof(MainLayout)">
+ <p>Sorry, there's nothing at this address.</p>
+ </LayoutView>
+ </NotFound>
+</Router> \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Plugins.Debugger/DebugPlugin.cs b/Impostor-dev/src/Impostor.Plugins.Debugger/DebugPlugin.cs
new file mode 100644
index 0000000..8f57e89
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Plugins.Debugger/DebugPlugin.cs
@@ -0,0 +1,13 @@
+using Impostor.Api.Plugins;
+
+namespace Impostor.Plugins.Debugger
+{
+ [ImpostorPlugin(
+ package: "gg.impostor.debugger",
+ name: "Debugger",
+ author: "Gerard",
+ version: "1.0.0")]
+ public class DebugPlugin : PluginBase
+ {
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Plugins.Debugger/DebugPluginStartup.cs b/Impostor-dev/src/Impostor.Plugins.Debugger/DebugPluginStartup.cs
new file mode 100644
index 0000000..cc36ce6
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Plugins.Debugger/DebugPluginStartup.cs
@@ -0,0 +1,35 @@
+using Impostor.Api.Plugins;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+
+namespace Impostor.Plugins.Debugger
+{
+ public class DebugPluginStartup : IPluginStartup
+ {
+ public void ConfigureServices(IServiceCollection services)
+ {
+ services.AddRazorPages();
+ services.AddServerSideBlazor();
+ }
+
+ public void ConfigureHost(IHostBuilder host)
+ {
+ host.ConfigureWebHostDefaults(webBuilder =>
+ {
+ webBuilder.Configure(app =>
+ {
+ app.UseStaticFiles();
+ app.UseRouting();
+
+ app.UseEndpoints(endpoints =>
+ {
+ endpoints.MapBlazorHub();
+ endpoints.MapFallbackToPage("/_Host");
+ });
+ });
+ });
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Plugins.Debugger/Impostor.Plugins.Debugger.csproj b/Impostor-dev/src/Impostor.Plugins.Debugger/Impostor.Plugins.Debugger.csproj
new file mode 100644
index 0000000..9518e48
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Plugins.Debugger/Impostor.Plugins.Debugger.csproj
@@ -0,0 +1,16 @@
+<Project Sdk="Microsoft.NET.Sdk.Web">
+
+ <PropertyGroup>
+ <TargetFramework>net5.0</TargetFramework>
+ <OutputType>Library</OutputType>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\Impostor.Api\Impostor.Api.csproj" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Folder Include="Properties\" />
+ </ItemGroup>
+
+</Project> \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Plugins.Debugger/Pages/Index.razor b/Impostor-dev/src/Impostor.Plugins.Debugger/Pages/Index.razor
new file mode 100644
index 0000000..1bfa478
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Plugins.Debugger/Pages/Index.razor
@@ -0,0 +1,69 @@
+@page "/"
+@implements IDisposable
+@using Impostor.Api.Events.Managers
+@using Impostor.Api.Games.Managers
+@using Impostor.Api.Events
+@using Impostor.Api.Events.Player
+@implements Impostor.Api.Events.IEventListener
+@inject IEventManager EventManager
+@inject IGameManager GameManager
+
+<div class="container">
+ <h2>Games</h2>
+ @if (GameManager.Games.Any())
+ {
+ <table class="table table-striped">
+ <thead>
+ <tr>
+ <th>Code</th>
+ <th>Players</th>
+ </tr>
+ </thead>
+ <tbody>
+ @foreach (var game in GameManager.Games)
+ {
+ <tr>
+ <td>@game.Code</td>
+ <td>
+ <ul class="mb-0">
+ @foreach (var player in game.Players)
+ {
+ <li>@player.Client.Name</li>
+ }
+ </ul>
+ </td>
+ </tr>
+ }
+ </tbody>
+ </table>
+ }
+ else
+ {
+ <div class="text-center">
+ <i class="text-muted">There are no active games.</i>
+ </div>
+ }
+</div>
+
+@code {
+ private IDisposable _disposable;
+
+ [EventListener(typeof(IGameCreatedEvent))]
+ [EventListener(typeof(IGameDestroyedEvent))]
+ [EventListener(typeof(IGamePlayerJoinedEvent))]
+ [EventListener(typeof(IGamePlayerLeftEvent))]
+ public void OnGameCreated(IGameEvent e)
+ {
+ StateHasChanged();
+ }
+
+ protected override void OnInitialized()
+ {
+ _disposable = EventManager.RegisterListener(this, InvokeAsync);
+ }
+
+ public void Dispose()
+ {
+ _disposable?.Dispose();
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Plugins.Debugger/Pages/_Host.cshtml b/Impostor-dev/src/Impostor.Plugins.Debugger/Pages/_Host.cshtml
new file mode 100644
index 0000000..eed3aaf
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Plugins.Debugger/Pages/_Host.cshtml
@@ -0,0 +1,19 @@
+@page "/"
+@namespace Impostor.Plugins.Debugger.Pages
+@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
+
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8"/>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
+ <title>Impostor Debugger</title>
+ <base href="~/"/>
+ <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
+</head>
+<body>
+<component type="typeof(App)" render-mode="Server"/>
+
+<script src="_framework/blazor.server.js"></script>
+</body>
+</html> \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Plugins.Debugger/Shared/MainLayout.razor b/Impostor-dev/src/Impostor.Plugins.Debugger/Shared/MainLayout.razor
new file mode 100644
index 0000000..07da9d6
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Plugins.Debugger/Shared/MainLayout.razor
@@ -0,0 +1,5 @@
+@inherits LayoutComponentBase
+
+<div class="page">
+ @Body
+</div> \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Plugins.Debugger/_Imports.razor b/Impostor-dev/src/Impostor.Plugins.Debugger/_Imports.razor
new file mode 100644
index 0000000..109ec4e
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Plugins.Debugger/_Imports.razor
@@ -0,0 +1,8 @@
+@using System.Net.Http
+@using Microsoft.AspNetCore.Authorization
+@using Microsoft.AspNetCore.Components.Authorization
+@using Microsoft.AspNetCore.Components.Forms
+@using Microsoft.AspNetCore.Components.Routing
+@using Microsoft.AspNetCore.Components.Web
+@using Microsoft.JSInterop
+@using Impostor.Plugins.Debugger.Shared \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Plugins.Example/ExamplePlugin.cs b/Impostor-dev/src/Impostor.Plugins.Example/ExamplePlugin.cs
new file mode 100644
index 0000000..dafba7c
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Plugins.Example/ExamplePlugin.cs
@@ -0,0 +1,33 @@
+using System.Threading.Tasks;
+using Impostor.Api.Plugins;
+using Microsoft.Extensions.Logging;
+
+namespace Impostor.Plugins.Example
+{
+ [ImpostorPlugin(
+ package: "gg.impostor.example",
+ name: "Example",
+ author: "AeonLucid",
+ version: "1.0.0")]
+ public class ExamplePlugin : PluginBase
+ {
+ private readonly ILogger<ExamplePlugin> _logger;
+
+ public ExamplePlugin(ILogger<ExamplePlugin> logger)
+ {
+ _logger = logger;
+ }
+
+ public override ValueTask EnableAsync()
+ {
+ _logger.LogInformation("Example is being enabled.");
+ return default;
+ }
+
+ public override ValueTask DisableAsync()
+ {
+ _logger.LogInformation("Example is being disabled.");
+ return default;
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Plugins.Example/ExamplePluginStartup.cs b/Impostor-dev/src/Impostor.Plugins.Example/ExamplePluginStartup.cs
new file mode 100644
index 0000000..936f15e
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Plugins.Example/ExamplePluginStartup.cs
@@ -0,0 +1,22 @@
+using Impostor.Api.Events;
+using Impostor.Api.Plugins;
+using Impostor.Plugins.Example.Handlers;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+
+namespace Impostor.Plugins.Example
+{
+ public class ExamplePluginStartup : IPluginStartup
+ {
+ public void ConfigureHost(IHostBuilder host)
+ {
+ }
+
+ public void ConfigureServices(IServiceCollection services)
+ {
+ services.AddSingleton<IEventListener, GameEventListener>();
+ services.AddSingleton<IEventListener, PlayerEventListener>();
+ services.AddSingleton<IEventListener, MeetingEventListener>();
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Plugins.Example/Handlers/GameEventListener.cs b/Impostor-dev/src/Impostor.Plugins.Example/Handlers/GameEventListener.cs
new file mode 100644
index 0000000..be2d0f3
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Plugins.Example/Handlers/GameEventListener.cs
@@ -0,0 +1,64 @@
+using System;
+using Impostor.Api.Events;
+
+namespace Impostor.Plugins.Example.Handlers
+{
+ public class GameEventListener : IEventListener
+ {
+ [EventListener(EventPriority.Monitor)]
+ public void OnGame(IGameEvent e)
+ {
+ Console.WriteLine(e.GetType().Name + " triggered");
+ }
+
+ [EventListener]
+ public void OnGameCreated(IGameCreatedEvent e)
+ {
+ Console.WriteLine("Game > created");
+ }
+
+ [EventListener]
+ public void OnGameStarting(IGameStartingEvent e)
+ {
+ Console.WriteLine("Game > starting");
+ }
+
+ [EventListener]
+ public void OnGameStarted(IGameStartedEvent e)
+ {
+ Console.WriteLine("Game > started");
+
+ foreach (var player in e.Game.Players)
+ {
+ var info = player.Character.PlayerInfo;
+
+ Console.WriteLine($"- {info.PlayerName} {info.IsImpostor}");
+ }
+ }
+
+ [EventListener]
+ public void OnGameEnded(IGameEndedEvent e)
+ {
+ Console.WriteLine("Game > ended");
+ Console.WriteLine("- Reason: " + e.GameOverReason);
+ }
+
+ [EventListener]
+ public void OnGameDestroyed(IGameDestroyedEvent e)
+ {
+ Console.WriteLine("Game > destroyed");
+ }
+
+ [EventListener]
+ public void OnPlayerJoined(IGamePlayerJoinedEvent e)
+ {
+ Console.WriteLine("Player joined a game.");
+ }
+
+ [EventListener]
+ public void OnPlayerLeftGame(IGamePlayerLeftEvent e)
+ {
+ Console.WriteLine("Player left a game.");
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Plugins.Example/Handlers/MeetingEventListener.cs b/Impostor-dev/src/Impostor.Plugins.Example/Handlers/MeetingEventListener.cs
new file mode 100644
index 0000000..847532c
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Plugins.Example/Handlers/MeetingEventListener.cs
@@ -0,0 +1,21 @@
+using System;
+using Impostor.Api.Events;
+using Impostor.Api.Events.Meeting;
+
+namespace Impostor.Plugins.Example.Handlers
+{
+ public class MeetingEventListener : IEventListener
+ {
+ [EventListener]
+ public void OnMeetingStarted(IMeetingStartedEvent e)
+ {
+ Console.WriteLine("Meeting > started");
+ }
+
+ [EventListener]
+ public void OnMeetingEnded(IMeetingEndedEvent e)
+ {
+ Console.WriteLine("Meeting > ended");
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Plugins.Example/Handlers/PlayerEventListener.cs b/Impostor-dev/src/Impostor.Plugins.Example/Handlers/PlayerEventListener.cs
new file mode 100644
index 0000000..0190d3b
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Plugins.Example/Handlers/PlayerEventListener.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Numerics;
+using System.Threading.Tasks;
+using Impostor.Api.Events;
+using Impostor.Api.Events.Player;
+using Impostor.Api.Innersloth.Customization;
+using Microsoft.Extensions.Logging;
+
+namespace Impostor.Plugins.Example.Handlers
+{
+ public class PlayerEventListener : IEventListener
+ {
+ private static readonly Random Random = new Random();
+
+ private readonly ILogger<PlayerEventListener> _logger;
+
+ public PlayerEventListener(ILogger<PlayerEventListener> logger)
+ {
+ _logger = logger;
+ }
+
+ [EventListener]
+ public void OnPlayerSpawned(IPlayerSpawnedEvent e)
+ {
+ _logger.LogDebug(e.PlayerControl.PlayerInfo.PlayerName + " spawned");
+
+ // Need to make a local copy because it might be possible that
+ // the event gets changed after being handled.
+ var clientPlayer = e.ClientPlayer;
+ var playerControl = e.PlayerControl;
+
+ /*
+ Task.Run(async () =>
+ {
+ Console.WriteLine("Starting player task.");
+
+ // Give the player time to load.
+ await Task.Delay(TimeSpan.FromSeconds(3));
+
+ while (clientPlayer.Client.Connection != null &&
+ clientPlayer.Client.Connection.IsConnected)
+ {
+ // Modify player properties.
+ await playerControl.SetColorAsync((byte) Random.Next(1, 9));
+ await playerControl.SetHatAsync((uint) Random.Next(1, 9));
+ await playerControl.SetSkinAsync((uint) Random.Next(1, 9));
+ await playerControl.SetPetAsync((uint) Random.Next(1, 9));
+
+ await Task.Delay(TimeSpan.FromMilliseconds(5000));
+ }
+
+ _logger.LogDebug("Stopping player task.");
+ });
+ */
+ }
+
+ [EventListener]
+ public void OnPlayerDestroyed(IPlayerDestroyedEvent e)
+ {
+ _logger.LogDebug(e.PlayerControl.PlayerInfo.PlayerName + " destroyed");
+ }
+
+ [EventListener]
+ public async ValueTask OnPlayerChat(IPlayerChatEvent e)
+ {
+ _logger.LogDebug(e.PlayerControl.PlayerInfo.PlayerName + " said " + e.Message);
+
+ if (e.Message == "test")
+ {
+ e.Game.Options.KillCooldown = 0;
+ e.Game.Options.NumImpostors = 2;
+ e.Game.Options.PlayerSpeedMod = 5;
+
+ await e.Game.SyncSettingsAsync();
+ }
+
+ if (e.Message == "look")
+ {
+ await e.PlayerControl.SetColorAsync(ColorType.Pink);
+ await e.PlayerControl.SetHatAsync(HatType.Cheese);
+ await e.PlayerControl.SetSkinAsync(SkinType.Police);
+ await e.PlayerControl.SetPetAsync(PetType.Ufo);
+ }
+
+ if (e.Message == "snap")
+ {
+ await e.PlayerControl.NetworkTransform.SnapToAsync(new Vector2(1, 1));
+ }
+
+ await e.PlayerControl.SetNameAsync(e.Message);
+ await e.PlayerControl.SendChatAsync(e.Message);
+ }
+
+ [EventListener]
+ public void OnPlayerStartMeetingEvent(IPlayerStartMeetingEvent e)
+ {
+ _logger.LogDebug($"Player {e.PlayerControl.PlayerInfo.PlayerName} start meeting, reason: " + (e.Body==null ? "Emergency call button" : "Found the body of the player "+e.Body.PlayerInfo.PlayerName));
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Plugins.Example/Impostor.Plugins.Example.csproj b/Impostor-dev/src/Impostor.Plugins.Example/Impostor.Plugins.Example.csproj
new file mode 100644
index 0000000..dd4724d
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Plugins.Example/Impostor.Plugins.Example.csproj
@@ -0,0 +1,11 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>netstandard2.1</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\Impostor.Api\Impostor.Api.csproj" />
+ </ItemGroup>
+
+</Project>
diff --git a/Impostor-dev/src/Impostor.Server/Config/AntiCheatConfig.cs b/Impostor-dev/src/Impostor.Server/Config/AntiCheatConfig.cs
new file mode 100644
index 0000000..f4807e7
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Config/AntiCheatConfig.cs
@@ -0,0 +1,9 @@
+namespace Impostor.Server.Config
+{
+ public class AntiCheatConfig
+ {
+ public const string Section = "AntiCheat";
+
+ public bool BanIpFromGame { get; set; } = true;
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Config/DebugConfig.cs b/Impostor-dev/src/Impostor.Server/Config/DebugConfig.cs
new file mode 100644
index 0000000..630d1b4
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Config/DebugConfig.cs
@@ -0,0 +1,11 @@
+namespace Impostor.Server.Config
+{
+ public class DebugConfig
+ {
+ public const string Section = "Debug";
+
+ public bool GameRecorderEnabled { get; set; }
+
+ public string GameRecorderPath { get; set; }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Config/DisconnectMessages.cs b/Impostor-dev/src/Impostor.Server/Config/DisconnectMessages.cs
new file mode 100644
index 0000000..a86735f
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Config/DisconnectMessages.cs
@@ -0,0 +1,19 @@
+namespace Impostor.Server.Config
+{
+ public static class DisconnectMessages
+ {
+ public const string Error = "There was an internal server error. " +
+ "Check the server console for more information. " +
+ "Please report the issue on the AmongUsServer GitHub if it keeps happening.";
+
+ public const string Destroyed = "The game you tried to join is being destroyed. " +
+ "Please create a new game.";
+
+ public const string NotImplemented = "Game listing has not been implemented in Impostor yet for servers " +
+ "running in server redirection mode.";
+
+ public const string UsernameLength = "Your username is too long, please make it shorter.";
+
+ public const string UsernameIllegalCharacters = "Your username contains illegal characters, please remove them.";
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Config/ServerConfig.cs b/Impostor-dev/src/Impostor.Server/Config/ServerConfig.cs
new file mode 100644
index 0000000..1c58433
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Config/ServerConfig.cs
@@ -0,0 +1,30 @@
+using Impostor.Server.Utils;
+
+namespace Impostor.Server.Config
+{
+ internal class ServerConfig
+ {
+ private string? _resolvedPublicIp;
+ private string? _resolvedListenIp;
+
+ public const string Section = "Server";
+
+ public string PublicIp { get; set; } = "127.0.0.1";
+
+ public ushort PublicPort { get; set; } = 22023;
+
+ public string ListenIp { get; set; } = "127.0.0.1";
+
+ public ushort ListenPort { get; set; } = 22023;
+
+ public string ResolvePublicIp()
+ {
+ return _resolvedPublicIp ??= IpUtils.ResolveIp(PublicIp);
+ }
+
+ public string ResolveListenIp()
+ {
+ return _resolvedListenIp ??= IpUtils.ResolveIp(ListenIp);
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Config/ServerRedirectorConfig.cs b/Impostor-dev/src/Impostor.Server/Config/ServerRedirectorConfig.cs
new file mode 100644
index 0000000..0ccfa0d
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Config/ServerRedirectorConfig.cs
@@ -0,0 +1,24 @@
+using System.Collections.Generic;
+
+namespace Impostor.Server.Config
+{
+ public class ServerRedirectorConfig
+ {
+ public const string Section = "ServerRedirector";
+
+ public bool Enabled { get; set; }
+
+ public bool Master { get; set; }
+
+ public NodeLocator Locator { get; set; }
+
+ public List<ServerRedirectorNode> Nodes { get; set; }
+
+ public class NodeLocator
+ {
+ public string Redis { get; set; }
+
+ public string UdpMasterEndpoint { get; set; }
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Config/ServerRedirectorNode.cs b/Impostor-dev/src/Impostor.Server/Config/ServerRedirectorNode.cs
new file mode 100644
index 0000000..d11b60f
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Config/ServerRedirectorNode.cs
@@ -0,0 +1,9 @@
+namespace Impostor.Server.Config
+{
+ public class ServerRedirectorNode
+ {
+ public string Ip { get; set; }
+
+ public ushort Port { get; set; }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Constants.cs b/Impostor-dev/src/Impostor.Server/Constants.cs
new file mode 100644
index 0000000..62d90b2
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Constants.cs
@@ -0,0 +1,8 @@
+namespace Impostor.Server
+{
+ internal static class Constants
+ {
+ public const int SpawnTimeout = 2500;
+ public const int ConnectionTimeout = 2500;
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Events/EventHandler.cs b/Impostor-dev/src/Impostor.Server/Events/EventHandler.cs
new file mode 100644
index 0000000..190f7f3
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Events/EventHandler.cs
@@ -0,0 +1,24 @@
+using Impostor.Api.Events;
+using Impostor.Server.Events.Register;
+
+namespace Impostor.Server.Events
+{
+ internal readonly struct EventHandler
+ {
+ public EventHandler(IEventListener o, IRegisteredEventListener listener)
+ {
+ Object = o;
+ Listener = listener;
+ }
+
+ public IEventListener Object { get; }
+
+ public IRegisteredEventListener Listener { get; }
+
+ public void Deconstruct(out IEventListener o, out IRegisteredEventListener listener)
+ {
+ o = Object;
+ listener = Listener;
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Events/EventManager.cs b/Impostor-dev/src/Impostor.Server/Events/EventManager.cs
new file mode 100644
index 0000000..5625c4d
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Events/EventManager.cs
@@ -0,0 +1,167 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Impostor.Api;
+using Impostor.Api.Events;
+using Impostor.Api.Events.Managers;
+using Impostor.Server.Events.Register;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Impostor.Server.Events
+{
+ internal class EventManager : IEventManager
+ {
+ private readonly ConcurrentDictionary<Type, TemporaryEventRegister> _temporaryEventListeners;
+ private readonly ConcurrentDictionary<Type, List<EventHandler>> _cachedEventHandlers;
+ private readonly ILogger<EventManager> _logger;
+ private readonly IServiceProvider _serviceProvider;
+
+ public EventManager(ILogger<EventManager> logger, IServiceProvider serviceProvider)
+ {
+ _logger = logger;
+ _serviceProvider = serviceProvider;
+ _temporaryEventListeners = new ConcurrentDictionary<Type, TemporaryEventRegister>();
+ _cachedEventHandlers = new ConcurrentDictionary<Type, List<EventHandler>>();
+ }
+
+ /// <inheritdoc />
+ public IDisposable RegisterListener<TListener>(TListener listener, Func<Func<Task>, Task> invoker = null)
+ where TListener : IEventListener
+ {
+ if (listener == null)
+ {
+ throw new ArgumentNullException(nameof(listener));
+ }
+
+ var eventListeners = RegisteredEventListener.FromType(listener.GetType());
+ var disposes = new IDisposable[eventListeners.Count];
+
+ foreach (var eventListener in eventListeners)
+ {
+ IRegisteredEventListener wrappedEventListener = new WrappedRegisteredEventListener(eventListener, listener);
+
+ if (invoker != null)
+ {
+ wrappedEventListener = new InvokedRegisteredEventListener(wrappedEventListener, invoker);
+ }
+
+ var register = _temporaryEventListeners.GetOrAdd(
+ wrappedEventListener.EventType,
+ _ => new TemporaryEventRegister());
+
+ register.Add(wrappedEventListener);
+ }
+
+ if (eventListeners.Count > 0)
+ {
+ _cachedEventHandlers.TryRemove(typeof(TListener), out _);
+ }
+
+ return new MultiDisposable(disposes);
+ }
+
+ /// <inheritdoc />
+ public bool IsRegistered<TEvent>()
+ where TEvent : IEvent
+ {
+ if (_cachedEventHandlers.TryGetValue(typeof(TEvent), out var handlers))
+ {
+ return handlers.Count > 0;
+ }
+
+ return GetHandlers<TEvent>().Any();
+ }
+
+ /// <inheritdoc />
+ public async ValueTask CallAsync<T>(T @event)
+ where T : IEvent
+ {
+ try
+ {
+ if (!_cachedEventHandlers.TryGetValue(typeof(T), out var handlers))
+ {
+ handlers = CacheEventHandlers<T>();
+ }
+
+ foreach (var (handler, eventListener) in handlers)
+ {
+ await eventListener.InvokeAsync(handler, @event, _serviceProvider);
+ }
+ }
+ catch (ImpostorCheatException)
+ {
+ throw;
+ }
+ catch (Exception e)
+ {
+ _logger.LogError(e, "Invocation of event {0} threw an exception.", @event.GetType().Name);
+ }
+ }
+
+ private List<EventHandler> CacheEventHandlers<TEvent>()
+ where TEvent : IEvent
+ {
+ var handlers = GetHandlers<TEvent>()
+ .OrderByDescending(e => e.Listener.Priority)
+ .ToList();
+
+ _cachedEventHandlers[typeof(TEvent)] = handlers;
+
+ return handlers;
+ }
+
+ /// <summary>
+ /// Get all the event listeners for the given event type.
+ /// </summary>
+ /// <returns>The event listeners.</returns>
+ private IEnumerable<EventHandler> GetHandlers<TEvent>()
+ where TEvent : IEvent
+ {
+ var eventType = typeof(TEvent);
+ var interfaces = eventType.GetInterfaces();
+
+ foreach (var @interface in interfaces)
+ {
+ if (_temporaryEventListeners.TryGetValue(@interface, out var cb))
+ {
+ foreach (var eventListener in cb.GetEventListeners())
+ {
+ yield return new EventHandler(null, eventListener);
+ }
+ }
+ }
+
+ foreach (var handler in _serviceProvider.GetServices<IEventListener>())
+ {
+ if (handler is IManualEventListener manualEventListener && manualEventListener.CanExecute<TEvent>())
+ {
+ yield return new EventHandler(handler, new ManualRegisteredEventListener(manualEventListener));
+ continue;
+ }
+
+ var events = RegisteredEventListener.FromType(handler.GetType());
+
+ foreach (var eventHandler in events)
+ {
+ if (eventHandler.EventType != typeof(TEvent) && !interfaces.Contains(eventHandler.EventType))
+ {
+ continue;
+ }
+
+ yield return new EventHandler(handler, eventHandler);
+ }
+ }
+
+ if (_temporaryEventListeners.TryGetValue(eventType, out var cb2))
+ {
+ foreach (var eventListener in cb2.GetEventListeners())
+ {
+ yield return new EventHandler(null, eventListener);
+ }
+ }
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Events/Game/GameAlterEvent.cs b/Impostor-dev/src/Impostor.Server/Events/Game/GameAlterEvent.cs
new file mode 100644
index 0000000..3fb2368
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Events/Game/GameAlterEvent.cs
@@ -0,0 +1,18 @@
+using Impostor.Api.Events;
+using Impostor.Api.Games;
+
+namespace Impostor.Server.Events
+{
+ public class GameAlterEvent : IGameAlterEvent
+ {
+ public GameAlterEvent(IGame game, bool isPublic)
+ {
+ Game = game;
+ IsPublic = isPublic;
+ }
+
+ public IGame Game { get; }
+
+ public bool IsPublic { get; }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Events/Game/GameCreatedEvent.cs b/Impostor-dev/src/Impostor.Server/Events/Game/GameCreatedEvent.cs
new file mode 100644
index 0000000..57e7a20
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Events/Game/GameCreatedEvent.cs
@@ -0,0 +1,15 @@
+using Impostor.Api.Events;
+using Impostor.Api.Games;
+
+namespace Impostor.Server.Events
+{
+ public class GameCreatedEvent : IGameCreatedEvent
+ {
+ public GameCreatedEvent(IGame game)
+ {
+ Game = game;
+ }
+
+ public IGame Game { get; }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Events/Game/GameDestroyedEvent.cs b/Impostor-dev/src/Impostor.Server/Events/Game/GameDestroyedEvent.cs
new file mode 100644
index 0000000..5ee1b11
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Events/Game/GameDestroyedEvent.cs
@@ -0,0 +1,15 @@
+using Impostor.Api.Events;
+using Impostor.Api.Games;
+
+namespace Impostor.Server.Events
+{
+ public class GameDestroyedEvent : IGameDestroyedEvent
+ {
+ public GameDestroyedEvent(IGame game)
+ {
+ Game = game;
+ }
+
+ public IGame Game { get; }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Events/Game/GameEndedEvent.cs b/Impostor-dev/src/Impostor.Server/Events/Game/GameEndedEvent.cs
new file mode 100644
index 0000000..4ec4dcf
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Events/Game/GameEndedEvent.cs
@@ -0,0 +1,19 @@
+using Impostor.Api.Events;
+using Impostor.Api.Games;
+using Impostor.Api.Innersloth;
+
+namespace Impostor.Server.Events
+{
+ public class GameEndedEvent : IGameEndedEvent
+ {
+ public GameEndedEvent(IGame game, GameOverReason gameOverReason)
+ {
+ Game = game;
+ GameOverReason = gameOverReason;
+ }
+
+ public IGame Game { get; }
+
+ public GameOverReason GameOverReason { get; }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Events/Game/GamePlayerJoinedEvent.cs b/Impostor-dev/src/Impostor.Server/Events/Game/GamePlayerJoinedEvent.cs
new file mode 100644
index 0000000..d728c59
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Events/Game/GamePlayerJoinedEvent.cs
@@ -0,0 +1,19 @@
+using Impostor.Api.Events;
+using Impostor.Api.Games;
+using Impostor.Api.Net;
+
+namespace Impostor.Server.Events
+{
+ public class GamePlayerJoinedEvent : IGamePlayerJoinedEvent
+ {
+ public GamePlayerJoinedEvent(IGame game, IClientPlayer player)
+ {
+ Game = game;
+ Player = player;
+ }
+
+ public IGame Game { get; }
+
+ public IClientPlayer Player { get; }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Events/Game/GamePlayerLeftEvent.cs b/Impostor-dev/src/Impostor.Server/Events/Game/GamePlayerLeftEvent.cs
new file mode 100644
index 0000000..d295103
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Events/Game/GamePlayerLeftEvent.cs
@@ -0,0 +1,22 @@
+using Impostor.Api.Events;
+using Impostor.Api.Games;
+using Impostor.Api.Net;
+
+namespace Impostor.Server.Events
+{
+ public class GamePlayerLeftEvent : IGamePlayerLeftEvent
+ {
+ public GamePlayerLeftEvent(IGame game, IClientPlayer player, bool isBan)
+ {
+ Game = game;
+ Player = player;
+ IsBan = isBan;
+ }
+
+ public IGame Game { get; }
+
+ public IClientPlayer Player { get; }
+
+ public bool IsBan { get; }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Events/Game/GameStartedEvent.cs b/Impostor-dev/src/Impostor.Server/Events/Game/GameStartedEvent.cs
new file mode 100644
index 0000000..d21f9ec
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Events/Game/GameStartedEvent.cs
@@ -0,0 +1,15 @@
+using Impostor.Api.Events;
+using Impostor.Api.Games;
+
+namespace Impostor.Server.Events
+{
+ public class GameStartedEvent : IGameStartedEvent
+ {
+ public GameStartedEvent(IGame game)
+ {
+ Game = game;
+ }
+
+ public IGame Game { get; }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Events/Game/GameStartingEvent.cs b/Impostor-dev/src/Impostor.Server/Events/Game/GameStartingEvent.cs
new file mode 100644
index 0000000..a0763e8
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Events/Game/GameStartingEvent.cs
@@ -0,0 +1,15 @@
+using Impostor.Api.Events;
+using Impostor.Api.Games;
+
+namespace Impostor.Server.Events
+{
+ public class GameStartingEvent : IGameStartingEvent
+ {
+ public GameStartingEvent(IGame game)
+ {
+ Game = game;
+ }
+
+ public IGame Game { get; }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Events/Game/Meeting/MeetingEndedEvent.cs b/Impostor-dev/src/Impostor.Server/Events/Game/Meeting/MeetingEndedEvent.cs
new file mode 100644
index 0000000..cf55f7d
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Events/Game/Meeting/MeetingEndedEvent.cs
@@ -0,0 +1,19 @@
+using Impostor.Api.Events.Meeting;
+using Impostor.Api.Games;
+using Impostor.Api.Net.Inner.Objects;
+
+namespace Impostor.Server.Events.Meeting
+{
+ public class MeetingEndedEvent : IMeetingEndedEvent
+ {
+ public MeetingEndedEvent(IGame game, IInnerMeetingHud meetingHud)
+ {
+ Game = game;
+ MeetingHud = meetingHud;
+ }
+
+ public IGame Game { get; }
+
+ public IInnerMeetingHud MeetingHud { get; }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Events/Game/Meeting/MeetingStartedEvent.cs b/Impostor-dev/src/Impostor.Server/Events/Game/Meeting/MeetingStartedEvent.cs
new file mode 100644
index 0000000..aa689a3
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Events/Game/Meeting/MeetingStartedEvent.cs
@@ -0,0 +1,19 @@
+using Impostor.Api.Events.Meeting;
+using Impostor.Api.Games;
+using Impostor.Api.Net.Inner.Objects;
+
+namespace Impostor.Server.Events.Meeting
+{
+ public class MeetingStartedEvent : IMeetingStartedEvent
+ {
+ public MeetingStartedEvent(IGame game, IInnerMeetingHud meetingHud)
+ {
+ Game = game;
+ MeetingHud = meetingHud;
+ }
+
+ public IGame Game { get; }
+
+ public IInnerMeetingHud MeetingHud { get; }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Events/Game/Player/PlayerChatEvent.cs b/Impostor-dev/src/Impostor.Server/Events/Game/Player/PlayerChatEvent.cs
new file mode 100644
index 0000000..7b7eb22
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Events/Game/Player/PlayerChatEvent.cs
@@ -0,0 +1,26 @@
+using Impostor.Api.Events.Player;
+using Impostor.Api.Games;
+using Impostor.Api.Net;
+using Impostor.Api.Net.Inner.Objects;
+
+namespace Impostor.Server.Events.Player
+{
+ public class PlayerChatEvent : IPlayerChatEvent
+ {
+ public PlayerChatEvent(IGame game, IClientPlayer clientPlayer, IInnerPlayerControl playerControl, string message)
+ {
+ Game = game;
+ ClientPlayer = clientPlayer;
+ PlayerControl = playerControl;
+ Message = message;
+ }
+
+ public IGame Game { get; }
+
+ public IClientPlayer ClientPlayer { get; }
+
+ public IInnerPlayerControl PlayerControl { get; }
+
+ public string Message { get; }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Events/Game/Player/PlayerCompletedTaskEvent.cs b/Impostor-dev/src/Impostor.Server/Events/Game/Player/PlayerCompletedTaskEvent.cs
new file mode 100644
index 0000000..330135d
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Events/Game/Player/PlayerCompletedTaskEvent.cs
@@ -0,0 +1,27 @@
+using Impostor.Api.Events.Player;
+using Impostor.Api.Games;
+using Impostor.Api.Innersloth;
+using Impostor.Api.Net;
+using Impostor.Api.Net.Inner.Objects;
+
+namespace Impostor.Server.Events.Player
+{
+ public class PlayerCompletedTaskEvent : IPlayerCompletedTaskEvent
+ {
+ public PlayerCompletedTaskEvent(IGame game, IClientPlayer clientPlayer, IInnerPlayerControl playerControl, ITaskInfo task)
+ {
+ Game = game;
+ ClientPlayer = clientPlayer;
+ PlayerControl = playerControl;
+ Task = task;
+ }
+
+ public IGame Game { get; }
+
+ public IClientPlayer ClientPlayer { get; }
+
+ public IInnerPlayerControl PlayerControl { get; }
+
+ public ITaskInfo Task { get; }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Events/Game/Player/PlayerDestroyedEvent.cs b/Impostor-dev/src/Impostor.Server/Events/Game/Player/PlayerDestroyedEvent.cs
new file mode 100644
index 0000000..69a20c9
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Events/Game/Player/PlayerDestroyedEvent.cs
@@ -0,0 +1,23 @@
+using Impostor.Api.Events.Player;
+using Impostor.Api.Games;
+using Impostor.Api.Net;
+using Impostor.Api.Net.Inner.Objects;
+
+namespace Impostor.Server.Events.Player
+{
+ public class PlayerDestroyedEvent : IPlayerDestroyedEvent
+ {
+ public PlayerDestroyedEvent(IGame game, IClientPlayer clientPlayer, IInnerPlayerControl playerControl)
+ {
+ Game = game;
+ ClientPlayer = clientPlayer;
+ PlayerControl = playerControl;
+ }
+
+ public IGame Game { get; }
+
+ public IClientPlayer ClientPlayer { get; }
+
+ public IInnerPlayerControl PlayerControl { get; }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Events/Game/Player/PlayerExileEvent.cs b/Impostor-dev/src/Impostor.Server/Events/Game/Player/PlayerExileEvent.cs
new file mode 100644
index 0000000..a8660da
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Events/Game/Player/PlayerExileEvent.cs
@@ -0,0 +1,23 @@
+using Impostor.Api.Events.Player;
+using Impostor.Api.Games;
+using Impostor.Api.Net;
+using Impostor.Api.Net.Inner.Objects;
+
+namespace Impostor.Server.Events.Player
+{
+ public class PlayerExileEvent : IPlayerExileEvent
+ {
+ public PlayerExileEvent(IGame game, IClientPlayer clientPlayer, IInnerPlayerControl playerControl)
+ {
+ Game = game;
+ ClientPlayer = clientPlayer;
+ PlayerControl = playerControl;
+ }
+
+ public IGame Game { get; }
+
+ public IClientPlayer ClientPlayer { get; }
+
+ public IInnerPlayerControl PlayerControl { get; }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Events/Game/Player/PlayerMovementEvent.cs b/Impostor-dev/src/Impostor.Server/Events/Game/Player/PlayerMovementEvent.cs
new file mode 100644
index 0000000..31ac388
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Events/Game/Player/PlayerMovementEvent.cs
@@ -0,0 +1,24 @@
+using Impostor.Api.Events.Player;
+using Impostor.Api.Games;
+using Impostor.Api.Net;
+using Impostor.Api.Net.Inner.Objects;
+
+namespace Impostor.Server.Events.Player
+{
+ // TODO: Finish and use event, needs to be pooled
+ public class PlayerMovementEvent : IPlayerEvent
+ {
+ public PlayerMovementEvent(IGame game, IClientPlayer clientPlayer, IInnerPlayerControl playerControl)
+ {
+ Game = game;
+ ClientPlayer = clientPlayer;
+ PlayerControl = playerControl;
+ }
+
+ public IGame Game { get; }
+
+ public IClientPlayer ClientPlayer { get; }
+
+ public IInnerPlayerControl PlayerControl { get; }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Events/Game/Player/PlayerMurderEvent.cs b/Impostor-dev/src/Impostor.Server/Events/Game/Player/PlayerMurderEvent.cs
new file mode 100644
index 0000000..ca64c35
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Events/Game/Player/PlayerMurderEvent.cs
@@ -0,0 +1,26 @@
+using Impostor.Api.Events.Player;
+using Impostor.Api.Games;
+using Impostor.Api.Net;
+using Impostor.Api.Net.Inner.Objects;
+
+namespace Impostor.Server.Events.Player
+{
+ public class PlayerMurderEvent : IPlayerMurderEvent
+ {
+ public PlayerMurderEvent(IGame game, IClientPlayer clientPlayer, IInnerPlayerControl playerControl, IInnerPlayerControl victim)
+ {
+ Game = game;
+ ClientPlayer = clientPlayer;
+ PlayerControl = playerControl;
+ Victim = victim;
+ }
+
+ public IGame Game { get; }
+
+ public IClientPlayer ClientPlayer { get; }
+
+ public IInnerPlayerControl PlayerControl { get; }
+
+ public IInnerPlayerControl Victim { get; }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Events/Game/Player/PlayerSetStartCounterEvent.cs b/Impostor-dev/src/Impostor.Server/Events/Game/Player/PlayerSetStartCounterEvent.cs
new file mode 100644
index 0000000..71c25d7
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Events/Game/Player/PlayerSetStartCounterEvent.cs
@@ -0,0 +1,26 @@
+using Impostor.Api.Events.Player;
+using Impostor.Api.Games;
+using Impostor.Api.Net;
+using Impostor.Api.Net.Inner.Objects;
+
+namespace Impostor.Server.Events.Player
+{
+ public class PlayerSetStartCounterEvent : IPlayerSetStartCounterEvent
+ {
+ public PlayerSetStartCounterEvent(IGame game, IClientPlayer clientPlayer, IInnerPlayerControl playerControl, byte secondsLeft)
+ {
+ Game = game;
+ ClientPlayer = clientPlayer;
+ PlayerControl = playerControl;
+ SecondsLeft = secondsLeft;
+ }
+
+ public byte SecondsLeft { get; }
+
+ public IClientPlayer ClientPlayer { get; }
+
+ public IInnerPlayerControl PlayerControl { get; }
+
+ public IGame Game { get; }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Events/Game/Player/PlayerSpawnedEvent.cs b/Impostor-dev/src/Impostor.Server/Events/Game/Player/PlayerSpawnedEvent.cs
new file mode 100644
index 0000000..4b6fda1
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Events/Game/Player/PlayerSpawnedEvent.cs
@@ -0,0 +1,23 @@
+using Impostor.Api.Events.Player;
+using Impostor.Api.Games;
+using Impostor.Api.Net;
+using Impostor.Api.Net.Inner.Objects;
+
+namespace Impostor.Server.Events.Player
+{
+ public class PlayerSpawnedEvent : IPlayerSpawnedEvent
+ {
+ public PlayerSpawnedEvent(IGame game, IClientPlayer clientPlayer, IInnerPlayerControl playerControl)
+ {
+ Game = game;
+ ClientPlayer = clientPlayer;
+ PlayerControl = playerControl;
+ }
+
+ public IGame Game { get; }
+
+ public IClientPlayer ClientPlayer { get; }
+
+ public IInnerPlayerControl PlayerControl { get; }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Events/Game/Player/PlayerStartMeetingEvent.cs b/Impostor-dev/src/Impostor.Server/Events/Game/Player/PlayerStartMeetingEvent.cs
new file mode 100644
index 0000000..70cb0d8
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Events/Game/Player/PlayerStartMeetingEvent.cs
@@ -0,0 +1,26 @@
+using Impostor.Api.Events.Player;
+using Impostor.Api.Games;
+using Impostor.Api.Net;
+using Impostor.Api.Net.Inner.Objects;
+
+namespace Impostor.Server.Events.Player
+{
+ public class PlayerStartMeetingEvent : IPlayerStartMeetingEvent
+ {
+ public PlayerStartMeetingEvent(IGame game, IClientPlayer clientPlayer, IInnerPlayerControl playerControl, IInnerPlayerControl? body)
+ {
+ Game = game;
+ ClientPlayer = clientPlayer;
+ PlayerControl = playerControl;
+ Body = body;
+ }
+
+ public IGame Game { get; }
+
+ public IClientPlayer ClientPlayer { get; }
+
+ public IInnerPlayerControl PlayerControl { get; }
+
+ public IInnerPlayerControl? Body { get; }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Events/Game/Player/PlayerVentEvent.cs b/Impostor-dev/src/Impostor.Server/Events/Game/Player/PlayerVentEvent.cs
new file mode 100644
index 0000000..0798d40
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Events/Game/Player/PlayerVentEvent.cs
@@ -0,0 +1,30 @@
+using Impostor.Api.Events.Player;
+using Impostor.Api.Games;
+using Impostor.Api.Innersloth;
+using Impostor.Api.Net;
+using Impostor.Api.Net.Inner.Objects;
+
+namespace Impostor.Server.Events.Player
+{
+ public class PlayerVentEvent : IPlayerVentEvent
+ {
+ public PlayerVentEvent(IGame game, IClientPlayer sender, IInnerPlayerControl innerPlayerPhysics, VentLocation ventId, bool ventEnter)
+ {
+ Game = game;
+ ClientPlayer = sender;
+ PlayerControl = innerPlayerPhysics;
+ VentId = ventId;
+ VentEnter = ventEnter;
+ }
+
+ public IGame Game { get; }
+
+ public IClientPlayer ClientPlayer { get; }
+
+ public IInnerPlayerControl PlayerControl { get; }
+
+ public VentLocation VentId { get; }
+
+ public bool VentEnter { get; }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Events/MultiDisposable.cs b/Impostor-dev/src/Impostor.Server/Events/MultiDisposable.cs
new file mode 100644
index 0000000..b68f064
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Events/MultiDisposable.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+
+namespace Impostor.Server.Events
+{
+ /// <summary>
+ /// Disposes multiple <see cref="IDisposable"/>.
+ /// </summary>
+ internal class MultiDisposable : IDisposable
+ {
+ private readonly IEnumerable<IDisposable> _disposables;
+
+ public MultiDisposable(IEnumerable<IDisposable> disposables)
+ {
+ _disposables = disposables;
+ }
+
+ public void Dispose()
+ {
+ foreach (var disposable in _disposables)
+ {
+ disposable?.Dispose();
+ }
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Events/Register/IRegisteredEventListener.cs b/Impostor-dev/src/Impostor.Server/Events/Register/IRegisteredEventListener.cs
new file mode 100644
index 0000000..479a3f6
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Events/Register/IRegisteredEventListener.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Threading.Tasks;
+using Impostor.Api.Events;
+
+namespace Impostor.Server.Events.Register
+{
+ internal interface IRegisteredEventListener
+ {
+ Type EventType { get; }
+
+ EventPriority Priority { get; }
+
+ ValueTask InvokeAsync(object eventHandler, object @event, IServiceProvider provider);
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Events/Register/InvokedRegisteredEventListener.cs b/Impostor-dev/src/Impostor.Server/Events/Register/InvokedRegisteredEventListener.cs
new file mode 100644
index 0000000..a21c3b1
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Events/Register/InvokedRegisteredEventListener.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Threading.Tasks;
+using Impostor.Api.Events;
+
+namespace Impostor.Server.Events.Register
+{
+ internal class InvokedRegisteredEventListener : IRegisteredEventListener
+ {
+ private readonly IRegisteredEventListener _innerObject;
+ private readonly Func<Func<Task>, Task> _invoker;
+
+ public InvokedRegisteredEventListener(IRegisteredEventListener innerObject, Func<Func<Task>, Task> invoker)
+ {
+ _innerObject = innerObject;
+ _invoker = invoker;
+ }
+
+ public Type EventType => _innerObject.EventType;
+
+ public EventPriority Priority => _innerObject.Priority;
+
+ public ValueTask InvokeAsync(object eventHandler, object @event, IServiceProvider provider)
+ {
+ return new ValueTask(_invoker(() => _innerObject.InvokeAsync(eventHandler, @event, provider).AsTask()));
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Events/Register/ManualRegisteredEventListener.cs b/Impostor-dev/src/Impostor.Server/Events/Register/ManualRegisteredEventListener.cs
new file mode 100644
index 0000000..e81e8f8
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Events/Register/ManualRegisteredEventListener.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Threading.Tasks;
+using Impostor.Api.Events;
+
+namespace Impostor.Server.Events.Register
+{
+ internal class ManualRegisteredEventListener : IRegisteredEventListener
+ {
+ public Type EventType { get; } = typeof(object);
+
+ private readonly IManualEventListener _manualEventListener;
+
+ public ManualRegisteredEventListener(IManualEventListener manualEventListener)
+ {
+ _manualEventListener = manualEventListener;
+ }
+
+ public EventPriority Priority => _manualEventListener.Priority;
+
+ public ValueTask InvokeAsync(object eventHandler, object @event, IServiceProvider provider)
+ {
+ if (@event is IEvent typedEvent)
+ {
+ return _manualEventListener.Execute(typedEvent);
+ }
+
+ return ValueTask.CompletedTask;
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Events/Register/RegisteredEventListener.cs b/Impostor-dev/src/Impostor.Server/Events/Register/RegisteredEventListener.cs
new file mode 100644
index 0000000..120a45e
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Events/Register/RegisteredEventListener.cs
@@ -0,0 +1,166 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Reflection;
+using System.Threading.Tasks;
+using Impostor.Api.Events;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Impostor.Server.Events.Register
+{
+ internal class RegisteredEventListener : IRegisteredEventListener
+ {
+ private static readonly PropertyInfo IsCancelledProperty = typeof(IEventCancelable).GetProperty(nameof(IEventCancelable.IsCancelled))!;
+
+ private static readonly ConcurrentDictionary<Type, RegisteredEventListener[]> Instances = new ConcurrentDictionary<Type, RegisteredEventListener[]>();
+ private readonly Func<object, object, IServiceProvider, ValueTask> _invoker;
+ private readonly Type _eventListenerType;
+
+ public RegisteredEventListener(Type eventType, MethodInfo method, EventListenerAttribute attribute, Type eventListenerType)
+ {
+ EventType = eventType;
+ _eventListenerType = eventListenerType;
+ Priority = attribute.Priority;
+ IgnoreCancelled = attribute.IgnoreCancelled;
+ Method = method.GetFriendlyName(showParameters: false);
+ _invoker = CreateInvoker(method, attribute.IgnoreCancelled);
+ }
+
+ public Type EventType { get; }
+
+ public EventPriority Priority { get; }
+
+ public int PriorityOrder { get; set; }
+
+ public bool IgnoreCancelled { get; }
+
+ public string Method { get; }
+
+ public ValueTask InvokeAsync(object eventHandler, object @event, IServiceProvider provider)
+ {
+ return _invoker(eventHandler, @event, provider);
+ }
+
+ private Func<object, object, IServiceProvider, ValueTask> CreateInvoker(MethodInfo method, bool ignoreCancelled)
+ {
+ var instance = Expression.Parameter(typeof(object), "instance");
+ var eventParameter = Expression.Parameter(typeof(object), "event");
+ var provider = Expression.Parameter(typeof(IServiceProvider), "provider");
+ var @event = Expression.Convert(eventParameter, EventType);
+
+ var getRequiredService = typeof(ServiceProviderServiceExtensions)
+ .GetMethod("GetRequiredService", new[] { typeof(IServiceProvider) });
+
+ if (getRequiredService == null)
+ {
+ throw new InvalidOperationException("The method GetRequiredService could not be found.");
+ }
+
+ var methodArguments = method.GetParameters();
+ var arguments = new Expression[methodArguments.Length];
+
+ for (var i = 0; i < methodArguments.Length; i++)
+ {
+ var methodArgument = methodArguments[i];
+
+ if (typeof(IEvent).IsAssignableFrom(methodArgument.ParameterType)
+ && methodArgument.ParameterType.IsAssignableFrom(EventType))
+ {
+ arguments[i] = @event;
+ }
+ else
+ {
+ arguments[i] = Expression.Call(
+ getRequiredService.MakeGenericMethod(methodArgument.ParameterType),
+ provider);
+ }
+ }
+
+ var returnTarget = Expression.Label(typeof(ValueTask));
+
+ Expression invoke = Expression.Call(Expression.Convert(instance, _eventListenerType), method, arguments);
+
+ if (method.ReturnType == typeof(void))
+ {
+ if (!ignoreCancelled && typeof(IEventCancelable).IsAssignableFrom(EventType))
+ {
+ invoke = Expression.Block(
+ Expression.IfThenElse(
+ Expression.Property(@event, IsCancelledProperty),
+ Expression.Return(returnTarget, Expression.Default(typeof(ValueTask))),
+ Expression.Block(
+ invoke,
+ Expression.Return(returnTarget, Expression.Default(typeof(ValueTask))))),
+ Expression.Label(returnTarget, Expression.Default(typeof(ValueTask))));
+ }
+ else
+ {
+ invoke = Expression.Block(
+ invoke,
+ Expression.Label(returnTarget, Expression.Default(typeof(ValueTask))));
+ }
+ }
+ else if (method.ReturnType == typeof(ValueTask))
+ {
+ if (!ignoreCancelled && typeof(IEventCancelable).IsAssignableFrom(EventType))
+ {
+ invoke = Expression.Block(
+ Expression.IfThenElse(
+ Expression.Property(@event, IsCancelledProperty),
+ Expression.Return(returnTarget, Expression.Default(typeof(ValueTask))),
+ Expression.Return(returnTarget, invoke)),
+ Expression.Label(returnTarget, Expression.Default(typeof(ValueTask))));
+ }
+ }
+ else
+ {
+ throw new InvalidOperationException($"The method {method.GetFriendlyName()} must return void or ValueTask.");
+ }
+
+ return Expression.Lambda<Func<object, object, IServiceProvider, ValueTask>>(invoke, instance, eventParameter, provider)
+ .Compile();
+ }
+
+ public static IReadOnlyList<RegisteredEventListener> FromType(Type type)
+ {
+ return Instances.GetOrAdd(type, t =>
+ {
+ return t.GetMethods()
+ .Where(m => !m.IsStatic && m.GetCustomAttributes(typeof(EventListenerAttribute), false).Any())
+ .SelectMany(m => FromMethod(t, m))
+ .ToArray();
+ });
+ }
+
+ public static IEnumerable<RegisteredEventListener> FromMethod(Type listenerType, MethodInfo methodType)
+ {
+ // Get the return type.
+ var returnType = methodType.ReturnType;
+
+ if (returnType != typeof(void) && returnType != typeof(ValueTask))
+ {
+ throw new InvalidOperationException($"The method {methodType.GetFriendlyName()} does not return void or ValueTask.");
+ }
+
+ // Register the event.
+ foreach (var attribute in methodType.GetCustomAttributes<EventListenerAttribute>(false))
+ {
+ var eventType = attribute.Event;
+
+ if (eventType == null)
+ {
+ if (methodType.GetParameters().Length == 0 || !typeof(IEvent).IsAssignableFrom(methodType.GetParameters()[0].ParameterType))
+ {
+ throw new InvalidOperationException($"The first parameter of the method {methodType.GetFriendlyName()} should be the type {nameof(IEvent)}.");
+ }
+
+ eventType = methodType.GetParameters()[0].ParameterType;
+ }
+
+ yield return new RegisteredEventListener(eventType, methodType, attribute, listenerType);
+ }
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Events/Register/TemporaryEventRegister.cs b/Impostor-dev/src/Impostor.Server/Events/Register/TemporaryEventRegister.cs
new file mode 100644
index 0000000..1446ad1
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Events/Register/TemporaryEventRegister.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading;
+
+namespace Impostor.Server.Events.Register
+{
+ internal class TemporaryEventRegister
+ {
+ private readonly ConcurrentDictionary<int, IRegisteredEventListener> _callbacks;
+ private int _idLast;
+
+ public TemporaryEventRegister()
+ {
+ _callbacks = new ConcurrentDictionary<int, IRegisteredEventListener>();
+ }
+
+ public IEnumerable<IRegisteredEventListener> GetEventListeners()
+ {
+ return _callbacks.Select(i => i.Value);
+ }
+
+ public IDisposable Add(IRegisteredEventListener callback)
+ {
+ var id = Interlocked.Increment(ref _idLast);
+
+ if (!_callbacks.TryAdd(id, callback))
+ {
+ Debug.Fail("Failed to register the event listener");
+ }
+
+ return new UnregisterEvent(this, id);
+ }
+
+ private void Remove(int id)
+ {
+ _callbacks.TryRemove(id, out _);
+ }
+
+ private class UnregisterEvent : IDisposable
+ {
+ private readonly TemporaryEventRegister _register;
+ private readonly int _id;
+
+ public UnregisterEvent(TemporaryEventRegister register, int id)
+ {
+ _register = register;
+ _id = id;
+ }
+
+ public void Dispose()
+ {
+ _register.Remove(_id);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Events/Register/WrappedRegisteredEventListener.cs b/Impostor-dev/src/Impostor.Server/Events/Register/WrappedRegisteredEventListener.cs
new file mode 100644
index 0000000..dd668c5
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Events/Register/WrappedRegisteredEventListener.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Threading.Tasks;
+using Impostor.Api.Events;
+
+namespace Impostor.Server.Events.Register
+{
+ internal class WrappedRegisteredEventListener : IRegisteredEventListener
+ {
+ private readonly IRegisteredEventListener _innerObject;
+ private readonly object _object;
+
+ public WrappedRegisteredEventListener(IRegisteredEventListener innerObject, object o)
+ {
+ _innerObject = innerObject;
+ _object = o;
+ }
+
+ public Type EventType => _innerObject.EventType;
+
+ public EventPriority Priority => _innerObject.Priority;
+
+ public ValueTask InvokeAsync(object eventHandler, object @event, IServiceProvider provider)
+ {
+ return _innerObject.InvokeAsync(_object, @event, provider);
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Extensions/MessageReaderExtensions.cs b/Impostor-dev/src/Impostor.Server/Extensions/MessageReaderExtensions.cs
new file mode 100644
index 0000000..5f25e89
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Extensions/MessageReaderExtensions.cs
@@ -0,0 +1,15 @@
+using Impostor.Api.Net.Messages;
+using Impostor.Server.Net.Inner;
+using Impostor.Server.Net.State;
+
+namespace Impostor.Server
+{
+ internal static class MessageReaderExtensions
+ {
+ public static T ReadNetObject<T>(this IMessageReader reader, Game game)
+ where T : InnerNetObject
+ {
+ return game.FindObjectByNetId<T>(reader.ReadPackedUInt32());
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Extensions/NodeLocatorExtensions.cs b/Impostor-dev/src/Impostor.Server/Extensions/NodeLocatorExtensions.cs
new file mode 100644
index 0000000..370bca6
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Extensions/NodeLocatorExtensions.cs
@@ -0,0 +1,13 @@
+using System.Threading.Tasks;
+using Impostor.Server.Net.Redirector;
+
+namespace Impostor.Server
+{
+ public static class NodeLocatorExtensions
+ {
+ public static async ValueTask<bool> ExistsAsync(this INodeLocator nodeLocator, string gameCode)
+ {
+ return await nodeLocator.FindAsync(gameCode) != null;
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Extensions/TypeExtensions.cs b/Impostor-dev/src/Impostor.Server/Extensions/TypeExtensions.cs
new file mode 100644
index 0000000..55d42fb
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Extensions/TypeExtensions.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Reflection;
+
+namespace Impostor.Server
+{
+ internal static class TypeExtensions
+ {
+ /// <summary>
+ /// Get the friendly name for the type.
+ /// </summary>
+ /// <param name="type">The type.</param>
+ /// <returns>The friendly name.</returns>
+ [SuppressMessage("ReSharper", "SA1503", Justification = "Readability")]
+ public static string GetFriendlyName(this Type type)
+ {
+ if (type == null)
+ return "null";
+ if (type == typeof(int))
+ return "int";
+ if (type == typeof(short))
+ return "short";
+ if (type == typeof(byte))
+ return "byte";
+ if (type == typeof(bool))
+ return "bool";
+ if (type == typeof(long))
+ return "long";
+ if (type == typeof(float))
+ return "float";
+ if (type == typeof(double))
+ return "double";
+ if (type == typeof(decimal))
+ return "decimal";
+ if (type == typeof(string))
+ return "string";
+ if (type.IsGenericType)
+ return type.Name.Split('`')[0] + "<" + string.Join(", ", type.GetGenericArguments().Select(GetFriendlyName).ToArray()) + ">";
+ return type.Name;
+ }
+
+ /// <summary>
+ /// Get the friendly name for the method.
+ /// </summary>
+ /// <param name="method">The method.</param>
+ /// <param name="showParameters">True if the parameters should be included in the name.</param>
+ /// <returns>Friendly name of the method</returns>
+ public static string GetFriendlyName(this MethodBase method, bool showParameters = true)
+ {
+ var str = method.Name;
+
+ if (method.DeclaringType != null)
+ {
+ str = method.DeclaringType.GetFriendlyName() + '.' + str;
+ }
+
+ if (showParameters)
+ {
+ var parameters = string.Join(", ", method.GetParameters().Select(p => p.ParameterType.GetFriendlyName()));
+ str += $"({parameters})";
+ }
+
+ return str;
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Impostor.Server.csproj b/Impostor-dev/src/Impostor.Server/Impostor.Server.csproj
new file mode 100644
index 0000000..ad5a6db
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Impostor.Server.csproj
@@ -0,0 +1,58 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net5.0</TargetFramework>
+ <RuntimeIdentifiers>win-x64;linux-x64;linux-arm;linux-arm64;osx-x64</RuntimeIdentifiers>
+ <IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
+ <ApplicationIcon>icon.ico</ApplicationIcon>
+ <CodeAnalysisRuleSet>ProjectRules.ruleset</CodeAnalysisRuleSet>
+ <Nullable>enable</Nullable>
+ <SelfContained>false</SelfContained>
+ </PropertyGroup>
+
+ <PropertyGroup>
+ <AssemblyName>Impostor.Server</AssemblyName>
+ <AssemblyTitle>Impostor.Server</AssemblyTitle>
+ <Product>Impostor.Server</Product>
+ <Copyright>Copyright © AeonLucid 2020</Copyright>
+ <Version>1.0.0</Version>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\Impostor.Api\Impostor.Api.csproj" />
+ <ProjectReference Include="..\Impostor.Hazel\Impostor.Hazel.csproj" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="5.0.0" />
+ <PackageReference Include="Microsoft.Extensions.FileSystemGlobbing" Version="5.0.0" />
+ <PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
+ <PackageReference Include="Microsoft.Extensions.ObjectPool" Version="5.0.0" />
+ <PackageReference Include="Serilog.Extensions.Hosting" Version="3.1.0" />
+ <PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ </PackageReference>
+ </ItemGroup>
+
+ <ItemGroup>
+ <Content Include="config.json">
+ <CopyToPublishDirectory>Always</CopyToPublishDirectory>
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
+ </Content>
+ <Content Include="config.*.json">
+ <CopyToPublishDirectory>Never</CopyToPublishDirectory>
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
+ </Content>
+ <Content Include="config-full.json">
+ <CopyToPublishDirectory>Never</CopyToPublishDirectory>
+ <CopyToOutputDirectory>Never</CopyToOutputDirectory>
+ <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
+ </Content>
+ </ItemGroup>
+
+</Project> \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Impostor.Server.csproj.DotSettings b/Impostor-dev/src/Impostor.Server/Impostor.Server.csproj.DotSettings
new file mode 100644
index 0000000..5c07b47
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Impostor.Server.csproj.DotSettings
@@ -0,0 +1,3 @@
+<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
+ <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=events_005Cgame/@EntryIndexedValue">True</s:Boolean>
+ <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=extensions/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary> \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Net/Client.cs b/Impostor-dev/src/Impostor.Server/Net/Client.cs
new file mode 100644
index 0000000..87a1bb4
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Client.cs
@@ -0,0 +1,338 @@
+using System;
+using System.Threading.Tasks;
+using Impostor.Api;
+using Impostor.Api.Games;
+using Impostor.Api.Innersloth;
+using Impostor.Api.Net;
+using Impostor.Api.Net.Messages;
+using Impostor.Api.Net.Messages.C2S;
+using Impostor.Api.Net.Messages.S2C;
+using Impostor.Hazel;
+using Impostor.Server.Config;
+using Impostor.Server.Net.Manager;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+
+namespace Impostor.Server.Net
+{
+ internal class Client : ClientBase
+ {
+ private readonly ILogger<Client> _logger;
+ private readonly AntiCheatConfig _antiCheatConfig;
+ private readonly ClientManager _clientManager;
+ private readonly GameManager _gameManager;
+
+ public Client(ILogger<Client> logger, IOptions<AntiCheatConfig> antiCheatOptions, ClientManager clientManager, GameManager gameManager, string name, IHazelConnection connection)
+ : base(name, connection)
+ {
+ _logger = logger;
+ _antiCheatConfig = antiCheatOptions.Value;
+ _clientManager = clientManager;
+ _gameManager = gameManager;
+ }
+
+ public override async ValueTask HandleMessageAsync(IMessageReader reader, MessageType messageType)
+ {
+ var flag = reader.Tag;
+
+ _logger.LogTrace("[{0}] Server got {1}.", Id, flag);
+
+ switch (flag)
+ {
+ case MessageFlags.HostGame:
+ {
+ // Read game settings.
+ var gameInfo = Message00HostGameC2S.Deserialize(reader);
+
+ // Create game.
+ var game = await _gameManager.CreateAsync(gameInfo);
+
+ // Code in the packet below will be used in JoinGame.
+ using (var writer = MessageWriter.Get(MessageType.Reliable))
+ {
+ Message00HostGameS2C.Serialize(writer, game.Code);
+ await Connection.SendAsync(writer);
+ }
+
+ break;
+ }
+
+ case MessageFlags.JoinGame:
+ {
+ Message01JoinGameC2S.Deserialize(
+ reader,
+ out var gameCode,
+ out _);
+
+ var game = _gameManager.Find(gameCode);
+ if (game == null)
+ {
+ await DisconnectAsync(DisconnectReason.GameMissing);
+ return;
+ }
+
+ var result = await game.AddClientAsync(this);
+
+ switch (result.Error)
+ {
+ case GameJoinError.None:
+ break;
+ case GameJoinError.InvalidClient:
+ await DisconnectAsync(DisconnectReason.Custom, "Client is in an invalid state.");
+ break;
+ case GameJoinError.Banned:
+ await DisconnectAsync(DisconnectReason.Banned);
+ break;
+ case GameJoinError.GameFull:
+ await DisconnectAsync(DisconnectReason.GameFull);
+ break;
+ case GameJoinError.InvalidLimbo:
+ await DisconnectAsync(DisconnectReason.Custom, "Invalid limbo state while joining.");
+ break;
+ case GameJoinError.GameStarted:
+ await DisconnectAsync(DisconnectReason.GameStarted);
+ break;
+ case GameJoinError.GameDestroyed:
+ await DisconnectAsync(DisconnectReason.Custom, DisconnectMessages.Destroyed);
+ break;
+ case GameJoinError.Custom:
+ await DisconnectAsync(DisconnectReason.Custom, result.Message);
+ break;
+ default:
+ await DisconnectAsync(DisconnectReason.Custom, "Unknown error.");
+ break;
+ }
+
+ break;
+ }
+
+ case MessageFlags.StartGame:
+ {
+ if (!IsPacketAllowed(reader, true))
+ {
+ return;
+ }
+
+ await Player.Game.HandleStartGame(reader);
+ break;
+ }
+
+ // No idea how this flag is triggered.
+ case MessageFlags.RemoveGame:
+ break;
+
+ case MessageFlags.RemovePlayer:
+ {
+ if (!IsPacketAllowed(reader, true))
+ {
+ return;
+ }
+
+ Message04RemovePlayerC2S.Deserialize(
+ reader,
+ out var playerId,
+ out var reason);
+
+ await Player.Game.HandleRemovePlayer(playerId, (DisconnectReason)reason);
+ break;
+ }
+
+ case MessageFlags.GameData:
+ case MessageFlags.GameDataTo:
+ {
+ if (!IsPacketAllowed(reader, false))
+ {
+ return;
+ }
+
+ var toPlayer = flag == MessageFlags.GameDataTo;
+
+ // Handle packet.
+ using var readerCopy = reader.Copy();
+
+ // TODO: Return value, either a bool (to cancel) or a writer (to cancel (null) or modify/overwrite).
+ try
+ {
+ var verified = await Player.Game.HandleGameDataAsync(readerCopy, Player, toPlayer);
+ if (verified)
+ {
+ // Broadcast packet to all other players.
+ using (var writer = MessageWriter.Get(messageType))
+ {
+ if (toPlayer)
+ {
+ var target = reader.ReadPackedInt32();
+ reader.CopyTo(writer);
+ await Player.Game.SendToAsync(writer, target);
+ }
+ else
+ {
+ reader.CopyTo(writer);
+ await Player.Game.SendToAllExceptAsync(writer, Id);
+ }
+ }
+ }
+ }
+ catch (ImpostorCheatException e)
+ {
+ if (_antiCheatConfig.BanIpFromGame)
+ {
+ Player.Game.BanIp(Connection.EndPoint.Address);
+ }
+
+ await DisconnectAsync(DisconnectReason.Hacking, e.Message);
+ }
+
+ break;
+ }
+
+ case MessageFlags.EndGame:
+ {
+ if (!IsPacketAllowed(reader, true))
+ {
+ return;
+ }
+
+ Message08EndGameC2S.Deserialize(
+ reader,
+ out var gameOverReason);
+
+ await Player.Game.HandleEndGame(reader, gameOverReason);
+ break;
+ }
+
+ case MessageFlags.AlterGame:
+ {
+ if (!IsPacketAllowed(reader, true))
+ {
+ return;
+ }
+
+ Message10AlterGameC2S.Deserialize(
+ reader,
+ out var gameTag,
+ out var value);
+
+ if (gameTag != AlterGameTags.ChangePrivacy)
+ {
+ return;
+ }
+
+ await Player.Game.HandleAlterGame(reader, Player, value);
+ break;
+ }
+
+ case MessageFlags.KickPlayer:
+ {
+ if (!IsPacketAllowed(reader, true))
+ {
+ return;
+ }
+
+ Message11KickPlayerC2S.Deserialize(
+ reader,
+ out var playerId,
+ out var isBan);
+
+ await Player.Game.HandleKickPlayer(playerId, isBan);
+ break;
+ }
+
+ case MessageFlags.GetGameListV2:
+ {
+ Message16GetGameListC2S.Deserialize(reader, out var options);
+ await OnRequestGameListAsync(options);
+ break;
+ }
+
+ default:
+ _logger.LogWarning("Server received unknown flag {0}.", flag);
+ break;
+ }
+
+#if DEBUG
+ if (flag != MessageFlags.GameData &&
+ flag != MessageFlags.GameDataTo &&
+ flag != MessageFlags.EndGame &&
+ reader.Position < reader.Length)
+ {
+ _logger.LogWarning(
+ "Server did not consume all bytes from {0} ({1} < {2}).",
+ flag,
+ reader.Position,
+ reader.Length);
+ }
+#endif
+ }
+
+ public override async ValueTask HandleDisconnectAsync(string reason)
+ {
+ try
+ {
+ if (Player != null)
+ {
+ await Player.Game.HandleRemovePlayer(Id, DisconnectReason.ExitGame);
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Exception caught in client disconnection.");
+ }
+
+ _logger.LogInformation("Client {0} disconnecting, reason: {1}", Id, reason);
+ _clientManager.Remove(this);
+ }
+
+ private bool IsPacketAllowed(IMessageReader message, bool hostOnly)
+ {
+ if (Player == null)
+ {
+ return false;
+ }
+
+ var game = Player.Game;
+
+ // GameCode must match code of the current game assigned to the player.
+ if (message.ReadInt32() != game.Code)
+ {
+ return false;
+ }
+
+ // Some packets should only be sent by the host of the game.
+ if (hostOnly)
+ {
+ if (game.HostId == Id)
+ {
+ return true;
+ }
+
+ _logger.LogWarning("[{0}] Client sent packet only allowed by the host ({1}).", Id, game.HostId);
+ return false;
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// Triggered when the connected client requests the game listing.
+ /// </summary>
+ /// <param name="options">
+ /// All options given.
+ /// At this moment, the client can only specify the map, impostor count and chat language.
+ /// </param>
+ private ValueTask OnRequestGameListAsync(GameOptionsData options)
+ {
+ using var message = MessageWriter.Get(MessageType.Reliable);
+
+ var games = _gameManager.FindListings((MapFlags)options.MapId, options.NumImpostors, options.Keywords);
+
+ var skeldGameCount = _gameManager.GetGameCount(MapFlags.Skeld);
+ var miraHqGameCount = _gameManager.GetGameCount(MapFlags.MiraHQ);
+ var polusGameCount = _gameManager.GetGameCount(MapFlags.Polus);
+
+ Message16GetGameListS2C.Serialize(message, skeldGameCount, miraHqGameCount, polusGameCount, games);
+
+ return Connection.SendAsync(message);
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Net/ClientBase.cs b/Impostor-dev/src/Impostor.Server/Net/ClientBase.cs
new file mode 100644
index 0000000..5279192
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/ClientBase.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Impostor.Api.Innersloth;
+using Impostor.Api.Net;
+using Impostor.Api.Net.Messages;
+using Impostor.Api.Net.Messages.S2C;
+using Impostor.Hazel;
+using Impostor.Server.Net.State;
+
+namespace Impostor.Server.Net
+{
+ internal abstract class ClientBase : IClient
+ {
+ protected ClientBase(string name, IHazelConnection connection)
+ {
+ Name = name;
+ Connection = connection;
+ Items = new ConcurrentDictionary<object, object>();
+ }
+
+ public int Id { get; set; }
+
+ public string Name { get; }
+
+ public IHazelConnection Connection { get; }
+
+ public IDictionary<object, object> Items { get; }
+
+ public ClientPlayer? Player { get; set; }
+
+ IClientPlayer? IClient.Player => Player;
+
+ public abstract ValueTask HandleMessageAsync(IMessageReader message, MessageType messageType);
+
+ public abstract ValueTask HandleDisconnectAsync(string reason);
+
+
+ public async ValueTask DisconnectAsync(DisconnectReason reason, string? message = null)
+ {
+ if (!Connection.IsConnected)
+ {
+ return;
+ }
+
+ using var packet = MessageWriter.Get(MessageType.Reliable);
+ Message01JoinGameS2C.SerializeError(packet, false, reason, message);
+
+ await Connection.SendAsync(packet);
+
+ // Need this to show the correct message, otherwise it shows a generic disconnect message.
+ await Task.Delay(TimeSpan.FromMilliseconds(250));
+
+ await Connection.DisconnectAsync(message ?? reason.ToString());
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Net/Factories/ClientFactory.cs b/Impostor-dev/src/Impostor.Server/Net/Factories/ClientFactory.cs
new file mode 100644
index 0000000..ad1fdc7
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Factories/ClientFactory.cs
@@ -0,0 +1,24 @@
+using System;
+using Impostor.Api.Net;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Impostor.Server.Net.Factories
+{
+ internal class ClientFactory<TClient> : IClientFactory
+ where TClient : ClientBase
+ {
+ private readonly IServiceProvider _serviceProvider;
+
+ public ClientFactory(IServiceProvider serviceProvider)
+ {
+ _serviceProvider = serviceProvider;
+ }
+
+ public ClientBase Create(IHazelConnection connection, string name, int clientVersion)
+ {
+ var client = ActivatorUtilities.CreateInstance<TClient>(_serviceProvider, name, connection);
+ connection.Client = client;
+ return client;
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Net/Factories/IClientFactory.cs b/Impostor-dev/src/Impostor.Server/Net/Factories/IClientFactory.cs
new file mode 100644
index 0000000..6859ae3
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Factories/IClientFactory.cs
@@ -0,0 +1,9 @@
+using Impostor.Api.Net;
+
+namespace Impostor.Server.Net.Factories
+{
+ internal interface IClientFactory
+ {
+ ClientBase Create(IHazelConnection connection, string name, int clientVersion);
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Net/GameCodeFactory.cs b/Impostor-dev/src/Impostor.Server/Net/GameCodeFactory.cs
new file mode 100644
index 0000000..2a0553f
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/GameCodeFactory.cs
@@ -0,0 +1,12 @@
+using Impostor.Api.Games;
+
+namespace Impostor.Server.Net
+{
+ public class GameCodeFactory : IGameCodeFactory
+ {
+ public GameCode Create()
+ {
+ return GameCode.Create();
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Net/Hazel/HazelConnection.cs b/Impostor-dev/src/Impostor.Server/Net/Hazel/HazelConnection.cs
new file mode 100644
index 0000000..45d2be8
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Hazel/HazelConnection.cs
@@ -0,0 +1,74 @@
+using System.Net;
+using System.Threading.Tasks;
+using Impostor.Api.Net;
+using Impostor.Api.Net.Messages;
+using Impostor.Hazel;
+using Microsoft.Extensions.Logging;
+
+namespace Impostor.Server.Net.Hazel
+{
+ internal class HazelConnection : IHazelConnection
+ {
+ private readonly ILogger<HazelConnection> _logger;
+
+ public HazelConnection(Connection innerConnection, ILogger<HazelConnection> logger)
+ {
+ _logger = logger;
+ InnerConnection = innerConnection;
+ innerConnection.DataReceived = ConnectionOnDataReceived;
+ innerConnection.Disconnected = ConnectionOnDisconnected;
+ }
+
+ public Connection InnerConnection { get; }
+
+ public IPEndPoint EndPoint => InnerConnection.EndPoint;
+
+ public bool IsConnected => InnerConnection.State == ConnectionState.Connected;
+
+ public IClient Client { get; set; }
+
+ public ValueTask SendAsync(IMessageWriter writer)
+ {
+ return InnerConnection.SendAsync(writer);
+ }
+
+ public ValueTask DisconnectAsync(string reason)
+ {
+ return InnerConnection.Disconnect(reason);
+ }
+
+ public void DisposeInnerConnection()
+ {
+ InnerConnection.Dispose();
+ }
+
+ private async ValueTask ConnectionOnDisconnected(DisconnectedEventArgs e)
+ {
+ if (Client != null)
+ {
+ await Client.HandleDisconnectAsync(e.Reason);
+ }
+ }
+
+ private async ValueTask ConnectionOnDataReceived(DataReceivedEventArgs e)
+ {
+ if (Client == null)
+ {
+ return;
+ }
+
+ while (true)
+ {
+ if (e.Message.Position >= e.Message.Length)
+ {
+ break;
+ }
+
+ using (var message = e.Message.ReadMessage())
+ {
+ await Client.HandleMessageAsync(message, e.Type);
+ }
+ }
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/GameDataTag.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/GameDataTag.cs
new file mode 100644
index 0000000..fa9ddb8
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Inner/GameDataTag.cs
@@ -0,0 +1,13 @@
+namespace Impostor.Server.Net.Inner
+{
+ public static class GameDataTag
+ {
+ public const byte DataFlag = 1;
+ public const byte RpcFlag = 2;
+ public const byte SpawnFlag = 4;
+ public const byte DespawnFlag = 5;
+ public const byte SceneChangeFlag = 6;
+ public const byte ReadyFlag = 7;
+ public const byte ChangeSettingsFlag = 8;
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/GameObject.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/GameObject.cs
new file mode 100644
index 0000000..9b53d70
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Inner/GameObject.cs
@@ -0,0 +1,29 @@
+using System.Collections.Generic;
+
+namespace Impostor.Server.Net.Inner
+{
+ internal class GameObject
+ {
+ public GameObject()
+ {
+ Components = new List<object>();
+ }
+
+ protected List<object> Components { get; }
+
+ public List<T> GetComponentsInChildren<T>()
+ {
+ var result = new List<T>();
+
+ foreach (var component in Components)
+ {
+ if (component is T c)
+ {
+ result.Add(c);
+ }
+ }
+
+ return result;
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/InnerNetObject.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/InnerNetObject.cs
new file mode 100644
index 0000000..78f1a55
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Inner/InnerNetObject.cs
@@ -0,0 +1,31 @@
+using System.Threading.Tasks;
+using Impostor.Api.Net;
+using Impostor.Api.Net.Inner;
+using Impostor.Api.Net.Messages;
+using Impostor.Server.Net.State;
+
+namespace Impostor.Server.Net.Inner
+{
+ internal abstract class InnerNetObject : GameObject, IInnerNetObject
+ {
+ private const int HostInheritId = -2;
+
+ public uint NetId { get; internal set; }
+
+ public int OwnerId { get; internal set; }
+
+ public SpawnFlags SpawnFlags { get; internal set; }
+
+ public abstract ValueTask HandleRpc(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader);
+
+ public abstract bool Serialize(IMessageWriter writer, bool initialState);
+
+ public abstract void Deserialize(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState);
+
+ public bool IsOwnedBy(IClientPlayer player)
+ {
+ return OwnerId == player.Client.Id ||
+ (OwnerId == HostInheritId && player.IsHost);
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Components/InnerCustomNetworkTransform.Api.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Components/InnerCustomNetworkTransform.Api.cs
new file mode 100644
index 0000000..6cdd6b9
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Components/InnerCustomNetworkTransform.Api.cs
@@ -0,0 +1,25 @@
+using System.Numerics;
+using System.Threading.Tasks;
+using Impostor.Api.Net.Inner.Objects.Components;
+
+namespace Impostor.Server.Net.Inner.Objects.Components
+{
+ internal partial class InnerCustomNetworkTransform : IInnerCustomNetworkTransform
+ {
+ public async ValueTask SnapToAsync(Vector2 position)
+ {
+ var minSid = (ushort)(_lastSequenceId + 5U);
+
+ // Snap in the server.
+ SnapTo(position, minSid);
+
+ // Broadcast to all clients.
+ using (var writer = _game.StartRpc(NetId, RpcCalls.SnapTo))
+ {
+ WriteVector2(writer, position);
+ writer.Write(_lastSequenceId);
+ await _game.FinishRpcAsync(writer);
+ }
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Components/InnerCustomNetworkTransform.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Components/InnerCustomNetworkTransform.cs
new file mode 100644
index 0000000..d261c11
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Components/InnerCustomNetworkTransform.cs
@@ -0,0 +1,148 @@
+using System.Numerics;
+using System.Threading.Tasks;
+using Impostor.Api;
+using Impostor.Api.Innersloth;
+using Impostor.Api.Net;
+using Impostor.Api.Net.Messages;
+using Impostor.Server.Net.State;
+using Microsoft.Extensions.Logging;
+
+namespace Impostor.Server.Net.Inner.Objects.Components
+{
+ internal partial class InnerCustomNetworkTransform : InnerNetObject
+ {
+ private static readonly FloatRange XRange = new FloatRange(-40f, 40f);
+ private static readonly FloatRange YRange = new FloatRange(-40f, 40f);
+
+ private readonly ILogger<InnerCustomNetworkTransform> _logger;
+ private readonly InnerPlayerControl _playerControl;
+ private readonly Game _game;
+
+ private ushort _lastSequenceId;
+ private Vector2 _targetSyncPosition;
+ private Vector2 _targetSyncVelocity;
+
+ public InnerCustomNetworkTransform(ILogger<InnerCustomNetworkTransform> logger, InnerPlayerControl playerControl, Game game)
+ {
+ _logger = logger;
+ _playerControl = playerControl;
+ _game = game;
+ }
+
+ private static bool SidGreaterThan(ushort newSid, ushort prevSid)
+ {
+ var num = (ushort)(prevSid + (uint) short.MaxValue);
+
+ return (int) prevSid < (int) num
+ ? newSid > prevSid && newSid <= num
+ : newSid > prevSid || newSid <= num;
+ }
+
+ private static void WriteVector2(IMessageWriter writer, Vector2 vec)
+ {
+ writer.Write((ushort)(XRange.ReverseLerp(vec.X) * (double) ushort.MaxValue));
+ writer.Write((ushort)(YRange.ReverseLerp(vec.Y) * (double) ushort.MaxValue));
+ }
+
+ private static Vector2 ReadVector2(IMessageReader reader)
+ {
+ var v1 = reader.ReadUInt16() / (float) ushort.MaxValue;
+ var v2 = reader.ReadUInt16() / (float) ushort.MaxValue;
+
+ return new Vector2(XRange.Lerp(v1), YRange.Lerp(v2));
+ }
+
+ public override ValueTask HandleRpc(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader)
+ {
+ if (call == RpcCalls.SnapTo)
+ {
+ if (!sender.IsOwner(this))
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SnapTo)} to an unowned {nameof(InnerPlayerControl)}");
+ }
+
+ if (target != null)
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SnapTo)} to a specific player instead of broadcast");
+ }
+
+ if (!sender.Character.PlayerInfo.IsImpostor)
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SnapTo)} as crewmate");
+ }
+
+ SnapTo(ReadVector2(reader), reader.ReadUInt16());
+ }
+ else
+ {
+ _logger.LogWarning("{0}: Unknown rpc call {1}", nameof(InnerCustomNetworkTransform), call);
+ }
+
+ return default;
+ }
+
+ public override bool Serialize(IMessageWriter writer, bool initialState)
+ {
+ if (initialState)
+ {
+ writer.Write(_lastSequenceId);
+ WriteVector2(writer, _targetSyncPosition);
+ WriteVector2(writer, _targetSyncVelocity);
+ return true;
+ }
+
+ // TODO: DirtyBits == 0 return false.
+ _lastSequenceId++;
+
+ writer.Write(_lastSequenceId);
+ WriteVector2(writer, _targetSyncPosition);
+ WriteVector2(writer, _targetSyncVelocity);
+ return true;
+ }
+
+ public override void Deserialize(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState)
+ {
+ var sequenceId = reader.ReadUInt16();
+
+ if (initialState)
+ {
+ _lastSequenceId = sequenceId;
+ _targetSyncPosition = ReadVector2(reader);
+ _targetSyncVelocity = ReadVector2(reader);
+ }
+ else
+ {
+ if (!sender.IsOwner(this))
+ {
+ throw new ImpostorCheatException($"Client attempted to send unowned {nameof(InnerCustomNetworkTransform)} data");
+ }
+
+ if (target != null)
+ {
+ throw new ImpostorCheatException($"Client attempted to send {nameof(InnerCustomNetworkTransform)} data to a specific player, must be broadcast");
+ }
+
+ if (!SidGreaterThan(sequenceId, _lastSequenceId))
+ {
+ return;
+ }
+
+ _lastSequenceId = sequenceId;
+ _targetSyncPosition = ReadVector2(reader);
+ _targetSyncVelocity = ReadVector2(reader);
+ }
+ }
+
+ private void SnapTo(Vector2 position, ushort minSid)
+ {
+ if (!SidGreaterThan(minSid, _lastSequenceId))
+ {
+ return;
+ }
+
+ _lastSequenceId = minSid;
+ _targetSyncPosition = position;
+ _targetSyncVelocity = Vector2.Zero;
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Components/InnerPlayerPhysics.Api.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Components/InnerPlayerPhysics.Api.cs
new file mode 100644
index 0000000..6af54a0
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Components/InnerPlayerPhysics.Api.cs
@@ -0,0 +1,8 @@
+using Impostor.Api.Net.Inner.Objects.Components;
+
+namespace Impostor.Server.Net.Inner.Objects.Components
+{
+ internal partial class InnerPlayerPhysics : IInnerPlayerPhysics
+ {
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Components/InnerPlayerPhysics.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Components/InnerPlayerPhysics.cs
new file mode 100644
index 0000000..29bc996
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Components/InnerPlayerPhysics.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Threading.Tasks;
+using Impostor.Api;
+using Impostor.Api.Events.Managers;
+using Impostor.Api.Innersloth;
+using Impostor.Api.Net;
+using Impostor.Api.Net.Messages;
+using Impostor.Server.Events.Player;
+using Impostor.Server.Net.State;
+using Microsoft.Extensions.Logging;
+
+namespace Impostor.Server.Net.Inner.Objects.Components
+{
+ internal partial class InnerPlayerPhysics : InnerNetObject
+ {
+ private readonly ILogger<InnerPlayerPhysics> _logger;
+ private readonly InnerPlayerControl _playerControl;
+ private readonly IEventManager _eventManager;
+ private readonly Game _game;
+
+ public InnerPlayerPhysics(ILogger<InnerPlayerPhysics> logger, InnerPlayerControl playerControl, IEventManager eventManager, Game game)
+ {
+ _logger = logger;
+ _playerControl = playerControl;
+ _eventManager = eventManager;
+ _game = game;
+ }
+
+ public override async ValueTask HandleRpc(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader)
+ {
+ if (call != RpcCalls.EnterVent && call != RpcCalls.ExitVent)
+ {
+ _logger.LogWarning("{0}: Unknown rpc call {1}", nameof(InnerPlayerPhysics), call);
+ return;
+ }
+
+ if (!sender.IsOwner(this))
+ {
+ throw new ImpostorCheatException($"Client sent {call} to an unowned {nameof(InnerPlayerControl)}");
+ }
+
+ if (target != null)
+ {
+ throw new ImpostorCheatException($"Client sent {call} to a specific player instead of broadcast");
+ }
+
+ if (!sender.Character.PlayerInfo.IsImpostor)
+ {
+ throw new ImpostorCheatException($"Client sent {call} as crewmate");
+ }
+
+ var ventId = reader.ReadPackedUInt32();
+ var ventEnter = call == RpcCalls.EnterVent;
+
+ await _eventManager.CallAsync(new PlayerVentEvent(_game, sender, _playerControl, (VentLocation)ventId, ventEnter));
+
+ return;
+ }
+
+ public override bool Serialize(IMessageWriter writer, bool initialState)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void Deserialize(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Components/InnerVoteBanSystem.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Components/InnerVoteBanSystem.cs
new file mode 100644
index 0000000..58b9b54
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Components/InnerVoteBanSystem.cs
@@ -0,0 +1,88 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Impostor.Api;
+using Impostor.Api.Net;
+using Impostor.Api.Net.Inner.Objects;
+using Impostor.Api.Net.Messages;
+using Impostor.Server.Net.State;
+using Microsoft.Extensions.Logging;
+
+namespace Impostor.Server.Net.Inner.Objects.Components
+{
+ internal class InnerVoteBanSystem : InnerNetObject, IInnerVoteBanSystem
+ {
+ private readonly ILogger<InnerVoteBanSystem> _logger;
+ private readonly Dictionary<int, int[]> _votes;
+
+ public InnerVoteBanSystem(ILogger<InnerVoteBanSystem> logger)
+ {
+ _logger = logger;
+ _votes = new Dictionary<int, int[]>();
+ }
+
+ public override ValueTask HandleRpc(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader)
+ {
+ if (call != RpcCalls.AddVote)
+ {
+ _logger.LogWarning("{0}: Unknown rpc call {1}", nameof(InnerVoteBanSystem), call);
+ return default;
+ }
+
+ var clientId = reader.ReadInt32();
+ if (clientId != sender.Client.Id)
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.AddVote)} as other client");
+ }
+
+ if (target != null)
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CastVote)} to wrong destinition, must be broadcast");
+ }
+
+ var targetClientId = reader.ReadInt32();
+
+ // TODO: Use.
+
+ return default;
+ }
+
+ public override bool Serialize(IMessageWriter writer, bool initialState)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void Deserialize(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState)
+ {
+ if (!sender.IsHost)
+ {
+ throw new ImpostorCheatException($"Client attempted to send data for {nameof(InnerShipStatus)} as non-host");
+ }
+
+ var votes = _votes;
+ var unknown = reader.ReadByte();
+ if (unknown != 0)
+ {
+ for (var i = 0; i < unknown; i++)
+ {
+ var v4 = reader.ReadInt32();
+ if (v4 == 0)
+ {
+ break;
+ }
+
+ if (!votes.TryGetValue(v4, out var v12))
+ {
+ v12 = new int[3];
+ votes[v4] = v12;
+ }
+
+ for (var j = 0; j < 3; j++)
+ {
+ v12[j] = reader.ReadPackedInt32();
+ }
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerGameData.TaskInfo.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerGameData.TaskInfo.cs
new file mode 100644
index 0000000..29ca50a
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerGameData.TaskInfo.cs
@@ -0,0 +1,30 @@
+using Impostor.Api.Innersloth;
+using Impostor.Api.Net.Inner.Objects;
+using Impostor.Api.Net.Messages;
+
+namespace Impostor.Server.Net.Inner.Objects
+{
+ internal partial class InnerGameData
+ {
+ public class TaskInfo : ITaskInfo
+ {
+ public uint Id { get; internal set; }
+
+ public bool Complete { get; internal set; }
+
+ public TaskTypes Type { get; internal set; }
+
+ public void Serialize(IMessageWriter writer)
+ {
+ writer.WritePacked((uint)Id);
+ writer.Write(Complete);
+ }
+
+ public void Deserialize(IMessageReader reader)
+ {
+ Id = reader.ReadPackedUInt32();
+ Complete = reader.ReadBoolean();
+ }
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerGameData.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerGameData.cs
new file mode 100644
index 0000000..ed68038
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerGameData.cs
@@ -0,0 +1,187 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Impostor.Api;
+using Impostor.Api.Innersloth;
+using Impostor.Api.Net;
+using Impostor.Api.Net.Inner.Objects;
+using Impostor.Api.Net.Messages;
+using Impostor.Server.Net.Inner.Objects.Components;
+using Impostor.Server.Net.State;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Impostor.Server.Net.Inner.Objects
+{
+ internal partial class InnerGameData : InnerNetObject, IInnerGameData
+ {
+ private readonly ILogger<InnerGameData> _logger;
+ private readonly Game _game;
+ private readonly ConcurrentDictionary<byte, InnerPlayerInfo> _allPlayers;
+
+ public InnerGameData(ILogger<InnerGameData> logger, Game game, IServiceProvider serviceProvider)
+ {
+ _logger = logger;
+ _game = game;
+ _allPlayers = new ConcurrentDictionary<byte, InnerPlayerInfo>();
+
+ Components.Add(this);
+ Components.Add(ActivatorUtilities.CreateInstance<InnerVoteBanSystem>(serviceProvider));
+ }
+
+ public int PlayerCount => _allPlayers.Count;
+
+ public IReadOnlyDictionary<byte, InnerPlayerInfo> Players => _allPlayers;
+
+ public InnerPlayerInfo? GetPlayerById(byte id)
+ {
+ if (id == byte.MaxValue)
+ {
+ return null;
+ }
+
+ return _allPlayers.TryGetValue(id, out var player) ? player : null;
+ }
+
+ public override ValueTask HandleRpc(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader)
+ {
+ switch (call)
+ {
+ case RpcCalls.SetTasks:
+ {
+ if (!sender.IsHost)
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetTasks)} but was not a host");
+ }
+
+ if (target != null)
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetTasks)} to a specific player instead of broadcast");
+ }
+
+ var playerId = reader.ReadByte();
+ var taskTypeIds = reader.ReadBytesAndSize();
+
+ SetTasks(playerId, taskTypeIds);
+ break;
+ }
+
+ case RpcCalls.UpdateGameData:
+ {
+ if (!sender.IsHost)
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetTasks)} but was not a host");
+ }
+
+ if (target != null)
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetTasks)} to a specific player instead of broadcast");
+ }
+
+ while (reader.Position < reader.Length)
+ {
+ using var message = reader.ReadMessage();
+ var player = GetPlayerById(message.Tag);
+ if (player != null)
+ {
+ player.Deserialize(message);
+ }
+ else
+ {
+ var playerInfo = new InnerPlayerInfo(message.Tag);
+
+ playerInfo.Deserialize(reader);
+
+ if (!_allPlayers.TryAdd(playerInfo.PlayerId, playerInfo))
+ {
+ throw new ImpostorException("Failed to add player to InnerGameData.");
+ }
+ }
+ }
+
+ break;
+ }
+
+ default:
+ {
+ _logger.LogWarning("{0}: Unknown rpc call {1}", nameof(InnerGameData), call);
+ break;
+ }
+ }
+
+ return default;
+ }
+
+ public override bool Serialize(IMessageWriter writer, bool initialState)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void Deserialize(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState)
+ {
+ if (!sender.IsHost)
+ {
+ throw new ImpostorCheatException($"Client attempted to send data for {nameof(InnerGameData)} as non-host");
+ }
+
+ if (initialState)
+ {
+ var num = reader.ReadPackedInt32();
+
+ for (var i = 0; i < num; i++)
+ {
+ var playerId = reader.ReadByte();
+ var playerInfo = new InnerPlayerInfo(playerId);
+
+ playerInfo.Deserialize(reader);
+
+ if (!_allPlayers.TryAdd(playerInfo.PlayerId, playerInfo))
+ {
+ throw new ImpostorException("Failed to add player to InnerGameData.");
+ }
+ }
+ }
+ else
+ {
+ throw new NotImplementedException("This shouldn't happen, according to Among Us disassembly.");
+ }
+ }
+
+ internal void AddPlayer(InnerPlayerControl control)
+ {
+ var playerId = control.PlayerId;
+ var playerInfo = new InnerPlayerInfo(control.PlayerId);
+
+ if (_allPlayers.TryAdd(playerId, playerInfo))
+ {
+ control.PlayerInfo = playerInfo;
+ }
+ }
+
+ private void SetTasks(byte playerId, ReadOnlyMemory<byte> taskTypeIds)
+ {
+ var player = GetPlayerById(playerId);
+ if (player == null)
+ {
+ _logger.LogTrace("Could not set tasks for playerId {0}.", playerId);
+ return;
+ }
+
+ if (player.Disconnected)
+ {
+ return;
+ }
+
+ player.Tasks = new List<TaskInfo>(taskTypeIds.Length);
+
+ foreach (var taskId in taskTypeIds.ToArray())
+ {
+ player.Tasks.Add(new TaskInfo
+ {
+ Id = taskId,
+ });
+ }
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerLobbyBehaviour.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerLobbyBehaviour.cs
new file mode 100644
index 0000000..63448ed
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerLobbyBehaviour.cs
@@ -0,0 +1,36 @@
+using System.Threading.Tasks;
+using Impostor.Api.Games;
+using Impostor.Api.Net;
+using Impostor.Api.Net.Inner.Objects;
+using Impostor.Api.Net.Messages;
+using Impostor.Server.Net.State;
+
+namespace Impostor.Server.Net.Inner.Objects
+{
+ internal class InnerLobbyBehaviour : InnerNetObject, IInnerLobbyBehaviour
+ {
+ private readonly IGame _game;
+
+ public InnerLobbyBehaviour(IGame game)
+ {
+ _game = game;
+
+ Components.Add(this);
+ }
+
+ public override ValueTask HandleRpc(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader)
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public override bool Serialize(IMessageWriter writer, bool initialState)
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public override void Deserialize(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState)
+ {
+ throw new System.NotImplementedException();
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerMeetingHud.Api.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerMeetingHud.Api.cs
new file mode 100644
index 0000000..5d120c5
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerMeetingHud.Api.cs
@@ -0,0 +1,9 @@
+using Impostor.Api.Net.Inner.Objects;
+
+namespace Impostor.Server.Net.Inner.Objects
+{
+ internal partial class InnerMeetingHud : IInnerMeetingHud
+ {
+
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerMeetingHud.PlayerVoteArea.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerMeetingHud.PlayerVoteArea.cs
new file mode 100644
index 0000000..fbf2510
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerMeetingHud.PlayerVoteArea.cs
@@ -0,0 +1,49 @@
+using Impostor.Api.Net.Messages;
+
+namespace Impostor.Server.Net.Inner.Objects
+{
+ internal partial class InnerMeetingHud
+ {
+ public class PlayerVoteArea
+ {
+ private const byte VoteMask = 15;
+ private const byte ReportedBit = 32;
+ private const byte VotedBit = 64;
+ private const byte DeadBit = 128;
+
+ public PlayerVoteArea(InnerMeetingHud parent, byte targetPlayerId)
+ {
+ Parent = parent;
+ TargetPlayerId = targetPlayerId;
+ }
+
+ public InnerMeetingHud Parent { get; }
+
+ public byte TargetPlayerId { get; }
+
+ public bool IsDead { get; private set; }
+
+ public bool DidVote { get; private set; }
+
+ public bool DidReport { get; private set; }
+
+ public sbyte VotedFor { get; private set; }
+
+ internal void SetDead(bool didReport, bool isDead)
+ {
+ DidReport = didReport;
+ IsDead = isDead;
+ }
+
+ public void Deserialize(IMessageReader reader)
+ {
+ var num = reader.ReadByte();
+
+ VotedFor = (sbyte)((num & VoteMask) - 1);
+ IsDead = (num & DeadBit) > 0;
+ DidVote = (num & VotedBit) > 0;
+ DidReport = (num & ReportedBit) > 0;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerMeetingHud.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerMeetingHud.cs
new file mode 100644
index 0000000..c2bcb9d
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerMeetingHud.cs
@@ -0,0 +1,176 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Impostor.Api;
+using Impostor.Api.Events.Managers;
+using Impostor.Api.Innersloth;
+using Impostor.Api.Net;
+using Impostor.Api.Net.Messages;
+using Impostor.Server.Events.Meeting;
+using Impostor.Server.Events.Player;
+using Impostor.Server.Net.State;
+using Microsoft.Extensions.Logging;
+
+namespace Impostor.Server.Net.Inner.Objects
+{
+ internal partial class InnerMeetingHud : InnerNetObject
+ {
+ private readonly ILogger<InnerMeetingHud> _logger;
+ private readonly IEventManager _eventManager;
+ private readonly Game _game;
+ private readonly GameNet _gameNet;
+ private PlayerVoteArea[] _playerStates;
+
+ public InnerMeetingHud(ILogger<InnerMeetingHud> logger, IEventManager eventManager, Game game)
+ {
+ _logger = logger;
+ _eventManager = eventManager;
+ _game = game;
+ _gameNet = game.GameNet;
+ _playerStates = null;
+
+ Components.Add(this);
+ }
+
+ public byte ReporterId { get; private set; }
+
+ private void PopulateButtons(byte reporter)
+ {
+ _playerStates = _gameNet.GameData.Players
+ .Select(x =>
+ {
+ var area = new PlayerVoteArea(this, x.Key);
+ area.SetDead(x.Value.PlayerId == reporter, x.Value.Disconnected || x.Value.IsDead);
+ return area;
+ })
+ .ToArray();
+ }
+
+ public override async ValueTask HandleRpc(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader)
+ {
+ switch (call)
+ {
+ case RpcCalls.Close:
+ {
+ if (!sender.IsHost)
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.Close)} but was not a host");
+ }
+
+ if (target != null)
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.Close)} to a specific player instead of broadcast");
+ }
+
+ break;
+ }
+
+ case RpcCalls.VotingComplete:
+ {
+ if (!sender.IsHost)
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.VotingComplete)} but was not a host");
+ }
+
+ if (target != null)
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.VotingComplete)} to a specific player instead of broadcast");
+ }
+
+ var states = reader.ReadBytesAndSize();
+ var playerId = reader.ReadByte();
+ var tie = reader.ReadBoolean();
+
+ if (playerId != byte.MaxValue)
+ {
+ var player = _game.GameNet.GameData.GetPlayerById(playerId);
+ if (player != null)
+ {
+ player.Controller.Die(DeathReason.Exile);
+ await _eventManager.CallAsync(new PlayerExileEvent(_game, sender, player.Controller));
+ }
+ }
+
+ await _eventManager.CallAsync(new MeetingEndedEvent(_game, this));
+
+ break;
+ }
+
+ case RpcCalls.CastVote:
+ {
+ var srcPlayerId = reader.ReadByte();
+ if (srcPlayerId != sender.Character.PlayerId)
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CastVote)} to an unowned {nameof(InnerPlayerControl)}");
+ }
+
+ // Host broadcasts vote to others.
+ if (sender.IsHost && target != null)
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CastVote)} to a specific player instead of broadcast");
+ }
+
+ // Player sends vote to host.
+ if (target == null || !target.IsHost)
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CastVote)} to wrong destinition, must be host");
+ }
+
+ var targetPlayerId = reader.ReadByte();
+ break;
+ }
+
+ default:
+ {
+ _logger.LogWarning("{0}: Unknown rpc call {1}", nameof(InnerMeetingHud), call);
+ break;
+ }
+ }
+ }
+
+ public override bool Serialize(IMessageWriter writer, bool initialState)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void Deserialize(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState)
+ {
+ if (!sender.IsHost)
+ {
+ throw new ImpostorCheatException($"Client attempted to send data for {nameof(InnerMeetingHud)} as non-host");
+ }
+
+ if (target != null)
+ {
+ throw new ImpostorCheatException($"Client attempted to send {nameof(InnerMeetingHud)} data to a specific player, must be broadcast");
+ }
+
+ if (initialState)
+ {
+ PopulateButtons(0);
+
+ foreach (var playerState in _playerStates)
+ {
+ playerState.Deserialize(reader);
+
+ if (playerState.DidReport)
+ {
+ ReporterId = playerState.TargetPlayerId;
+ }
+ }
+ }
+ else
+ {
+ var num = reader.ReadPackedUInt32();
+
+ for (var i = 0; i < _playerStates.Length; i++)
+ {
+ if ((num & 1 << i) != 0)
+ {
+ _playerStates[i].Deserialize(reader);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.Api.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.Api.cs
new file mode 100644
index 0000000..0a7997d
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.Api.cs
@@ -0,0 +1,138 @@
+using System.Threading.Tasks;
+using Impostor.Api;
+using Impostor.Api.Innersloth;
+using Impostor.Api.Innersloth.Customization;
+using Impostor.Api.Net;
+using Impostor.Api.Net.Inner.Objects;
+using Impostor.Api.Net.Inner.Objects.Components;
+using Impostor.Server.Events.Player;
+
+namespace Impostor.Server.Net.Inner.Objects
+{
+ internal partial class InnerPlayerControl : IInnerPlayerControl
+ {
+ IInnerPlayerPhysics IInnerPlayerControl.Physics => Physics;
+
+ IInnerCustomNetworkTransform IInnerPlayerControl.NetworkTransform => NetworkTransform;
+
+ IInnerPlayerInfo IInnerPlayerControl.PlayerInfo => PlayerInfo;
+
+ public async ValueTask SetNameAsync(string name)
+ {
+ PlayerInfo.PlayerName = name;
+
+ using var writer = _game.StartRpc(NetId, RpcCalls.SetName);
+ writer.Write(name);
+ await _game.FinishRpcAsync(writer);
+ }
+
+ public async ValueTask SetColorAsync(byte colorId)
+ {
+ PlayerInfo.ColorId = colorId;
+
+ using var writer = _game.StartRpc(NetId, RpcCalls.SetColor);
+ writer.Write(colorId);
+ await _game.FinishRpcAsync(writer);
+ }
+
+ public ValueTask SetColorAsync(ColorType colorType)
+ {
+ return SetColorAsync((byte)colorType);
+ }
+
+ public async ValueTask SetHatAsync(uint hatId)
+ {
+ PlayerInfo.HatId = hatId;
+
+ using var writer = _game.StartRpc(NetId, RpcCalls.SetHat);
+ writer.WritePacked(hatId);
+ await _game.FinishRpcAsync(writer);
+ }
+
+ public ValueTask SetHatAsync(HatType hatType)
+ {
+ return SetHatAsync((uint)hatType);
+ }
+
+ public async ValueTask SetPetAsync(uint petId)
+ {
+ PlayerInfo.PetId = petId;
+
+ using var writer = _game.StartRpc(NetId, RpcCalls.SetPet);
+ writer.WritePacked(petId);
+ await _game.FinishRpcAsync(writer);
+ }
+
+ public ValueTask SetPetAsync(PetType petType)
+ {
+ return SetPetAsync((uint)petType);
+ }
+
+ public async ValueTask SetSkinAsync(uint skinId)
+ {
+ PlayerInfo.SkinId = skinId;
+
+ using var writer = _game.StartRpc(NetId, RpcCalls.SetSkin);
+ writer.WritePacked(skinId);
+ await _game.FinishRpcAsync(writer);
+ }
+
+ public ValueTask SetSkinAsync(SkinType skinType)
+ {
+ return SetSkinAsync((uint)skinType);
+ }
+
+ public async ValueTask SendChatAsync(string text)
+ {
+ using var writer = _game.StartRpc(NetId, RpcCalls.SendChat);
+ writer.Write(text);
+ await _game.FinishRpcAsync(writer);
+ }
+
+ public async ValueTask SendChatToPlayerAsync(string text, IInnerPlayerControl? player = null)
+ {
+ if (player == null)
+ {
+ player = this;
+ }
+
+ using var writer = _game.StartRpc(NetId, RpcCalls.SendChat);
+ writer.Write(text);
+ await _game.FinishRpcAsync(writer, player.OwnerId);
+ }
+
+ public async ValueTask SetMurderedByAsync(IClientPlayer impostor)
+ {
+ if (impostor.Character == null)
+ {
+ throw new ImpostorException("Character is null.");
+ }
+
+ if (!impostor.Character.PlayerInfo.IsImpostor)
+ {
+ throw new ImpostorProtocolException("Plugin tried to murder a player while the impostor specified was not an impostor.");
+ }
+
+ if (impostor.Character.PlayerInfo.IsDead)
+ {
+ throw new ImpostorProtocolException("Plugin tried to murder a player while the impostor specified was dead.");
+ }
+
+ if (PlayerInfo.IsDead)
+ {
+ return;
+ }
+
+ // Update player.
+ Die(DeathReason.Kill);
+
+ // Send RPC.
+ using var writer = _game.StartRpc(impostor.Character.NetId, RpcCalls.MurderPlayer);
+ writer.WritePacked(NetId);
+ await _game.FinishRpcAsync(writer);
+
+ // Notify plugins.
+ await _eventManager.CallAsync(new PlayerMurderEvent(_game, impostor, impostor.Character, this));
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs
new file mode 100644
index 0000000..c71fcf7
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs
@@ -0,0 +1,455 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Impostor.Api;
+using Impostor.Api.Events.Managers;
+using Impostor.Api.Innersloth;
+using Impostor.Api.Net;
+using Impostor.Api.Net.Messages;
+using Impostor.Server.Events.Player;
+using Impostor.Server.Net.Inner.Objects.Components;
+using Impostor.Server.Net.State;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Impostor.Server.Net.Inner.Objects
+{
+ internal partial class InnerPlayerControl : InnerNetObject
+ {
+ private readonly ILogger<InnerPlayerControl> _logger;
+ private readonly IEventManager _eventManager;
+ private readonly Game _game;
+
+ public InnerPlayerControl(ILogger<InnerPlayerControl> logger, IServiceProvider serviceProvider, IEventManager eventManager, Game game)
+ {
+ _logger = logger;
+ _eventManager = eventManager;
+ _game = game;
+
+ Physics = ActivatorUtilities.CreateInstance<InnerPlayerPhysics>(serviceProvider, this, _eventManager, _game);
+ NetworkTransform = ActivatorUtilities.CreateInstance<InnerCustomNetworkTransform>(serviceProvider, this, _game);
+
+ Components.Add(this);
+ Components.Add(Physics);
+ Components.Add(NetworkTransform);
+
+ PlayerId = byte.MaxValue;
+ }
+
+ public bool IsNew { get; private set; }
+
+ public byte PlayerId { get; private set; }
+
+ public InnerPlayerPhysics Physics { get; }
+
+ public InnerCustomNetworkTransform NetworkTransform { get; }
+
+ public InnerPlayerInfo PlayerInfo { get; internal set; }
+
+ public override async ValueTask HandleRpc(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader)
+ {
+ switch (call)
+ {
+ // Play an animation.
+ case RpcCalls.PlayAnimation:
+ {
+ if (!sender.IsOwner(this))
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.PlayAnimation)} to an unowned {nameof(InnerPlayerControl)}");
+ }
+
+ if (target != null)
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.PlayAnimation)} to a specific player instead of broadcast");
+ }
+
+ var animation = reader.ReadByte();
+ break;
+ }
+
+ // Complete a task.
+ case RpcCalls.CompleteTask:
+ {
+ if (!sender.IsOwner(this))
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CompleteTask)} to an unowned {nameof(InnerPlayerControl)}");
+ }
+
+ if (target != null)
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CompleteTask)} to a specific player instead of broadcast");
+ }
+
+ var taskId = reader.ReadPackedUInt32();
+ var task = PlayerInfo.Tasks[(int)taskId];
+ if (task == null)
+ {
+ _logger.LogWarning($"Client sent {nameof(RpcCalls.CompleteTask)} with a taskIndex that is not in their {nameof(InnerPlayerInfo)}");
+ }
+ else
+ {
+ task.Complete = true;
+ await _eventManager.CallAsync(new PlayerCompletedTaskEvent(_game, sender, this, task));
+ }
+
+ break;
+ }
+
+ // Update GameOptions.
+ case RpcCalls.SyncSettings:
+ {
+ if (!sender.IsHost)
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SyncSettings)} but was not a host");
+ }
+
+ _game.Options.Deserialize(reader.ReadBytesAndSize());
+ break;
+ }
+
+ // Set Impostors.
+ case RpcCalls.SetInfected:
+ {
+ if (!sender.IsHost)
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetInfected)} but was not a host");
+ }
+
+ var length = reader.ReadPackedInt32();
+
+ for (var i = 0; i < length; i++)
+ {
+ var playerId = reader.ReadByte();
+ var player = _game.GameNet.GameData.GetPlayerById(playerId);
+ if (player != null)
+ {
+ player.IsImpostor = true;
+ }
+ }
+
+ if (_game.GameState == GameStates.Starting)
+ {
+ await _game.StartedAsync();
+ }
+
+ break;
+ }
+
+ // Player was voted out.
+ case RpcCalls.Exiled:
+ {
+ if (!sender.IsHost)
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.Exiled)} but was not a host");
+ }
+
+ if (target != null)
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.Exiled)} to a specific player instead of broadcast");
+ }
+
+ // TODO: Not hit?
+ Die(DeathReason.Exile);
+
+ await _eventManager.CallAsync(new PlayerExileEvent(_game, sender, this));
+ break;
+ }
+
+ // Validates the player name at the host.
+ case RpcCalls.CheckName:
+ {
+ if (target == null || !target.IsHost)
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CheckName)} to the wrong player");
+ }
+
+ var name = reader.ReadString();
+ break;
+ }
+
+ // Update the name of a player.
+ case RpcCalls.SetName:
+ {
+ if (!sender.IsHost)
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetName)} but was not a host");
+ }
+
+ if (target != null)
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetName)} to a specific player instead of broadcast");
+ }
+
+ PlayerInfo.PlayerName = reader.ReadString();
+ break;
+ }
+
+ // Validates the color at the host.
+ case RpcCalls.CheckColor:
+ {
+ if (target == null || !target.IsHost)
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CheckColor)} to the wrong player");
+ }
+
+ var color = reader.ReadByte();
+ break;
+ }
+
+ // Update the color of a player.
+ case RpcCalls.SetColor:
+ {
+ if (!sender.IsHost)
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetColor)} but was not a host");
+ }
+
+ if (target != null)
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetColor)} to a specific player instead of broadcast");
+ }
+
+ PlayerInfo.ColorId = reader.ReadByte();
+ break;
+ }
+
+ // Update the hat of a player.
+ case RpcCalls.SetHat:
+ {
+ if (!sender.IsOwner(this))
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetHat)} to an unowned {nameof(InnerPlayerControl)}");
+ }
+
+ if (target != null)
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetHat)} to a specific player instead of broadcast");
+ }
+
+ PlayerInfo.HatId = reader.ReadPackedUInt32();
+ break;
+ }
+
+ case RpcCalls.SetSkin:
+ {
+ if (!sender.IsOwner(this))
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetSkin)} to an unowned {nameof(InnerPlayerControl)}");
+ }
+
+ if (target != null)
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetHat)} to a specific player instead of broadcast");
+ }
+
+ PlayerInfo.SkinId = reader.ReadPackedUInt32();
+ break;
+ }
+
+ // TODO: (ANTICHEAT) Location check?
+ // only called by a non-host player on to start meeting
+ case RpcCalls.ReportDeadBody:
+ {
+ if (!sender.IsOwner(this))
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.ReportDeadBody)} to an unowned {nameof(InnerPlayerControl)}");
+ }
+
+ if (target != null)
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.ReportDeadBody)} to a specific player instead of broadcast");
+ }
+
+
+ var deadBodyPlayerId = reader.ReadByte();
+ // deadBodyPlayerId == byte.MaxValue -- means emergency call by button
+
+ break;
+ }
+
+ // TODO: (ANTICHEAT) Cooldown check?
+ case RpcCalls.MurderPlayer:
+ {
+ if (!sender.IsOwner(this))
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.MurderPlayer)} to an unowned {nameof(InnerPlayerControl)}");
+ }
+
+ if (target != null)
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.MurderPlayer)} to a specific player instead of broadcast");
+ }
+
+ if (!sender.Character.PlayerInfo.IsImpostor)
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.MurderPlayer)} as crewmate");
+ }
+
+ if (!sender.Character.PlayerInfo.CanMurder(_game))
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.MurderPlayer)} too fast");
+ }
+
+ sender.Character.PlayerInfo.LastMurder = DateTimeOffset.UtcNow;
+
+ var player = reader.ReadNetObject<InnerPlayerControl>(_game);
+ if (!player.PlayerInfo.IsDead)
+ {
+ player.Die(DeathReason.Kill);
+ await _eventManager.CallAsync(new PlayerMurderEvent(_game, sender, this, player));
+ }
+
+ break;
+ }
+
+ case RpcCalls.SendChat:
+ {
+ if (!sender.IsOwner(this))
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SendChat)} to an unowned {nameof(InnerPlayerControl)}");
+ }
+
+ if (target != null)
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SendChat)} to a specific player instead of broadcast");
+ }
+
+ var chat = reader.ReadString();
+
+ await _eventManager.CallAsync(new PlayerChatEvent(_game, sender, this, chat));
+ break;
+ }
+
+ case RpcCalls.StartMeeting:
+ {
+ if (!sender.IsHost)
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.StartMeeting)} but was not a host");
+ }
+
+ if (target != null)
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.StartMeeting)} to a specific player instead of broadcast");
+ }
+
+ // deadBodyPlayerId == byte.MaxValue -- means emergency call by button
+ var deadBodyPlayerId = reader.ReadByte();
+ var deadPlayer = deadBodyPlayerId != byte.MaxValue
+ ? _game.GameNet.GameData.GetPlayerById(deadBodyPlayerId)?.Controller
+ : null;
+
+ await _eventManager.CallAsync(new PlayerStartMeetingEvent(_game, _game.GetClientPlayer(this.OwnerId), this, deadPlayer));
+ break;
+ }
+
+ case RpcCalls.SetScanner:
+ {
+ if (!sender.IsOwner(this))
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetScanner)} to an unowned {nameof(InnerPlayerControl)}");
+ }
+
+ if (target != null)
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetScanner)} to a specific player instead of broadcast");
+ }
+
+ var on = reader.ReadBoolean();
+ var count = reader.ReadByte();
+ break;
+ }
+
+ case RpcCalls.SendChatNote:
+ {
+ if (!sender.IsOwner(this))
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SendChatNote)} to an unowned {nameof(InnerPlayerControl)}");
+ }
+
+ if (target != null)
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SendChatNote)} to a specific player instead of broadcast");
+ }
+
+ var playerId = reader.ReadByte();
+ var chatNote = (ChatNoteType)reader.ReadByte();
+ break;
+ }
+
+ case RpcCalls.SetPet:
+ {
+ if (!sender.IsOwner(this))
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetPet)} to an unowned {nameof(InnerPlayerControl)}");
+ }
+
+ if (target != null)
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetPet)} to a specific player instead of broadcast");
+ }
+
+ PlayerInfo.PetId = reader.ReadPackedUInt32();
+ break;
+ }
+
+ // TODO: Understand this RPC
+ case RpcCalls.SetStartCounter:
+ {
+ if (!sender.IsOwner(this))
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetStartCounter)} to an unowned {nameof(InnerPlayerControl)}");
+ }
+
+ if (target != null)
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetStartCounter)} to a specific player instead of broadcast");
+ }
+
+ // Used to compare with LastStartCounter.
+ var startCounter = reader.ReadPackedUInt32();
+
+ // Is either start countdown or byte.MaxValue
+ var secondsLeft = reader.ReadByte();
+ if (secondsLeft < byte.MaxValue)
+ {
+ await _eventManager.CallAsync(new PlayerSetStartCounterEvent(_game, sender, this, secondsLeft));
+ }
+
+ break;
+ }
+
+ default:
+ {
+ _logger.LogWarning("{0}: Unknown rpc call {1}", nameof(InnerPlayerControl), call);
+ break;
+ }
+ }
+ }
+
+ public override bool Serialize(IMessageWriter writer, bool initialState)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void Deserialize(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState)
+ {
+ if (!sender.IsHost)
+ {
+ throw new ImpostorCheatException($"Client attempted to send data for {nameof(InnerPlayerControl)} as non-host");
+ }
+
+ if (initialState)
+ {
+ IsNew = reader.ReadBoolean();
+ }
+
+ PlayerId = reader.ReadByte();
+ }
+
+ internal void Die(DeathReason reason)
+ {
+ PlayerInfo.IsDead = true;
+ PlayerInfo.LastDeathReason = reason;
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.Api.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.Api.cs
new file mode 100644
index 0000000..512a4f1
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.Api.cs
@@ -0,0 +1,10 @@
+using System.Collections.Generic;
+using Impostor.Api.Net.Inner.Objects;
+
+namespace Impostor.Server.Net.Inner.Objects
+{
+ internal partial class InnerPlayerInfo : IInnerPlayerInfo
+ {
+ IEnumerable<ITaskInfo> IInnerPlayerInfo.Tasks => Tasks;
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs
new file mode 100644
index 0000000..f248994
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs
@@ -0,0 +1,76 @@
+using System;
+using System.Collections.Generic;
+using Impostor.Api.Games;
+using Impostor.Api.Innersloth;
+using Impostor.Api.Net.Messages;
+
+namespace Impostor.Server.Net.Inner.Objects
+{
+ internal partial class InnerPlayerInfo
+ {
+ public InnerPlayerInfo(byte playerId)
+ {
+ PlayerId = playerId;
+ }
+
+ public InnerPlayerControl Controller { get; internal set; }
+
+ public byte PlayerId { get; }
+
+ public string PlayerName { get; internal set; }
+
+ public byte ColorId { get; internal set; }
+
+ public uint HatId { get; internal set; }
+
+ public uint PetId { get; internal set; }
+
+ public uint SkinId { get; internal set; }
+
+ public bool Disconnected { get; internal set; }
+
+ public bool IsImpostor { get; internal set; }
+
+ public bool IsDead { get; internal set; }
+
+ public DeathReason LastDeathReason { get; internal set; }
+
+ public List<InnerGameData.TaskInfo> Tasks { get; internal set; }
+
+ public DateTimeOffset LastMurder { get; set; }
+
+ public bool CanMurder(IGame game)
+ {
+ if (!IsImpostor)
+ {
+ return false;
+ }
+
+ return DateTimeOffset.UtcNow.Subtract(LastMurder).TotalSeconds >= game.Options.KillCooldown;
+ }
+
+ public void Serialize(IMessageWriter writer)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Deserialize(IMessageReader reader)
+ {
+ PlayerName = reader.ReadString();
+ ColorId = reader.ReadByte();
+ HatId = reader.ReadPackedUInt32();
+ PetId = reader.ReadPackedUInt32();
+ SkinId = reader.ReadPackedUInt32();
+ var flag = reader.ReadByte();
+ Disconnected = (flag & 1) > 0;
+ IsImpostor = (flag & 2) > 0;
+ IsDead = (flag & 4) > 0;
+ var taskCount = reader.ReadByte();
+ for (var i = 0; i < taskCount; i++)
+ {
+ Tasks[i] ??= new InnerGameData.TaskInfo();
+ Tasks[i].Deserialize(reader);
+ }
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerShipStatus.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerShipStatus.cs
new file mode 100644
index 0000000..b1a3f18
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerShipStatus.cs
@@ -0,0 +1,147 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Impostor.Api;
+using Impostor.Api.Innersloth;
+using Impostor.Api.Net;
+using Impostor.Api.Net.Inner.Objects;
+using Impostor.Api.Net.Messages;
+using Impostor.Server.Net.Inner.Objects.Systems;
+using Impostor.Server.Net.Inner.Objects.Systems.ShipStatus;
+using Impostor.Server.Net.State;
+using Microsoft.Extensions.Logging;
+
+namespace Impostor.Server.Net.Inner.Objects
+{
+ internal class InnerShipStatus : InnerNetObject, IInnerShipStatus
+ {
+ private readonly ILogger<InnerShipStatus> _logger;
+ private readonly Game _game;
+ private readonly Dictionary<SystemTypes, ISystemType> _systems;
+
+ public InnerShipStatus(ILogger<InnerShipStatus> logger, Game game)
+ {
+ _logger = logger;
+ _game = game;
+
+ _systems = new Dictionary<SystemTypes, ISystemType>
+ {
+ [SystemTypes.Electrical] = new SwitchSystem(),
+ [SystemTypes.MedBay] = new MedScanSystem(),
+ [SystemTypes.Reactor] = new ReactorSystemType(),
+ [SystemTypes.LifeSupp] = new LifeSuppSystemType(),
+ [SystemTypes.Security] = new SecurityCameraSystemType(),
+ [SystemTypes.Comms] = new HudOverrideSystemType(),
+ [SystemTypes.Doors] = new DoorsSystemType(_game),
+ };
+
+ _systems.Add(SystemTypes.Sabotage, new SabotageSystemType(new[]
+ {
+ (IActivatable)_systems[SystemTypes.Comms],
+ (IActivatable)_systems[SystemTypes.Reactor],
+ (IActivatable)_systems[SystemTypes.LifeSupp],
+ (IActivatable)_systems[SystemTypes.Electrical],
+ }));
+
+ Components.Add(this);
+ }
+
+ public override ValueTask HandleRpc(ClientPlayer sender, ClientPlayer? target, RpcCalls call,
+ IMessageReader reader)
+ {
+ switch (call)
+ {
+ case RpcCalls.CloseDoorsOfType:
+ {
+ if (target == null || !target.IsHost)
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CloseDoorsOfType)} to wrong destinition, must be host");
+ }
+
+ if (!sender.Character.PlayerInfo.IsImpostor)
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CloseDoorsOfType)} as crewmate");
+ }
+
+ var systemType = (SystemTypes)reader.ReadByte();
+
+ break;
+ }
+
+ case RpcCalls.RepairSystem:
+ {
+ if (target == null || !target.IsHost)
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.RepairSystem)} to wrong destinition, must be host");
+ }
+
+ var systemType = (SystemTypes)reader.ReadByte();
+ if (systemType == SystemTypes.Sabotage && !sender.Character.PlayerInfo.IsImpostor)
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.RepairSystem)} for {systemType} as crewmate");
+ }
+
+ var player = reader.ReadNetObject<InnerPlayerControl>(_game);
+ var amount = reader.ReadByte();
+
+ // TODO: Modify data (?)
+ break;
+ }
+
+ default:
+ {
+ _logger.LogWarning("{0}: Unknown rpc call {1}", nameof(InnerShipStatus), call);
+ break;
+ }
+ }
+
+ return default;
+ }
+
+ public override bool Serialize(IMessageWriter writer, bool initialState)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void Deserialize(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState)
+ {
+ if (!sender.IsHost)
+ {
+ throw new ImpostorCheatException($"Client attempted to send data for {nameof(InnerShipStatus)} as non-host");
+ }
+
+ if (target != null)
+ {
+ throw new ImpostorCheatException($"Client attempted to send {nameof(InnerShipStatus)} data to a specific player, must be broadcast");
+ }
+
+ if (initialState)
+ {
+ // TODO: (_systems[SystemTypes.Doors] as DoorsSystemType).SetDoors();
+ foreach (var systemType in SystemTypeHelpers.AllTypes)
+ {
+ if (_systems.TryGetValue(systemType, out var system))
+ {
+ system.Deserialize(reader, true);
+ }
+ }
+ }
+ else
+ {
+ var count = reader.ReadPackedUInt32();
+
+ foreach (var systemType in SystemTypeHelpers.AllTypes)
+ {
+ // TODO: Not sure what is going on here, check.
+ if ((count & 1 << (int)(systemType & (SystemTypes.ShipTasks | SystemTypes.Doors))) != 0L)
+ {
+ if (_systems.TryGetValue(systemType, out var system))
+ {
+ system.Deserialize(reader, false);
+ }
+ }
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/IActivatable.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/IActivatable.cs
new file mode 100644
index 0000000..e595b25
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/IActivatable.cs
@@ -0,0 +1,7 @@
+namespace Impostor.Server.Net.Inner.Objects.Systems
+{
+ public interface IActivatable
+ {
+ bool IsActive { get; }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ISystemType.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ISystemType.cs
new file mode 100644
index 0000000..a6ef88e
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ISystemType.cs
@@ -0,0 +1,11 @@
+using Impostor.Api.Net.Messages;
+
+namespace Impostor.Server.Net.Inner.Objects.Systems
+{
+ public interface ISystemType
+ {
+ void Serialize(IMessageWriter writer, bool initialState);
+
+ void Deserialize(IMessageReader reader, bool initialState);
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/DoorsSystemType.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/DoorsSystemType.cs
new file mode 100644
index 0000000..64b1f5f
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/DoorsSystemType.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+using Impostor.Api.Games;
+using Impostor.Api.Innersloth;
+using Impostor.Api.Net.Messages;
+
+namespace Impostor.Server.Net.Inner.Objects.Systems.ShipStatus
+{
+ public class DoorsSystemType : ISystemType
+ {
+ // TODO: AutoDoors
+ private readonly Dictionary<int, bool> _doors;
+
+ public DoorsSystemType(IGame game)
+ {
+ var doorCount = game.Options.Map switch
+ {
+ MapTypes.Skeld => 13,
+ MapTypes.MiraHQ => 2,
+ MapTypes.Polus => 12,
+ _ => throw new ArgumentOutOfRangeException()
+ };
+
+ _doors = new Dictionary<int, bool>(doorCount);
+
+ for (var i = 0; i < doorCount; i++)
+ {
+ _doors.Add(i, false);
+ }
+ }
+
+ public void Serialize(IMessageWriter writer, bool initialState)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Deserialize(IMessageReader reader, bool initialState)
+ {
+ if (initialState)
+ {
+ for (var i = 0; i < _doors.Count; i++)
+ {
+ _doors[i] = reader.ReadBoolean();
+ }
+ }
+ else
+ {
+ var num = reader.ReadPackedUInt32();
+
+ for (var i = 0; i < _doors.Count; i++)
+ {
+ if ((num & 1 << i) != 0)
+ {
+ _doors[i] = reader.ReadBoolean();
+ }
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/HudOverrideSystemType.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/HudOverrideSystemType.cs
new file mode 100644
index 0000000..42aa8d3
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/HudOverrideSystemType.cs
@@ -0,0 +1,19 @@
+using Impostor.Api.Net.Messages;
+
+namespace Impostor.Server.Net.Inner.Objects.Systems.ShipStatus
+{
+ public class HudOverrideSystemType : ISystemType, IActivatable
+ {
+ public bool IsActive { get; private set; }
+
+ public void Serialize(IMessageWriter writer, bool initialState)
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public void Deserialize(IMessageReader reader, bool initialState)
+ {
+ IsActive = reader.ReadBoolean();
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/LifeSuppSystemType.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/LifeSuppSystemType.cs
new file mode 100644
index 0000000..f644024
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/LifeSuppSystemType.cs
@@ -0,0 +1,44 @@
+using System.Collections.Generic;
+using Impostor.Api.Net.Messages;
+
+namespace Impostor.Server.Net.Inner.Objects.Systems.ShipStatus
+{
+ public class LifeSuppSystemType : ISystemType, IActivatable
+ {
+ public LifeSuppSystemType()
+ {
+ Countdown = 10000f;
+ CompletedConsoles = new HashSet<int>();
+ }
+
+ public float Countdown { get; private set; }
+
+ public HashSet<int> CompletedConsoles { get; }
+
+ public bool IsActive => Countdown < 10000.0;
+
+ public void Serialize(IMessageWriter writer, bool initialState)
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public void Deserialize(IMessageReader reader, bool initialState)
+ {
+ Countdown = reader.ReadSingle();
+
+ if (reader.Position >= reader.Length)
+ {
+ return;
+ }
+
+ CompletedConsoles.Clear(); // TODO: Thread safety
+
+ var num = reader.ReadPackedInt32();
+
+ for (var i = 0; i < num; i++)
+ {
+ CompletedConsoles.Add(reader.ReadPackedInt32());
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/MedScanSystem.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/MedScanSystem.cs
new file mode 100644
index 0000000..007b9d0
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/MedScanSystem.cs
@@ -0,0 +1,32 @@
+using System.Collections.Generic;
+using Impostor.Api.Net.Messages;
+
+namespace Impostor.Server.Net.Inner.Objects.Systems.ShipStatus
+{
+ public class MedScanSystem : ISystemType
+ {
+ public MedScanSystem()
+ {
+ UsersList = new List<byte>();
+ }
+
+ public List<byte> UsersList { get; }
+
+ public void Serialize(IMessageWriter writer, bool initialState)
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public void Deserialize(IMessageReader reader, bool initialState)
+ {
+ UsersList.Clear();
+
+ var num = reader.ReadPackedInt32();
+
+ for (var i = 0; i < num; i++)
+ {
+ UsersList.Add(reader.ReadByte());
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/ReactorSystemType.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/ReactorSystemType.cs
new file mode 100644
index 0000000..4380918
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/ReactorSystemType.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Collections.Generic;
+using Impostor.Api.Net.Messages;
+
+namespace Impostor.Server.Net.Inner.Objects.Systems.ShipStatus
+{
+ public class ReactorSystemType : ISystemType, IActivatable
+ {
+ public ReactorSystemType()
+ {
+ Countdown = 10000f;
+ UserConsolePairs = new HashSet<Tuple<byte, byte>>();
+ }
+
+ public float Countdown { get; private set; }
+
+ public HashSet<Tuple<byte, byte>> UserConsolePairs { get; }
+
+ public bool IsActive => Countdown < 10000.0;
+
+ public void Serialize(IMessageWriter writer, bool initialState)
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public void Deserialize(IMessageReader reader, bool initialState)
+ {
+ Countdown = reader.ReadSingle();
+ UserConsolePairs.Clear(); // TODO: Thread safety
+
+ var count = reader.ReadPackedInt32();
+
+ for (var i = 0; i < count; i++)
+ {
+ UserConsolePairs.Add(new Tuple<byte, byte>(reader.ReadByte(), reader.ReadByte()));
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/SabotageSystemType.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/SabotageSystemType.cs
new file mode 100644
index 0000000..193cfe8
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/SabotageSystemType.cs
@@ -0,0 +1,26 @@
+using Impostor.Api.Net.Messages;
+
+namespace Impostor.Server.Net.Inner.Objects.Systems.ShipStatus
+{
+ public class SabotageSystemType : ISystemType
+ {
+ private readonly IActivatable[] _specials;
+
+ public SabotageSystemType(IActivatable[] specials)
+ {
+ _specials = specials;
+ }
+
+ public float Timer { get; set; }
+
+ public void Serialize(IMessageWriter writer, bool initialState)
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public void Deserialize(IMessageReader reader, bool initialState)
+ {
+ Timer = reader.ReadSingle();
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/SecurityCameraSystemType.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/SecurityCameraSystemType.cs
new file mode 100644
index 0000000..df41b68
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/SecurityCameraSystemType.cs
@@ -0,0 +1,19 @@
+using Impostor.Api.Net.Messages;
+
+namespace Impostor.Server.Net.Inner.Objects.Systems.ShipStatus
+{
+ public class SecurityCameraSystemType : ISystemType
+ {
+ public byte InUse { get; internal set; }
+
+ public void Serialize(IMessageWriter writer, bool initialState)
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public void Deserialize(IMessageReader reader, bool initialState)
+ {
+ InUse = reader.ReadByte();
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/SwitchSystem.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/SwitchSystem.cs
new file mode 100644
index 0000000..925774a
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/SwitchSystem.cs
@@ -0,0 +1,27 @@
+using Impostor.Api.Net.Messages;
+
+namespace Impostor.Server.Net.Inner.Objects.Systems.ShipStatus
+{
+ public class SwitchSystem : ISystemType, IActivatable
+ {
+ public byte ExpectedSwitches { get; set; }
+
+ public byte ActualSwitches { get; set; }
+
+ public byte Value { get; set; } = byte.MaxValue;
+
+ public bool IsActive { get; }
+
+ public void Serialize(IMessageWriter writer, bool initialState)
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public void Deserialize(IMessageReader reader, bool initialState)
+ {
+ ExpectedSwitches = reader.ReadByte();
+ ActualSwitches = reader.ReadByte();
+ Value = reader.ReadByte();
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/RpcCalls.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/RpcCalls.cs
new file mode 100644
index 0000000..ce48965
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Inner/RpcCalls.cs
@@ -0,0 +1,37 @@
+namespace Impostor.Server.Net.Inner
+{
+ public enum RpcCalls : byte
+ {
+ PlayAnimation = 0,
+ CompleteTask = 1,
+ SyncSettings = 2,
+ SetInfected = 3,
+ Exiled = 4,
+ CheckName = 5,
+ SetName = 6,
+ CheckColor = 7,
+ SetColor = 8,
+ SetHat = 9,
+ SetSkin = 10,
+ ReportDeadBody = 11,
+ MurderPlayer = 12,
+ SendChat = 13,
+ StartMeeting = 14,
+ SetScanner = 15,
+ SendChatNote = 16,
+ SetPet = 17,
+ SetStartCounter = 18,
+ EnterVent = 19,
+ ExitVent = 20,
+ SnapTo = 21,
+ Close = 22,
+ VotingComplete = 23,
+ CastVote = 24,
+ ClearVote = 25,
+ AddVote = 26,
+ CloseDoorsOfType = 27,
+ RepairSystem = 28,
+ SetTasks = 29,
+ UpdateGameData = 30,
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/SpawnFlags.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/SpawnFlags.cs
new file mode 100644
index 0000000..1860098
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Inner/SpawnFlags.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace Impostor.Server.Net.Inner
+{
+ [Flags]
+ public enum SpawnFlags : byte
+ {
+ None = 0,
+ IsClientCharacter = 1,
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Net/Manager/ClientManager.Api.cs b/Impostor-dev/src/Impostor.Server/Net/Manager/ClientManager.Api.cs
new file mode 100644
index 0000000..6cbe5bf
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Manager/ClientManager.Api.cs
@@ -0,0 +1,11 @@
+using System.Collections.Generic;
+using Impostor.Api.Net;
+using Impostor.Api.Net.Manager;
+
+namespace Impostor.Server.Net.Manager
+{
+ internal partial class ClientManager : IClientManager
+ {
+ IEnumerable<IClient> IClientManager.Clients => _clients.Values;
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Net/Manager/ClientManager.cs b/Impostor-dev/src/Impostor.Server/Net/Manager/ClientManager.cs
new file mode 100644
index 0000000..51e22d8
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Manager/ClientManager.cs
@@ -0,0 +1,103 @@
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Impostor.Api.Innersloth;
+using Impostor.Api.Net;
+using Impostor.Api.Net.Messages;
+using Impostor.Api.Net.Messages.S2C;
+using Impostor.Hazel;
+using Impostor.Server.Config;
+using Impostor.Server.Net.Factories;
+using Microsoft.Extensions.Logging;
+
+namespace Impostor.Server.Net.Manager
+{
+ internal partial class ClientManager
+ {
+ private static HashSet<int> SupportedVersions { get; } = new HashSet<int>
+ {
+ GameVersion.GetVersion(2020, 09, 07), // 2020.09.07 - 2020.09.22
+ GameVersion.GetVersion(2020, 10, 08), // 2020.10.08
+ GameVersion.GetVersion(2020, 11, 17), // 2020.11.17
+ };
+
+ private readonly ILogger<ClientManager> _logger;
+ private readonly ConcurrentDictionary<int, ClientBase> _clients;
+ private readonly IClientFactory _clientFactory;
+ private int _idLast;
+
+ public ClientManager(ILogger<ClientManager> logger, IClientFactory clientFactory)
+ {
+ _logger = logger;
+ _clientFactory = clientFactory;
+ _clients = new ConcurrentDictionary<int, ClientBase>();
+ }
+
+ public IEnumerable<ClientBase> Clients => _clients.Values;
+
+ public int NextId()
+ {
+ var clientId = Interlocked.Increment(ref _idLast);
+
+ if (clientId < 1)
+ {
+ // Super rare but reset the _idLast because of overflow.
+ _idLast = 0;
+
+ // And get a new id.
+ clientId = Interlocked.Increment(ref _idLast);
+ }
+
+ return clientId;
+ }
+
+ public async ValueTask RegisterConnectionAsync(IHazelConnection connection, string name, int clientVersion)
+ {
+ if (!SupportedVersions.Contains(clientVersion))
+ {
+ using var packet = MessageWriter.Get(MessageType.Reliable);
+ Message01JoinGameS2C.SerializeError(packet, false, DisconnectReason.IncorrectVersion);
+ await connection.SendAsync(packet);
+ return;
+ }
+
+ if (name.Length > 10)
+ {
+ using var packet = MessageWriter.Get(MessageType.Reliable);
+ Message01JoinGameS2C.SerializeError(packet, false, DisconnectReason.Custom, DisconnectMessages.UsernameLength);
+ await connection.SendAsync(packet);
+ return;
+ }
+
+ if (string.IsNullOrWhiteSpace(name) || !name.All(TextBox.IsCharAllowed))
+ {
+ using var packet = MessageWriter.Get(MessageType.Reliable);
+ Message01JoinGameS2C.SerializeError(packet, false, DisconnectReason.Custom, DisconnectMessages.UsernameIllegalCharacters);
+ await connection.SendAsync(packet);
+ return;
+ }
+
+ var client = _clientFactory.Create(connection, name, clientVersion);
+ var id = NextId();
+
+ client.Id = id;
+ _logger.LogTrace("Client connected.");
+ _clients.TryAdd(id, client);
+ }
+
+ public void Remove(IClient client)
+ {
+ _logger.LogTrace("Client disconnected.");
+ _clients.TryRemove(client.Id, out _);
+ }
+
+ public bool Validate(IClient client)
+ {
+ return client.Id != 0
+ && _clients.TryGetValue(client.Id, out var registeredClient)
+ && ReferenceEquals(client, registeredClient);
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Net/Manager/GameManager.cs b/Impostor-dev/src/Impostor.Server/Net/Manager/GameManager.cs
new file mode 100644
index 0000000..a37829e
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Manager/GameManager.cs
@@ -0,0 +1,150 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Threading.Tasks;
+using Impostor.Api;
+using Impostor.Api.Events.Managers;
+using Impostor.Api.Games;
+using Impostor.Api.Games.Managers;
+using Impostor.Api.Innersloth;
+using Impostor.Server.Config;
+using Impostor.Server.Events;
+using Impostor.Server.Net.Redirector;
+using Impostor.Server.Net.State;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+
+namespace Impostor.Server.Net.Manager
+{
+ internal class GameManager : IGameManager
+ {
+ private readonly ILogger<GameManager> _logger;
+ private readonly INodeLocator _nodeLocator;
+ private readonly IPEndPoint _publicIp;
+ private readonly ConcurrentDictionary<int, Game> _games;
+ private readonly IServiceProvider _serviceProvider;
+ private readonly IEventManager _eventManager;
+ private readonly IGameCodeFactory _gameCodeFactory;
+
+ public GameManager(ILogger<GameManager> logger, IOptions<ServerConfig> config, INodeLocator nodeLocator, IServiceProvider serviceProvider, IEventManager eventManager, IGameCodeFactory gameCodeFactory)
+ {
+ _logger = logger;
+ _nodeLocator = nodeLocator;
+ _serviceProvider = serviceProvider;
+ _eventManager = eventManager;
+ _gameCodeFactory = gameCodeFactory;
+ _publicIp = new IPEndPoint(IPAddress.Parse(config.Value.ResolvePublicIp()), config.Value.PublicPort);
+ _games = new ConcurrentDictionary<int, Game>();
+ }
+
+ IEnumerable<IGame> IGameManager.Games => _games.Select(kv => kv.Value);
+
+ IGame IGameManager.Find(GameCode code) => Find(code);
+
+ public async ValueTask<IGame> CreateAsync(GameOptionsData options)
+ {
+ // TODO: Prevent duplicates when using server redirector using INodeProvider.
+ var (success, game) = await TryCreateAsync(options);
+
+ for (int i = 0; i < 10 && !success; i++)
+ {
+ (success, game) = await TryCreateAsync(options);
+ }
+
+ if (!success)
+ {
+ throw new ImpostorException("Could not create new game"); // TODO: Fix generic exception.
+ }
+
+ return game;
+ }
+
+ private async ValueTask<(bool success, Game game)> TryCreateAsync(GameOptionsData options)
+ {
+ var gameCode = _gameCodeFactory.Create();
+ var gameCodeStr = gameCode.Code;
+ var game = ActivatorUtilities.CreateInstance<Game>(_serviceProvider, _publicIp, gameCode, options);
+
+ if (await _nodeLocator.ExistsAsync(gameCodeStr) || !_games.TryAdd(gameCode, game))
+ {
+ return (false, null);
+ }
+
+ await _nodeLocator.SaveAsync(gameCodeStr, _publicIp);
+ _logger.LogDebug("Created game with code {0}.", game.Code);
+
+ await _eventManager.CallAsync(new GameCreatedEvent(game));
+
+ return (true, game);
+ }
+
+ public Game Find(GameCode code)
+ {
+ _games.TryGetValue(code, out var game);
+ return game;
+ }
+
+ public IEnumerable<Game> FindListings(MapFlags map, int impostorCount, GameKeywords language, int count = 10)
+ {
+ var results = 0;
+
+ // Find games that have not started yet.
+ foreach (var (_, game) in _games.Where(x =>
+ x.Value.IsPublic &&
+ x.Value.GameState == GameStates.NotStarted &&
+ x.Value.PlayerCount < x.Value.Options.MaxPlayers))
+ {
+ // Check for options.
+ if (!map.HasFlag((MapFlags)(1 << game.Options.MapId)))
+ {
+ continue;
+ }
+
+ if (!language.HasFlag(game.Options.Keywords))
+ {
+ continue;
+ }
+
+ if (impostorCount != 0 && game.Options.NumImpostors != impostorCount)
+ {
+ continue;
+ }
+
+ // Add to result.
+ yield return game;
+
+ // Break out if we have enough.
+ if (++results == count)
+ {
+ yield break;
+ }
+ }
+ }
+
+ public async ValueTask RemoveAsync(GameCode gameCode)
+ {
+ if (_games.TryGetValue(gameCode, out var game) && game.PlayerCount > 0)
+ {
+ foreach (var player in game.Players)
+ {
+ await player.KickAsync();
+ }
+
+ return;
+ }
+
+ if (!_games.TryRemove(gameCode, out game))
+ {
+ return;
+ }
+
+ _logger.LogDebug("Remove game with code {0} ({1}).", GameCodeParser.IntToGameName(gameCode), gameCode);
+ await _nodeLocator.RemoveAsync(GameCodeParser.IntToGameName(gameCode));
+
+ await _eventManager.CallAsync(new GameDestroyedEvent(game));
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Net/Matchmaker.cs b/Impostor-dev/src/Impostor.Server/Net/Matchmaker.cs
new file mode 100644
index 0000000..64ece55
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Matchmaker.cs
@@ -0,0 +1,66 @@
+using System;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading.Tasks;
+using Impostor.Hazel;
+using Impostor.Hazel.Udp;
+using Impostor.Server.Net.Hazel;
+using Impostor.Server.Net.Manager;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.ObjectPool;
+
+namespace Impostor.Server.Net
+{
+ internal class Matchmaker
+ {
+ private readonly ClientManager _clientManager;
+ private readonly ObjectPool<MessageReader> _readerPool;
+ private readonly ILogger<Matchmaker> _logger;
+ private readonly ILogger<HazelConnection> _connectionLogger;
+ private UdpConnectionListener _connection;
+
+ public Matchmaker(
+ ILogger<Matchmaker> logger,
+ ClientManager clientManager,
+ ObjectPool<MessageReader> readerPool,
+ ILogger<HazelConnection> connectionLogger)
+ {
+ _logger = logger;
+ _clientManager = clientManager;
+ _readerPool = readerPool;
+ _connectionLogger = connectionLogger;
+ }
+
+ public async ValueTask StartAsync(IPEndPoint ipEndPoint)
+ {
+ var mode = ipEndPoint.AddressFamily switch
+ {
+ AddressFamily.InterNetwork => IPMode.IPv4,
+ AddressFamily.InterNetworkV6 => IPMode.IPv6,
+ _ => throw new InvalidOperationException()
+ };
+
+ _connection = new UdpConnectionListener(ipEndPoint, _readerPool, mode);
+ _connection.NewConnection = OnNewConnection;
+
+ await _connection.StartAsync();
+ }
+
+ public async ValueTask StopAsync()
+ {
+ await _connection.DisposeAsync();
+ }
+
+ private async ValueTask OnNewConnection(NewConnectionEventArgs e)
+ {
+ // Handshake.
+ var clientVersion = e.HandshakeData.ReadInt32();
+ var name = e.HandshakeData.ReadString();
+
+ var connection = new HazelConnection(e.Connection, _connectionLogger);
+
+ // Register client
+ await _clientManager.RegisterConnectionAsync(connection, name, clientVersion);
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Net/MatchmakerService.cs b/Impostor-dev/src/Impostor.Server/Net/MatchmakerService.cs
new file mode 100644
index 0000000..bd9a855
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/MatchmakerService.cs
@@ -0,0 +1,57 @@
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+using Impostor.Server.Config;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+
+namespace Impostor.Server.Net
+{
+ internal class MatchmakerService : IHostedService
+ {
+ private readonly ILogger<MatchmakerService> _logger;
+ private readonly ServerConfig _serverConfig;
+ private readonly ServerRedirectorConfig _redirectorConfig;
+ private readonly Matchmaker _matchmaker;
+
+ public MatchmakerService(
+ ILogger<MatchmakerService> logger,
+ IOptions<ServerConfig> serverConfig,
+ IOptions<ServerRedirectorConfig> redirectorConfig,
+ Matchmaker matchmaker)
+ {
+ _logger = logger;
+ _serverConfig = serverConfig.Value;
+ _redirectorConfig = redirectorConfig.Value;
+ _matchmaker = matchmaker;
+ }
+
+ public async Task StartAsync(CancellationToken cancellationToken)
+ {
+ var endpoint = new IPEndPoint(IPAddress.Parse(_serverConfig.ResolveListenIp()), _serverConfig.ListenPort);
+
+ await _matchmaker.StartAsync(endpoint);
+
+ _logger.LogInformation(
+ "Matchmaker is listening on {0}:{1}, the public server ip is {2}:{3}.",
+ endpoint.Address,
+ endpoint.Port,
+ _serverConfig.ResolvePublicIp(),
+ _serverConfig.PublicPort);
+
+ if (_redirectorConfig.Enabled)
+ {
+ _logger.LogWarning(_redirectorConfig.Master
+ ? "Server redirection is enabled as master, this instance will redirect clients to other nodes."
+ : "Server redirection is enabled as node, this instance will accept clients.");
+ }
+ }
+
+ public async Task StopAsync(CancellationToken cancellationToken)
+ {
+ _logger.LogWarning("Matchmaker is shutting down!");
+ await _matchmaker.StopAsync();
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Net/Messages/MessageWriterProvider.cs b/Impostor-dev/src/Impostor.Server/Net/Messages/MessageWriterProvider.cs
new file mode 100644
index 0000000..707860e
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Messages/MessageWriterProvider.cs
@@ -0,0 +1,13 @@
+using Impostor.Api.Net.Messages;
+using Impostor.Hazel;
+
+namespace Impostor.Server.Net.Messages
+{
+ public class MessageWriterProvider : IMessageWriterProvider
+ {
+ public IMessageWriter Get(MessageType sendOption = MessageType.Unreliable)
+ {
+ return MessageWriter.Get(sendOption);
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Net/Redirector/ClientRedirector.cs b/Impostor-dev/src/Impostor.Server/Net/Redirector/ClientRedirector.cs
new file mode 100644
index 0000000..3dad4b3
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Redirector/ClientRedirector.cs
@@ -0,0 +1,97 @@
+using System.Threading.Tasks;
+using Impostor.Api.Innersloth;
+using Impostor.Api.Net.Messages;
+using Impostor.Api.Net.Messages.C2S;
+using Impostor.Api.Net.Messages.S2C;
+using Impostor.Hazel;
+using Impostor.Server.Config;
+using Impostor.Server.Net.Hazel;
+using Impostor.Server.Net.Manager;
+using Serilog;
+using ILogger = Serilog.ILogger;
+
+namespace Impostor.Server.Net.Redirector
+{
+ internal class ClientRedirector : ClientBase
+ {
+ private static readonly ILogger Logger = Log.ForContext<ClientRedirector>();
+
+ private readonly ClientManager _clientManager;
+ private readonly INodeProvider _nodeProvider;
+ private readonly INodeLocator _nodeLocator;
+
+ public ClientRedirector(
+ string name,
+ HazelConnection connection,
+ ClientManager clientManager,
+ INodeProvider nodeProvider,
+ INodeLocator nodeLocator)
+ : base(name, connection)
+ {
+ _clientManager = clientManager;
+ _nodeProvider = nodeProvider;
+ _nodeLocator = nodeLocator;
+ }
+
+ public override async ValueTask HandleMessageAsync(IMessageReader reader, MessageType messageType)
+ {
+ var flag = reader.Tag;
+
+ Logger.Verbose("Server got {0}.", flag);
+
+ switch (flag)
+ {
+ case MessageFlags.HostGame:
+ {
+ using var packet = MessageWriter.Get(MessageType.Reliable);
+ Message13RedirectS2C.Serialize(packet, false, _nodeProvider.Get());
+ await Connection.SendAsync(packet);
+ break;
+ }
+
+ case MessageFlags.JoinGame:
+ {
+ Message01JoinGameC2S.Deserialize(
+ reader,
+ out var gameCode,
+ out _);
+
+ using var packet = MessageWriter.Get(MessageType.Reliable);
+ var endpoint = await _nodeLocator.FindAsync(GameCodeParser.IntToGameName(gameCode));
+ if (endpoint == null)
+ {
+ Message01JoinGameS2C.SerializeError(packet, false, DisconnectReason.GameMissing);
+ }
+ else
+ {
+ Message13RedirectS2C.Serialize(packet, false, endpoint);
+ }
+
+ await Connection.SendAsync(packet);
+ break;
+ }
+
+ case MessageFlags.GetGameListV2:
+ {
+ // TODO: Implement.
+ using var packet = MessageWriter.Get(MessageType.Reliable);
+ Message01JoinGameS2C.SerializeError(packet, false, DisconnectReason.Custom, DisconnectMessages.NotImplemented);
+ await Connection.SendAsync(packet);
+ break;
+ }
+
+ default:
+ {
+ Logger.Warning("Received unsupported message flag on the redirector ({0}).", flag);
+ break;
+ }
+ }
+ }
+
+ public override ValueTask HandleDisconnectAsync(string reason)
+ {
+ _clientManager.Remove(this);
+ return default;
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Net/Redirector/INodeLocator.cs b/Impostor-dev/src/Impostor.Server/Net/Redirector/INodeLocator.cs
new file mode 100644
index 0000000..12563b9
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Redirector/INodeLocator.cs
@@ -0,0 +1,14 @@
+using System.Net;
+using System.Threading.Tasks;
+
+namespace Impostor.Server.Net.Redirector
+{
+ public interface INodeLocator
+ {
+ ValueTask<IPEndPoint> FindAsync(string gameCode);
+
+ ValueTask SaveAsync(string gameCode, IPEndPoint endPoint);
+
+ ValueTask RemoveAsync(string gameCode);
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Net/Redirector/INodeProvider.cs b/Impostor-dev/src/Impostor.Server/Net/Redirector/INodeProvider.cs
new file mode 100644
index 0000000..318fcb2
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Redirector/INodeProvider.cs
@@ -0,0 +1,9 @@
+using System.Net;
+
+namespace Impostor.Server.Net.Redirector
+{
+ internal interface INodeProvider
+ {
+ IPEndPoint Get();
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Net/Redirector/NodeLocatorNoOp.cs b/Impostor-dev/src/Impostor.Server/Net/Redirector/NodeLocatorNoOp.cs
new file mode 100644
index 0000000..fd4cd56
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Redirector/NodeLocatorNoOp.cs
@@ -0,0 +1,14 @@
+using System.Net;
+using System.Threading.Tasks;
+
+namespace Impostor.Server.Net.Redirector
+{
+ public class NodeLocatorNoOp : INodeLocator
+ {
+ public ValueTask<IPEndPoint> FindAsync(string gameCode) => ValueTask.FromResult(default(IPEndPoint));
+
+ public ValueTask SaveAsync(string gameCode, IPEndPoint endPoint) => ValueTask.CompletedTask;
+
+ public ValueTask RemoveAsync(string gameCode) => ValueTask.CompletedTask;
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Net/Redirector/NodeLocatorRedis.cs b/Impostor-dev/src/Impostor.Server/Net/Redirector/NodeLocatorRedis.cs
new file mode 100644
index 0000000..0b6fdff
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Redirector/NodeLocatorRedis.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Net;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Caching.Distributed;
+using Microsoft.Extensions.Logging;
+
+namespace Impostor.Server.Net.Redirector
+{
+ public class NodeLocatorRedis : INodeLocator
+ {
+ private readonly IDistributedCache _cache;
+
+ public NodeLocatorRedis(ILogger<NodeLocatorRedis> logger, IDistributedCache cache)
+ {
+ logger.LogWarning("Using the redis NodeLocator.");
+ _cache = cache;
+ }
+
+ public async ValueTask<IPEndPoint> FindAsync(string gameCode)
+ {
+ var entry = await _cache.GetStringAsync(gameCode);
+ if (entry == null)
+ {
+ return null;
+ }
+
+ return IPEndPoint.Parse(entry);
+ }
+
+ public async ValueTask SaveAsync(string gameCode, IPEndPoint endPoint)
+ {
+ await _cache.SetStringAsync(gameCode, endPoint.ToString(), new DistributedCacheEntryOptions
+ {
+ SlidingExpiration = TimeSpan.FromHours(1),
+ });
+ }
+
+ public async ValueTask RemoveAsync(string gameCode)
+ {
+ await _cache.RemoveAsync(gameCode);
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Net/Redirector/NodeLocatorUDP.cs b/Impostor-dev/src/Impostor.Server/Net/Redirector/NodeLocatorUDP.cs
new file mode 100644
index 0000000..2539a8f
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Redirector/NodeLocatorUDP.cs
@@ -0,0 +1,134 @@
+using System;
+using System.Collections.Concurrent;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading.Tasks;
+using Impostor.Server.Config;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+
+namespace Impostor.Server.Net.Redirector
+{
+ public class NodeLocatorUdp : INodeLocator, IDisposable
+ {
+ private readonly ILogger<NodeLocatorUdp> _logger;
+ private readonly bool _isMaster;
+ private readonly IPEndPoint _server;
+ private readonly UdpClient _client;
+ private readonly ConcurrentDictionary<string, AvailableNode> _availableNodes;
+
+ public NodeLocatorUdp(ILogger<NodeLocatorUdp> logger, IOptions<ServerRedirectorConfig> config)
+ {
+ _logger = logger;
+
+ if (config.Value.Master)
+ {
+ _isMaster = true;
+ _availableNodes = new ConcurrentDictionary<string, AvailableNode>();
+ }
+ else
+ {
+ _isMaster = false;
+
+ if (!IPEndPoint.TryParse(config.Value.Locator.UdpMasterEndpoint, out var endpoint))
+ {
+ throw new ArgumentException("UdpMasterEndpoint should be in the ip:port format.");
+ }
+
+ _logger.LogWarning("Node server will send updates to {0}.", endpoint);
+ _server = endpoint;
+ _client = new UdpClient();
+
+ try
+ {
+ _client.DontFragment = true;
+ }
+ catch (SocketException)
+ {
+ }
+ }
+ }
+
+ public void Update(IPEndPoint ip, string gameCode)
+ {
+ _logger.LogDebug("Received update {0} -> {1}", gameCode, ip);
+
+ _availableNodes.AddOrUpdate(
+ gameCode,
+ s => new AvailableNode
+ {
+ Endpoint = ip,
+ LastUpdated = DateTimeOffset.UtcNow,
+ },
+ (s, node) =>
+ {
+ node.Endpoint = ip;
+ node.LastUpdated = DateTimeOffset.UtcNow;
+
+ return node;
+ });
+
+ foreach (var (key, value) in _availableNodes)
+ {
+ if (value.Expired)
+ {
+ _availableNodes.TryRemove(key, out _);
+ }
+ }
+ }
+
+ public ValueTask<IPEndPoint> FindAsync(string gameCode)
+ {
+ if (!_isMaster)
+ {
+ return ValueTask.FromResult(default(IPEndPoint));
+ }
+
+ if (_availableNodes.TryGetValue(gameCode, out var node))
+ {
+ if (node.Expired)
+ {
+ _availableNodes.TryRemove(gameCode, out _);
+ return ValueTask.FromResult(default(IPEndPoint));
+ }
+
+ return ValueTask.FromResult(node.Endpoint);
+ }
+
+ return ValueTask.FromResult(default(IPEndPoint));
+ }
+
+ public ValueTask RemoveAsync(string gameCode)
+ {
+ if (!_isMaster)
+ {
+ return ValueTask.CompletedTask;
+ }
+
+ _availableNodes.TryRemove(gameCode, out _);
+ return ValueTask.CompletedTask;
+ }
+
+ public ValueTask SaveAsync(string gameCode, IPEndPoint endPoint)
+ {
+ var data = Encoding.UTF8.GetBytes($"{gameCode},{endPoint}");
+ _client.Send(data, data.Length, _server);
+ return ValueTask.CompletedTask;
+ }
+
+ public void Dispose()
+ {
+ _client?.Dispose();
+ }
+
+ private class AvailableNode
+ {
+ public IPEndPoint Endpoint { get; set; }
+
+ public DateTimeOffset LastUpdated { get; set; }
+
+ public bool Expired => LastUpdated < DateTimeOffset.UtcNow.AddHours(-1);
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Net/Redirector/NodeLocatorUDPService.cs b/Impostor-dev/src/Impostor.Server/Net/Redirector/NodeLocatorUDPService.cs
new file mode 100644
index 0000000..3706bb4
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Redirector/NodeLocatorUDPService.cs
@@ -0,0 +1,101 @@
+using System;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Impostor.Server.Config;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+
+namespace Impostor.Server.Net.Redirector
+{
+ public class NodeLocatorUdpService : BackgroundService
+ {
+ private readonly NodeLocatorUdp _nodeLocator;
+ private readonly ILogger<NodeLocatorUdpService> _logger;
+ private readonly UdpClient _client;
+
+ public NodeLocatorUdpService(
+ INodeLocator nodeLocator,
+ ILogger<NodeLocatorUdpService> logger,
+ IOptions<ServerRedirectorConfig> options)
+ {
+ _nodeLocator = (NodeLocatorUdp)nodeLocator;
+ _logger = logger;
+
+ if (!IPEndPoint.TryParse(options.Value.Locator.UdpMasterEndpoint, out var endpoint))
+ {
+ throw new ArgumentException("UdpMasterEndpoint should be in the ip:port format.");
+ }
+
+ _client = new UdpClient(endpoint);
+
+ try
+ {
+ _client.DontFragment = true;
+ }
+ catch (SocketException)
+ {
+ }
+ }
+
+ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
+ {
+ _logger.LogWarning("Master server is listening for node updates on {0}.", _client.Client.LocalEndPoint);
+
+ stoppingToken.Register(() =>
+ {
+ _client.Close();
+ _client.Dispose();
+ });
+
+ try
+ {
+ while (!stoppingToken.IsCancellationRequested)
+ {
+ // Receive data from a node.
+ UdpReceiveResult data;
+
+ try
+ {
+ data = await _client.ReceiveAsync();
+ }
+ catch (ObjectDisposedException)
+ {
+ break;
+ }
+
+ // Check if data is valid.
+ if (data.Buffer.Length == 0)
+ {
+ break;
+ }
+
+ // Parse the data.
+ var message = Encoding.UTF8.GetString(data.Buffer);
+ var parts = message.Split(',', 2);
+ if (parts.Length != 2)
+ {
+ continue;
+ }
+
+ if (!IPEndPoint.TryParse(parts[1], out var ipEndPoint))
+ {
+ continue;
+ }
+
+ // Update the NodeLocator.
+ _nodeLocator.Update(ipEndPoint, parts[0]);
+ }
+ }
+ catch (Exception e)
+ {
+ _logger.LogError(e, "Error in NodeLocatorUDPService.");
+ }
+
+ _logger.LogWarning("Master server node update listener is stopping.");
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Net/Redirector/NodeProviderConfig.cs b/Impostor-dev/src/Impostor.Server/Net/Redirector/NodeProviderConfig.cs
new file mode 100644
index 0000000..4e19c43
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/Redirector/NodeProviderConfig.cs
@@ -0,0 +1,43 @@
+using System.Collections.Generic;
+using System.Net;
+using Impostor.Server.Config;
+using Microsoft.Extensions.Options;
+
+namespace Impostor.Server.Net.Redirector
+{
+ internal class NodeProviderConfig : INodeProvider
+ {
+ private readonly List<IPEndPoint> _nodes;
+ private readonly object _lock;
+ private int _currentIndex;
+
+ public NodeProviderConfig(IOptions<ServerRedirectorConfig> redirectorConfig)
+ {
+ _nodes = new List<IPEndPoint>();
+ _lock = new object();
+
+ if (redirectorConfig.Value.Nodes != null)
+ {
+ foreach (var node in redirectorConfig.Value.Nodes)
+ {
+ _nodes.Add(new IPEndPoint(IPAddress.Parse(node.Ip), node.Port));
+ }
+ }
+ }
+
+ public IPEndPoint Get()
+ {
+ lock (_lock)
+ {
+ var node = _nodes[_currentIndex++];
+
+ if (_currentIndex == _nodes.Count)
+ {
+ _currentIndex = 0;
+ }
+
+ return node;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Net/State/ClientPlayer.Api.cs b/Impostor-dev/src/Impostor.Server/Net/State/ClientPlayer.Api.cs
new file mode 100644
index 0000000..2e2467e
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/State/ClientPlayer.Api.cs
@@ -0,0 +1,18 @@
+using Impostor.Api.Games;
+using Impostor.Api.Net;
+using Impostor.Api.Net.Inner.Objects;
+
+namespace Impostor.Server.Net.State
+{
+ internal partial class ClientPlayer
+ {
+ /// <inheritdoc />
+ IClient IClientPlayer.Client => Client;
+
+ /// <inheritdoc />
+ IGame IClientPlayer.Game => Game;
+
+ /// <inheritdoc />
+ IInnerPlayerControl? IClientPlayer.Character => Character;
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Net/State/ClientPlayer.cs b/Impostor-dev/src/Impostor.Server/Net/State/ClientPlayer.cs
new file mode 100644
index 0000000..107edbf
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/State/ClientPlayer.cs
@@ -0,0 +1,88 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Impostor.Api.Net;
+using Impostor.Api.Net.Inner;
+using Impostor.Server.Net.Inner.Objects;
+using Microsoft.Extensions.Logging;
+
+namespace Impostor.Server.Net.State
+{
+ internal partial class ClientPlayer : IClientPlayer
+ {
+ private readonly ILogger<ClientPlayer> _logger;
+ private readonly Timer _spawnTimeout;
+
+ public ClientPlayer(ILogger<ClientPlayer> logger, ClientBase client, Game game)
+ {
+ _logger = logger;
+ _spawnTimeout = new Timer(RunSpawnTimeout, null, -1, -1);
+
+ Game = game;
+ Client = client;
+ Limbo = LimboStates.PreSpawn;
+ }
+
+ public ClientBase Client { get; }
+
+ public Game Game { get; }
+
+ /// <inheritdoc />
+ public LimboStates Limbo { get; set; }
+
+ public InnerPlayerControl? Character { get; internal set; }
+
+ public bool IsHost => Game?.Host == this;
+
+ public string Scene { get; internal set; }
+
+ public void InitializeSpawnTimeout()
+ {
+ _spawnTimeout.Change(Constants.SpawnTimeout, -1);
+ }
+
+ public void DisableSpawnTimeout()
+ {
+ _spawnTimeout.Change(-1, -1);
+ }
+
+ /// <inheritdoc />
+ public bool IsOwner(IInnerNetObject netObject)
+ {
+ return Client.Id == netObject.OwnerId;
+ }
+
+ /// <inheritdoc />
+ public ValueTask KickAsync()
+ {
+ return Game.HandleKickPlayer(Client.Id, false);
+ }
+
+ /// <inheritdoc />
+ public ValueTask BanAsync()
+ {
+ return Game.HandleKickPlayer(Client.Id, true);
+ }
+
+ private async void RunSpawnTimeout(object state)
+ {
+ try
+ {
+ if (Character == null)
+ {
+ _logger.LogInformation("{0} - Player {1} spawn timed out, kicking.", Game.Code, Client.Id);
+
+ await KickAsync();
+ }
+ }
+ catch (Exception e)
+ {
+ _logger.LogError(e, "Exception caught while kicking player for spawn timeout.");
+ }
+ finally
+ {
+ await _spawnTimeout.DisposeAsync();
+ }
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Net/State/Game.Api.cs b/Impostor-dev/src/Impostor.Server/Net/State/Game.Api.cs
new file mode 100644
index 0000000..f395be2
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/State/Game.Api.cs
@@ -0,0 +1,70 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Threading.Tasks;
+using Impostor.Api;
+using Impostor.Api.Games;
+using Impostor.Api.Innersloth;
+using Impostor.Api.Net;
+using Impostor.Api.Net.Inner;
+using Impostor.Api.Net.Inner.Objects;
+using Impostor.Server.Net.Inner;
+
+namespace Impostor.Server.Net.State
+{
+ internal partial class Game : IGame
+ {
+ IClientPlayer IGame.Host => Host;
+
+ IGameNet IGame.GameNet => GameNet;
+
+ public void BanIp(IPAddress ipAddress)
+ {
+ _bannedIps.Add(ipAddress);
+ }
+
+ public async ValueTask SyncSettingsAsync()
+ {
+ if (Host.Character == null)
+ {
+ throw new ImpostorException("Attempted to set infected when the host was not spawned.");
+ }
+
+ using (var writer = StartRpc(Host.Character.NetId, RpcCalls.SyncSettings))
+ {
+ // Someone will probably forget to do this, so we include it here.
+ // If this is not done, the host will overwrite changes later with the defaults.
+ Options.IsDefaults = false;
+
+ await using (var memory = new MemoryStream())
+ await using (var writerBin = new BinaryWriter(memory))
+ {
+ Options.Serialize(writerBin, GameOptionsData.LatestVersion);
+ writer.WriteBytesAndSize(memory.ToArray());
+ }
+
+ await FinishRpcAsync(writer);
+ }
+ }
+
+ public async ValueTask SetInfectedAsync(IEnumerable<IInnerPlayerControl> players)
+ {
+ if (Host.Character == null)
+ {
+ throw new ImpostorException("Attempted to set infected when the host was not spawned.");
+ }
+
+ using (var writer = StartRpc(Host.Character.NetId, RpcCalls.SetInfected))
+ {
+ writer.Write((byte)Host.Character.NetId);
+
+ foreach (var player in players)
+ {
+ writer.Write((byte)player.PlayerId);
+ }
+
+ await FinishRpcAsync(writer);
+ }
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Net/State/Game.Data.cs b/Impostor-dev/src/Impostor.Server/Net/State/Game.Data.cs
new file mode 100644
index 0000000..a84d9b5
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/State/Game.Data.cs
@@ -0,0 +1,449 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Impostor.Api;
+using Impostor.Api.Innersloth;
+using Impostor.Api.Net.Messages;
+using Impostor.Api.Net.Messages.S2C;
+using Impostor.Hazel;
+using Impostor.Server.Events.Meeting;
+using Impostor.Server.Events.Player;
+using Impostor.Server.Net.Inner;
+using Impostor.Server.Net.Inner.Objects;
+using Impostor.Server.Net.Inner.Objects.Components;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Impostor.Server.Net.State
+{
+ internal partial class Game
+ {
+ private const int FakeClientId = int.MaxValue - 1;
+
+ /// <summary>
+ /// Used for global object, spawned by the host.
+ /// </summary>
+ private const int InvalidClient = -2;
+
+ /// <summary>
+ /// Used internally to set the OwnerId to the current ClientId.
+ /// i.e: <code>ownerId = ownerId == -3 ? this.ClientId : ownerId;</code>
+ /// </summary>
+ private const int CurrentClient = -3;
+
+ private static readonly Type[] SpawnableObjects =
+ {
+ typeof(InnerShipStatus), // ShipStatus
+ typeof(InnerMeetingHud),
+ typeof(InnerLobbyBehaviour),
+ typeof(InnerGameData),
+ typeof(InnerPlayerControl),
+ typeof(InnerShipStatus), // HeadQuarters
+ typeof(InnerShipStatus), // PlanetMap
+ typeof(InnerShipStatus), // AprilShipStatus
+ };
+
+ private readonly List<InnerNetObject> _allObjects = new List<InnerNetObject>();
+ private readonly Dictionary<uint, InnerNetObject> _allObjectsFast = new Dictionary<uint, InnerNetObject>();
+
+ private int _gamedataInitialized;
+ private bool _gamedataFakeReceived;
+
+ private async ValueTask OnSpawnAsync(InnerNetObject netObj)
+ {
+ switch (netObj)
+ {
+ case InnerLobbyBehaviour lobby:
+ {
+ GameNet.LobbyBehaviour = lobby;
+ break;
+ }
+
+ case InnerGameData data:
+ {
+ GameNet.GameData = data;
+ break;
+ }
+
+ case InnerVoteBanSystem voteBan:
+ {
+ GameNet.VoteBan = voteBan;
+ break;
+ }
+
+ case InnerShipStatus shipStatus:
+ {
+ GameNet.ShipStatus = shipStatus;
+ break;
+ }
+
+ case InnerPlayerControl control:
+ {
+ // Hook up InnerPlayerControl <-> IClientPlayer.
+ if (!TryGetPlayer(control.OwnerId, out var player))
+ {
+ throw new ImpostorException("Failed to find player that spawned the InnerPlayerControl");
+ }
+
+ player.Character = control;
+ player.DisableSpawnTimeout();
+
+ // Hook up InnerPlayerControl <-> InnerPlayerControl.PlayerInfo.
+ control.PlayerInfo = GameNet.GameData.GetPlayerById(control.PlayerId)!;
+
+ if (control.PlayerInfo == null)
+ {
+ GameNet.GameData.AddPlayer(control);
+ }
+
+ if (control.PlayerInfo != null)
+ {
+ control.PlayerInfo!.Controller = control;
+ }
+
+ await _eventManager.CallAsync(new PlayerSpawnedEvent(this, player, control));
+
+ break;
+ }
+
+ case InnerMeetingHud meetingHud:
+ {
+ await _eventManager.CallAsync(new MeetingStartedEvent(this, meetingHud));
+ break;
+ }
+ }
+ }
+
+ private async ValueTask OnDestroyAsync(InnerNetObject netObj)
+ {
+ switch (netObj)
+ {
+ case InnerLobbyBehaviour:
+ {
+ GameNet.LobbyBehaviour = null;
+ break;
+ }
+
+ case InnerGameData:
+ {
+ GameNet.GameData = null;
+ break;
+ }
+
+ case InnerVoteBanSystem:
+ {
+ GameNet.VoteBan = null;
+ break;
+ }
+
+ case InnerShipStatus:
+ {
+ GameNet.ShipStatus = null;
+ break;
+ }
+
+ case InnerPlayerControl control:
+ {
+ // Remove InnerPlayerControl <-> IClientPlayer.
+ if (TryGetPlayer(control.OwnerId, out var player))
+ {
+ player.Character = null;
+ }
+
+ await _eventManager.CallAsync(new PlayerDestroyedEvent(this, player, control));
+
+ break;
+ }
+ }
+ }
+
+ private async ValueTask InitGameDataAsync(ClientPlayer player)
+ {
+ if (Interlocked.Exchange(ref _gamedataInitialized, 1) != 0)
+ {
+ return;
+ }
+
+ /*
+ * The Among Us client on 20.9.22i spawns some components on the host side and
+ * only spawns these on other clients when someone else connects. This means that we can't
+ * parse data until someone connects because we don't know which component belongs to the NetId.
+ *
+ * We solve this by spawning a fake player and removing the player when the spawn GameData
+ * is received in HandleGameDataAsync.
+ */
+ using (var message = MessageWriter.Get(MessageType.Reliable))
+ {
+ // Spawn a fake player.
+ Message01JoinGameS2C.SerializeJoin(message, false, Code, FakeClientId, HostId);
+
+ message.StartMessage(MessageFlags.GameData);
+ message.Write(Code);
+ message.StartMessage(GameDataTag.SceneChangeFlag);
+ message.WritePacked(FakeClientId);
+ message.Write("OnlineGame");
+ message.EndMessage();
+ message.EndMessage();
+
+ await player.Client.Connection.SendAsync(message);
+ }
+ }
+
+ public async ValueTask<bool> HandleGameDataAsync(IMessageReader parent, ClientPlayer sender, bool toPlayer)
+ {
+ // Find target player.
+ ClientPlayer target = null;
+
+ if (toPlayer)
+ {
+ var targetId = parent.ReadPackedInt32();
+ if (targetId == FakeClientId && !_gamedataFakeReceived && sender.IsHost)
+ {
+ _gamedataFakeReceived = true;
+
+ // Remove the fake client, we received the data.
+ using (var message = MessageWriter.Get(MessageType.Reliable))
+ {
+ WriteRemovePlayerMessage(message, false, FakeClientId, (byte)DisconnectReason.ExitGame);
+
+ await sender.Client.Connection.SendAsync(message);
+ }
+ }
+ else if (!TryGetPlayer(targetId, out target))
+ {
+ _logger.LogWarning("Player {0} tried to send GameData to unknown player {1}.", sender.Client.Id, targetId);
+ return false;
+ }
+
+ _logger.LogTrace("Received GameData for target {0}.", targetId);
+ }
+
+ // Parse GameData messages.
+ while (parent.Position < parent.Length)
+ {
+ using var reader = parent.ReadMessage();
+
+ switch (reader.Tag)
+ {
+ case GameDataTag.DataFlag:
+ {
+ var netId = reader.ReadPackedUInt32();
+ if (_allObjectsFast.TryGetValue(netId, out var obj))
+ {
+ obj.Deserialize(sender, target, reader, false);
+ }
+ else
+ {
+ _logger.LogWarning("Received DataFlag for unregistered NetId {0}.", netId);
+ }
+
+ break;
+ }
+
+ case GameDataTag.RpcFlag:
+ {
+ var netId = reader.ReadPackedUInt32();
+ if (_allObjectsFast.TryGetValue(netId, out var obj))
+ {
+ await obj.HandleRpc(sender, target, (RpcCalls) reader.ReadByte(), reader);
+ }
+ else
+ {
+ _logger.LogWarning("Received RpcFlag for unregistered NetId {0}.", netId);
+ }
+
+ break;
+ }
+
+ case GameDataTag.SpawnFlag:
+ {
+ // Only the host is allowed to despawn objects.
+ if (!sender.IsHost)
+ {
+ throw new ImpostorCheatException("Tried to send SpawnFlag as non-host.");
+ }
+
+ var objectId = reader.ReadPackedUInt32();
+ if (objectId < SpawnableObjects.Length)
+ {
+ var innerNetObject = (InnerNetObject) ActivatorUtilities.CreateInstance(_serviceProvider, SpawnableObjects[objectId], this);
+ var ownerClientId = reader.ReadPackedInt32();
+
+ // Prevent fake client from being broadcasted.
+ // TODO: Remove message from stream properly.
+ if (ownerClientId == FakeClientId)
+ {
+ return false;
+ }
+
+ innerNetObject.SpawnFlags = (SpawnFlags) reader.ReadByte();
+
+ var components = innerNetObject.GetComponentsInChildren<InnerNetObject>();
+ var componentsCount = reader.ReadPackedInt32();
+
+ if (componentsCount != components.Count)
+ {
+ _logger.LogError(
+ "Children didn't match for spawnable {0}, name {1} ({2} != {3})",
+ objectId,
+ innerNetObject.GetType().Name,
+ componentsCount,
+ components.Count);
+ continue;
+ }
+
+ _logger.LogDebug(
+ "Spawning {0} components, SpawnFlags {1}",
+ innerNetObject.GetType().Name,
+ innerNetObject.SpawnFlags);
+
+ for (var i = 0; i < componentsCount; i++)
+ {
+ var obj = components[i];
+
+ obj.NetId = reader.ReadPackedUInt32();
+ obj.OwnerId = ownerClientId;
+
+ _logger.LogDebug(
+ "- {0}, NetId {1}, OwnerId {2}",
+ obj.GetType().Name,
+ obj.NetId,
+ obj.OwnerId);
+
+ if (!AddNetObject(obj))
+ {
+ _logger.LogTrace("Failed to AddNetObject, it already exists.");
+
+ obj.NetId = uint.MaxValue;
+ break;
+ }
+
+ using var readerSub = reader.ReadMessage();
+ if (readerSub.Length > 0)
+ {
+ obj.Deserialize(sender, target, readerSub, true);
+ }
+
+ await OnSpawnAsync(obj);
+ }
+
+ continue;
+ }
+
+ _logger.LogError("Couldn't find spawnable object {0}.", objectId);
+ break;
+ }
+
+ // Only the host is allowed to despawn objects.
+ case GameDataTag.DespawnFlag:
+ {
+ var netId = reader.ReadPackedUInt32();
+ if (_allObjectsFast.TryGetValue(netId, out var obj))
+ {
+ if (sender.Client.Id != obj.OwnerId && !sender.IsHost)
+ {
+ _logger.LogWarning(
+ "Player {0} ({1}) tried to send DespawnFlag for {2} but was denied.",
+ sender.Client.Name,
+ sender.Client.Id,
+ netId);
+ return false;
+ }
+
+ RemoveNetObject(obj);
+ await OnDestroyAsync(obj);
+ _logger.LogDebug("Destroyed InnerNetObject {0} ({1}), OwnerId {2}", obj.GetType().Name, netId, obj.OwnerId);
+ }
+ else
+ {
+ _logger.LogDebug(
+ "Player {0} ({1}) sent DespawnFlag for unregistered NetId {2}.",
+ sender.Client.Name,
+ sender.Client.Id,
+ netId);
+ }
+
+ break;
+ }
+
+ case GameDataTag.SceneChangeFlag:
+ {
+ // Sender is only allowed to change his own scene.
+ var clientId = reader.ReadPackedInt32();
+ if (clientId != sender.Client.Id)
+ {
+ _logger.LogWarning(
+ "Player {0} ({1}) tried to send SceneChangeFlag for another player.",
+ sender.Client.Name,
+ sender.Client.Id);
+ return false;
+ }
+
+ sender.Scene = reader.ReadString();
+
+ _logger.LogTrace("> Scene {0} to {1}", clientId, sender.Scene);
+ break;
+ }
+
+ case GameDataTag.ReadyFlag:
+ {
+ var clientId = reader.ReadPackedInt32();
+ _logger.LogTrace("> IsReady {0}", clientId);
+ break;
+ }
+
+ default:
+ {
+ _logger.LogTrace("Bad GameData tag {0}", reader.Tag);
+ break;
+ }
+ }
+
+ if (sender.Client.Player == null)
+ {
+ // Disconnect handler was probably invoked, cancel the rest.
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private bool AddNetObject(InnerNetObject obj)
+ {
+ if (_allObjectsFast.ContainsKey(obj.NetId))
+ {
+ return false;
+ }
+
+ _allObjects.Add(obj);
+ _allObjectsFast.Add(obj.NetId, obj);
+ return true;
+ }
+
+ private void RemoveNetObject(InnerNetObject obj)
+ {
+ var index = _allObjects.IndexOf(obj);
+ if (index > -1)
+ {
+ _allObjects.RemoveAt(index);
+ }
+
+ _allObjectsFast.Remove(obj.NetId);
+
+ obj.NetId = uint.MaxValue;
+ }
+
+ public T FindObjectByNetId<T>(uint netId)
+ where T : InnerNetObject
+ {
+ if (_allObjectsFast.TryGetValue(netId, out var obj))
+ {
+ return (T) obj;
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Net/State/Game.Incoming.cs b/Impostor-dev/src/Impostor.Server/Net/State/Game.Incoming.cs
new file mode 100644
index 0000000..4bf1c43
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/State/Game.Incoming.cs
@@ -0,0 +1,240 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Impostor.Api.Games;
+using Impostor.Api.Innersloth;
+using Impostor.Api.Net;
+using Impostor.Api.Net.Messages;
+using Impostor.Hazel;
+using Impostor.Server.Events;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Impostor.Server.Net.State
+{
+ internal partial class Game
+ {
+ private readonly SemaphoreSlim _clientAddLock = new SemaphoreSlim(1, 1);
+
+ public async ValueTask HandleStartGame(IMessageReader message)
+ {
+ GameState = GameStates.Starting;
+
+ using var packet = MessageWriter.Get(MessageType.Reliable);
+ message.CopyTo(packet);
+ await SendToAllAsync(packet);
+
+ await _eventManager.CallAsync(new GameStartingEvent(this));
+ }
+
+ public async ValueTask<GameJoinResult> AddClientAsync(ClientBase client)
+ {
+ var hasLock = false;
+
+ try
+ {
+ hasLock = await _clientAddLock.WaitAsync(TimeSpan.FromMinutes(1));
+
+ if (hasLock)
+ {
+ return await AddClientSafeAsync(client);
+ }
+ }
+ finally
+ {
+ if (hasLock)
+ {
+ _clientAddLock.Release();
+ }
+ }
+
+ return GameJoinResult.FromError(GameJoinError.InvalidClient);
+ }
+
+ private async ValueTask<GameJoinResult> AddClientSafeAsync(ClientBase client)
+ {
+ // Check if the IP of the player is banned.
+ if (client.Connection != null && _bannedIps.Contains(client.Connection.EndPoint.Address))
+ {
+ return GameJoinResult.FromError(GameJoinError.Banned);
+ }
+
+ var player = client.Player;
+
+ // Check if;
+ // - The player is already in this game.
+ // - The game is full.
+ if (player?.Game != this && _players.Count >= Options.MaxPlayers)
+ {
+ return GameJoinResult.FromError(GameJoinError.GameFull);
+ }
+
+ if (GameState == GameStates.Starting || GameState == GameStates.Started)
+ {
+ return GameJoinResult.FromError(GameJoinError.GameStarted);
+ }
+
+ if (GameState == GameStates.Destroyed)
+ {
+ return GameJoinResult.FromError(GameJoinError.GameDestroyed);
+ }
+
+ var isNew = false;
+
+ if (player == null || player.Game != this)
+ {
+ var clientPlayer = new ClientPlayer(_serviceProvider.GetRequiredService<ILogger<ClientPlayer>>(), client, this);
+
+ if (!_clientManager.Validate(client))
+ {
+ return GameJoinResult.FromError(GameJoinError.InvalidClient);
+ }
+
+ isNew = true;
+ player = clientPlayer;
+ client.Player = clientPlayer;
+ }
+
+ // Check current player state.
+ if (player.Limbo == LimboStates.NotLimbo)
+ {
+ return GameJoinResult.FromError(GameJoinError.InvalidLimbo);
+ }
+
+ if (GameState == GameStates.Ended)
+ {
+ await HandleJoinGameNext(player, isNew);
+ return GameJoinResult.CreateSuccess(player);
+ }
+
+ await HandleJoinGameNew(player, isNew);
+ return GameJoinResult.CreateSuccess(player);
+ }
+
+ public async ValueTask HandleEndGame(IMessageReader message, GameOverReason gameOverReason)
+ {
+ GameState = GameStates.Ended;
+
+ // Broadcast end of the game.
+ using (var packet = MessageWriter.Get(MessageType.Reliable))
+ {
+ message.CopyTo(packet);
+ await SendToAllAsync(packet);
+ }
+
+ // Put all players in the correct limbo state.
+ foreach (var player in _players)
+ {
+ player.Value.Limbo = LimboStates.PreSpawn;
+ }
+
+ await _eventManager.CallAsync(new GameEndedEvent(this, gameOverReason));
+ }
+
+ public async ValueTask HandleAlterGame(IMessageReader message, IClientPlayer sender, bool isPublic)
+ {
+ IsPublic = isPublic;
+
+ using var packet = MessageWriter.Get(MessageType.Reliable);
+ message.CopyTo(packet);
+ await SendToAllExceptAsync(packet, sender.Client.Id);
+
+ await _eventManager.CallAsync(new GameAlterEvent(this, isPublic));
+ }
+
+ public async ValueTask HandleRemovePlayer(int playerId, DisconnectReason reason)
+ {
+ await PlayerRemove(playerId);
+
+ // It's possible that the last player was removed, so check if the game is still around.
+ if (GameState == GameStates.Destroyed)
+ {
+ return;
+ }
+
+ using var packet = MessageWriter.Get(MessageType.Reliable);
+ WriteRemovePlayerMessage(packet, false, playerId, reason);
+ await SendToAllExceptAsync(packet, playerId);
+ }
+
+ public async ValueTask HandleKickPlayer(int playerId, bool isBan)
+ {
+ _logger.LogInformation("{0} - Player {1} has left.", Code, playerId);
+
+ using var message = MessageWriter.Get(MessageType.Reliable);
+
+ // Send message to everyone that this player was kicked.
+ WriteKickPlayerMessage(message, false, playerId, isBan);
+
+ await SendToAllAsync(message);
+ await PlayerRemove(playerId, isBan);
+
+ // Remove the player from everyone's game.
+ WriteRemovePlayerMessage(
+ message,
+ true,
+ playerId,
+ isBan ? DisconnectReason.Banned : DisconnectReason.Kicked);
+
+ await SendToAllExceptAsync(message, playerId);
+ }
+
+ private async ValueTask HandleJoinGameNew(ClientPlayer sender, bool isNew)
+ {
+ _logger.LogInformation("{0} - Player {1} ({2}) is joining.", Code, sender.Client.Name, sender.Client.Id);
+
+ // Add player to the game.
+ if (isNew)
+ {
+ await PlayerAdd(sender);
+ }
+
+ sender.InitializeSpawnTimeout();
+
+ using (var message = MessageWriter.Get(MessageType.Reliable))
+ {
+ WriteJoinedGameMessage(message, false, sender);
+ WriteAlterGameMessage(message, false, IsPublic);
+
+ sender.Limbo = LimboStates.NotLimbo;
+
+ await SendToAsync(message, sender.Client.Id);
+ await BroadcastJoinMessage(message, true, sender);
+ }
+ }
+
+ private async ValueTask HandleJoinGameNext(ClientPlayer sender, bool isNew)
+ {
+ _logger.LogInformation("{0} - Player {1} ({2}) is rejoining.", Code, sender.Client.Name, sender.Client.Id);
+
+ // Add player to the game.
+ if (isNew)
+ {
+ await PlayerAdd(sender);
+ }
+
+ // Check if the host joined and let everyone join.
+ if (sender.Client.Id == HostId)
+ {
+ GameState = GameStates.NotStarted;
+
+ // Spawn the host.
+ await HandleJoinGameNew(sender, false);
+
+ // Pull players out of limbo.
+ await CheckLimboPlayers();
+ return;
+ }
+
+ sender.Limbo = LimboStates.WaitingForHost;
+
+ using (var packet = MessageWriter.Get(MessageType.Reliable))
+ {
+ WriteWaitForHostMessage(packet, false, sender);
+
+ await SendToAsync(packet, sender.Client.Id);
+ await BroadcastJoinMessage(packet, true, sender);
+ }
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Net/State/Game.Outgoing.cs b/Impostor-dev/src/Impostor.Server/Net/State/Game.Outgoing.cs
new file mode 100644
index 0000000..21b6067
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/State/Game.Outgoing.cs
@@ -0,0 +1,103 @@
+using System.Linq;
+using System.Threading.Tasks;
+using Impostor.Api.Innersloth;
+using Impostor.Api.Net;
+using Impostor.Api.Net.Messages;
+using Impostor.Api.Net.Messages.S2C;
+using Impostor.Hazel;
+using Impostor.Server.Net.Inner;
+
+namespace Impostor.Server.Net.State
+{
+ internal partial class Game
+ {
+ public async ValueTask SendToAllAsync(IMessageWriter writer, LimboStates states = LimboStates.NotLimbo)
+ {
+ foreach (var connection in GetConnections(x => x.Limbo.HasFlag(states)))
+ {
+ await connection.SendAsync(writer);
+ }
+ }
+
+ public async ValueTask SendToAllExceptAsync(IMessageWriter writer, int senderId, LimboStates states = LimboStates.NotLimbo)
+ {
+ foreach (var connection in GetConnections(x =>
+ x.Limbo.HasFlag(states) &&
+ x.Client.Id != senderId))
+ {
+ await connection.SendAsync(writer);
+ }
+ }
+
+ public async ValueTask SendToAsync(IMessageWriter writer, int id)
+ {
+ if (TryGetPlayer(id, out var player))
+ {
+ await player.Client.Connection.SendAsync(writer);
+ }
+ }
+
+ internal IMessageWriter StartRpc(uint targetNetId, RpcCalls callId, int targetClientId = -1, MessageType type = MessageType.Reliable)
+ {
+ var writer = MessageWriter.Get(type);
+
+ if (targetClientId < 0)
+ {
+ writer.StartMessage(MessageFlags.GameData);
+ writer.Write(Code);
+ }
+ else
+ {
+ writer.StartMessage(MessageFlags.GameDataTo);
+ writer.Write(Code);
+ writer.WritePacked(targetClientId);
+ }
+
+ writer.StartMessage(GameDataTag.RpcFlag);
+ writer.WritePacked(targetNetId);
+ writer.Write((byte) callId);
+
+ return writer;
+ }
+
+ internal ValueTask FinishRpcAsync(IMessageWriter writer, int? targetClientId = null)
+ {
+ writer.EndMessage();
+ writer.EndMessage();
+
+ return targetClientId.HasValue
+ ? SendToAsync(writer, targetClientId.Value)
+ : SendToAllAsync(writer);
+ }
+
+ private void WriteRemovePlayerMessage(IMessageWriter message, bool clear, int playerId, DisconnectReason reason)
+ {
+ Message04RemovePlayerS2C.Serialize(message, clear, Code, playerId, HostId, reason);
+ }
+
+ private void WriteJoinedGameMessage(IMessageWriter message, bool clear, IClientPlayer player)
+ {
+ var playerIds = _players
+ .Where(x => x.Value != player)
+ .Select(x => x.Key)
+ .ToArray();
+
+ Message07JoinedGameS2C.Serialize(message, clear, Code, player.Client.Id, HostId, playerIds);
+ }
+
+ private void WriteAlterGameMessage(IMessageWriter message, bool clear, bool isPublic)
+ {
+ Message10AlterGameS2C.Serialize(message, clear, Code, isPublic);
+ }
+
+ private void WriteKickPlayerMessage(IMessageWriter message, bool clear, int playerId, bool isBan)
+ {
+ Message11KickPlayerS2C.Serialize(message, clear, Code, playerId, isBan);
+ }
+
+ private void WriteWaitForHostMessage(IMessageWriter message, bool clear, IClientPlayer player)
+ {
+ Message12WaitForHostS2C.Serialize(message, clear, Code, player.Client.Id);
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Net/State/Game.State.cs b/Impostor-dev/src/Impostor.Server/Net/State/Game.State.cs
new file mode 100644
index 0000000..e311776
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/State/Game.State.cs
@@ -0,0 +1,136 @@
+using System.Linq;
+using System.Threading.Tasks;
+using Impostor.Api;
+using Impostor.Api.Innersloth;
+using Impostor.Api.Net;
+using Impostor.Api.Net.Messages;
+using Impostor.Hazel;
+using Impostor.Server.Events;
+using Impostor.Server.Net.Hazel;
+using Microsoft.Extensions.Logging;
+
+namespace Impostor.Server.Net.State
+{
+ internal partial class Game
+ {
+ private async ValueTask PlayerAdd(ClientPlayer player)
+ {
+ // Store player.
+ if (!_players.TryAdd(player.Client.Id, player))
+ {
+ throw new ImpostorException("Failed to add player to game.");
+ }
+
+ // Assign hostId if none is set.
+ if (HostId == -1)
+ {
+ HostId = player.Client.Id;
+ await InitGameDataAsync(player);
+ }
+
+ await _eventManager.CallAsync(new GamePlayerJoinedEvent(this, player));
+ }
+
+ private async ValueTask<bool> PlayerRemove(int playerId, bool isBan = false)
+ {
+ if (!_players.TryRemove(playerId, out var player))
+ {
+ return false;
+ }
+
+ _logger.LogInformation("{0} - Player {1} ({2}) has left.", Code, player.Client.Name, playerId);
+
+ if (GameState == GameStates.Starting || GameState == GameStates.Started)
+ {
+ if (player.Character?.PlayerInfo != null)
+ {
+ player.Character.PlayerInfo.Disconnected = true;
+ player.Character.PlayerInfo.LastDeathReason = DeathReason.Disconnect;
+ }
+ }
+
+ player.Client.Player = null;
+
+ // Game is empty, remove it.
+ if (_players.IsEmpty)
+ {
+ GameState = GameStates.Destroyed;
+
+ // Remove instance reference.
+ await _gameManager.RemoveAsync(Code);
+ return true;
+ }
+
+ // Host migration.
+ if (HostId == playerId)
+ {
+ await MigrateHost();
+ }
+
+ if (isBan && player.Client.Connection != null)
+ {
+ BanIp(player.Client.Connection.EndPoint.Address);
+ }
+
+ await _eventManager.CallAsync(new GamePlayerLeftEvent(this, player, isBan));
+
+ // Player can refuse to be kicked and keep the connection open, check for this.
+ _ = Task.Run(async () =>
+ {
+ await Task.Delay(Constants.ConnectionTimeout);
+
+ if (player.Client.Connection.IsConnected && player.Client.Connection is HazelConnection hazel)
+ {
+ _logger.LogInformation("{0} - Player {1} ({2}) kept connection open after leaving, disposing.", Code, player.Client.Name, playerId);
+ hazel.DisposeInnerConnection();
+ }
+ });
+
+ return true;
+ }
+
+ private async ValueTask MigrateHost()
+ {
+ // Pick the first player as new host.
+ var host = _players
+ .Select(p => p.Value)
+ .FirstOrDefault();
+
+ if (host == null)
+ {
+ await EndAsync();
+ return;
+ }
+
+ HostId = host.Client.Id;
+ _logger.LogInformation("{0} - Assigned {1} ({2}) as new host.", Code, host.Client.Name, host.Client.Id);
+
+ // Check our current game state.
+ if (GameState == GameStates.Ended && host.Limbo == LimboStates.WaitingForHost)
+ {
+ GameState = GameStates.NotStarted;
+
+ // Spawn the host.
+ await HandleJoinGameNew(host, false);
+
+ // Pull players out of limbo.
+ await CheckLimboPlayers();
+ }
+ }
+
+ private async ValueTask CheckLimboPlayers()
+ {
+ using var message = MessageWriter.Get(MessageType.Reliable);
+
+ foreach (var (_, player) in _players.Where(x => x.Value.Limbo == LimboStates.WaitingForHost))
+ {
+ WriteJoinedGameMessage(message, true, player);
+ WriteAlterGameMessage(message, false, IsPublic);
+
+ player.Limbo = LimboStates.NotLimbo;
+
+ await SendToAsync(message, player.Client.Id);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Net/State/Game.cs b/Impostor-dev/src/Impostor.Server/Net/State/Game.cs
new file mode 100644
index 0000000..1919867
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/State/Game.cs
@@ -0,0 +1,126 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Threading.Tasks;
+using Impostor.Api.Events.Managers;
+using Impostor.Api.Games;
+using Impostor.Api.Innersloth;
+using Impostor.Api.Net;
+using Impostor.Api.Net.Messages;
+using Impostor.Api.Net.Messages.S2C;
+using Impostor.Server.Events;
+using Impostor.Server.Net.Manager;
+using Microsoft.Extensions.Logging;
+
+namespace Impostor.Server.Net.State
+{
+ internal partial class Game
+ {
+ private readonly ILogger<Game> _logger;
+ private readonly IServiceProvider _serviceProvider;
+ private readonly GameManager _gameManager;
+ private readonly ClientManager _clientManager;
+ private readonly ConcurrentDictionary<int, ClientPlayer> _players;
+ private readonly HashSet<IPAddress> _bannedIps;
+ private readonly IEventManager _eventManager;
+
+ public Game(
+ ILogger<Game> logger,
+ IServiceProvider serviceProvider,
+ GameManager gameManager,
+ IPEndPoint publicIp,
+ GameCode code,
+ GameOptionsData options,
+ ClientManager clientManager,
+ IEventManager eventManager)
+ {
+ _logger = logger;
+ _serviceProvider = serviceProvider;
+ _gameManager = gameManager;
+ _players = new ConcurrentDictionary<int, ClientPlayer>();
+ _bannedIps = new HashSet<IPAddress>();
+
+ PublicIp = publicIp;
+ Code = code;
+ HostId = -1;
+ GameState = GameStates.NotStarted;
+ GameNet = new GameNet();
+ Options = options;
+ _clientManager = clientManager;
+ _eventManager = eventManager;
+ Items = new ConcurrentDictionary<object, object>();
+ }
+
+ public IPEndPoint PublicIp { get; }
+
+ public GameCode Code { get; }
+
+ public bool IsPublic { get; private set; }
+
+ public int HostId { get; private set; }
+
+ public GameStates GameState { get; private set; }
+
+ internal GameNet GameNet { get; }
+
+ public GameOptionsData Options { get; }
+
+ public IDictionary<object, object> Items { get; }
+
+ public int PlayerCount => _players.Count;
+
+ public ClientPlayer Host => _players[HostId];
+
+ public IEnumerable<IClientPlayer> Players => _players.Select(p => p.Value);
+
+ public bool TryGetPlayer(int id, out ClientPlayer player)
+ {
+ if (_players.TryGetValue(id, out var result))
+ {
+ player = result;
+ return true;
+ }
+
+ player = default;
+ return false;
+ }
+
+ public IClientPlayer GetClientPlayer(int clientId)
+ {
+ return _players.TryGetValue(clientId, out var clientPlayer) ? clientPlayer : null;
+ }
+
+ internal ValueTask StartedAsync()
+ {
+ if (GameState == GameStates.Starting)
+ {
+ GameState = GameStates.Started;
+
+ return _eventManager.CallAsync(new GameStartedEvent(this));
+ }
+
+ return default;
+ }
+
+ public ValueTask EndAsync()
+ {
+ return _gameManager.RemoveAsync(Code);
+ }
+
+ private ValueTask BroadcastJoinMessage(IMessageWriter message, bool clear, ClientPlayer player)
+ {
+ Message01JoinGameS2C.SerializeJoin(message, clear, Code, player.Client.Id, HostId);
+
+ return SendToAllExceptAsync(message, player.Client.Id);
+ }
+
+ private IEnumerable<IHazelConnection> GetConnections(Func<IClientPlayer, bool> filter)
+ {
+ return Players
+ .Where(filter)
+ .Select(p => p.Client.Connection);
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Net/State/GameNet.Api.cs b/Impostor-dev/src/Impostor.Server/Net/State/GameNet.Api.cs
new file mode 100644
index 0000000..34ea0fe
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/State/GameNet.Api.cs
@@ -0,0 +1,17 @@
+using Impostor.Api.Net.Inner;
+using Impostor.Api.Net.Inner.Objects;
+
+namespace Impostor.Server.Net.State
+{
+ /// <inheritdoc />
+ internal partial class GameNet : IGameNet
+ {
+ IInnerLobbyBehaviour IGameNet.LobbyBehaviour => LobbyBehaviour;
+
+ IInnerGameData IGameNet.GameData => GameData;
+
+ IInnerVoteBanSystem IGameNet.VoteBan => VoteBan;
+
+ IInnerShipStatus IGameNet.ShipStatus => ShipStatus;
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Net/State/GameNet.cs b/Impostor-dev/src/Impostor.Server/Net/State/GameNet.cs
new file mode 100644
index 0000000..c5542f9
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Net/State/GameNet.cs
@@ -0,0 +1,16 @@
+using Impostor.Server.Net.Inner.Objects;
+using Impostor.Server.Net.Inner.Objects.Components;
+
+namespace Impostor.Server.Net.State
+{
+ internal partial class GameNet
+ {
+ public InnerLobbyBehaviour LobbyBehaviour { get; internal set; }
+
+ public InnerGameData GameData { get; internal set; }
+
+ public InnerVoteBanSystem VoteBan { get; internal set; }
+
+ public InnerShipStatus ShipStatus { get; internal set; }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Plugins/AssemblyInformation.cs b/Impostor-dev/src/Impostor.Server/Plugins/AssemblyInformation.cs
new file mode 100644
index 0000000..5f6aee1
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Plugins/AssemblyInformation.cs
@@ -0,0 +1,38 @@
+using System.IO;
+using System.Reflection;
+using System.Runtime.Loader;
+
+namespace Impostor.Server.Plugins
+{
+ public class AssemblyInformation : IAssemblyInformation
+ {
+ private Assembly _assembly;
+
+ public AssemblyInformation(AssemblyName assemblyName, string path, bool isPlugin)
+ {
+ AssemblyName = assemblyName;
+ Path = path;
+ IsPlugin = isPlugin;
+ }
+
+ public string Path { get; }
+
+ public bool IsPlugin { get; }
+
+ public AssemblyName AssemblyName { get; }
+
+ public Assembly Load(AssemblyLoadContext context)
+ {
+ if (_assembly != null)
+ {
+ return _assembly;
+ }
+
+ using var stream = File.Open(Path, FileMode.Open, FileAccess.Read, FileShare.Read);
+
+ _assembly = context.LoadFromStream(stream);
+
+ return _assembly;
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Plugins/IAssemblyInformation.cs b/Impostor-dev/src/Impostor.Server/Plugins/IAssemblyInformation.cs
new file mode 100644
index 0000000..fb36e92
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Plugins/IAssemblyInformation.cs
@@ -0,0 +1,14 @@
+using System.Reflection;
+using System.Runtime.Loader;
+
+namespace Impostor.Server.Plugins
+{
+ public interface IAssemblyInformation
+ {
+ AssemblyName AssemblyName { get; }
+
+ bool IsPlugin { get; }
+
+ Assembly Load(AssemblyLoadContext context);
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Plugins/LoadedAssemblyInformation.cs b/Impostor-dev/src/Impostor.Server/Plugins/LoadedAssemblyInformation.cs
new file mode 100644
index 0000000..720367c
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Plugins/LoadedAssemblyInformation.cs
@@ -0,0 +1,25 @@
+using System.Reflection;
+using System.Runtime.Loader;
+
+namespace Impostor.Server.Plugins
+{
+ public class LoadedAssemblyInformation : IAssemblyInformation
+ {
+ private readonly Assembly _assembly;
+
+ public LoadedAssemblyInformation(Assembly assembly)
+ {
+ AssemblyName = assembly.GetName();
+ _assembly = assembly;
+ }
+
+ public AssemblyName AssemblyName { get; }
+
+ public bool IsPlugin => false;
+
+ public Assembly Load(AssemblyLoadContext context)
+ {
+ return _assembly;
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Plugins/PluginConfig.cs b/Impostor-dev/src/Impostor.Server/Plugins/PluginConfig.cs
new file mode 100644
index 0000000..22dc9e9
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Plugins/PluginConfig.cs
@@ -0,0 +1,11 @@
+using System.Collections.Generic;
+
+namespace Impostor.Server.Plugins
+{
+ public class PluginConfig
+ {
+ public List<string> Paths { get; set; } = new List<string>();
+
+ public List<string> LibraryPaths { get; set; } = new List<string>();
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Plugins/PluginInformation.cs b/Impostor-dev/src/Impostor.Server/Plugins/PluginInformation.cs
new file mode 100644
index 0000000..e6a5b6c
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Plugins/PluginInformation.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Reflection;
+using Impostor.Api.Plugins;
+
+namespace Impostor.Server.Plugins
+{
+ public class PluginInformation
+ {
+ private readonly ImpostorPluginAttribute _attribute;
+
+ public PluginInformation(IPluginStartup startup, Type pluginType)
+ {
+ _attribute = pluginType.GetCustomAttribute<ImpostorPluginAttribute>();
+
+ Startup = startup;
+ PluginType = pluginType;
+ }
+
+ public string Package => _attribute.Package;
+
+ public string Name => _attribute.Name;
+
+ public string Author => _attribute.Author;
+
+ public string Version => _attribute.Version;
+
+ public IPluginStartup Startup { get; }
+
+ public Type PluginType { get; }
+
+ public IPlugin Instance { get; set; }
+
+ public override string ToString()
+ {
+ return $"{Package} {Name} ({Version}) by {Author}";
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Plugins/PluginLoader.cs b/Impostor-dev/src/Impostor.Server/Plugins/PluginLoader.cs
new file mode 100644
index 0000000..4e72886
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Plugins/PluginLoader.cs
@@ -0,0 +1,147 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.Loader;
+using Impostor.Api.Plugins;
+using Impostor.Server.Utils;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.FileSystemGlobbing;
+using Microsoft.Extensions.Hosting;
+using Serilog;
+
+namespace Impostor.Server.Plugins
+{
+ public static class PluginLoader
+ {
+ private static readonly ILogger Logger = Log.ForContext(typeof(PluginLoader));
+
+ public static IHostBuilder UsePluginLoader(this IHostBuilder builder, PluginConfig config)
+ {
+ var assemblyInfos = new List<IAssemblyInformation>();
+ var context = AssemblyLoadContext.Default;
+
+ // Add the plugins and libraries.
+ var pluginPaths = new List<string>(config.Paths);
+ var libraryPaths = new List<string>(config.LibraryPaths);
+
+ var rootFolder = Directory.GetCurrentDirectory();
+
+ pluginPaths.Add(Path.Combine(rootFolder, "plugins"));
+ libraryPaths.Add(Path.Combine(rootFolder, "libraries"));
+
+ var matcher = new Matcher(StringComparison.OrdinalIgnoreCase);
+ matcher.AddInclude("*.dll");
+ matcher.AddExclude("Impostor.Api.dll");
+
+ RegisterAssemblies(pluginPaths, matcher, assemblyInfos, true);
+ RegisterAssemblies(libraryPaths, matcher, assemblyInfos, false);
+
+ // Register the resolver to the current context.
+ // TODO: Move this to a new context so we can unload/reload plugins.
+ context.Resolving += (loadContext, name) =>
+ {
+ Logger.Verbose("Loading assembly {0} v{1}", name.Name, name.Version);
+
+ // Some plugins may be referencing another Impostor.Api version and try to load it.
+ // We want to only use the one shipped with the server.
+ if (name.Name.Equals("Impostor.Api"))
+ {
+ return typeof(IPlugin).Assembly;
+ }
+
+ var info = assemblyInfos.FirstOrDefault(a => a.AssemblyName.Name == name.Name);
+
+ return info?.Load(loadContext);
+ };
+
+ // TODO: Catch uncaught exceptions.
+ var assemblies = assemblyInfos
+ .Where(a => a.IsPlugin)
+ .Select(a => context.LoadFromAssemblyName(a.AssemblyName))
+ .ToList();
+
+ // Find all plugins.
+ var plugins = new List<PluginInformation>();
+
+ foreach (var assembly in assemblies)
+ {
+ // Find plugin startup.
+ var pluginStartup = assembly
+ .GetTypes()
+ .Where(t => typeof(IPluginStartup).IsAssignableFrom(t) && t.IsClass)
+ .ToList();
+
+ if (pluginStartup.Count > 1)
+ {
+ Logger.Warning("A plugin may only define zero or one IPluginStartup implementation ({0}).", assembly);
+ continue;
+ }
+
+ // Find plugin.
+ var plugin = assembly
+ .GetTypes()
+ .Where(t => typeof(IPlugin).IsAssignableFrom(t)
+ && t.IsClass
+ && !t.IsAbstract
+ && t.GetCustomAttribute<ImpostorPluginAttribute>() != null)
+ .ToList();
+
+ if (plugin.Count != 1)
+ {
+ Logger.Warning("A plugin must define exactly one IPlugin or PluginBase implementation ({0}).", assembly);
+ continue;
+ }
+
+ // Save plugin.
+ plugins.Add(new PluginInformation(
+ pluginStartup
+ .Select(Activator.CreateInstance)
+ .Cast<IPluginStartup>()
+ .FirstOrDefault(),
+ plugin.First()));
+ }
+
+ foreach (var plugin in plugins.Where(plugin => plugin.Startup != null))
+ {
+ plugin.Startup.ConfigureHost(builder);
+ }
+
+ builder.ConfigureServices(services =>
+ {
+ services.AddHostedService(provider => ActivatorUtilities.CreateInstance<PluginLoaderService>(provider, plugins));
+
+ foreach (var plugin in plugins.Where(plugin => plugin.Startup != null))
+ {
+ plugin.Startup.ConfigureServices(services);
+ }
+ });
+
+ return builder;
+ }
+
+ private static void RegisterAssemblies(
+ IEnumerable<string> paths,
+ Matcher matcher,
+ ICollection<IAssemblyInformation> assemblyInfos,
+ bool isPlugin)
+ {
+ foreach (var path in paths.SelectMany(matcher.GetResultsInFullPath))
+ {
+ AssemblyName assemblyName;
+
+ try
+ {
+ assemblyName = AssemblyName.GetAssemblyName(path);
+ }
+ catch (BadImageFormatException)
+ {
+ continue;
+ }
+
+ assemblyInfos.Add(new AssemblyInformation(assemblyName, path, isPlugin));
+ }
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Plugins/PluginLoaderException.cs b/Impostor-dev/src/Impostor.Server/Plugins/PluginLoaderException.cs
new file mode 100644
index 0000000..64424a1
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Plugins/PluginLoaderException.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Runtime.Serialization;
+using Impostor.Api;
+
+namespace Impostor.Server.Plugins
+{
+ public class PluginLoaderException : ImpostorException
+ {
+ public PluginLoaderException()
+ {
+ }
+
+ protected PluginLoaderException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+
+ public PluginLoaderException(string? message) : base(message)
+ {
+ }
+
+ public PluginLoaderException(string? message, Exception? innerException) : base(message, innerException)
+ {
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Plugins/PluginLoaderService.cs b/Impostor-dev/src/Impostor.Server/Plugins/PluginLoaderService.cs
new file mode 100644
index 0000000..0afbc22
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Plugins/PluginLoaderService.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Impostor.Api.Plugins;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+
+namespace Impostor.Server.Plugins
+{
+ public class PluginLoaderService : IHostedService
+ {
+ private readonly ILogger<PluginLoaderService> _logger;
+ private readonly IServiceProvider _serviceProvider;
+ private readonly List<PluginInformation> _plugins;
+
+ public PluginLoaderService(ILogger<PluginLoaderService> logger, IServiceProvider serviceProvider, List<PluginInformation> plugins)
+ {
+ _logger = logger;
+ _serviceProvider = serviceProvider;
+ _plugins = plugins;
+ }
+
+ public async Task StartAsync(CancellationToken cancellationToken)
+ {
+ _logger.LogInformation("Loading plugins.");
+
+ foreach (var plugin in _plugins)
+ {
+ _logger.LogInformation("Enabling plugin {0}.", plugin);
+
+ // Create instance and inject services.
+ plugin.Instance = (IPlugin) ActivatorUtilities.CreateInstance(_serviceProvider, plugin.PluginType);
+
+ // Enable plugin.
+ await plugin.Instance.EnableAsync();
+ }
+
+ _logger.LogInformation(
+ _plugins.Count == 1
+ ? "Loaded {0} plugin."
+ : "Loaded {0} plugins.", _plugins.Count);
+ }
+
+ public async Task StopAsync(CancellationToken cancellationToken)
+ {
+ // Disable all plugins with a valid instance set.
+ // In the case of a failed startup, some can be null.
+ foreach (var plugin in _plugins.Where(plugin => plugin.Instance != null))
+ {
+ _logger.LogInformation("Disabling plugin {0}.", plugin);
+
+ // Disable plugin.
+ await plugin.Instance.DisableAsync();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Program.cs b/Impostor-dev/src/Impostor.Server/Program.cs
new file mode 100644
index 0000000..32a28fb
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Program.cs
@@ -0,0 +1,207 @@
+using System;
+using System.IO;
+using System.Linq;
+using Impostor.Api.Events.Managers;
+using Impostor.Api.Games;
+using Impostor.Api.Games.Managers;
+using Impostor.Api.Net.Manager;
+using Impostor.Api.Net.Messages;
+using Impostor.Hazel.Extensions;
+using Impostor.Server.Config;
+using Impostor.Server.Events;
+using Impostor.Server.Net;
+using Impostor.Server.Net.Factories;
+using Impostor.Server.Net.Manager;
+using Impostor.Server.Net.Messages;
+using Impostor.Server.Net.Redirector;
+using Impostor.Server.Plugins;
+using Impostor.Server.Recorder;
+using Impostor.Server.Utils;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.ObjectPool;
+using Serilog;
+using Serilog.Events;
+
+namespace Impostor.Server
+{
+ internal static class Program
+ {
+ private static int Main(string[] args)
+ {
+#if DEBUG
+ var logLevel = LogEventLevel.Debug;
+#else
+ var logLevel = LogEventLevel.Information;
+#endif
+
+ if (args.Contains("--verbose"))
+ {
+ logLevel = LogEventLevel.Verbose;
+ }
+ else if (args.Contains("--errors-only"))
+ {
+ logLevel = LogEventLevel.Error;
+ }
+
+ Log.Logger = new LoggerConfiguration()
+ .MinimumLevel.Is(logLevel)
+#if DEBUG
+ .MinimumLevel.Override("Microsoft", LogEventLevel.Debug)
+#else
+ .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
+#endif
+ .Enrich.FromLogContext()
+ .WriteTo.Console()
+ .CreateLogger();
+
+ try
+ {
+ Log.Information("Starting Impostor v{0}", DotnetUtils.GetVersion());
+ CreateHostBuilder(args).Build().Run();
+ return 0;
+ }
+ catch (Exception ex)
+ {
+ Log.Fatal(ex, "Impostor terminated unexpectedly");
+ return 1;
+ }
+ finally
+ {
+ Log.CloseAndFlush();
+ }
+ }
+
+ private static IConfiguration CreateConfiguration(string[] args)
+ {
+ var configurationBuilder = new ConfigurationBuilder();
+
+ configurationBuilder.AddJsonFile("config.json", true);
+ configurationBuilder.AddJsonFile("config.Development.json", true);
+ configurationBuilder.AddEnvironmentVariables(prefix: "IMPOSTOR_");
+ configurationBuilder.AddCommandLine(args);
+
+ return configurationBuilder.Build();
+ }
+
+ //c 入口
+ private static IHostBuilder CreateHostBuilder(string[] args)
+ {
+ var configuration = CreateConfiguration(args);
+ var pluginConfig = configuration.GetSection("PluginLoader")
+ .Get<PluginConfig>() ?? new PluginConfig();
+
+ return Host.CreateDefaultBuilder(args)
+ .UseContentRoot(Directory.GetCurrentDirectory())
+#if DEBUG
+ .UseEnvironment(Environment.GetEnvironmentVariable("IMPOSTOR_ENV") ?? "Development")
+#else
+ .UseEnvironment("Production")
+#endif
+ .ConfigureAppConfiguration(builder =>
+ {
+ builder.AddConfiguration(configuration);
+ })
+ .ConfigureServices((host, services) =>
+ {
+ var debug = host.Configuration
+ .GetSection(DebugConfig.Section)
+ .Get<DebugConfig>() ?? new DebugConfig();
+
+ var redirector = host.Configuration
+ .GetSection(ServerRedirectorConfig.Section)
+ .Get<ServerRedirectorConfig>() ?? new ServerRedirectorConfig();
+
+ services.Configure<DebugConfig>(host.Configuration.GetSection(DebugConfig.Section));
+ services.Configure<AntiCheatConfig>(host.Configuration.GetSection(AntiCheatConfig.Section));
+ services.Configure<ServerConfig>(host.Configuration.GetSection(ServerConfig.Section));
+ services.Configure<ServerRedirectorConfig>(host.Configuration.GetSection(ServerRedirectorConfig.Section));
+
+ if (redirector.Enabled)
+ {
+ if (!string.IsNullOrEmpty(redirector.Locator.Redis))
+ {
+ // When joining a game, it retrieves the game server ip from redis.
+ // When a game has been created on this node, it stores the game code with its ip in redis.
+ services.AddSingleton<INodeLocator, NodeLocatorRedis>();
+
+ // Dependency for the NodeLocatorRedis.
+ services.AddStackExchangeRedisCache(options =>
+ {
+ options.Configuration = redirector.Locator.Redis;
+ options.InstanceName = "ImpostorRedis";
+ });
+ }
+ else if (!string.IsNullOrEmpty(redirector.Locator.UdpMasterEndpoint))
+ {
+ services.AddSingleton<INodeLocator, NodeLocatorUdp>();
+
+ if (redirector.Master)
+ {
+ services.AddHostedService<NodeLocatorUdpService>();
+ }
+ }
+ else
+ {
+ throw new Exception("Missing a valid NodeLocator config.");
+ }
+
+ // Use the configuration as source for the list of nodes to provide
+ // when creating a game.
+ services.AddSingleton<INodeProvider, NodeProviderConfig>();
+ }
+ else
+ {
+ // Redirector is not enabled but the dependency is still required.
+ // So we provide one that ignores all calls.
+ services.AddSingleton<INodeLocator, NodeLocatorNoOp>();
+ }
+
+ services.AddSingleton<ClientManager>();
+ services.AddSingleton<IClientManager>(p => p.GetRequiredService<ClientManager>());
+
+ if (redirector.Enabled && redirector.Master)
+ {
+ services.AddSingleton<IClientFactory, ClientFactory<ClientRedirector>>();
+
+ // For a master server, we don't need a GameManager.
+ }
+ else
+ {
+ if (debug.GameRecorderEnabled)
+ {
+ services.AddSingleton<ObjectPoolProvider>(new DefaultObjectPoolProvider());
+ services.AddSingleton<ObjectPool<PacketSerializationContext>>(serviceProvider =>
+ {
+ var provider = serviceProvider.GetRequiredService<ObjectPoolProvider>();
+ var policy = new PacketSerializationContextPooledObjectPolicy();
+ return provider.Create(policy);
+ });
+
+ services.AddSingleton<PacketRecorder>();
+ services.AddHostedService(sp => sp.GetRequiredService<PacketRecorder>());
+ services.AddSingleton<IClientFactory, ClientFactory<ClientRecorder>>();
+ }
+ else
+ {
+ services.AddSingleton<IClientFactory, ClientFactory<Client>>();
+ }
+
+ services.AddSingleton<GameManager>();
+ services.AddSingleton<IGameManager>(p => p.GetRequiredService<GameManager>());
+ }
+
+ services.AddHazel();
+ services.AddSingleton<IMessageWriterProvider, MessageWriterProvider>();
+ services.AddSingleton<IGameCodeFactory, GameCodeFactory>();
+ services.AddSingleton<IEventManager, EventManager>();
+ services.AddSingleton<Matchmaker>();
+ services.AddHostedService<MatchmakerService>();
+ })
+ .UseSerilog()
+ .UseConsoleLifetime()
+ .UsePluginLoader(pluginConfig);
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/ProjectRules.ruleset b/Impostor-dev/src/Impostor.Server/ProjectRules.ruleset
new file mode 100644
index 0000000..3654bc3
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/ProjectRules.ruleset
@@ -0,0 +1,21 @@
+<RuleSet Name="Rules for Hello World project" Description="These rules focus on critical issues for the Hello World app." ToolsVersion="10.0">
+ <Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.CSharp.OrderingRules">
+ <Rule Id="SA1200" Action="None" />
+ </Rules>
+ <Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.CSharp.DocumentationRules">
+ <Rule Id="SA1600" Action="None" />
+ <Rule Id="SA1601" Action="None" />
+ <Rule Id="SA1615" Action="None" />
+ <Rule Id="SA1633" Action="None" />
+ </Rules>
+ <Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.CSharp.ReadabilityRules">
+ <Rule Id="SA1101" Action="None" />
+ </Rules>
+ <Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.CSharp.NamingRules">
+ <Rule Id="SA1309" Action="None" />
+ </Rules>
+ <Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.CSharp.SpacingRules">
+ <Rule Id="SA1003" Action="None" />
+ <Rule Id="SA1009" Action="None" />
+ </Rules>
+</RuleSet> \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Properties/AssemblyInfo.cs b/Impostor-dev/src/Impostor.Server/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..84c5158
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Properties/AssemblyInfo.cs
@@ -0,0 +1,5 @@
+using System.Runtime.CompilerServices;
+
+[assembly:InternalsVisibleTo("Impostor.Benchmarks")]
+[assembly:InternalsVisibleTo("Impostor.Tests")]
+[assembly:InternalsVisibleTo("Impostor.Tools.ServerReplay")]
diff --git a/Impostor-dev/src/Impostor.Server/Recorder/ClientRecorder.cs b/Impostor-dev/src/Impostor.Server/Recorder/ClientRecorder.cs
new file mode 100644
index 0000000..5763c70
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Recorder/ClientRecorder.cs
@@ -0,0 +1,86 @@
+using System.Threading.Tasks;
+using Impostor.Api.Net.Messages;
+using Impostor.Server.Config;
+using Impostor.Server.Net;
+using Impostor.Server.Net.Hazel;
+using Impostor.Server.Net.Manager;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+
+namespace Impostor.Server.Recorder
+{
+ internal class ClientRecorder : Client
+ {
+ private readonly PacketRecorder _recorder;
+ private bool _isFirst;
+ private bool _createdGame;
+ private bool _recordAfter;
+
+ public ClientRecorder(ILogger<Client> logger, IOptions<AntiCheatConfig> antiCheatOptions, ClientManager clientManager, GameManager gameManager, string name, HazelConnection connection, PacketRecorder recorder)
+ : base(logger, antiCheatOptions, clientManager, gameManager, name, connection)
+ {
+ _recorder = recorder;
+ _isFirst = true;
+ _createdGame = false;
+ _recordAfter = true;
+ }
+
+ public override async ValueTask HandleMessageAsync(IMessageReader reader, MessageType messageType)
+ {
+ using var messageCopy = reader.Copy();
+
+ // Trigger connect event.
+ if (_isFirst)
+ {
+ _isFirst = false;
+
+ await _recorder.WriteConnectAsync(this);
+ }
+
+ // Check if we were in-game before handling the message.
+ var inGame = Player?.Game != null;
+
+ if (!_recordAfter)
+ {
+ // Trigger message event.
+ await _recorder.WriteMessageAsync(this, messageCopy, messageType);
+ }
+
+ // Handle the message.
+ await base.HandleMessageAsync(reader, messageType);
+
+ // Player created a game.
+ if (reader.Tag == MessageFlags.HostGame)
+ {
+ _createdGame = true;
+ }
+ else if (reader.Tag == MessageFlags.JoinGame && _createdGame)
+ {
+ _createdGame = false;
+
+ // We created a game and are now in-game, store that event.
+ if (!inGame && Player?.Game != null)
+ {
+ await _recorder.WriteGameCreatedAsync(this, Player.Game.Code);
+ }
+
+ _recordAfter = false;
+
+ // Trigger message event.
+ await _recorder.WriteMessageAsync(this, messageCopy, messageType);
+ }
+
+ if (_recordAfter)
+ {
+ // Trigger message event.
+ await _recorder.WriteMessageAsync(this, messageCopy, messageType);
+ }
+ }
+
+ public override async ValueTask HandleDisconnectAsync(string reason)
+ {
+ await _recorder.WriteDisconnectAsync(this, reason);
+ await base.HandleDisconnectAsync(reason);
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Recorder/PacketRecorder.cs b/Impostor-dev/src/Impostor.Server/Recorder/PacketRecorder.cs
new file mode 100644
index 0000000..af2ea4f
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Recorder/PacketRecorder.cs
@@ -0,0 +1,201 @@
+using System;
+using System.IO;
+using System.Net;
+using System.Threading;
+using System.Threading.Channels;
+using System.Threading.Tasks;
+using Impostor.Api.Games;
+using Impostor.Api.Net.Messages;
+using Impostor.Server.Config;
+using Impostor.Server.Net;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.ObjectPool;
+using Microsoft.Extensions.Options;
+
+namespace Impostor.Server.Recorder
+{
+ /// <summary>
+ /// Records all packets received in <see cref="ClientRecorder.HandleMessageAsync"/>.
+ /// </summary>
+ internal class PacketRecorder : BackgroundService
+ {
+ private readonly string _path;
+ private readonly ILogger<PacketRecorder> _logger;
+ private readonly ObjectPool<PacketSerializationContext> _pool;
+ private readonly Channel<byte[]> _channel;
+
+ public PacketRecorder(ILogger<PacketRecorder> logger, IOptions<DebugConfig> options, ObjectPool<PacketSerializationContext> pool)
+ {
+ var name = $"session_{DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}.dat";
+
+ _path = Path.Combine(options.Value.GameRecorderPath, name);
+ _logger = logger;
+ _pool = pool;
+
+ _channel = Channel.CreateUnbounded<byte[]>(new UnboundedChannelOptions
+ {
+ SingleReader = true,
+ SingleWriter = false,
+ });
+ }
+
+ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
+ {
+ _logger.LogInformation("PacketRecorder is enabled, writing packets to {0}.", _path);
+
+ var writer = File.Open(_path, FileMode.CreateNew, FileAccess.Write, FileShare.Read);
+
+ // Handle messages.
+ try
+ {
+ while (!stoppingToken.IsCancellationRequested)
+ {
+ var result = await _channel.Reader.ReadAsync(stoppingToken);
+
+ await writer.WriteAsync(result, stoppingToken);
+ await writer.FlushAsync(stoppingToken);
+ }
+ }
+ catch (TaskCanceledException)
+ {
+ }
+
+ // Clean up.
+ await writer.DisposeAsync();
+ }
+
+ public async Task WriteConnectAsync(ClientRecorder client)
+ {
+ _logger.LogTrace("Writing Connect.");
+
+ var context = _pool.Get();
+
+ try
+ {
+ WriteHeader(context, RecordedPacketType.Connect);
+ WriteClient(context, client, true);
+ WriteLength(context);
+
+ await WriteAsync(context.Stream);
+ }
+ finally
+ {
+ _pool.Return(context);
+ }
+ }
+
+ public async Task WriteDisconnectAsync(ClientRecorder client, string reason)
+ {
+ _logger.LogTrace("Writing Disconnect.");
+
+ var context = _pool.Get();
+
+ try
+ {
+ WriteHeader(context, RecordedPacketType.Disconnect);
+ WriteClient(context, client, false);
+ context.Writer.Write(reason);
+ WriteLength(context);
+
+ await WriteAsync(context.Stream);
+ }
+ finally
+ {
+ _pool.Return(context);
+ }
+ }
+
+ public async Task WriteMessageAsync(ClientRecorder client, IMessageReader reader, MessageType messageType)
+ {
+ _logger.LogTrace("Writing Message.");
+
+ var context = _pool.Get();
+
+ try
+ {
+ WriteHeader(context, RecordedPacketType.Message);
+ WriteClient(context, client, false);
+ WritePacket(context, reader, messageType);
+ WriteLength(context);
+
+ await WriteAsync(context.Stream);
+ }
+ finally
+ {
+ _pool.Return(context);
+ }
+ }
+
+ public async Task WriteGameCreatedAsync(ClientRecorder client, GameCode gameCode)
+ {
+ _logger.LogTrace("Writing GameCreated {0}.", gameCode);
+
+ var context = _pool.Get();
+
+ try
+ {
+ WriteHeader(context, RecordedPacketType.GameCreated);
+ WriteClient(context, client, false);
+ WriteGameCode(context, gameCode);
+ WriteLength(context);
+
+ await WriteAsync(context.Stream);
+ }
+ finally
+ {
+ _pool.Return(context);
+ }
+ }
+
+ private static void WriteHeader(PacketSerializationContext context, RecordedPacketType type)
+ {
+ // Length placeholder.
+ context.Writer.Write((int) 0);
+ context.Writer.Write((byte) type);
+ }
+
+ private static void WriteClient(PacketSerializationContext context, ClientBase client, bool full)
+ {
+ var address = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 12345);
+ var addressBytes = address.Address.GetAddressBytes();
+
+ context.Writer.Write(client.Id);
+
+ if (full)
+ {
+ context.Writer.Write((byte) addressBytes.Length);
+ context.Writer.Write(addressBytes);
+ context.Writer.Write((ushort) address.Port);
+ context.Writer.Write(client.Name);
+ }
+ }
+
+ private static void WritePacket(PacketSerializationContext context, IMessageReader reader, MessageType messageType)
+ {
+ context.Writer.Write((byte) messageType);
+ context.Writer.Write((byte) reader.Tag);
+ context.Writer.Write((int) reader.Length);
+ context.Writer.Write(reader.Buffer, reader.Offset, reader.Length);
+ }
+
+ private static void WriteGameCode(PacketSerializationContext context, in GameCode gameCode)
+ {
+ context.Writer.Write(gameCode.Code);
+ }
+
+ private static void WriteLength(PacketSerializationContext context)
+ {
+ var length = context.Stream.Position;
+
+ context.Stream.Position = 0;
+ context.Writer.Write((int) length);
+ context.Stream.Position = length;
+ }
+
+ private async Task WriteAsync(MemoryStream data)
+ {
+ await _channel.Writer.WriteAsync(data.ToArray());
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Recorder/PacketSerializationContext.cs b/Impostor-dev/src/Impostor.Server/Recorder/PacketSerializationContext.cs
new file mode 100644
index 0000000..07755f6
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Recorder/PacketSerializationContext.cs
@@ -0,0 +1,56 @@
+using System.IO;
+using System.Text;
+
+namespace Impostor.Server.Recorder
+{
+ public class PacketSerializationContext
+ {
+ private const int InitialStreamSize = 0x100;
+ private const int MaximumStreamSize = 0x100000;
+
+ private MemoryStream _memory;
+ private BinaryWriter _writer;
+
+ public MemoryStream Stream
+ {
+ get
+ {
+ if (_memory == null)
+ {
+ _memory = new MemoryStream(InitialStreamSize);
+ }
+
+ return _memory;
+ }
+ private set => _memory = value;
+ }
+
+ public BinaryWriter Writer
+ {
+ get
+ {
+ if (_writer == null)
+ {
+ _writer = new BinaryWriter(Stream, Encoding.UTF8, true);
+ }
+
+ return _writer;
+ }
+ private set => _writer = value;
+ }
+
+ public void Reset()
+ {
+ if (Stream.Capacity > MaximumStreamSize)
+ {
+ Stream = null;
+ Writer = null;
+ }
+ else
+ {
+ Stream.Position = 0L;
+ Stream.SetLength(0L);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Recorder/PacketSerializationContextPooledObjectPolicy.cs b/Impostor-dev/src/Impostor.Server/Recorder/PacketSerializationContextPooledObjectPolicy.cs
new file mode 100644
index 0000000..17b355f
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Recorder/PacketSerializationContextPooledObjectPolicy.cs
@@ -0,0 +1,18 @@
+using Microsoft.Extensions.ObjectPool;
+
+namespace Impostor.Server.Recorder
+{
+ public class PacketSerializationContextPooledObjectPolicy : IPooledObjectPolicy<PacketSerializationContext>
+ {
+ public PacketSerializationContext Create()
+ {
+ return new PacketSerializationContext();
+ }
+
+ public bool Return(PacketSerializationContext obj)
+ {
+ obj.Reset();
+ return true;
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Recorder/RecordedPacketType.cs b/Impostor-dev/src/Impostor.Server/Recorder/RecordedPacketType.cs
new file mode 100644
index 0000000..a8a20bc
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Recorder/RecordedPacketType.cs
@@ -0,0 +1,10 @@
+namespace Impostor.Server.Recorder
+{
+ internal enum RecordedPacketType : byte
+ {
+ Connect = 1,
+ Disconnect = 2,
+ Message = 3,
+ GameCreated = 4
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/Utils/DotnetUtils.cs b/Impostor-dev/src/Impostor.Server/Utils/DotnetUtils.cs
new file mode 100644
index 0000000..48a0ac0
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Utils/DotnetUtils.cs
@@ -0,0 +1,20 @@
+using System.Reflection;
+
+namespace Impostor.Server.Utils
+{
+ internal static class DotnetUtils
+ {
+ private const string DefaultUnknownBuild = "UNKNOWN";
+
+ public static string GetVersion()
+ {
+ var attribute = typeof(DotnetUtils).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
+ if (attribute != null)
+ {
+ return attribute.InformationalVersion;
+ }
+
+ return DefaultUnknownBuild;
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Utils/IpUtils.cs b/Impostor-dev/src/Impostor.Server/Utils/IpUtils.cs
new file mode 100644
index 0000000..649d45a
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Utils/IpUtils.cs
@@ -0,0 +1,42 @@
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using Impostor.Api;
+
+namespace Impostor.Server.Utils
+{
+ internal static class IpUtils
+ {
+ public static string ResolveIp(string ip)
+ {
+ // Check if valid ip was entered.
+ if (!IPAddress.TryParse(ip, out var ipAddress))
+ {
+ // Attempt to resolve DNS.
+ try
+ {
+ var hostAddresses = Dns.GetHostAddresses(ip);
+ if (hostAddresses.Length == 0)
+ {
+ throw new ImpostorConfigException($"Invalid IP Address entered '{ip}'.");
+ }
+
+ // Use first IPv4 result.
+ ipAddress = hostAddresses.First(x => x.AddressFamily == AddressFamily.InterNetwork);
+ }
+ catch (SocketException)
+ {
+ throw new ImpostorConfigException($"Failed to resolve hostname '{ip}'.");
+ }
+ }
+
+ // Only IPv4.
+ if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6)
+ {
+ throw new ImpostorConfigException($"Invalid IP Address entered '{ipAddress}', only IPv4 is supported by Among Us.");
+ }
+
+ return ipAddress.ToString();
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/config-full.json b/Impostor-dev/src/Impostor.Server/config-full.json
new file mode 100644
index 0000000..dfee1bc
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/config-full.json
@@ -0,0 +1,29 @@
+{
+ "Server": {
+ "PublicIp": "127.0.0.1",
+ "PublicPort": 22023,
+ "ListenIp": "0.0.0.0",
+ "ListenPort": 22023
+ },
+ "AntiCheat": {
+ "BanIpFromGame": true
+ },
+ "ServerRedirector": {
+ "Enabled": false,
+ "Master": true,
+ "Locator": {
+ "Redis": "127.0.0.1.6379",
+ "UdpMasterEndpoint": "127.0.0.1:32320"
+ },
+ "Nodes": [
+ {
+ "Ip": "127.0.0.1",
+ "Port": 22024
+ }
+ ]
+ },
+ "Debug": {
+ "GameRecorderEnabled": true,
+ "GameRecorderPath": ""
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/config.json b/Impostor-dev/src/Impostor.Server/config.json
new file mode 100644
index 0000000..d477c74
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/config.json
@@ -0,0 +1,11 @@
+{
+ "Server": {
+ "PublicIp": "127.0.0.1",
+ "PublicPort": 22023,
+ "ListenIp": "0.0.0.0",
+ "ListenPort": 22023
+ },
+ "AntiCheat": {
+ "BanIpFromGame": true
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Server/icon.ico b/Impostor-dev/src/Impostor.Server/icon.ico
new file mode 100644
index 0000000..8cc46f3
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/icon.ico
Binary files differ
diff --git a/Impostor-dev/src/Impostor.Tests/Events/EventManagerTests.cs b/Impostor-dev/src/Impostor.Tests/Events/EventManagerTests.cs
new file mode 100644
index 0000000..d222d79
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Tests/Events/EventManagerTests.cs
@@ -0,0 +1,175 @@
+using System.Collections.Generic;
+using System.Net.Sockets;
+using System.Threading.Tasks;
+using Impostor.Api.Events;
+using Impostor.Api.Events.Managers;
+using Impostor.Server.Events;
+using Microsoft.Extensions.DependencyInjection;
+using Xunit;
+
+namespace Impostor.Tests.Events
+{
+ public class EventManagerTests
+ {
+ public static readonly IEnumerable<object[]> TestModes = new []
+ {
+ new object[] { TestMode.Service },
+ new object[] { TestMode.Temporary }
+ };
+
+ [Theory]
+ [MemberData(nameof(TestModes))]
+ public async ValueTask CallEvent(TestMode mode)
+ {
+ var listener = new EventListener();
+ var eventManager = CreatEventManager(mode, listener);
+
+ await eventManager.CallAsync(new SetValueEvent(1));
+
+ Assert.Equal(1, listener.Value);
+ }
+
+ [Theory]
+ [MemberData(nameof(TestModes))]
+ public async Task CallPriority(TestMode mode)
+ {
+ var listener = new PriorityEventListener();
+ var eventManager = CreatEventManager(mode, listener);
+
+ await eventManager.CallAsync(new SetValueEvent(1));
+
+ Assert.Equal(new []
+ {
+ EventPriority.Monitor,
+ EventPriority.Highest,
+ EventPriority.High,
+ EventPriority.Normal,
+ EventPriority.Low,
+ EventPriority.Lowest
+ }, listener.Priorities);
+ }
+
+ [Theory]
+ [MemberData(nameof(TestModes))]
+ public async ValueTask CancelEvent(TestMode mode)
+ {
+ var listener = new EventListener();
+ var eventManager = CreatEventManager(
+ mode,
+ new CancelAtHighEventListener(),
+ listener
+ );
+
+ await eventManager.CallAsync(new SetValueEvent(1));
+
+ Assert.Equal(0, listener.Value);
+ }
+
+ [Theory]
+ [MemberData(nameof(TestModes))]
+ public async Task CancelPriority(TestMode mode)
+ {
+ var listener = new PriorityEventListener();
+ var eventManager = CreatEventManager(
+ mode,
+ new CancelAtHighEventListener(),
+ listener
+ );
+
+ await eventManager.CallAsync(new SetValueEvent(1));
+
+ Assert.Equal(new []
+ {
+ EventPriority.Monitor,
+ EventPriority.Highest
+ }, listener.Priorities);
+ }
+
+ private static IEventManager CreatEventManager(TestMode mode, params IEventListener[] listeners)
+ {
+ var services = new ServiceCollection();
+ services.AddLogging();
+ services.AddSingleton<IEventManager, EventManager>();
+
+ if (mode == TestMode.Service)
+ {
+ foreach (var listener in listeners)
+ {
+ services.AddSingleton(listener);
+ }
+ }
+
+ var eventManager = services.BuildServiceProvider().GetRequiredService<IEventManager>();
+
+ if (mode == TestMode.Temporary)
+ {
+ foreach (var listener in listeners)
+ {
+ eventManager.RegisterListener(listener);
+ }
+ }
+
+ return eventManager;
+ }
+
+ public enum TestMode
+ {
+ Service,
+ Temporary
+ }
+
+ public interface ISetValueEvent : IEventCancelable
+ {
+ int Value { get; }
+ }
+
+ public class SetValueEvent : ISetValueEvent
+ {
+ public SetValueEvent(int value)
+ {
+ Value = value;
+ }
+
+ public int Value { get; }
+
+ public bool IsCancelled { get; set; }
+ }
+
+ private class CancelAtHighEventListener : IEventListener
+ {
+ [EventListener(Priority = EventPriority.High)]
+ public void OnSetCalled(ISetValueEvent e) => e.IsCancelled = true;
+ }
+
+ private class EventListener : IEventListener
+ {
+ public int Value { get; private set; }
+
+ [EventListener]
+ public void OnSetCalled(ISetValueEvent e) => Value = e.Value;
+ }
+
+ private class PriorityEventListener : IEventListener
+ {
+ public List<EventPriority> Priorities { get; } = new List<EventPriority>();
+
+ [EventListener(EventPriority.Lowest)]
+ public void OnLowest(ISetValueEvent e) => Priorities.Add(EventPriority.Lowest);
+
+ [EventListener(EventPriority.Low)]
+ public void OnLow(ISetValueEvent e) => Priorities.Add(EventPriority.Low);
+
+ [EventListener]
+ public void OnNormal(ISetValueEvent e) => Priorities.Add(EventPriority.Normal);
+
+ [EventListener(EventPriority.High)]
+ public void OnHigh(ISetValueEvent e) => Priorities.Add(EventPriority.High);
+
+ [EventListener(EventPriority.Highest)]
+ public void OnHighest(ISetValueEvent e) => Priorities.Add(EventPriority.Highest);
+
+ [EventListener(EventPriority.Monitor)]
+ public void OnMonitor(ISetValueEvent e) => Priorities.Add(EventPriority.Monitor);
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Tests/GameCodeTests.cs b/Impostor-dev/src/Impostor.Tests/GameCodeTests.cs
new file mode 100644
index 0000000..de98123
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Tests/GameCodeTests.cs
@@ -0,0 +1,29 @@
+using Impostor.Api.Innersloth;
+
+using Xunit;
+
+namespace Impostor.Tests
+{
+ public class GameCodeTests
+ {
+ [Fact]
+ public void CodeV1()
+ {
+ const string code = "ABCD";
+ const int codeInt = 0x44434241;
+
+ Assert.Equal(code, GameCodeParser.IntToGameName(codeInt));
+ Assert.Equal(codeInt, GameCodeParser.GameNameToInt(code));
+ }
+
+ [Fact]
+ public void CodeV2()
+ {
+ const string code = "ABCDEF";
+ const int codeInt = -1943683525;
+
+ Assert.Equal(code, GameCodeParser.IntToGameName(codeInt));
+ Assert.Equal(codeInt, GameCodeParser.GameNameToInt(code));
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Tests/Hazel/MessageReaderTests.cs b/Impostor-dev/src/Impostor.Tests/Hazel/MessageReaderTests.cs
new file mode 100644
index 0000000..e7fa0df
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Tests/Hazel/MessageReaderTests.cs
@@ -0,0 +1,372 @@
+using System;
+using System.Linq;
+using Impostor.Api;
+using Impostor.Hazel;
+using Impostor.Hazel.Extensions;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.ObjectPool;
+using Xunit;
+
+namespace Impostor.Tests.Hazel
+{
+ public class MessageReaderTests
+ {
+ private ObjectPool<MessageReader> CreateReaderPool()
+ {
+ var services = new ServiceCollection();
+ services.AddHazel();
+ return services.BuildServiceProvider().GetRequiredService<ObjectPool<MessageReader>>();
+ }
+
+ [Fact]
+ public void ReadProperInt()
+ {
+ const int Test1 = int.MaxValue;
+ const int Test2 = int.MinValue;
+
+ var msg = new MessageWriter(128);
+ msg.StartMessage(1);
+ msg.Write(Test1);
+ msg.Write(Test2);
+ msg.EndMessage();
+
+ Assert.Equal(11, msg.Length);
+ Assert.Equal(msg.Length, msg.Position);
+
+ var readerPool = CreateReaderPool();
+ var reader = readerPool.Get();
+ reader.Update(msg.Buffer);
+ Assert.Equal(byte.MaxValue, reader.Tag);
+ var message = reader.ReadMessage();
+ Assert.Equal(1, message.Tag);
+ Assert.Equal(Test1, message.ReadInt32());
+ Assert.Equal(Test2, message.ReadInt32());
+ }
+
+ [Fact]
+ public void ReadProperBool()
+ {
+ const bool Test1 = true;
+ const bool Test2 = false;
+
+ var msg = new MessageWriter(128);
+ msg.StartMessage(1);
+ msg.Write(Test1);
+ msg.Write(Test2);
+ msg.EndMessage();
+
+ Assert.Equal(5, msg.Length);
+ Assert.Equal(msg.Length, msg.Position);
+
+ var readerPool = CreateReaderPool();
+ var reader = readerPool.Get();
+ reader.Update(msg.Buffer);
+ Assert.Equal(byte.MaxValue, reader.Tag);
+ var message = reader.ReadMessage();
+ Assert.Equal(1, message.Tag);
+ Assert.Equal(Test1, message.ReadBoolean());
+ Assert.Equal(Test2, message.ReadBoolean());
+ }
+
+ [Fact]
+ public void ReadProperString()
+ {
+ const string Test1 = "Hello";
+ string Test2 = new string(' ', 1024);
+ var msg = new MessageWriter(2048);
+ msg.StartMessage(1);
+ msg.Write(Test1);
+ msg.Write(Test2);
+ msg.Write(string.Empty);
+ msg.EndMessage();
+
+ Assert.Equal(msg.Length, msg.Position);
+
+ var readerPool = CreateReaderPool();
+ var reader = readerPool.Get();
+ reader.Update(msg.Buffer);
+ Assert.Equal(byte.MaxValue, reader.Tag);
+ var message = reader.ReadMessage();
+ Assert.Equal(1, message.Tag);
+ Assert.Equal(Test1, message.ReadString());
+ Assert.Equal(Test2, message.ReadString());
+ Assert.Equal(string.Empty, message.ReadString());
+ }
+
+ [Fact]
+ public void ReadProperFloat()
+ {
+ const float Test1 = 12.34f;
+
+ var msg = new MessageWriter(2048);
+ msg.StartMessage(1);
+ msg.Write(Test1);
+ msg.EndMessage();
+
+ Assert.Equal(7, msg.Length);
+ Assert.Equal(msg.Length, msg.Position);
+
+ var readerPool = CreateReaderPool();
+ var reader = readerPool.Get();
+ reader.Update(msg.Buffer);
+ Assert.Equal(byte.MaxValue, reader.Tag);
+ var message = reader.ReadMessage();
+ Assert.Equal(1, message.Tag);
+ Assert.Equal(Test1, message.ReadSingle());
+ }
+
+ [Fact]
+ public void CopyMessage()
+ {
+ var readerPool = CreateReaderPool();
+
+ // Create message.
+ const int msgLength = 18;
+ const byte Test1 = 12;
+ const byte Test2 = 146;
+
+ var msg = new MessageWriter(2048);
+
+ msg.StartMessage(1);
+ msg.StartMessage(2);
+ msg.Write(Test1);
+ msg.Write(Test2);
+ msg.StartMessage(2);
+ msg.Write(Test1);
+ msg.Write(Test2);
+ msg.StartMessage(2);
+ msg.Write(Test1);
+ msg.Write(Test2);
+ msg.EndMessage();
+ msg.EndMessage();
+ msg.EndMessage();
+ msg.EndMessage();
+
+ // Read message.
+ using var reader = readerPool.Get();
+
+ reader.Update(msg.Buffer);
+
+ // Read first message.
+ using var messageOne = reader.ReadMessage();
+
+ Assert.Equal(1, messageOne.Tag);
+ Assert.Equal(0, messageOne.Position);
+ Assert.Equal(3, messageOne.Offset);
+ Assert.Equal(msgLength - 3, messageOne.Length);
+
+ using var messageTwo = messageOne.ReadMessage();
+
+ Assert.Equal(2, messageTwo.Tag);
+ Assert.Equal(0, messageTwo.Position);
+ Assert.Equal(6, messageTwo.Offset);
+ Assert.Equal(msgLength - 6, messageTwo.Length);
+ Assert.Equal(Test1, messageTwo.ReadByte());
+ Assert.Equal(Test2, messageTwo.ReadByte());
+
+ using var messageThree = messageTwo.ReadMessage();
+
+ Assert.Equal(2, messageThree.Tag);
+ Assert.Equal(0, messageThree.Position);
+ Assert.Equal(11, messageThree.Offset);
+ Assert.Equal(msgLength - 11, messageThree.Length);
+ Assert.Equal(Test1, messageThree.ReadByte());
+ Assert.Equal(Test2, messageThree.ReadByte());
+ }
+
+ [Fact]
+ public void CopySubMessage()
+ {
+ const byte Test1 = 12;
+ const byte Test2 = 146;
+
+ var msg = new MessageWriter(2048);
+ msg.StartMessage(1);
+
+ msg.StartMessage(2);
+ msg.Write(Test1);
+ msg.Write(Test2);
+ msg.EndMessage();
+
+ msg.EndMessage();
+
+ var readerPool = CreateReaderPool();
+ var handleReader = readerPool.Get();
+ handleReader.Update(msg.Buffer);
+ var handleMessage = handleReader.ReadMessage();
+ Assert.Equal(1, handleMessage.Tag);
+
+ using var parentReader = handleMessage.Copy();
+
+ Assert.Equal(1, parentReader.Tag);
+
+ var reader = parentReader.ReadMessage();
+
+ Assert.Equal(2, reader.Tag);
+ Assert.Equal(Test1, reader.ReadByte());
+ Assert.Equal(Test2, reader.ReadByte());
+ }
+
+ [Fact]
+ public void CopyToMessage()
+ {
+ var expected = new byte[]
+ {
+ 0x2A, 0x00, 0x01, 0x27, 0x00, 0x02, 0x26, 0x54,
+ 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61,
+ 0x20, 0x6C, 0x6F, 0x6E, 0x67, 0x20, 0x70, 0x61,
+ 0x63, 0x6B, 0x65, 0x74, 0x20, 0x74, 0x6F, 0x20,
+ 0x74, 0x65, 0x73, 0x74, 0x20, 0x63, 0x6F, 0x70,
+ 0x79, 0x69, 0x6E, 0x67, 0x2E
+ };
+
+ var readerPool = CreateReaderPool();
+
+ // Create packet.
+ var msg = new MessageWriter(2048);
+ msg.StartMessage(1);
+ msg.StartMessage(2);
+ msg.Write("This is a long packet to test copying.");
+ msg.EndMessage();
+ msg.EndMessage();
+
+ // Create a reader.
+ var reader = readerPool.Get();
+
+ reader.Update(msg.Buffer);
+
+ // Read the initial message.
+ var message = reader.ReadMessage();
+
+ // Copy the message to a new writer.
+ var writer = new MessageWriter(2048);
+
+ message.CopyTo(writer);
+
+ // Compare.
+ Assert.Equal(expected, writer.ToByteArray(true));
+ }
+
+ [Fact]
+ public void ReadMessageLength()
+ {
+ var msg = new MessageWriter(2048);
+ msg.StartMessage(1);
+ msg.Write(65534);
+ msg.StartMessage(2);
+ msg.Write("HO");
+ msg.EndMessage();
+ msg.StartMessage(2);
+ msg.Write("NO");
+ msg.EndMessage();
+ msg.EndMessage();
+
+ Assert.Equal(msg.Length, msg.Position);
+
+ var readerPool = CreateReaderPool();
+ var reader = readerPool.Get();
+ reader.Update(msg.Buffer);
+ Assert.Equal(byte.MaxValue, reader.Tag);
+ var message = reader.ReadMessage();
+ Assert.Equal(1, message.Tag);
+ Assert.Equal(65534, message.ReadInt32()); // Content
+
+ var sub = message.ReadMessage();
+ Assert.Equal(3, sub.Length);
+ Assert.Equal(2, sub.Tag);
+ Assert.Equal("HO", sub.ReadString());
+
+ sub = message.ReadMessage();
+ Assert.Equal(3, sub.Length);
+ Assert.Equal(2, sub.Tag);
+ Assert.Equal("NO", sub.ReadString());
+ }
+
+ [Fact]
+ public void RemoveMessage()
+ {
+ // Create expected message.
+ var messageExpected = new MessageWriter(1024);
+
+ messageExpected.StartMessage(0);
+ messageExpected.StartMessage(1);
+ messageExpected.Write("HiTest1");
+ messageExpected.EndMessage();
+ messageExpected.StartMessage(2);
+ messageExpected.Write("HiTest2");
+ messageExpected.EndMessage();
+ messageExpected.EndMessage();
+
+ // Create message.
+ var messageWriter = new MessageWriter(1024);
+
+ messageWriter.StartMessage(0);
+ messageWriter.StartMessage(1);
+ messageWriter.Write("HiTest1");
+ messageWriter.StartMessage(2);
+ messageWriter.Write("RemoveMe!");
+ messageWriter.EndMessage();
+ messageWriter.EndMessage();
+ messageWriter.StartMessage(2);
+ messageWriter.Write("HiTest2");
+ messageWriter.EndMessage();
+ messageWriter.EndMessage();
+
+ // Copy buffer.
+ var bufferCopy = new byte[messageWriter.Length];
+ Buffer.BlockCopy(messageWriter.Buffer, 0, bufferCopy, 0, bufferCopy.Length);
+
+ var bufferCopyTwo = new byte[messageWriter.Length];
+ Buffer.BlockCopy(messageWriter.Buffer, 0, bufferCopyTwo, 0, bufferCopyTwo.Length);
+
+ // Do the magic.
+ var readerPool = CreateReaderPool();
+ var reader = readerPool.Get();
+ reader.Update(bufferCopy);
+ var inner = reader.ReadMessage();
+
+ while (inner.Position < inner.Length)
+ {
+ var message = inner.ReadMessage();
+ if (message.Tag == 1)
+ {
+ Assert.Equal("HiTest1", message.ReadString());
+
+ var messageSub = message.ReadMessage();
+ if (messageSub.Tag == 2)
+ {
+ Assert.Equal("RemoveMe!", messageSub.ReadString());
+
+ // Remove this message.
+ inner.RemoveMessage(messageSub);
+ }
+ }
+ else if (message.Tag == 2)
+ {
+ Assert.Equal("HiTest2", message.ReadString());
+ }
+ else
+ {
+ Assert.True(false, "Invalid tag was read.");
+ }
+ }
+
+ // Check if the magic was successful.
+ Assert.Equal(messageExpected.Length, reader.Length);
+ Assert.Equal(messageExpected.ToByteArray(true), reader.Buffer.Take(reader.Length).ToArray());
+
+ // Test ownership.
+ var readerTwo = readerPool.Get();
+
+ readerTwo.Update(bufferCopyTwo);
+
+ Assert.Throws<ImpostorProtocolException>(() => reader.RemoveMessage(readerTwo.ReadMessage()));
+ }
+
+ [Fact]
+ public void GetLittleEndian()
+ {
+ Assert.True(MessageWriter.IsLittleEndian());
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Tests/Hazel/MessageWriterTests.cs b/Impostor-dev/src/Impostor.Tests/Hazel/MessageWriterTests.cs
new file mode 100644
index 0000000..83d67da
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Tests/Hazel/MessageWriterTests.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Linq;
+using Impostor.Hazel;
+using Xunit;
+
+namespace Impostor.Tests.Hazel
+{
+ public class MessageWriterTests
+ {
+ [Fact]
+ public void ReadOnlyMemoryWriteWorksTheSameAsArray()
+ {
+ var oldVer = new MessageWriter(1024);
+ var newVer = new MessageWriter(1024);
+
+ var data = Enumerable.Repeat
+ (
+ Enumerable.Range(0, byte.MaxValue)
+ .Select(x => (byte)x),
+ 2
+ ).SelectMany(x => x).ToArray();
+
+ WriteSomeData(oldVer);
+ WriteSomeData(newVer);
+
+ oldVer.Write(data);
+ newVer.Write(data.AsMemory());
+
+ Assert.True(oldVer.Buffer.AsSpan().SequenceEqual(newVer.Buffer.AsSpan()));
+
+ static void WriteSomeData(MessageWriter oldVer)
+ {
+ oldVer.WritePacked(99);
+ oldVer.WritePacked(101);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Tests/Impostor.Tests.csproj b/Impostor-dev/src/Impostor.Tests/Impostor.Tests.csproj
new file mode 100644
index 0000000..3f96bf6
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Tests/Impostor.Tests.csproj
@@ -0,0 +1,20 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>net5.0</TargetFramework>
+ <IsPackable>false</IsPackable>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
+ <PackageReference Include="xunit" Version="2.4.1" />
+ <PackageReference Include="xunit.runner.reporters" Version="2.4.1" />
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
+ <PackageReference Include="coverlet.collector" Version="1.3.0" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\Impostor.Server\Impostor.Server.csproj" />
+ </ItemGroup>
+
+</Project>
diff --git a/Impostor-dev/src/Impostor.Tools.Proxy/HexUtils.cs b/Impostor-dev/src/Impostor.Tools.Proxy/HexUtils.cs
new file mode 100644
index 0000000..79517b4
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Tools.Proxy/HexUtils.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Text;
+
+namespace Impostor.Tools.Proxy
+{
+ public static class HexUtils
+ {
+ public static string HexDump(byte[] bytes, int bytesPerLine = 16)
+ {
+ if (bytes == null) return "<null>";
+ int bytesLength = bytes.Length;
+
+ char[] HexChars = "0123456789ABCDEF".ToCharArray();
+
+ int firstHexColumn =
+ 8 // 8 characters for the address
+ + 3; // 3 spaces
+
+ int firstCharColumn = firstHexColumn
+ + bytesPerLine * 3 // - 2 digit for the hexadecimal value and 1 space
+ + (bytesPerLine - 1) / 8 // - 1 extra space every 8 characters from the 9th
+ + 2; // 2 spaces
+
+ int lineLength = firstCharColumn
+ + bytesPerLine // - characters to show the ascii value
+ + Environment.NewLine.Length; // Carriage return and line feed (should normally be 2)
+
+ char[] line = (new String(' ', lineLength - Environment.NewLine.Length) + Environment.NewLine).ToCharArray();
+ int expectedLines = (bytesLength + bytesPerLine - 1) / bytesPerLine;
+ StringBuilder result = new StringBuilder(expectedLines * lineLength);
+
+ for (int i = 0; i < bytesLength; i += bytesPerLine)
+ {
+ line[0] = HexChars[(i >> 28) & 0xF];
+ line[1] = HexChars[(i >> 24) & 0xF];
+ line[2] = HexChars[(i >> 20) & 0xF];
+ line[3] = HexChars[(i >> 16) & 0xF];
+ line[4] = HexChars[(i >> 12) & 0xF];
+ line[5] = HexChars[(i >> 8) & 0xF];
+ line[6] = HexChars[(i >> 4) & 0xF];
+ line[7] = HexChars[(i >> 0) & 0xF];
+
+ int hexColumn = firstHexColumn;
+ int charColumn = firstCharColumn;
+
+ for (int j = 0; j < bytesPerLine; j++)
+ {
+ if (j > 0 && (j & 7) == 0) hexColumn++;
+ if (i + j >= bytesLength)
+ {
+ line[hexColumn] = ' ';
+ line[hexColumn + 1] = ' ';
+ line[charColumn] = ' ';
+ }
+ else
+ {
+ byte b = bytes[i + j];
+ line[hexColumn] = HexChars[(b >> 4) & 0xF];
+ line[hexColumn + 1] = HexChars[b & 0xF];
+ line[charColumn] = (b < 32 ? '·' : (char)b);
+ }
+ hexColumn += 3;
+ charColumn++;
+ }
+ result.Append(line);
+ }
+ return result.ToString();
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Tools.Proxy/Impostor.Tools.Proxy.csproj b/Impostor-dev/src/Impostor.Tools.Proxy/Impostor.Tools.Proxy.csproj
new file mode 100644
index 0000000..8f523dc
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Tools.Proxy/Impostor.Tools.Proxy.csproj
@@ -0,0 +1,17 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net5.0</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.0" />
+ <PackageReference Include="Pcap.Net.x64" Version="1.0.4.1" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\Impostor.Hazel\Impostor.Hazel.csproj" />
+ </ItemGroup>
+
+</Project>
diff --git a/Impostor-dev/src/Impostor.Tools.Proxy/Program.cs b/Impostor-dev/src/Impostor.Tools.Proxy/Program.cs
new file mode 100644
index 0000000..9e765f0
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Tools.Proxy/Program.cs
@@ -0,0 +1,198 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Impostor.Api.Net.Messages;
+using Impostor.Hazel;
+using Impostor.Hazel.Extensions;
+using Impostor.Hazel.Udp;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.ObjectPool;
+using PcapDotNet.Core;
+using PcapDotNet.Packets;
+
+namespace Impostor.Tools.Proxy
+{
+ internal static class Program
+ {
+ private const string DeviceName = "Intel(R) I211 Gigabit Network Connection";
+
+ private static readonly Dictionary<byte, string> TagMap = new Dictionary<byte, string>
+ {
+ {0, "HostGame"},
+ {1, "JoinGame"},
+ {2, "StartGame"},
+ {3, "RemoveGame"},
+ {4, "RemovePlayer"},
+ {5, "GameData"},
+ {6, "GameDataTo"},
+ {7, "JoinedGame"},
+ {8, "EndGame"},
+ {9, "GetGameList"},
+ {10, "AlterGame"},
+ {11, "KickPlayer"},
+ {12, "WaitForHost"},
+ {13, "Redirect"},
+ {14, "ReselectServer"},
+ {16, "GetGameListV2"}
+ };
+
+ private static IServiceProvider _serviceProvider;
+ private static ObjectPool<MessageReader> _readerPool;
+
+ //c 服务器入口
+ private static void Main(string[] args)
+ {
+ var services = new ServiceCollection();
+ services.AddHazel();
+
+ _serviceProvider = services.BuildServiceProvider();
+ _readerPool = _serviceProvider.GetRequiredService<ObjectPool<MessageReader>>();
+
+ var devices = LivePacketDevice.AllLocalMachine;
+ if (devices.Count == 0)
+ {
+ Console.WriteLine("No interfaces found! Make sure WinPcap is installed.");
+ return;
+ }
+
+ var device = devices.FirstOrDefault(x => x.Description.Contains(DeviceName));
+ if (device == null)
+ {
+ Console.WriteLine("Unable to find configured device.");
+ return;
+ }
+
+ using (var communicator = device.Open(65536, PacketDeviceOpenAttributes.Promiscuous, 1000))
+ {
+ using (var filter = communicator.CreateFilter("udp and port 22023"))
+ {
+ communicator.SetFilter(filter);
+ }
+
+ communicator.ReceivePackets(0, PacketHandler);
+ }
+ }
+
+ private static void PacketHandler(Packet packet)
+ {
+ var ip = packet.Ethernet.IpV4;
+ var ipSrc = ip.Source.ToString();
+ var udp = ip.Udp;
+
+ // True if this is our own packet.
+ using (var stream = udp.Payload.ToMemoryStream())
+ {
+ using var reader = _readerPool.Get();
+
+ reader.Update(stream.ToArray());
+
+ var option = reader.Buffer[0];
+ if (option == (byte) MessageType.Reliable)
+ {
+ reader.Seek(reader.Position + 3);
+ }
+ else if (option == (byte) UdpSendOption.Acknowledgement ||
+ option == (byte) UdpSendOption.Ping ||
+ option == (byte) UdpSendOption.Hello ||
+ option == (byte) UdpSendOption.Disconnect)
+ {
+ return;
+ }
+ else
+ {
+ reader.Seek(reader.Position + 1);
+ }
+
+ var isSent = ipSrc.StartsWith("192.");
+
+ while (true)
+ {
+ if (reader.Position >= reader.Length)
+ {
+ break;
+ }
+
+ //c 消息
+ using var message = reader.ReadMessage();
+ if (isSent)
+ {
+ HandleToServer(ipSrc, message);
+ }
+ else
+ {
+ HandleToClient(ipSrc, message);
+ }
+
+ if (message.Position < message.Length)
+ {
+ Console.ForegroundColor = ConsoleColor.Red;
+ Console.WriteLine("- Did not consume all bytes.");
+ }
+ }
+ }
+ }
+
+ private static void HandleToClient(string source, IMessageReader packet)
+ {
+ var tagName = TagMap.ContainsKey(packet.Tag) ? TagMap[packet.Tag] : "Unknown";
+ Console.ForegroundColor = ConsoleColor.Cyan;
+ Console.WriteLine($"{source,-15} Client received: {packet.Tag,-2} {tagName}");
+
+ switch (packet.Tag)
+ {
+ case 14:
+ case 13:
+ // packet.Position = packet.Length;
+ break;
+ case 0:
+ Console.WriteLine("- GameCode " + packet.ReadInt32());
+ break;
+ case 5:
+ case 6:
+ Console.WriteLine(HexUtils.HexDump(packet.Buffer.ToArray().Take(packet.Length).ToArray()));
+ // packet.Position = packet.Length;
+ break;
+ case 7:
+ Console.WriteLine("- GameCode " + packet.ReadInt32());
+ Console.WriteLine("- PlayerId " + packet.ReadInt32());
+ Console.WriteLine("- Host " + packet.ReadInt32());
+ var playerCount = packet.ReadPackedInt32();
+ Console.WriteLine("- PlayerCount " + playerCount);
+ for (var i = 0; i < playerCount; i++)
+ {
+ Console.WriteLine("- PlayerId " + packet.ReadPackedInt32());
+ }
+ break;
+ case 10:
+ Console.WriteLine("- GameCode " + packet.ReadInt32());
+ Console.WriteLine("- Flag " + packet.ReadSByte());
+ Console.WriteLine("- Value " + packet.ReadBoolean());
+ break;
+ }
+ }
+
+ private static void HandleToServer(string source, IMessageReader packet)
+ {
+ var tagName = TagMap.ContainsKey(packet.Tag) ? TagMap[packet.Tag] : "Unknown";
+ Console.ForegroundColor = ConsoleColor.White;
+ Console.WriteLine($"{source,-15} Server received: {packet.Tag,-2} {tagName}");
+
+ switch (packet.Tag)
+ {
+ case 0:
+ Console.WriteLine("- GameInfo length " + packet.ReadBytesAndSize().Length);
+ break;
+ case 1:
+ Console.WriteLine("- GameCode " + packet.ReadInt32());
+ Console.WriteLine("- Unknown " + packet.ReadByte());
+ break;
+ case 5:
+ case 6:
+ Console.WriteLine("- GameCode " + packet.ReadInt32());
+ Console.WriteLine(HexUtils.HexDump(packet.Buffer.ToArray().Take(packet.Length).ToArray()));
+ // packet.Position = packet.Length;
+ break;
+ }
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Tools.ServerReplay/Impostor.Tools.ServerReplay.csproj b/Impostor-dev/src/Impostor.Tools.ServerReplay/Impostor.Tools.ServerReplay.csproj
new file mode 100644
index 0000000..98fa689
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Tools.ServerReplay/Impostor.Tools.ServerReplay.csproj
@@ -0,0 +1,16 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net5.0</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\Impostor.Server\Impostor.Server.csproj"/>
+ </ItemGroup>
+
+ <ItemGroup>
+ <PackageReference Include="Serilog.Sinks.Console" Version="3.1.1"/>
+ </ItemGroup>
+
+</Project>
diff --git a/Impostor-dev/src/Impostor.Tools.ServerReplay/Mocks/MockGameCodeFactory.cs b/Impostor-dev/src/Impostor.Tools.ServerReplay/Mocks/MockGameCodeFactory.cs
new file mode 100644
index 0000000..1111b7e
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Tools.ServerReplay/Mocks/MockGameCodeFactory.cs
@@ -0,0 +1,14 @@
+using Impostor.Api.Games;
+
+namespace Impostor.Tools.ServerReplay.Mocks
+{
+ public class MockGameCodeFactory : IGameCodeFactory
+ {
+ public GameCode Result { get; set; }
+
+ public GameCode Create()
+ {
+ return Result;
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Tools.ServerReplay/Mocks/MockHazelConnection.cs b/Impostor-dev/src/Impostor.Tools.ServerReplay/Mocks/MockHazelConnection.cs
new file mode 100644
index 0000000..43f0257
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Tools.ServerReplay/Mocks/MockHazelConnection.cs
@@ -0,0 +1,31 @@
+using System.Net;
+using System.Threading.Tasks;
+using Impostor.Api.Net;
+using Impostor.Api.Net.Messages;
+
+namespace Impostor.Tools.ServerReplay.Mocks
+{
+ public class MockHazelConnection : IHazelConnection
+ {
+ public MockHazelConnection(IPEndPoint endPoint)
+ {
+ EndPoint = endPoint;
+ IsConnected = true;
+ Client = null;
+ }
+
+ public IPEndPoint EndPoint { get; }
+ public bool IsConnected { get; }
+ public IClient? Client { get; set; }
+
+ public ValueTask SendAsync(IMessageWriter writer)
+ {
+ return ValueTask.CompletedTask;
+ }
+
+ public ValueTask DisconnectAsync(string reason)
+ {
+ return ValueTask.CompletedTask;
+ }
+ }
+} \ No newline at end of file
diff --git a/Impostor-dev/src/Impostor.Tools.ServerReplay/Program.cs b/Impostor-dev/src/Impostor.Tools.ServerReplay/Program.cs
new file mode 100644
index 0000000..5aaa954
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Tools.ServerReplay/Program.cs
@@ -0,0 +1,195 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Net;
+using System.Threading.Tasks;
+using Impostor.Api.Events.Managers;
+using Impostor.Api.Games;
+using Impostor.Api.Games.Managers;
+using Impostor.Api.Innersloth;
+using Impostor.Api.Net;
+using Impostor.Api.Net.Messages;
+using Impostor.Api.Net.Messages.C2S;
+using Impostor.Hazel;
+using Impostor.Hazel.Extensions;
+using Impostor.Server.Events;
+using Impostor.Server.Net;
+using Impostor.Server.Net.Factories;
+using Impostor.Server.Net.Manager;
+using Impostor.Server.Net.Redirector;
+using Impostor.Server.Recorder;
+using Impostor.Tools.ServerReplay.Mocks;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.ObjectPool;
+using Serilog;
+using ILogger = Serilog.ILogger;
+
+namespace Impostor.Tools.ServerReplay
+{
+ internal static class Program
+ {
+ private static readonly ILogger Logger = Log.ForContext(typeof(Program));
+ private static readonly Dictionary<int, IHazelConnection> Connections = new Dictionary<int, IHazelConnection>();
+ private static readonly Dictionary<int, GameOptionsData> GameOptions = new Dictionary<int, GameOptionsData>();
+
+ private static ServiceProvider _serviceProvider;
+
+ private static ObjectPool<MessageReader> _readerPool;
+ private static MockGameCodeFactory _gameCodeFactory;
+ private static ClientManager _clientManager;
+ private static GameManager _gameManager;
+
+ private static async Task Main(string[] args)
+ {
+ Log.Logger = new LoggerConfiguration()
+ // .MinimumLevel.Verbose()
+ .MinimumLevel.Debug()
+ .WriteTo.Console()
+ .CreateLogger();
+
+ var stopwatch = Stopwatch.StartNew();
+
+ foreach (var file in Directory.GetFiles(args[0]))
+ {
+ // Clear.
+ Connections.Clear();
+ GameOptions.Clear();
+
+ // Create service provider.
+ _serviceProvider = BuildServices();
+
+ // Create required instances.
+ _readerPool = _serviceProvider.GetRequiredService<ObjectPool<MessageReader>>();
+ _gameCodeFactory = _serviceProvider.GetRequiredService<MockGameCodeFactory>();
+ _clientManager = _serviceProvider.GetRequiredService<ClientManager>();
+ _gameManager = _serviceProvider.GetRequiredService<GameManager>();
+
+ await using (var stream = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.Read))
+ using (var reader = new BinaryReader(stream))
+ {
+ await ParseSession(reader);
+ }
+ }
+
+ var elapsedMilliseconds = stopwatch.ElapsedMilliseconds;
+
+ Logger.Information($"Took {elapsedMilliseconds}ms.");
+ }
+
+ private static ServiceProvider BuildServices()
+ {
+ var services = new ServiceCollection();
+
+ services.AddLogging(builder =>
+ {
+ builder.ClearProviders();
+ builder.AddSerilog();
+ });
+
+ services.AddSingleton<GameManager>();
+ services.AddSingleton<IGameManager>(p => p.GetRequiredService<GameManager>());
+
+ services.AddSingleton<MockGameCodeFactory>();
+ services.AddSingleton<IGameCodeFactory>(p => p.GetRequiredService<MockGameCodeFactory>());
+
+ services.AddSingleton<ClientManager>();
+ services.AddSingleton<IClientFactory, ClientFactory<Client>>();
+ services.AddSingleton<INodeLocator, NodeLocatorNoOp>();
+ services.AddSingleton<IEventManager, EventManager>();
+
+ services.AddHazel();
+
+ return services.BuildServiceProvider();
+ }
+
+ private static async Task ParseSession(BinaryReader reader)
+ {
+ while (reader.BaseStream.Position < reader.BaseStream.Length)
+ {
+ var dataLength = reader.ReadInt32();
+ var data = reader.ReadBytes(dataLength - 4);
+
+ await using (var stream = new MemoryStream(data))
+ using (var readerInner = new BinaryReader(stream))
+ {
+ await ParsePacket(readerInner);
+ }
+ }
+ }
+
+ private static async Task ParsePacket(BinaryReader reader)
+ {
+ var dataType = (RecordedPacketType) reader.ReadByte();
+
+ // Read client id.
+ var clientId = reader.ReadInt32();
+
+ switch (dataType)
+ {
+ case RecordedPacketType.Connect:
+ // Read data.
+ var addressLength = reader.ReadByte();
+ var addressBytes = reader.ReadBytes(addressLength);
+ var addressPort = reader.ReadUInt16();
+ var address = new IPEndPoint(new IPAddress(addressBytes), addressPort);
+ var name = reader.ReadString();
+
+ // Create and register connection.
+ var connection = new MockHazelConnection(address);
+
+ await _clientManager.RegisterConnectionAsync(connection, name, 50516550);
+
+ // Store reference for ourselfs.
+ Connections.Add(clientId, connection);
+ break;
+
+ case RecordedPacketType.Disconnect:
+ string reason = null;
+
+ if (reader.BaseStream.Position < reader.BaseStream.Length)
+ {
+ reason = reader.ReadString();
+ }
+
+ await Connections[clientId].Client!.HandleDisconnectAsync(reason);
+ Connections.Remove(clientId);
+ break;
+
+ case RecordedPacketType.Message:
+ {
+ var messageType = (MessageType)reader.ReadByte();
+ var tag = reader.ReadByte();
+ var length = reader.ReadInt32();
+ var buffer = reader.ReadBytes(length);
+ using var message = _readerPool.Get();
+
+ message.Update(buffer, tag: tag);
+
+ if (tag == MessageFlags.HostGame)
+ {
+ GameOptions.Add(clientId, Message00HostGameC2S.Deserialize(message));
+ }
+ else if (Connections.TryGetValue(clientId, out var client))
+ {
+ await client.Client!.HandleMessageAsync(message, messageType);
+ }
+
+ break;
+ }
+
+ case RecordedPacketType.GameCreated:
+ _gameCodeFactory.Result = GameCode.From(reader.ReadString());
+
+ await _gameManager.CreateAsync(GameOptions[clientId]);
+
+ GameOptions.Remove(clientId);
+ break;
+
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Tools.ServerReplay/sessions/session_1604255331821_dead_player_exception.dat b/Impostor-dev/src/Impostor.Tools.ServerReplay/sessions/session_1604255331821_dead_player_exception.dat
new file mode 100644
index 0000000..e5f441b
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Tools.ServerReplay/sessions/session_1604255331821_dead_player_exception.dat
Binary files differ
diff --git a/Impostor-dev/src/Impostor.sln b/Impostor-dev/src/Impostor.sln
new file mode 100644
index 0000000..b9706de
--- /dev/null
+++ b/Impostor-dev/src/Impostor.sln
@@ -0,0 +1,177 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.30503.244
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impostor.Tests", "Impostor.Tests\Impostor.Tests.csproj", "{C1385C67-A7DD-46D2-80F6-FA428B85FB22}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{56DD9707-D811-4056-9E2C-8A9CC2479B07}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impostor.Tools.Proxy", "Impostor.Tools.Proxy\Impostor.Tools.Proxy.csproj", "{D16B8DE9-8EE2-4F6A-9352-305D5AB0233D}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impostor.Server", "Impostor.Server\Impostor.Server.csproj", "{1B0390AF-A4F3-4FE4-B093-708B0135C0B3}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impostor.Api", "Impostor.Api\Impostor.Api.csproj", "{E096A7D7-D693-4A13-A526-38CC574D84F8}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "patcher", "patcher", "{94FAED42-15BB-40CF-BE64-5D5C3B4ABC8F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impostor.Patcher.Shared", "Impostor.Patcher\Impostor.Patcher.Shared\Impostor.Patcher.Shared.csproj", "{7C3EB599-2292-4532-B280-D5BED1094DD4}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impostor.Patcher.WinForms", "Impostor.Patcher\Impostor.Patcher.WinForms\Impostor.Patcher.WinForms.csproj", "{804CF172-0C87-4423-9688-BD97D549891E}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impostor.Patcher.Cli", "Impostor.Patcher\Impostor.Patcher.Cli\Impostor.Patcher.Cli.csproj", "{82B36C4C-4EBD-49B7-A2DB-0B3308FBEF69}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "plugins", "plugins", "{36AA9913-E6EA-4A6C-90E6-2FD3CC2E3124}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impostor.Plugins.Debugger", "Impostor.Plugins.Debugger\Impostor.Plugins.Debugger.csproj", "{ECBCAA3B-B974-41CF-AFFC-6F5AA4C42FA7}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impostor.Hazel", "Impostor.Hazel\Impostor.Hazel.csproj", "{671B753B-31AE-4C36-AD71-09CF00FA17CA}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "client", "client", "{9F1919B0-915B-4749-9944-697DF7E7F67F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impostor.Client", "Impostor.Client\Impostor.Client.csproj", "{BE44C4A8-A202-4B63-A3BA-AC0AB5E7A0EB}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impostor.Client.App", "Impostor.Client.App\Impostor.Client.App.csproj", "{3DF86F12-7099-44F6-B98B-A148213D60B1}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impostor.Tools.ServerReplay", "Impostor.Tools.ServerReplay\Impostor.Tools.ServerReplay.csproj", "{4DB56ADD-6D3D-4D0E-A047-9D7E7D40EF99}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impostor.Plugins.Example", "Impostor.Plugins.Example\Impostor.Plugins.Example.csproj", "{70F97BB7-12A1-4C7A-B2A5-B962D14B813E}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impostor.Benchmarks", "Impostor.Benchmarks\Impostor.Benchmarks.csproj", "{EA04E386-6CCB-4C52-8C82-64F32C7EB377}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {1B0390AF-A4F3-4FE4-B093-708B0135C0B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1B0390AF-A4F3-4FE4-B093-708B0135C0B3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1B0390AF-A4F3-4FE4-B093-708B0135C0B3}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {1B0390AF-A4F3-4FE4-B093-708B0135C0B3}.Debug|x86.Build.0 = Debug|Any CPU
+ {1B0390AF-A4F3-4FE4-B093-708B0135C0B3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1B0390AF-A4F3-4FE4-B093-708B0135C0B3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1B0390AF-A4F3-4FE4-B093-708B0135C0B3}.Release|x86.ActiveCfg = Release|Any CPU
+ {1B0390AF-A4F3-4FE4-B093-708B0135C0B3}.Release|x86.Build.0 = Release|Any CPU
+ {C1385C67-A7DD-46D2-80F6-FA428B85FB22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C1385C67-A7DD-46D2-80F6-FA428B85FB22}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C1385C67-A7DD-46D2-80F6-FA428B85FB22}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {C1385C67-A7DD-46D2-80F6-FA428B85FB22}.Debug|x86.Build.0 = Debug|Any CPU
+ {C1385C67-A7DD-46D2-80F6-FA428B85FB22}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C1385C67-A7DD-46D2-80F6-FA428B85FB22}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C1385C67-A7DD-46D2-80F6-FA428B85FB22}.Release|x86.ActiveCfg = Release|Any CPU
+ {C1385C67-A7DD-46D2-80F6-FA428B85FB22}.Release|x86.Build.0 = Release|Any CPU
+ {D16B8DE9-8EE2-4F6A-9352-305D5AB0233D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D16B8DE9-8EE2-4F6A-9352-305D5AB0233D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D16B8DE9-8EE2-4F6A-9352-305D5AB0233D}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D16B8DE9-8EE2-4F6A-9352-305D5AB0233D}.Debug|x86.Build.0 = Debug|Any CPU
+ {D16B8DE9-8EE2-4F6A-9352-305D5AB0233D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D16B8DE9-8EE2-4F6A-9352-305D5AB0233D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D16B8DE9-8EE2-4F6A-9352-305D5AB0233D}.Release|x86.ActiveCfg = Release|Any CPU
+ {D16B8DE9-8EE2-4F6A-9352-305D5AB0233D}.Release|x86.Build.0 = Release|Any CPU
+ {804CF172-0C87-4423-9688-BD97D549891E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {804CF172-0C87-4423-9688-BD97D549891E}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {804CF172-0C87-4423-9688-BD97D549891E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {804CF172-0C87-4423-9688-BD97D549891E}.Release|x86.ActiveCfg = Release|Any CPU
+ {804CF172-0C87-4423-9688-BD97D549891E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E096A7D7-D693-4A13-A526-38CC574D84F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E096A7D7-D693-4A13-A526-38CC574D84F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E096A7D7-D693-4A13-A526-38CC574D84F8}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {E096A7D7-D693-4A13-A526-38CC574D84F8}.Debug|x86.Build.0 = Debug|Any CPU
+ {E096A7D7-D693-4A13-A526-38CC574D84F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E096A7D7-D693-4A13-A526-38CC574D84F8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E096A7D7-D693-4A13-A526-38CC574D84F8}.Release|x86.ActiveCfg = Release|Any CPU
+ {E096A7D7-D693-4A13-A526-38CC574D84F8}.Release|x86.Build.0 = Release|Any CPU
+ {7C3EB599-2292-4532-B280-D5BED1094DD4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7C3EB599-2292-4532-B280-D5BED1094DD4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7C3EB599-2292-4532-B280-D5BED1094DD4}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {7C3EB599-2292-4532-B280-D5BED1094DD4}.Debug|x86.Build.0 = Debug|Any CPU
+ {7C3EB599-2292-4532-B280-D5BED1094DD4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7C3EB599-2292-4532-B280-D5BED1094DD4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7C3EB599-2292-4532-B280-D5BED1094DD4}.Release|x86.ActiveCfg = Release|Any CPU
+ {7C3EB599-2292-4532-B280-D5BED1094DD4}.Release|x86.Build.0 = Release|Any CPU
+ {82B36C4C-4EBD-49B7-A2DB-0B3308FBEF69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {82B36C4C-4EBD-49B7-A2DB-0B3308FBEF69}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {82B36C4C-4EBD-49B7-A2DB-0B3308FBEF69}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {82B36C4C-4EBD-49B7-A2DB-0B3308FBEF69}.Debug|x86.Build.0 = Debug|Any CPU
+ {82B36C4C-4EBD-49B7-A2DB-0B3308FBEF69}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {82B36C4C-4EBD-49B7-A2DB-0B3308FBEF69}.Release|Any CPU.Build.0 = Release|Any CPU
+ {82B36C4C-4EBD-49B7-A2DB-0B3308FBEF69}.Release|x86.ActiveCfg = Release|Any CPU
+ {82B36C4C-4EBD-49B7-A2DB-0B3308FBEF69}.Release|x86.Build.0 = Release|Any CPU
+ {ECBCAA3B-B974-41CF-AFFC-6F5AA4C42FA7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {ECBCAA3B-B974-41CF-AFFC-6F5AA4C42FA7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {ECBCAA3B-B974-41CF-AFFC-6F5AA4C42FA7}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {ECBCAA3B-B974-41CF-AFFC-6F5AA4C42FA7}.Debug|x86.Build.0 = Debug|Any CPU
+ {ECBCAA3B-B974-41CF-AFFC-6F5AA4C42FA7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {ECBCAA3B-B974-41CF-AFFC-6F5AA4C42FA7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {ECBCAA3B-B974-41CF-AFFC-6F5AA4C42FA7}.Release|x86.ActiveCfg = Release|Any CPU
+ {ECBCAA3B-B974-41CF-AFFC-6F5AA4C42FA7}.Release|x86.Build.0 = Release|Any CPU
+ {671B753B-31AE-4C36-AD71-09CF00FA17CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {671B753B-31AE-4C36-AD71-09CF00FA17CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {671B753B-31AE-4C36-AD71-09CF00FA17CA}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {671B753B-31AE-4C36-AD71-09CF00FA17CA}.Debug|x86.Build.0 = Debug|Any CPU
+ {671B753B-31AE-4C36-AD71-09CF00FA17CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {671B753B-31AE-4C36-AD71-09CF00FA17CA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {671B753B-31AE-4C36-AD71-09CF00FA17CA}.Release|x86.ActiveCfg = Release|Any CPU
+ {671B753B-31AE-4C36-AD71-09CF00FA17CA}.Release|x86.Build.0 = Release|Any CPU
+ {BE44C4A8-A202-4B63-A3BA-AC0AB5E7A0EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BE44C4A8-A202-4B63-A3BA-AC0AB5E7A0EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BE44C4A8-A202-4B63-A3BA-AC0AB5E7A0EB}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {BE44C4A8-A202-4B63-A3BA-AC0AB5E7A0EB}.Debug|x86.Build.0 = Debug|Any CPU
+ {BE44C4A8-A202-4B63-A3BA-AC0AB5E7A0EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BE44C4A8-A202-4B63-A3BA-AC0AB5E7A0EB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BE44C4A8-A202-4B63-A3BA-AC0AB5E7A0EB}.Release|x86.ActiveCfg = Release|Any CPU
+ {BE44C4A8-A202-4B63-A3BA-AC0AB5E7A0EB}.Release|x86.Build.0 = Release|Any CPU
+ {3DF86F12-7099-44F6-B98B-A148213D60B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3DF86F12-7099-44F6-B98B-A148213D60B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3DF86F12-7099-44F6-B98B-A148213D60B1}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {3DF86F12-7099-44F6-B98B-A148213D60B1}.Debug|x86.Build.0 = Debug|Any CPU
+ {3DF86F12-7099-44F6-B98B-A148213D60B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3DF86F12-7099-44F6-B98B-A148213D60B1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3DF86F12-7099-44F6-B98B-A148213D60B1}.Release|x86.ActiveCfg = Release|Any CPU
+ {3DF86F12-7099-44F6-B98B-A148213D60B1}.Release|x86.Build.0 = Release|Any CPU
+ {4DB56ADD-6D3D-4D0E-A047-9D7E7D40EF99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4DB56ADD-6D3D-4D0E-A047-9D7E7D40EF99}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4DB56ADD-6D3D-4D0E-A047-9D7E7D40EF99}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {4DB56ADD-6D3D-4D0E-A047-9D7E7D40EF99}.Debug|x86.Build.0 = Debug|Any CPU
+ {4DB56ADD-6D3D-4D0E-A047-9D7E7D40EF99}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4DB56ADD-6D3D-4D0E-A047-9D7E7D40EF99}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4DB56ADD-6D3D-4D0E-A047-9D7E7D40EF99}.Release|x86.ActiveCfg = Release|Any CPU
+ {4DB56ADD-6D3D-4D0E-A047-9D7E7D40EF99}.Release|x86.Build.0 = Release|Any CPU
+ {70F97BB7-12A1-4C7A-B2A5-B962D14B813E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {70F97BB7-12A1-4C7A-B2A5-B962D14B813E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {70F97BB7-12A1-4C7A-B2A5-B962D14B813E}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {70F97BB7-12A1-4C7A-B2A5-B962D14B813E}.Debug|x86.Build.0 = Debug|Any CPU
+ {70F97BB7-12A1-4C7A-B2A5-B962D14B813E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {70F97BB7-12A1-4C7A-B2A5-B962D14B813E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {70F97BB7-12A1-4C7A-B2A5-B962D14B813E}.Release|x86.ActiveCfg = Release|Any CPU
+ {70F97BB7-12A1-4C7A-B2A5-B962D14B813E}.Release|x86.Build.0 = Release|Any CPU
+ {EA04E386-6CCB-4C52-8C82-64F32C7EB377}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EA04E386-6CCB-4C52-8C82-64F32C7EB377}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EA04E386-6CCB-4C52-8C82-64F32C7EB377}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {EA04E386-6CCB-4C52-8C82-64F32C7EB377}.Debug|x86.Build.0 = Debug|Any CPU
+ {EA04E386-6CCB-4C52-8C82-64F32C7EB377}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EA04E386-6CCB-4C52-8C82-64F32C7EB377}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EA04E386-6CCB-4C52-8C82-64F32C7EB377}.Release|x86.ActiveCfg = Release|Any CPU
+ {EA04E386-6CCB-4C52-8C82-64F32C7EB377}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {06245255-E585-483C-A4AD-71B7B568C6C8}
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {D16B8DE9-8EE2-4F6A-9352-305D5AB0233D} = {56DD9707-D811-4056-9E2C-8A9CC2479B07}
+ {804CF172-0C87-4423-9688-BD97D549891E} = {94FAED42-15BB-40CF-BE64-5D5C3B4ABC8F}
+ {7C3EB599-2292-4532-B280-D5BED1094DD4} = {94FAED42-15BB-40CF-BE64-5D5C3B4ABC8F}
+ {82B36C4C-4EBD-49B7-A2DB-0B3308FBEF69} = {94FAED42-15BB-40CF-BE64-5D5C3B4ABC8F}
+ {ECBCAA3B-B974-41CF-AFFC-6F5AA4C42FA7} = {36AA9913-E6EA-4A6C-90E6-2FD3CC2E3124}
+ {BE44C4A8-A202-4B63-A3BA-AC0AB5E7A0EB} = {9F1919B0-915B-4749-9944-697DF7E7F67F}
+ {3DF86F12-7099-44F6-B98B-A148213D60B1} = {9F1919B0-915B-4749-9944-697DF7E7F67F}
+ {4DB56ADD-6D3D-4D0E-A047-9D7E7D40EF99} = {56DD9707-D811-4056-9E2C-8A9CC2479B07}
+ {70F97BB7-12A1-4C7A-B2A5-B962D14B813E} = {36AA9913-E6EA-4A6C-90E6-2FD3CC2E3124}
+ EndGlobalSection
+EndGlobal