├── .editorconfig ├── .gitignore ├── Directory.Build.props ├── Directory.Build.targets ├── LICENSE ├── OniMod.sln ├── README.md ├── docs └── CONTRIBUTING.md ├── scripts ├── install.bat └── install.sh ├── src ├── MultiplayerMod.Test │ ├── Compatibility.cs │ ├── Core │ │ ├── Collections │ │ │ ├── BidirectionalMapTests.cs │ │ │ ├── IndexedObjectExtensionMapTests.cs │ │ │ └── LinkedHashSetTests.cs │ │ ├── Dependency │ │ │ ├── DependencyContainerBuilderTests.cs │ │ │ └── DependencyContainerTests.cs │ │ ├── Events │ │ │ ├── EventDispatcherTests.cs │ │ │ └── TypedEventDispatcherTests.cs │ │ ├── Extensions │ │ │ ├── LinkedListExtensionTests.cs │ │ │ ├── MethodSignatureExtensionTests.cs │ │ │ └── TypeSignatureExtensionTests.cs │ │ ├── Patch │ │ │ ├── Compatibility │ │ │ │ ├── PatchesCompatibility.cs │ │ │ │ └── PatchesCompatibilityMetadata.cs │ │ │ ├── ControlFlowCustomizerTests.cs │ │ │ ├── Evaluators │ │ │ │ └── MethodBoundedDetourTests.cs │ │ │ └── Generics │ │ │ │ └── HarmonyGenericsRouterTests.cs │ │ └── Paths │ │ │ └── SecurePathTests.cs │ ├── Directory.Build.targets │ ├── Environment │ │ ├── EventRaiserExtension.cs │ │ ├── Network │ │ │ ├── CommandTools.cs │ │ │ ├── TestMultiplayerClient.cs │ │ │ ├── TestMultiplayerClientId.cs │ │ │ ├── TestMultiplayerEndpoint.cs │ │ │ └── TestMultiplayerServer.cs │ │ ├── Patches │ │ │ └── PatchesSetup.cs │ │ ├── TestRuntime.cs │ │ └── Unity │ │ │ ├── Patches │ │ │ ├── LoadingOverlayPatch.cs │ │ │ ├── SpeedControlScreenPatch.cs │ │ │ └── Unity │ │ │ │ ├── ApplicationPatch.cs │ │ │ │ ├── BehaviourPatch.cs │ │ │ │ ├── ComponentPatch.cs │ │ │ │ ├── DebugLogHandlerPatch.cs │ │ │ │ ├── GameObjectPatch.cs │ │ │ │ ├── LayerMaskPatch.cs │ │ │ │ ├── MaterialPatch.cs │ │ │ │ ├── MeshPatch.cs │ │ │ │ ├── ObjectPatch.cs │ │ │ │ ├── RandomPatch.cs │ │ │ │ ├── RendererPatch.cs │ │ │ │ ├── ResourcesAPIInternalPatch.cs │ │ │ │ ├── ScriptableObjectPatch.cs │ │ │ │ ├── ShaderPatch.cs │ │ │ │ ├── SystemInfoPatch.cs │ │ │ │ ├── TextAssetPatch.cs │ │ │ │ ├── TimePatch.cs │ │ │ │ └── TransformPatch.cs │ │ │ ├── UnityEvent.cs │ │ │ ├── UnityPlayerObjectManager.cs │ │ │ ├── UnityTestRuntime.cs │ │ │ └── UnityTestRuntimeTest.cs │ ├── Game │ │ └── Chores │ │ │ ├── AbstractChoreTest.cs │ │ │ ├── ChoreListTest.cs │ │ │ └── States │ │ │ └── ChoreStateEventsTest.cs │ ├── GameRuntime │ │ ├── Patches │ │ │ ├── AssetsPatch.cs │ │ │ ├── DbPatch.cs │ │ │ └── ElementLoaderPatch.cs │ │ └── PlayableGameTest.cs │ ├── ModRuntime │ │ ├── ExecutionContextManagerTests.cs │ │ └── ExecutionLevelTests.cs │ ├── Multiplayer │ │ ├── Chores │ │ │ ├── ChoreTest.cs │ │ │ ├── ChoresInstantiationTests.cs │ │ │ ├── ChoresMultiplayerConfigurationTest.cs │ │ │ └── CreateChoreTest.cs │ │ ├── Commands │ │ │ └── Chores │ │ │ │ └── States │ │ │ │ ├── AllowStateTransitionTest.cs │ │ │ │ ├── SetStateArgumentsTest.cs │ │ │ │ ├── StartMoveTest.cs │ │ │ │ ├── TransitToStateTest.cs │ │ │ │ └── UpdateMoveTest.cs │ │ ├── MultiplayerConnectionTests.cs │ │ ├── MultiplayerStatusOverlayPatch.cs │ │ ├── MultiplayerTools.cs │ │ ├── Patches │ │ │ └── Chores │ │ │ │ └── States │ │ │ │ └── AdjustStateMachineTransitionsTest.cs │ │ ├── SpeedControlScreenContext.cs │ │ ├── StateMachines │ │ │ ├── RuntimeTools │ │ │ │ ├── StateMachineContextRuntimeToolsTests.cs │ │ │ │ └── StateMachineRuntimeToolsTests.cs │ │ │ ├── StateMachineDslTests.cs │ │ │ └── StateMachinesTest.cs │ │ ├── States │ │ │ ├── StatesManagerTest.cs │ │ │ └── WaitHostStateTest.cs │ │ ├── TestRuntimeExtensions.cs │ │ └── WorldManagerPatch.cs │ ├── MultiplayerMod.Test.csproj │ └── Network │ │ └── CommandTests.cs └── MultiplayerMod │ ├── Compatibility.cs │ ├── Core │ ├── Collections │ │ ├── BidirectionalMap.cs │ │ ├── BoxedValue.cs │ │ ├── Deconstructable.cs │ │ ├── IdentityEqualityComparer.cs │ │ ├── IndexedObjectExtensionMap.cs │ │ ├── LinkedHashSet.cs │ │ └── SingletonCollection.cs │ ├── Dependency │ │ ├── AmbiguousDependencyException.cs │ │ ├── DependencyAlreadyRegisteredException.cs │ │ ├── DependencyAttribute.cs │ │ ├── DependencyContainer.cs │ │ ├── DependencyContainerBuilder.cs │ │ ├── DependencyInfo.cs │ │ ├── DependencyInstantiationException.cs │ │ ├── DependencyIsInstantiatingException.cs │ │ ├── IDependencyContainer.cs │ │ ├── IDependencyInjector.cs │ │ ├── InjectDependencyAttribute.cs │ │ ├── InvalidDependencyException.cs │ │ └── MissingDependencyException.cs │ ├── Events │ │ ├── EventDispatcher.cs │ │ ├── EventSubscription.cs │ │ ├── EventSubscriptions.cs │ │ └── IDispatchableEvent.cs │ ├── Extensions │ │ ├── ArraysExtensions.cs │ │ ├── DictionaryExtensions.cs │ │ ├── EnumerableExtensions.cs │ │ ├── HashAlgorithmExtensions.cs │ │ ├── LinkedListExtensions.cs │ │ ├── MethodSignatureExtension.cs │ │ ├── NumberExtensions.cs │ │ ├── SetExtensions.cs │ │ ├── TypeExtensions.cs │ │ └── TypeSignatureExtension.cs │ ├── Logging │ │ ├── LogLevel.cs │ │ ├── Logger.cs │ │ ├── LoggerFactory.cs │ │ └── TcpLogger.cs │ ├── Patch │ │ ├── Capture │ │ │ ├── CaptureUnavailableException.cs │ │ │ ├── ILocalCapture.cs │ │ │ ├── InvalidLocalVariableReferenceException.cs │ │ │ ├── LocalCaptor.cs │ │ │ ├── LocalCaptureContainer.cs │ │ │ ├── LocalVariableAttribute.cs │ │ │ ├── LocalVariableCaptureAttribute.cs │ │ │ └── LocalVariableCaptureSupport.cs │ │ ├── CodeInstructionExtensions.cs │ │ ├── CodeInstructionsOffsetAccessor.cs │ │ ├── ControlFlow │ │ │ ├── ControlFlowContext.cs │ │ │ ├── ControlFlowCustomizer.cs │ │ │ ├── Evaluators │ │ │ │ ├── CompositeDetour.cs │ │ │ │ └── MethodBoundedDetour.cs │ │ │ ├── ExecutionFlow.cs │ │ │ └── IDetourEvaluator.cs │ │ ├── Generics │ │ │ ├── HarmonyGenericsRouter.cs │ │ │ └── HarmonyGenericsRouterException.cs │ │ ├── HarmonyManualAttribute.cs │ │ ├── HarmonyOptionalAttribute.cs │ │ ├── PatchTargetResolver.cs │ │ └── StackTraceExtensions.cs │ ├── Paths │ │ ├── AccessDeniedException.cs │ │ └── SecurePath.cs │ ├── Reflection │ │ └── ReflectionExtension.cs │ ├── Scheduling │ │ ├── UnityTaskExecutor.cs │ │ ├── UnityTaskSchedulerConfigurer.cs │ │ └── UntiyTaskScheduler.cs │ └── Unity │ │ ├── MultiplayerKMonoBehaviour.cs │ │ ├── MultiplayerMonoBehaviour.cs │ │ ├── UnityObject.cs │ │ └── UnityPlayerObject.cs │ ├── Directory.Build.targets │ ├── Exceptions │ └── MultiplayerException.cs │ ├── Game │ ├── Chores │ │ ├── ChoreDriverEvents.cs │ │ ├── ChoreList.cs │ │ ├── States │ │ │ ├── ChoreStateEvents.cs │ │ │ ├── ChoreTransitStateArgs.cs │ │ │ └── MoveToArgs.cs │ │ └── Types │ │ │ ├── ChoreSyncConfig.cs │ │ │ ├── CreationStatusEnum.cs │ │ │ ├── GlobalChoreProviderStatusEnum.cs │ │ │ ├── StateTransitionConfig.cs │ │ │ ├── StatesTransitionConfig.cs │ │ │ ├── StatesTransitionStatus.cs │ │ │ └── TransitionTypeEnum.cs │ ├── Configuration │ │ └── MethodPatchesDisabler.cs │ ├── Context │ │ ├── GameContext.cs │ │ ├── GameContextComposite.cs │ │ ├── GameStaticContext.cs │ │ └── IGameContext.cs │ ├── Debug │ │ └── GameDebugEvents.cs │ ├── Dev │ │ └── DevToolSceneInspectorPatch.cs │ ├── Effects │ │ ├── DisablePopUpEffects.cs │ │ └── DisablePriorityConfirmSound.cs │ ├── Extension │ │ ├── GameExtensionsConfigurator.cs │ │ └── GameObjectExtension.cs │ ├── GameEvents.cs │ ├── GameState.cs │ ├── KEventSystemExtensions.cs │ ├── Location │ │ └── GridObjectLayerIndexerPatch.cs │ ├── MainMenuExtensions.cs │ ├── Mechanics │ │ ├── Minions │ │ │ └── MinionIdentityExtensions.cs │ │ ├── Objects │ │ │ ├── ComponentEventsArgs.cs │ │ │ ├── ObjectEvents.cs │ │ │ └── StateMachineEventsArgs.cs │ │ ├── Printing │ │ │ ├── TelepadAcceptDeliveryCapture.cs │ │ │ └── TelepadEvents.cs │ │ └── Schedules │ │ │ └── ScheduleConditionalUpdatePatch.cs │ ├── NameOf │ │ └── StateMachineMemberReference.cs │ ├── UI │ │ ├── GameSpeedControlEvents.cs │ │ ├── MainMenuEvents.cs │ │ ├── Overlay │ │ │ └── DisinfectOverlayEvents.cs │ │ ├── Screens │ │ │ ├── Events │ │ │ │ ├── ConsumableScreenEvents.cs │ │ │ │ ├── ImmigrantScreenEvents.cs │ │ │ │ ├── MeterScreenEvents.cs │ │ │ │ ├── PauseScreenEvents.cs │ │ │ │ ├── PrioritiesScreenEvents.cs │ │ │ │ ├── ResearchScreenEvents.cs │ │ │ │ ├── ScheduleScreenEvents.cs │ │ │ │ ├── SkillScreenEvents.cs │ │ │ │ ├── UserMenuButtonEvents.cs │ │ │ │ └── UserMenuScreenEvents.cs │ │ │ ├── ImmigrantScreenPatch.cs │ │ │ └── ScreensUtils.cs │ │ ├── SideScreens │ │ │ ├── AlarmSideScreenEvents.cs │ │ │ ├── CounterSideScreenEvents.cs │ │ │ ├── CritterSensorSideScreenEvents.cs │ │ │ ├── RailGunSideScreenEvents.cs │ │ │ ├── TemperatureSwitchSideScreenEvents.cs │ │ │ ├── TimeRangeSideScreenEvents.cs │ │ │ └── TimerSideScreenEvents.cs │ │ └── Tools │ │ │ ├── Context │ │ │ ├── DebugToolContext.cs │ │ │ ├── DisableBuildingValidation.cs │ │ │ ├── PrioritySettingsContext.cs │ │ │ └── StampCompletionOverride.cs │ │ │ └── Events │ │ │ ├── BuildEventArgs.cs │ │ │ ├── BuildEvents.cs │ │ │ ├── CopySettingsEventArgs.cs │ │ │ ├── CopySettingsEvents.cs │ │ │ ├── DebugToolEvents.cs │ │ │ ├── DragCompleteEventArgs.cs │ │ │ ├── DragToolEvents.cs │ │ │ ├── InterfaceToolEvents.cs │ │ │ ├── ModifyEventArgs.cs │ │ │ ├── MoveToLocationToolEvents.cs │ │ │ ├── StampEventArgs.cs │ │ │ ├── StampToolEvents.cs │ │ │ ├── UtilityBuildEventArgs.cs │ │ │ └── UtilityBuildEvents.cs │ └── World │ │ └── SaveLoaderEvents.cs │ ├── ModRuntime │ ├── Context │ │ ├── ExecutionContext.cs │ │ ├── ExecutionContextIntegrityFailureException.cs │ │ ├── ExecutionContextManager.cs │ │ ├── ExecutionContextStack.cs │ │ ├── ExecutionLevel.cs │ │ ├── ExecutionLevelManager.cs │ │ ├── RequireDebugBuildAttribute.cs │ │ └── RequireExecutionLevelAttribute.cs │ ├── DependenciesStaticTargetAttribute.cs │ ├── EventDispatcherMonitor.cs │ ├── Loader │ │ ├── DelayedModLoader.cs │ │ ├── IModComponentConfigurer.cs │ │ ├── LaunchInitializerPatch.cs │ │ ├── ModComponentOrder.cs │ │ └── ModLoader.cs │ ├── Runtime.cs │ ├── RuntimeReadyEvent.cs │ └── StaticCompatibility │ │ ├── Dependencies.cs │ │ └── Execution.cs │ ├── Multiplayer │ ├── Chores │ │ ├── ChoreConfiguration.cs │ │ ├── ChoresMultiplayerConfiguration.cs │ │ ├── ChoresPatcher.cs │ │ ├── Commands │ │ │ ├── CreateChore.cs │ │ │ └── States │ │ │ │ ├── AllowStateTransition.cs │ │ │ │ ├── SetStateArguments.cs │ │ │ │ ├── StartMove.cs │ │ │ │ ├── TransitToState.cs │ │ │ │ └── UpdateMove.cs │ │ ├── Driver │ │ │ ├── ChoreDriverSynchronization.cs │ │ │ ├── Commands │ │ │ │ ├── ReleaseChoreDriver.cs │ │ │ │ └── SetDriverChore.cs │ │ │ └── MultiplayerDriverChores.cs │ │ ├── Dsl │ │ │ └── ChoresConfigurationDsl.cs │ │ ├── Events │ │ │ ├── ChoreCleanupEvent.cs │ │ │ └── ChoreCreatedEvent.cs │ │ ├── Patches │ │ │ └── States │ │ │ │ └── AdjustStateMachineTransitions.cs │ │ ├── RuntimeChoresStateManager.cs │ │ ├── Serialization │ │ │ └── ChoreArgumentsWrapper.cs │ │ └── Synchronizers │ │ │ ├── DeathMonitorSynchronizer.cs │ │ │ ├── IdleChoreSynchronizer.cs │ │ │ ├── IdleStatesSynchronizer.cs │ │ │ └── MoveToSafetyChoreSynchronizer.cs │ ├── Commands │ │ ├── Alerts │ │ │ └── ChangeRedAlertState.cs │ │ ├── ArgumentUtils.cs │ │ ├── Debug │ │ │ ├── DebugGameFrameStep.cs │ │ │ ├── DebugSimulationStep.cs │ │ │ └── SyncWorldDebugSnapshot.cs │ │ ├── Gameplay │ │ │ ├── AcceptDelivery.cs │ │ │ ├── CallMethod.cs │ │ │ ├── ChangePriority.cs │ │ │ └── RejectDelivery.cs │ │ ├── IMultiplayerCommand.cs │ │ ├── MultiplayerCommand.cs │ │ ├── MultiplayerCommandAttribute.cs │ │ ├── MultiplayerCommandContext.cs │ │ ├── MultiplayerCommandRuntimeAccessor.cs │ │ ├── MultiplayerCommandType.cs │ │ ├── MultiplayerCommandsConfigurer.cs │ │ ├── Objects │ │ │ └── SynchronizeObjectPosition.cs │ │ ├── Overlay │ │ │ └── SetDisinfectSettings.cs │ │ ├── Player │ │ │ └── UpdatePlayerCursorPosition.cs │ │ ├── Registry │ │ │ ├── MultiplayerCommandAlreadyRegisteredException.cs │ │ │ ├── MultiplayerCommandConfiguration.cs │ │ │ ├── MultiplayerCommandInvalidInterfaceException.cs │ │ │ ├── MultiplayerCommandNotFoundException.cs │ │ │ └── MultiplayerCommandRegistry.cs │ │ ├── Screens │ │ │ ├── Consumable │ │ │ │ ├── PermitConsumableByDefault.cs │ │ │ │ └── PermitConsumableToMinion.cs │ │ │ ├── Immigration │ │ │ │ └── InitializeImmigration.cs │ │ │ ├── Priorities │ │ │ │ ├── SetDefaultPriority.cs │ │ │ │ ├── SetPersonalPrioritiesAdvanced.cs │ │ │ │ └── SetPersonalPriority.cs │ │ │ ├── Research │ │ │ │ ├── AbstractResearchEntryCommand.cs │ │ │ │ ├── CancelResearch.cs │ │ │ │ └── SelectResearch.cs │ │ │ ├── Schedule │ │ │ │ └── ChangeSchedulesList.cs │ │ │ ├── SideScreen │ │ │ │ ├── UpdateAlarm.cs │ │ │ │ ├── UpdateCritterSensor.cs │ │ │ │ ├── UpdateLogicCounter.cs │ │ │ │ ├── UpdateLogicTimeOfDaySensor.cs │ │ │ │ ├── UpdateLogicTimeSensor.cs │ │ │ │ ├── UpdateRailGunCapacity.cs │ │ │ │ └── UpdateTemperatureSwitch.cs │ │ │ ├── Skill │ │ │ │ ├── MasterSkill.cs │ │ │ │ └── SetHat.cs │ │ │ └── UserMenu │ │ │ │ └── ClickUserMenuButton.cs │ │ ├── Speed │ │ │ ├── ChangeGameSpeed.cs │ │ │ ├── PauseGame.cs │ │ │ └── ResumeGame.cs │ │ └── Tools │ │ │ ├── AbstractBuildUtilityCommand.cs │ │ │ ├── AbstractDragToolCommand.cs │ │ │ ├── Attack.cs │ │ │ ├── Build.cs │ │ │ ├── BuildUtility.cs │ │ │ ├── BuildWire.cs │ │ │ ├── Cancel.cs │ │ │ ├── CopySettings.cs │ │ │ ├── Deconstruct.cs │ │ │ ├── Dig.cs │ │ │ ├── Disconnect.cs │ │ │ ├── Disinfect.cs │ │ │ ├── EmptyPipe.cs │ │ │ ├── Harvest.cs │ │ │ ├── Modify.cs │ │ │ ├── Mop.cs │ │ │ ├── MoveToLocation.cs │ │ │ ├── Prioritize.cs │ │ │ ├── Stamp.cs │ │ │ ├── Sweep.cs │ │ │ └── Wrangle.cs │ ├── Components │ │ ├── AssignedMultiplayerPlayer.cs │ │ ├── CursorComponent.cs │ │ ├── CursorManager.cs │ │ ├── DestroyOnPlayerLeave.cs │ │ └── MultiplayerPlayerNotifier.cs │ ├── CoreOperations │ │ ├── Binders │ │ │ ├── GameEventsBinder.cs │ │ │ └── HostEventsBinder.cs │ │ ├── CommandExecution │ │ │ ├── CommandConfigurationException.cs │ │ │ ├── CommandExceptionHandler.cs │ │ │ └── MultiplayerCommandExecutor.cs │ │ ├── Events │ │ │ ├── ConnectionLostEvent.cs │ │ │ ├── GameQuitEvent.cs │ │ │ ├── GameReadyEvent.cs │ │ │ ├── GameStartedEvent.cs │ │ │ ├── MultiplayerModeSelectedEvent.cs │ │ │ ├── SinglePlayerModeSelectedEvent.cs │ │ │ ├── StopMultiplayerEvent.cs │ │ │ ├── WorldLoadingEvent.cs │ │ │ ├── WorldSavedEvent.cs │ │ │ └── WorldStateInitializingEvent.cs │ │ ├── ExecutionLevelController.cs │ │ ├── GameStateEventsRelay.cs │ │ ├── MultiplayerCommandController.cs │ │ ├── MultiplayerGameObjectsSpawner.cs │ │ ├── MultiplayerJoinRequestController.cs │ │ ├── MultiplayerServerController.cs │ │ ├── PlayersManagement │ │ │ ├── ClientInitializationRequestEvent.cs │ │ │ ├── Commands │ │ │ │ ├── AddPlayerCommand.cs │ │ │ │ ├── ChangePlayerStateCommand.cs │ │ │ │ ├── InitializeClientCommand.cs │ │ │ │ ├── RemovePlayerCommand.cs │ │ │ │ ├── RequestPlayerStateChangeCommand.cs │ │ │ │ ├── RequestWorldSyncCommand.cs │ │ │ │ └── SyncPlayersCommand.cs │ │ │ ├── CurrentPlayerInitializedEvent.cs │ │ │ ├── PlayersManagementController.cs │ │ │ ├── PlayersManagementException.cs │ │ │ └── WorldSyncRequestedEvent.cs │ │ └── RequireMultiplayerModeAttribute.cs │ ├── IMultiplayerOperations.cs │ ├── MultiplayerGame.cs │ ├── MultiplayerMode.cs │ ├── Objects │ │ ├── Extensions │ │ │ ├── ChoreExtensions.cs │ │ │ ├── ComponentReferenceExtensions.cs │ │ │ ├── GameObjectExtensions.cs │ │ │ └── StateMachineReferenceExtensions.cs │ │ ├── InternalMultiplayerIdType.cs │ │ ├── MultiplayerId.cs │ │ ├── MultiplayerIdType.cs │ │ ├── MultiplayerInstance.cs │ │ ├── MultiplayerObject.cs │ │ ├── MultiplayerObjects.cs │ │ ├── MultiplayerObjectsDebugHelper.cs │ │ ├── MultiplayerObjectsIndex.cs │ │ ├── Reference │ │ │ ├── ChoreReference.cs │ │ │ ├── ComponentReference.cs │ │ │ ├── GameObjectReference.cs │ │ │ ├── GridReference.cs │ │ │ ├── MultiplayerIdReference.cs │ │ │ ├── ObjectNotFoundException.cs │ │ │ ├── Reference.cs │ │ │ └── StateMachineReference.cs │ │ ├── SaveGameObjectsInitializer.cs │ │ └── Support │ │ │ └── MinionStorageRedirectionSupport.cs │ ├── Patches │ │ ├── KModalButtonMenuPatch.cs │ │ └── MainMenuPatch.cs │ ├── Players │ │ ├── Events │ │ │ ├── MultiplayerJoinRequestedEvent.cs │ │ │ ├── PlayerCursorPositionUpdatedEvent.cs │ │ │ ├── PlayerJoinedEvent.cs │ │ │ ├── PlayerLeftEvent.cs │ │ │ ├── PlayerStateChangedEvent.cs │ │ │ ├── PlayersReadyEvent.cs │ │ │ └── PlayersUpdatedEvent.cs │ │ ├── IPlayerProfileProvider.cs │ │ ├── MultiplayerPlayer.cs │ │ ├── MultiplayerPlayers.cs │ │ ├── PlayerIdentity.cs │ │ ├── PlayerProfile.cs │ │ ├── PlayerRole.cs │ │ └── PlayerState.cs │ ├── StateMachines │ │ ├── Commands │ │ │ ├── GoToState.cs │ │ │ ├── MoveObjectToCell.cs │ │ │ └── SetParameterValue.cs │ │ ├── Configuration │ │ │ ├── Configurers │ │ │ │ ├── InvalidStateExpressionException.cs │ │ │ │ ├── StateMachineBaseConfigurer.cs │ │ │ │ ├── StateMachineConfigurerDelegate.cs │ │ │ │ ├── StateMachinePostConfigurer.cs │ │ │ │ ├── StateMachinePreConfigurer.cs │ │ │ │ └── StateMachineRootConfigurer.cs │ │ │ ├── InvalidConfigurationPhaseException.cs │ │ │ ├── InvalidStateExpressionException.cs │ │ │ ├── Parameters │ │ │ │ ├── StateMachineMultiplayerParameter.cs │ │ │ │ └── StateMachineMultiplayerParameterInfo.cs │ │ │ ├── StateMachineBoundedConfigurer.cs │ │ │ ├── StateMachineConfiguration.cs │ │ │ ├── StateMachineConfigurationAction.cs │ │ │ ├── StateMachineConfigurationContext.cs │ │ │ ├── StateMachineConfigurationException.cs │ │ │ ├── StateMachineConfigurationPhase.cs │ │ │ ├── StateMachineConfigurer.cs │ │ │ ├── StateMachineConfigurerDsl.cs │ │ │ └── States │ │ │ │ └── StateMachineMultiplayerStateInfo.cs │ │ ├── RuntimeTools │ │ │ ├── StateMachineContextRuntimeTools.cs │ │ │ ├── StateMachineRuntimeParameter.cs │ │ │ ├── StateMachineRuntimeTools.cs │ │ │ └── StateMachineStateNotFoundException.cs │ │ └── StateMachinesPatcher.cs │ ├── States │ │ ├── ContinuationState.cs │ │ ├── IWaitHostState.cs │ │ ├── StatesManager.cs │ │ └── WaitHostState.cs │ ├── Tools │ │ └── CommandRateThrottle.cs │ ├── UI │ │ ├── Dev │ │ │ ├── DevToolMultiplayerObjects.cs │ │ │ └── DevToolsConfigurer.cs │ │ ├── Diagnostics │ │ │ ├── ColonyDiagnosticScreenPatch.cs │ │ │ ├── ColonyDiagnosticUtilityPatch.cs │ │ │ └── MultiplayerColonyDiagnostic.cs │ │ ├── Notifications.cs │ │ ├── Overlays │ │ │ └── MultiplayerStatusOverlay.cs │ │ └── WorldTrackers │ │ │ ├── MultiplayerErrorsTracker.cs │ │ │ └── TrackerToolPatch.cs │ └── World │ │ ├── Commands │ │ ├── LoadWorld.cs │ │ └── NotifyWorldSavePreparing.cs │ │ ├── Data │ │ ├── WorldSave.cs │ │ └── WorldState.cs │ │ ├── Debug │ │ ├── DebugSnapshotAvailableEvent.cs │ │ ├── WorldDebugSnapshot.cs │ │ ├── WorldDebugSnapshotComparator.cs │ │ └── WorldDebugSnapshotRunner.cs │ │ ├── IWorldStateManager.cs │ │ ├── WorldManager.cs │ │ └── WorldSyncEvent.cs │ ├── MultiplayerMod.csproj │ ├── Network │ ├── IMultiplayerClient.cs │ ├── IMultiplayerClientId.cs │ ├── IMultiplayerEndpoint.cs │ ├── IMultiplayerServer.cs │ ├── MultiplayerClientState.cs │ ├── MultiplayerCommandOptions.cs │ └── MultiplayerServerState.cs │ ├── Platform │ ├── PlatformException.cs │ └── Steam │ │ ├── Network │ │ ├── Components │ │ │ ├── LobbyJoinRequestComponent.cs │ │ │ ├── SteamClientComponent.cs │ │ │ └── SteamServerComponent.cs │ │ ├── Configuration.cs │ │ ├── Extensions.cs │ │ ├── Messaging │ │ │ ├── INetworkMessage.cs │ │ │ ├── INetworkMessageHandle.cs │ │ │ ├── NetworkMessage.cs │ │ │ ├── NetworkMessageFactory.cs │ │ │ ├── NetworkMessageFragment.cs │ │ │ ├── NetworkMessageFragmentsHeader.cs │ │ │ ├── NetworkMessageHandle.cs │ │ │ ├── NetworkMessageProcessor.cs │ │ │ ├── NetworkSerializer.cs │ │ │ ├── SerializedNetworkMessage.cs │ │ │ └── Surrogates │ │ │ │ ├── AssignmentGroupSurrogate.cs │ │ │ │ ├── CarePackageInstanceDataSurrogate.cs │ │ │ │ ├── ChoreTypeSurrogate.cs │ │ │ │ ├── ComplexRecipeSurrogate.cs │ │ │ │ ├── DeathSurrogate.cs │ │ │ │ ├── EmoteSurrogate.cs │ │ │ │ ├── ISurrogateType.cs │ │ │ │ ├── KAnimFileSurrogate.cs │ │ │ │ ├── MinionStartingStatsSurrogate.cs │ │ │ │ ├── PathNodeSurrogate.cs │ │ │ │ ├── PrioritySettingSurrogate.cs │ │ │ │ ├── RoomSurrogate.cs │ │ │ │ ├── ScheduleBlockTypeSurrogate.cs │ │ │ │ ├── SerializationSurrogates.cs │ │ │ │ ├── SpaceDestinationSurrogate.cs │ │ │ │ ├── SpiceGrinderSurrogate.cs │ │ │ │ ├── TagSurrogate.cs │ │ │ │ ├── Vector2SerializationSurrogate.cs │ │ │ │ ├── Vector2fSerializationSurrogate.cs │ │ │ │ └── Vector3SerializationSurrogate.cs │ │ ├── NetworkPlatformException.cs │ │ ├── SteamClient.cs │ │ ├── SteamLobby.cs │ │ ├── SteamMultiplayerClientId.cs │ │ ├── SteamServer.cs │ │ └── SteamServerEndpoint.cs │ │ ├── SteamMultiplayerOperations.cs │ │ ├── SteamPlatformConfigurer.cs │ │ └── SteamPlayerProfileProvider.cs │ └── mod.yaml └── steamworkshop ├── STEAMWORSHOP_DESCRIPTION.md ├── loading.png ├── menu.png └── preview.png /.gitignore: -------------------------------------------------------------------------------- 1 | *.user 2 | /.idea 3 | /packages 4 | src/Assembly-CSharp-firstpass 5 | src/Assembly-CSharp 6 | src/*/bin/ 7 | src/*/obj/ 8 | /lib 9 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | net48 6 | 12 7 | true 8 | 9 | 10 | 11 | 12 | C:\Program Files (x86)\Steam 13 | 14 | 15 | 16 | $(MSBuildThisFileDirectory)\lib\exposed 17 | $(SteamLibraryPath)\steamapps\common\OxygenNotIncluded\OxygenNotIncluded_Data\Managed 18 | $(UserProfile)\Documents\Klei\OxygenNotIncluded\mods\dev\$(MSBuildProjectName) 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Zuev Vladimir, Denis Pakhorukov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /scripts/install.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal enabledelayedexpansion 3 | 4 | for /F "tokens=2* skip=2" %%a in ('reg query "HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders" /v "personal"') do set DocumentsFolder=%%b 5 | 6 | call set "DocumentsFolder=!DocumentsFolder!" 7 | set InstallationPath=%DocumentsFolder%\Klei\OxygenNotIncluded\mods\Local\MultiplayerMod\ 8 | 9 | echo "Installing multiplayer mod in folder: %InstallationPath%" 10 | xcopy /s .\MultiplayerMod "%InstallationPath%" 11 | pause 12 | -------------------------------------------------------------------------------- /scripts/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd "$(dirname "$0")" 3 | 4 | if [[ "$OSTYPE" == "darwin"* ]]; then 5 | # macOS 6 | MOD_PATH=~/Library/Application\ Support/unity.Klei.Oxygen\ Not\ Included/mods/Local/MultiplayerMod 7 | else 8 | # Linux 9 | MOD_PATH=~/.config/unity3d/Klei/Oxygen\ Not\ Included/mods/Local/MultiplayerMod 10 | fi 11 | 12 | mkdir -p "$MOD_PATH" 13 | cp -r ./MultiplayerMod/. "$MOD_PATH" 14 | -------------------------------------------------------------------------------- /src/MultiplayerMod.Test/Compatibility.cs: -------------------------------------------------------------------------------- 1 | // Record type support for .NET < 5 2 | 3 | // ReSharper disable once CheckNamespace 4 | // ReSharper disable once ArrangeNamespaceBody 5 | 6 | namespace System.Runtime.CompilerServices; 7 | 8 | // ReSharper disable once UnusedType.Global 9 | internal static class IsExternalInit { } 10 | -------------------------------------------------------------------------------- /src/MultiplayerMod.Test/Core/Collections/LinkedHashSetTests.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Core.Collections; 2 | using NUnit.Framework; 3 | 4 | namespace MultiplayerMod.Test.Core.Collections; 5 | 6 | [TestFixture] 7 | [Parallelizable] 8 | public class LinkedHashSetTests { 9 | 10 | [Test] 11 | public void ItemRemovedCorrectly() { 12 | var set = new LinkedHashSet { "First", "Second" }; 13 | Assert.IsTrue(set.Remove("Second")); 14 | Assert.IsFalse(set.Remove("Second")); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/MultiplayerMod.Test/Core/Paths/SecurePathTests.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using MultiplayerMod.Core.Paths; 3 | using NUnit.Framework; 4 | 5 | namespace MultiplayerMod.Test.Core.Paths; 6 | 7 | [TestFixture] 8 | public class SecurePathTests { 9 | 10 | [Test] 11 | public void RootIsAccessible() { 12 | Assert.DoesNotThrow(() => SecurePath.Combine("root")); 13 | } 14 | 15 | [Test] 16 | public void PathsUnderRootAreAccessible() { 17 | Assert.DoesNotThrow(() => SecurePath.Combine("root", "foo.txt")); 18 | Assert.DoesNotThrow(() => SecurePath.Combine("root", ".", "foo.txt")); 19 | Assert.DoesNotThrow(() => SecurePath.Combine("root", "dir", "foo.txt")); 20 | } 21 | 22 | [Test] 23 | public void AccessDeniedForPathsOutsideRoot() { 24 | Assert.Throws(() => SecurePath.Combine("root", "..", "foo.txt")); 25 | } 26 | 27 | [Test] 28 | public void PathsAreSeparated() { 29 | Assert.IsTrue(SecurePath.Combine("root", "a").EndsWith($"{Path.DirectorySeparatorChar}a")); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/MultiplayerMod.Test/Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/MultiplayerMod.Test/Environment/Network/CommandTools.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Runtime.Serialization.Formatters.Binary; 3 | using MultiplayerMod.Multiplayer.Commands; 4 | using MultiplayerMod.Platform.Steam.Network.Messaging.Surrogates; 5 | 6 | namespace MultiplayerMod.Test.Environment.Network; 7 | 8 | public static class CommandTools { 9 | 10 | public static IMultiplayerCommand Copy(IMultiplayerCommand command) { 11 | using var memory = new MemoryStream(); 12 | var formatter = new BinaryFormatter { SurrogateSelector = SerializationSurrogates.Selector }; 13 | formatter.Serialize(memory, command); 14 | memory.Position = 0; 15 | return (IMultiplayerCommand) formatter.Deserialize(memory); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/MultiplayerMod.Test/Environment/Network/TestMultiplayerClientId.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Network; 3 | 4 | namespace MultiplayerMod.Test.Environment.Network; 5 | 6 | public class TestMultiplayerClientId : IMultiplayerClientId { 7 | 8 | public TestMultiplayerClient Client { get; set; } = null!; 9 | 10 | private readonly int id; 11 | 12 | public TestMultiplayerClientId(int id) { 13 | this.id = id; 14 | } 15 | 16 | bool IEquatable.Equals(IMultiplayerClientId other) { 17 | return other is TestMultiplayerClientId player && player.Equals(this); 18 | } 19 | 20 | protected bool Equals(TestMultiplayerClientId other) => id == other.id; 21 | 22 | public override bool Equals(object? obj) { 23 | if (ReferenceEquals(null, obj)) 24 | return false; 25 | if (ReferenceEquals(this, obj)) 26 | return true; 27 | 28 | return obj.GetType() == GetType() && Equals((TestMultiplayerClientId) obj); 29 | } 30 | 31 | public override int GetHashCode() => id; 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/MultiplayerMod.Test/Environment/Network/TestMultiplayerEndpoint.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Network; 2 | 3 | namespace MultiplayerMod.Test.Environment.Network; 4 | 5 | public record TestMultiplayerEndpoint(TestMultiplayerServer Server) : IMultiplayerEndpoint; 6 | -------------------------------------------------------------------------------- /src/MultiplayerMod.Test/Environment/Patches/PatchesSetup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using HarmonyLib; 5 | using MultiplayerMod.Core.Extensions; 6 | 7 | namespace MultiplayerMod.Test.Environment.Patches; 8 | 9 | public static class PatchesSetup { 10 | 11 | public static void Install(Harmony harmony, IEnumerable patches) => 12 | patches.ForEach(it => harmony.CreateClassProcessor(it).Patch()); 13 | 14 | public static void Uninstall(Harmony harmony) { 15 | foreach (var methodBase in harmony.GetPatchedMethods()) { 16 | if (!methodBase.HasMethodBody()) 17 | continue; 18 | 19 | var patches = Harmony.GetPatchInfo(methodBase); 20 | patches.Prefixes 21 | .Union(patches.Postfixes) 22 | .Union(patches.Transpilers) 23 | .Union(patches.Finalizers) 24 | .Where(it => it.owner == harmony.Id) 25 | .ForEach(it => harmony.Unpatch(methodBase, it.PatchMethod)); 26 | } 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/MultiplayerMod.Test/Environment/TestRuntime.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using MultiplayerMod.Core.Dependency; 4 | using MultiplayerMod.ModRuntime; 5 | 6 | namespace MultiplayerMod.Test.Environment; 7 | 8 | public class TestRuntime : Runtime { 9 | 10 | public event Action? Deactivated; 11 | public event Action? Activated; 12 | 13 | public TestRuntime(DependencyContainer container) : base(container) { } 14 | 15 | public void Activate() { 16 | var oldRuntime = Instance; 17 | var property = typeof(Runtime).GetProperty("Instance", BindingFlags.Static | BindingFlags.Public)!; 18 | property.SetValue(null, this); 19 | Deactivated?.Invoke(oldRuntime); 20 | Activated?.Invoke(this); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/MultiplayerMod.Test/Environment/Unity/Patches/LoadingOverlayPatch.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | using JetBrains.Annotations; 3 | 4 | namespace MultiplayerMod.Test.Environment.Unity.Patches; 5 | 6 | [UsedImplicitly] 7 | [HarmonyPatch(typeof(LoadingOverlay))] 8 | public static class LoadingOverlayPatch { 9 | 10 | [UsedImplicitly] 11 | [HarmonyPrefix] 12 | [HarmonyPatch(nameof(LoadingOverlay.Load))] 13 | private static bool Load(System.Action cb) { 14 | cb(); 15 | return false; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/MultiplayerMod.Test/Environment/Unity/Patches/SpeedControlScreenPatch.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Reflection.Emit; 3 | using HarmonyLib; 4 | using JetBrains.Annotations; 5 | using MultiplayerMod.Core.Patch; 6 | 7 | namespace MultiplayerMod.Test.Environment.Unity.Patches; 8 | 9 | [UsedImplicitly] 10 | [HarmonyPatch(typeof(SpeedControlScreen))] 11 | public static class SpeedControlScreenPatch { 12 | 13 | [UsedImplicitly] 14 | [HarmonyTranspiler] 15 | [HarmonyPatch(nameof(SpeedControlScreen.Pause))] 16 | private static IEnumerable Pause(IEnumerable instructions) { 17 | using var source = instructions.GetEnumerator(); 18 | var result = new List(); 19 | result.AddConditional( 20 | source, 21 | it => it.StoresField(AccessTools.Field(typeof(SpeedControlScreen), "pauseCount")) 22 | ); 23 | result.Add(new CodeInstruction(OpCodes.Ret)); 24 | return result; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/MultiplayerMod.Test/Environment/Unity/Patches/Unity/BehaviourPatch.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Reflection.Emit; 3 | using HarmonyLib; 4 | using JetBrains.Annotations; 5 | using UnityEngine; 6 | 7 | namespace MultiplayerMod.Test.Environment.Unity.Patches.Unity; 8 | 9 | [UsedImplicitly] 10 | [HarmonyPatch(typeof(Behaviour))] 11 | public class BehaviourPatch { 12 | 13 | [UsedImplicitly] 14 | [HarmonyTranspiler] 15 | [HarmonyPatch("get_isActiveAndEnabled")] 16 | private static IEnumerable Behaviour_get_isActiveAndEnabled(IEnumerable instructions) { 17 | return new List { 18 | new(OpCodes.Ldc_I4_1), // true 19 | new(OpCodes.Ret) 20 | }; 21 | } 22 | 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/MultiplayerMod.Test/Environment/Unity/Patches/Unity/LayerMaskPatch.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Reflection.Emit; 3 | using HarmonyLib; 4 | using JetBrains.Annotations; 5 | using UnityEngine; 6 | 7 | namespace MultiplayerMod.Test.Environment.Unity.Patches.Unity; 8 | 9 | [UsedImplicitly] 10 | [HarmonyPatch(typeof(LayerMask))] 11 | public class LayerMaskPatch { 12 | 13 | [UsedImplicitly] 14 | [HarmonyTranspiler] 15 | [HarmonyPatch("NameToLayer")] 16 | private static IEnumerable LayerMask_NameToLayer(IEnumerable instructions) { 17 | return new List { 18 | new(OpCodes.Ldc_I4_0), 19 | new(OpCodes.Ret) 20 | }; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/MultiplayerMod.Test/Environment/Unity/Patches/Unity/MeshPatch.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Reflection.Emit; 3 | using HarmonyLib; 4 | using JetBrains.Annotations; 5 | using UnityEngine; 6 | 7 | namespace MultiplayerMod.Test.Environment.Unity.Patches.Unity; 8 | 9 | [UsedImplicitly] 10 | [HarmonyPatch(typeof(Mesh))] 11 | public class MeshPatch { 12 | 13 | [UsedImplicitly] 14 | [HarmonyTranspiler] 15 | [HarmonyPatch("Internal_Create")] 16 | private static IEnumerable Mesh_Internal_Create(IEnumerable instructions) { 17 | return new List { 18 | new(OpCodes.Ret) 19 | }; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/MultiplayerMod.Test/Environment/Unity/Patches/Unity/RendererPatch.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Reflection.Emit; 3 | using HarmonyLib; 4 | using JetBrains.Annotations; 5 | using UnityEngine; 6 | 7 | namespace MultiplayerMod.Test.Environment.Unity.Patches.Unity; 8 | 9 | [UsedImplicitly] 10 | [HarmonyPatch(typeof(Renderer))] 11 | public class RendererPatch { 12 | 13 | [UsedImplicitly] 14 | [HarmonyTranspiler] 15 | [HarmonyPatch("get_material")] 16 | private static IEnumerable Renderer_get_material(IEnumerable instructions) { 17 | return new List { 18 | new(OpCodes.Ldarg_0), // this 19 | CodeInstruction.Call(typeof(RendererPatch), nameof(CreateMaterial)), 20 | new(OpCodes.Ret) 21 | }; 22 | } 23 | 24 | public static Material CreateMaterial(Renderer _) { 25 | #pragma warning disable CS0618 // Type or member is obsolete 26 | return new Material(""); 27 | #pragma warning restore CS0618 // Type or member is obsolete 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/MultiplayerMod.Test/Environment/Unity/Patches/Unity/ShaderPatch.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Reflection.Emit; 3 | using HarmonyLib; 4 | using JetBrains.Annotations; 5 | using UnityEngine; 6 | 7 | namespace MultiplayerMod.Test.Environment.Unity.Patches.Unity; 8 | 9 | [UsedImplicitly] 10 | [HarmonyPatch(typeof(Shader))] 11 | public class ShaderPatch { 12 | 13 | [UsedImplicitly] 14 | [HarmonyTranspiler] 15 | [HarmonyPatch(nameof(Shader.PropertyToID))] 16 | private static IEnumerable SystemInfo_get_processorCount( 17 | IEnumerable instructions 18 | ) { 19 | return new List { 20 | new(OpCodes.Ldc_I4_0), // 0 21 | new(OpCodes.Ret) 22 | }; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/MultiplayerMod.Test/Environment/Unity/Patches/Unity/SystemInfoPatch.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Reflection.Emit; 3 | using HarmonyLib; 4 | using JetBrains.Annotations; 5 | using UnityEngine; 6 | 7 | namespace MultiplayerMod.Test.Environment.Unity.Patches.Unity; 8 | 9 | [UsedImplicitly] 10 | [HarmonyPatch(typeof(SystemInfo))] 11 | public class SystemInfoPatch { 12 | 13 | [UsedImplicitly] 14 | [HarmonyTranspiler] 15 | [HarmonyPatch("get_processorCount")] 16 | private static IEnumerable SystemInfo_get_processorCount( 17 | IEnumerable instructions 18 | ) { 19 | return new List { 20 | new(OpCodes.Ldc_I4_1), // 1 21 | new(OpCodes.Ret) 22 | }; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/MultiplayerMod.Test/Environment/Unity/UnityEvent.cs: -------------------------------------------------------------------------------- 1 | namespace MultiplayerMod.Test.Environment.Unity; 2 | 3 | public class UnityEvent { 4 | 5 | public static UnityEvent Awake = new("Awake"); 6 | public static UnityEvent Update = new("Update"); 7 | public static UnityEvent LateUpdate = new("LateUpdate"); 8 | public static UnityEvent Enable = new("OnEnable"); 9 | public static UnityEvent Start = new("Start"); 10 | 11 | public string MethodName { get; } 12 | 13 | public UnityEvent(string methodName) { 14 | MethodName = methodName; 15 | } 16 | 17 | public override int GetHashCode() => MethodName.GetHashCode(); 18 | public override bool Equals(object? other) => other is UnityEvent @event && @event.MethodName.Equals(MethodName); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/MultiplayerMod.Test/Environment/Unity/UnityPlayerObjectManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using MultiplayerMod.Core.Unity; 4 | using MultiplayerMod.Test.Environment.Unity.Patches.Unity; 5 | using Object = UnityEngine.Object; 6 | 7 | namespace MultiplayerMod.Test.Environment.Unity; 8 | 9 | public static class UnityPlayerObjectManager { 10 | 11 | private static int currentInstanceId; 12 | 13 | public static void Allocate(Object obj) { 14 | var pointer = UnityPlayerObject.CreateEmpty(); 15 | obj.m_CachedPtr = pointer; 16 | // Since KObjectManager.GetOrCreateObject uses GameObject.GetInstanceID() 17 | // we have to provide different values for different objects. 18 | unsafe { 19 | var instanceId = (int*)IntPtr.Add(pointer, ObjectPatch.OffsetOfInstanceIDInCPlusPlusObject).ToPointer(); 20 | *instanceId = currentInstanceId; 21 | } 22 | Interlocked.Increment(ref currentInstanceId); 23 | } 24 | 25 | public static void Release(Object obj) { 26 | UnityPlayerObject.Free(obj.m_CachedPtr); 27 | obj.m_CachedPtr = IntPtr.Zero; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/MultiplayerMod.Test/GameRuntime/Patches/ElementLoaderPatch.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | using JetBrains.Annotations; 3 | 4 | namespace MultiplayerMod.Test.GameRuntime.Patches; 5 | 6 | [UsedImplicitly] 7 | [HarmonyPatch(typeof(ElementLoader))] 8 | public class ElementLoaderPatch { 9 | 10 | [UsedImplicitly] 11 | [HarmonyPrefix] 12 | [HarmonyPatch(nameof(ElementLoader.FindElementByHash))] 13 | private static bool ElementLoader_FindElementByHash(ref Element __result) { 14 | __result = new Element() { name = "Patched element", substance = new Substance() { anim = new KAnimFile() {IsBuildLoaded = true} } }; 15 | return false; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/MultiplayerMod.Test/Multiplayer/Chores/ChoresInstantiationTests.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Multiplayer.Chores.Events; 2 | using NUnit.Framework; 3 | 4 | namespace MultiplayerMod.Test.Multiplayer.Chores; 5 | 6 | [TestFixture] 7 | public class ChoresInstantiationTests : ChoreTest { 8 | 9 | [Test, TestCaseSource(nameof(ChoresInstantiationTestCases))] 10 | public void ChoreCreatedEventMustBeFired(ChoreFactory factory) { 11 | ChoreCreatedEvent firedEvent = null!; 12 | Events.Subscribe((@event, subscription) => { 13 | firedEvent = @event; 14 | subscription.Cancel(); 15 | }); 16 | factory.Create(); 17 | Assert.NotNull(firedEvent); 18 | Assert.That(firedEvent, Is.Not.Null); 19 | Assert.That(firedEvent.Type, Is.EqualTo(factory.Type)); 20 | Assert.That(firedEvent.Arguments, Is.EqualTo(factory.GetArguments())); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/MultiplayerMod.Test/Multiplayer/Chores/ChoresMultiplayerConfigurationTest.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Reflection; 3 | using HarmonyLib; 4 | using MultiplayerMod.Multiplayer.Chores; 5 | using NUnit.Framework; 6 | 7 | namespace MultiplayerMod.Test.Multiplayer.Chores; 8 | 9 | [TestFixture] 10 | public class ChoresMultiplayerConfigurationTest { 11 | 12 | [Test] 13 | public void GeneralChoresMustBeSynchronized() { 14 | var choreTypes = AccessTools.GetTypesFromAssembly(Assembly.GetAssembly(typeof(Chore))) 15 | .Where(it => typeof(Chore).IsAssignableFrom(it)) 16 | .Where(it => it != typeof(Chore)) 17 | .Where(it => !it.IsGenericType); 18 | 19 | var configuredTypes = ChoresMultiplayerConfiguration.Configuration.Select(it => it.ChoreType); 20 | 21 | Assert.That(configuredTypes, Is.EquivalentTo(choreTypes)); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/MultiplayerMod.Test/Multiplayer/MultiplayerStatusOverlayPatch.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | using JetBrains.Annotations; 3 | using MultiplayerMod.Multiplayer.UI.Overlays; 4 | 5 | namespace MultiplayerMod.Test.Multiplayer; 6 | 7 | [UsedImplicitly] 8 | [HarmonyPatch(typeof(MultiplayerStatusOverlay))] 9 | public class MultiplayerStatusOverlayPatch { 10 | 11 | [UsedImplicitly] 12 | [HarmonyPrefix] 13 | [HarmonyPatch(nameof(MultiplayerStatusOverlay.Show))] 14 | private static bool Show() { 15 | return false; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/MultiplayerMod.Test/Multiplayer/SpeedControlScreenContext.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using JetBrains.Annotations; 3 | using MultiplayerMod.Game.Context; 4 | 5 | namespace MultiplayerMod.Test.Multiplayer; 6 | 7 | [UsedImplicitly] 8 | public class SpeedControlScreenContext : IGameContext { 9 | 10 | private SpeedControlScreen? instance; 11 | 12 | private readonly PropertyInfo property = typeof(SpeedControlScreen).GetProperty( 13 | nameof(SpeedControlScreen.Instance), 14 | BindingFlags.Static | BindingFlags.Public 15 | )!; 16 | 17 | // ReSharper disable once Unity.IncorrectMonoBehaviourInstantiation 18 | public void Apply() { 19 | instance = SpeedControlScreen.Instance; 20 | property.SetValue(null, new SpeedControlScreen()); 21 | } 22 | 23 | public void Restore() { 24 | property.SetValue(null, instance); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/MultiplayerMod.Test/Multiplayer/StateMachines/RuntimeTools/StateMachineRuntimeToolsTests.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Multiplayer.StateMachines.RuntimeTools; 2 | using NUnit.Framework; 3 | 4 | namespace MultiplayerMod.Test.Multiplayer.StateMachines.RuntimeTools; 5 | 6 | [TestFixture] 7 | public class StateMachineRuntimeToolsTests : StateMachinesTest { 8 | 9 | [Test] 10 | public void ControllerIsResolved() { 11 | var instance = CreateStateMachineInstance(); 12 | var runtime = StateMachineRuntimeTools.Get(instance); 13 | var controller = runtime.GetController(); 14 | Assert.That(controller, Is.Not.Null); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/MultiplayerMod.Test/Multiplayer/StateMachines/StateMachinesTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Test.GameRuntime; 3 | using NUnit.Framework; 4 | using UnityEngine; 5 | 6 | namespace MultiplayerMod.Test.Multiplayer.StateMachines; 7 | 8 | [TestFixture] 9 | public class StateMachinesTest : PlayableGameTest { 10 | 11 | [TearDown] 12 | private void StateMachinesTearDown() { 13 | StateMachineManager.Instance.stateMachines.Clear(); 14 | } 15 | 16 | protected static T CreateStateMachineInstance() where T : StateMachine.Instance { 17 | var go = new GameObject(); 18 | var target = go.AddComponent(); 19 | target.Awake(); 20 | return (T) Activator.CreateInstance(typeof(T), [target]); 21 | } 22 | 23 | public class TestTarget : KMonoBehaviour; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/MultiplayerMod.Test/Multiplayer/WorldManagerPatch.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | using JetBrains.Annotations; 3 | using MultiplayerMod.Multiplayer.World; 4 | 5 | namespace MultiplayerMod.Test.Multiplayer; 6 | 7 | [UsedImplicitly] 8 | [HarmonyPatch(typeof(WorldManager))] 9 | public class WorldManagerPatch { 10 | 11 | [UsedImplicitly] 12 | [HarmonyPrefix] 13 | [HarmonyPatch("GetWorldSave")] 14 | // ReSharper disable once RedundantAssignment 15 | private static bool GetWorldSave(ref byte[] __result) { 16 | __result = new byte[] { 0xFF }; 17 | return false; 18 | } 19 | 20 | [UsedImplicitly] 21 | [HarmonyPrefix] 22 | [HarmonyPatch("LoadWorldSave")] 23 | // ReSharper disable once RedundantAssignment 24 | private static bool LoadWorldSave() { 25 | return false; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Compatibility.cs: -------------------------------------------------------------------------------- 1 | // Record type support for .NET < 5 2 | 3 | // ReSharper disable once CheckNamespace 4 | // ReSharper disable once ArrangeNamespaceBody 5 | 6 | namespace System.Runtime.CompilerServices; 7 | 8 | // ReSharper disable once UnusedType.Global 9 | internal static class IsExternalInit { } 10 | 11 | // Caller argument expression support for .NET < 5 12 | [AttributeUsage(AttributeTargets.Parameter)] 13 | internal sealed class CallerArgumentExpressionAttribute : Attribute { 14 | 15 | public CallerArgumentExpressionAttribute(string parameterName) { 16 | ParameterName = parameterName; 17 | } 18 | 19 | public string ParameterName { get; } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Collections/BoxedValue.cs: -------------------------------------------------------------------------------- 1 | namespace MultiplayerMod.Core.Collections; 2 | 3 | public class BoxedValue(T value) { 4 | public T Value { get; set; } = value; 5 | } 6 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Collections/Deconstructable.cs: -------------------------------------------------------------------------------- 1 | namespace MultiplayerMod.Core.Collections; 2 | 3 | public class Deconstructable(T1 v1, T2 v2) { 4 | 5 | public void Deconstruct(out T1 a1, out T2 a2) { 6 | a1 = v1; 7 | a2 = v2; 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Collections/IdentityEqualityComparer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Runtime.CompilerServices; 3 | 4 | namespace MultiplayerMod.Core.Collections; 5 | 6 | public class IdentityEqualityComparer : IEqualityComparer { 7 | public bool Equals(T x, T y) => ReferenceEquals(x, y); 8 | public int GetHashCode(T obj) => RuntimeHelpers.GetHashCode(obj); 9 | } 10 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Collections/IndexedObjectExtensionMap.cs: -------------------------------------------------------------------------------- 1 | namespace MultiplayerMod.Core.Collections; 2 | 3 | public class IndexedObjectExtensionMap : BidirectionalMap { 4 | public IndexedObjectExtensionMap() : base(new IdentityEqualityComparer()) { } 5 | } 6 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Collections/SingletonCollection.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | 4 | namespace MultiplayerMod.Core.Collections; 5 | 6 | public class SingletonCollection : IReadOnlyCollection { 7 | private readonly T value; 8 | 9 | public int Count => 1; 10 | 11 | public SingletonCollection(T value) { 12 | this.value = value; 13 | } 14 | 15 | public IEnumerator GetEnumerator() { 16 | yield return value; 17 | } 18 | 19 | IEnumerator IEnumerable.GetEnumerator() { 20 | yield return value; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Dependency/AmbiguousDependencyException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace MultiplayerMod.Core.Dependency; 5 | 6 | public class AmbiguousDependencyException : Exception { 7 | 8 | public AmbiguousDependencyException(Type type, ICollection infos) : base( 9 | $"Ambiguous dependencies of type \"{type}\": found {infos.Count} instances" 10 | ) { } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Dependency/DependencyAlreadyRegisteredException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Core.Dependency; 4 | 5 | public class DependencyAlreadyRegisteredException : Exception { 6 | public DependencyAlreadyRegisteredException(string name) : base($"Dependency \"{name}\" is already registered") { } 7 | } 8 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Dependency/DependencyAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Core.Dependency; 4 | 5 | [AttributeUsage(AttributeTargets.Class)] 6 | public class DependencyAttribute : Attribute { 7 | public string? Name { get; set; } = null; 8 | public bool Lazy { get; set; } = false; 9 | } 10 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Dependency/DependencyInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Core.Dependency; 4 | 5 | public class DependencyInfo { 6 | 7 | public string Name { get; } 8 | public Type Type { get; } 9 | public bool Lazy { get; } 10 | 11 | public DependencyInfo(string name, Type type, bool lazy) { 12 | if (!type.IsClass) 13 | throw new InvalidDependencyException($"\"{type}\" must be a class."); 14 | 15 | Name = name; 16 | Type = type; 17 | Lazy = lazy; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Dependency/DependencyInstantiationException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Core.Dependency; 4 | 5 | public class DependencyInstantiationException : Exception { 6 | 7 | private readonly DependencyInfo info; 8 | 9 | public DependencyInstantiationException(DependencyInfo info, Exception cause) : base( 10 | message: $"Unable to instantiate a dependency \"{info.Name}\"", 11 | innerException: cause 12 | ) { 13 | this.info = info; 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Dependency/DependencyIsInstantiatingException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Core.Dependency; 4 | 5 | public class DependencyIsInstantiatingException : Exception { 6 | 7 | private readonly DependencyInfo info; 8 | 9 | public DependencyIsInstantiatingException(DependencyInfo info) : base( 10 | $"A dependency \"{info.Name}\" is currently instantiating (circular dependency?)" 11 | ) { 12 | this.info = info; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Dependency/IDependencyContainer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Core.Collections; 3 | 4 | namespace MultiplayerMod.Core.Dependency; 5 | 6 | public interface IDependencyContainer { 7 | T Get() where T : notnull; 8 | Deconstructable Get() where T1 : notnull where T2 : notnull; 9 | object Get(Type type); 10 | } 11 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Dependency/IDependencyInjector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Core.Dependency; 4 | 5 | public interface IDependencyInjector { 6 | public T Inject(T instance) where T : notnull; 7 | public void Inject(Type type); 8 | } 9 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Dependency/InjectDependencyAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Core.Dependency; 4 | 5 | [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] 6 | public class InjectDependencyAttribute : Attribute { } 7 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Dependency/InvalidDependencyException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Core.Dependency; 4 | 5 | public class InvalidDependencyException : Exception { 6 | public InvalidDependencyException(string message) : base(message) { } 7 | } 8 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Dependency/MissingDependencyException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Core.Dependency; 4 | 5 | public class MissingDependencyException : Exception { 6 | public MissingDependencyException(Type type) : base($"Dependency of type \"{type}\" not found") { } 7 | } 8 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Events/EventSubscription.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Core.Events; 4 | 5 | public interface IEventSubscription { 6 | void Cancel(); 7 | } 8 | 9 | public class EventSubscription : IEventSubscription { 10 | 11 | private readonly EventDispatcher dispatcher; 12 | private readonly Delegate action; 13 | private readonly Type type; 14 | 15 | public EventSubscription(EventDispatcher dispatcher, Delegate action, Type type) { 16 | this.dispatcher = dispatcher; 17 | this.action = action; 18 | this.type = type; 19 | } 20 | 21 | public void Cancel() => dispatcher.Unsubscribe(type, action); 22 | 23 | } 24 | 25 | 26 | public class EventSubscription : IEventSubscription where T : IDispatchableEvent { 27 | 28 | private readonly EventDispatcher dispatcher; 29 | private readonly Delegate action; 30 | 31 | public EventSubscription(EventDispatcher dispatcher, Delegate action) { 32 | this.dispatcher = dispatcher; 33 | this.action = action; 34 | } 35 | 36 | public void Cancel() => dispatcher.Unsubscribe(action); 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Events/EventSubscriptions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | 4 | namespace MultiplayerMod.Core.Events; 5 | 6 | public class EventSubscriptions : IEnumerable { 7 | 8 | private readonly List subscriptions = new(); 9 | 10 | public EventSubscriptions Add(IEventSubscription subscription) { 11 | subscriptions.Add(subscription); 12 | return this; 13 | } 14 | 15 | public void Cancel() => subscriptions.ForEach(it => it.Cancel()); 16 | 17 | public IEnumerator GetEnumerator() => subscriptions.GetEnumerator(); 18 | 19 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Events/IDispatchableEvent.cs: -------------------------------------------------------------------------------- 1 | namespace MultiplayerMod.Core.Events; 2 | 3 | public interface IDispatchableEvent; 4 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Extensions/ArraysExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace MultiplayerMod.Core.Extensions; 2 | 3 | public static class ArraysExtensions { 4 | 5 | private static readonly uint[] hexLookup = CreateHexLookup(); 6 | 7 | private static uint[] CreateHexLookup() { 8 | const int byteCardinality = byte.MaxValue + 1; 9 | var result = new uint[byteCardinality]; 10 | for (var i = 0; i < byteCardinality; i++) { 11 | var value = $"{i:x2}"; 12 | result[i] = value[0] + (uint) (value[1] << 16); 13 | } 14 | return result; 15 | } 16 | 17 | public static string ToHexString(this byte[] bytes) { 18 | var result = new char[bytes.Length * 2]; 19 | for (var i = 0; i < bytes.Length; i++) { 20 | var value = hexLookup[bytes[i]]; 21 | result[2 * i] = (char) value; 22 | result[2 * i + 1] = (char) (value >> 16); 23 | } 24 | return new string(result); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Extensions/DictionaryExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace MultiplayerMod.Core.Extensions; 5 | 6 | public static class DictionaryExtensions { 7 | 8 | public static TValue GetOrAdd(this IDictionary dictionary, TKey key) 9 | where TValue : new() 10 | { 11 | if (dictionary.TryGetValue(key, out var val)) 12 | return val; 13 | 14 | val = new TValue(); 15 | dictionary.Add(key, val); 16 | return val; 17 | } 18 | 19 | public static TValue GetOrAdd( 20 | this IDictionary dictionary, 21 | TKey key, 22 | Func defaultValue 23 | ) { 24 | if (dictionary.TryGetValue(key, out var val)) 25 | return val; 26 | 27 | val = defaultValue(); 28 | dictionary.Add(key, val); 29 | return val; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Extensions/HashAlgorithmExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Security.Cryptography; 4 | 5 | namespace MultiplayerMod.Core.Extensions; 6 | 7 | public static class HashAlgorithmExtensions { 8 | 9 | public static byte[] ComputeHash(this HashAlgorithm algorithm, MethodBase method) { 10 | var body = method.GetMethodBody() ?? throw new Exception( 11 | $"Unable to compute hash of " + 12 | $"{method.DeclaringType?.GetSignature(SignatureOptions.Namespace)}:{method.Name}, " + 13 | $"no method body available" 14 | ); 15 | return algorithm.ComputeHash(body.GetILAsByteArray()); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Extensions/LinkedListExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace MultiplayerMod.Core.Extensions; 5 | 6 | public static class LinkedListExtensions { 7 | 8 | // ReSharper disable Unity.PerformanceAnalysis 9 | public static void ForEach(this LinkedList list, Action> action) { 10 | var node = list.First; 11 | while (node != null) { 12 | var next = node.Next; 13 | action(node.Value, node); 14 | node = next; 15 | } 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Extensions/NumberExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Core.Extensions; 4 | 5 | public static class NumberExtensions { 6 | 7 | public static readonly string Digits = "0123456789abcdefghijklmnopqrstuvwxyz"; 8 | 9 | private static string LongToString(ulong value, int radix) { 10 | if (radix is < 2 or > 36) 11 | throw new ArgumentException("Radix must be from 2 to 36", nameof(radix)); 12 | var result = ""; 13 | do 14 | result = Digits[(int)(value % (byte)radix)] + result; 15 | while ((value /= (byte)radix) != 0); 16 | return result; 17 | } 18 | 19 | public static string ToString(this long value, int radix) => LongToString((ulong)value, radix); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Extensions/SetExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace MultiplayerMod.Core.Extensions; 4 | 5 | public static class SetExtensions { 6 | 7 | public static void AddRange(this ISet set, IEnumerable items) => items.ForEach(it => set.Add(it)); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Extensions/TypeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | 6 | namespace MultiplayerMod.Core.Extensions; 7 | 8 | public static class TypeExtensions { 9 | 10 | public static MethodInfo[] GetAllMethods(this Type type) => type.GetMethods( 11 | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic 12 | ); 13 | 14 | public static IEnumerable GetInheritedTypes(this Type type) { 15 | var current = type; 16 | while (current != typeof(object) && current != null) { 17 | yield return current; 18 | 19 | current = current.BaseType; 20 | } 21 | } 22 | 23 | public static bool Inherits(this Type self, Type type) => self.GetInheritedTypes().Any(it => it == type); 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Logging/LogLevel.cs: -------------------------------------------------------------------------------- 1 | namespace MultiplayerMod.Core.Logging; 2 | 3 | public enum LogLevel { 4 | Trace, 5 | Debug, 6 | Info, 7 | Warning, 8 | Error 9 | } 10 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Patch/Capture/CaptureUnavailableException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Core.Patch.Capture; 4 | 5 | public class CaptureUnavailableException : Exception { 6 | public CaptureUnavailableException(string message) : base(message) { } 7 | } 8 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Patch/Capture/ILocalCapture.cs: -------------------------------------------------------------------------------- 1 | namespace MultiplayerMod.Core.Patch.Capture; 2 | 3 | public interface ILocalCapture { } 4 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Patch/Capture/InvalidLocalVariableReferenceException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Core.Patch.Capture; 4 | 5 | public class InvalidLocalVariableReferenceException : Exception { 6 | public InvalidLocalVariableReferenceException(string message) : base(message) { } 7 | } 8 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Patch/Capture/LocalCaptor.cs: -------------------------------------------------------------------------------- 1 | namespace MultiplayerMod.Core.Patch.Capture; 2 | 3 | public class LocalCaptor { 4 | 5 | private readonly LocalCaptureContainer container = new(); 6 | 7 | private LocalCaptor(System.Action action) { 8 | LocalVariableCaptureSupport.ContainerReference.Value = container; 9 | action(); 10 | LocalVariableCaptureSupport.ContainerReference.Value = null; 11 | } 12 | 13 | public static T Capture(System.Action action) where T : ILocalCapture { 14 | var captures = new LocalCaptor(action).container.Captures; 15 | if (captures.Count == 0 || captures[0].GetType() != typeof(T)) 16 | throw new CaptureUnavailableException($"Capture of type {typeof(T).FullName} is unavailable"); 17 | return (T) captures[0]; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Patch/Capture/LocalCaptureContainer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace MultiplayerMod.Core.Patch.Capture; 4 | 5 | public class LocalCaptureContainer { 6 | 7 | // ReSharper disable once CollectionNeverUpdated.Global 8 | public List Captures { get; } = new(); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Patch/Capture/LocalVariableAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Core.Patch.Capture; 4 | 5 | [AttributeUsage(AttributeTargets.Property)] 6 | public class LocalVariableAttribute : Attribute { 7 | 8 | public int Index { get; } 9 | 10 | public LocalVariableAttribute(int index) { 11 | Index = index; 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Patch/Capture/LocalVariableCaptureAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Core.Patch.Capture; 4 | 5 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] 6 | public class LocalVariableCaptureAttribute : Attribute { 7 | 8 | public Type DeclaringType { get; } 9 | public string MethodName { get; } 10 | public Type[]? ArgumentTypes { get; } 11 | 12 | public LocalVariableCaptureAttribute(Type declaringType, string methodName) { 13 | DeclaringType = declaringType; 14 | MethodName = methodName; 15 | } 16 | 17 | public LocalVariableCaptureAttribute(Type declaringType, string methodName, Type[] argumentTypes) { 18 | DeclaringType = declaringType; 19 | MethodName = methodName; 20 | ArgumentTypes = argumentTypes; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Patch/CodeInstructionsOffsetAccessor.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using HarmonyLib; 3 | 4 | namespace MultiplayerMod.Core.Patch; 5 | 6 | public class CodeInstructionsOffsetAccessor { 7 | 8 | private readonly List source; 9 | private readonly Dictionary offsetIndex = new(); 10 | 11 | public CodeInstructionsOffsetAccessor(List source) { 12 | this.source = source; 13 | var offset = 0; 14 | var index = 0; 15 | foreach (var instruction in source) { 16 | offsetIndex[offset] = index++; 17 | offset += instruction.GetSize(); 18 | } 19 | } 20 | 21 | public CodeInstruction GetInstruction(int offset) { 22 | return source[offsetIndex[offset]]; 23 | } 24 | 25 | public List GetInstructions(int from, int to) { 26 | var index = offsetIndex[from]; 27 | var count = offsetIndex[to] - index + 1; 28 | return source.GetRange(index, count); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Patch/ControlFlow/ControlFlowContext.cs: -------------------------------------------------------------------------------- 1 | namespace MultiplayerMod.Core.Patch.ControlFlow; 2 | 3 | public class ControlFlowContext(int adviceStackDepth) { 4 | public readonly int AdviceStackDepth = adviceStackDepth; 5 | } 6 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Patch/ControlFlow/Evaluators/CompositeDetour.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using static MultiplayerMod.Core.Patch.ControlFlow.ExecutionFlow; 3 | 4 | namespace MultiplayerMod.Core.Patch.ControlFlow.Evaluators; 5 | 6 | public class CompositeDetour(params IDetourEvaluator[] evaluators) : IDetourEvaluator { 7 | 8 | public ExecutionFlow Evaluate(ControlFlowContext context) { 9 | var newContext = new ControlFlowContext(context.AdviceStackDepth + 3); 10 | return evaluators.Any(evaluator => evaluator.Evaluate(newContext) == Continue) ? Continue : Break; 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Patch/ControlFlow/Evaluators/MethodBoundedDetour.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Reflection; 3 | using HarmonyLib; 4 | using static MultiplayerMod.Core.Patch.ControlFlow.ExecutionFlow; 5 | 6 | namespace MultiplayerMod.Core.Patch.ControlFlow.Evaluators; 7 | 8 | public class MethodBoundedDetour(MethodBase method) : IDetourEvaluator { 9 | 10 | public ExecutionFlow Evaluate(ControlFlowContext context) { 11 | var stack = new StackTrace(); 12 | var caller = Harmony.GetOriginalMethodFromStackframe(stack.GetFrame(context.AdviceStackDepth + 2)); 13 | return caller == method ? Break : Continue; 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Patch/ControlFlow/ExecutionFlow.cs: -------------------------------------------------------------------------------- 1 | namespace MultiplayerMod.Core.Patch.ControlFlow; 2 | 3 | public enum ExecutionFlow { 4 | Break, 5 | Continue 6 | } 7 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Patch/ControlFlow/IDetourEvaluator.cs: -------------------------------------------------------------------------------- 1 | namespace MultiplayerMod.Core.Patch.ControlFlow; 2 | 3 | public interface IDetourEvaluator { 4 | ExecutionFlow Evaluate(ControlFlowContext context); 5 | } 6 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Patch/Generics/HarmonyGenericsRouterException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Core.Patch.Generics; 4 | 5 | public class HarmonyGenericsRouterException(string message) : Exception(message); 6 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Patch/HarmonyManualAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Core.Patch; 4 | 5 | [AttributeUsage(AttributeTargets.Class)] 6 | public class HarmonyManualAttribute : Attribute; 7 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Patch/HarmonyOptionalAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Core.Patch; 4 | 5 | [AttributeUsage(AttributeTargets.Class)] 6 | public class HarmonyOptionalAttribute : Attribute; 7 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Paths/AccessDeniedException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Core.Paths; 4 | 5 | public class AccessDeniedException : Exception { 6 | public AccessDeniedException(string message) : base(message) { } 7 | } 8 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Paths/SecurePath.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace MultiplayerMod.Core.Paths; 4 | 5 | public static class SecurePath { 6 | 7 | public static string Combine(string root, params string[] paths) { 8 | var absoluteRoot = Path.GetFullPath(root); 9 | var relativePath = Path.Combine(absoluteRoot, Path.Combine(paths)); 10 | var absolutePath = Path.GetFullPath(relativePath); 11 | if (absolutePath == absoluteRoot || absolutePath.StartsWith(EnsureEndsWithSeparator(absoluteRoot))) 12 | return absolutePath; 13 | 14 | throw new AccessDeniedException($"Unable to access \"{relativePath}\" as it's outside of \"{absoluteRoot}\""); 15 | } 16 | 17 | private static string EnsureEndsWithSeparator(string value) => 18 | value.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Reflection/ReflectionExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | namespace MultiplayerMod.Core.Reflection; 5 | 6 | public static class ReflectionExtension { 7 | 8 | public static object GetFieldValue(this object obj, string fieldName) { 9 | return GetFieldValue(obj, fieldName); 10 | } 11 | 12 | public static T GetFieldValue(this object obj, string fieldName) { 13 | var type = obj.GetType(); 14 | var field = type.GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); 15 | if (field == null) 16 | throw new Exception($"Field {fieldName} not found in {obj.GetType()}"); 17 | 18 | return (T) field.GetValue(obj); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Scheduling/UnityTaskExecutor.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Core.Dependency; 2 | using MultiplayerMod.Core.Unity; 3 | 4 | // ReSharper disable FieldCanBeMadeReadOnly.Local 5 | 6 | namespace MultiplayerMod.Core.Scheduling; 7 | 8 | public class UnityTaskExecutor : MultiplayerMonoBehaviour { 9 | 10 | [InjectDependency] 11 | private UnityTaskScheduler scheduler = null!; 12 | 13 | private void LateUpdate() => scheduler.Tick(); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Scheduling/UnityTaskSchedulerConfigurer.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | using MultiplayerMod.Core.Dependency; 3 | using MultiplayerMod.Core.Unity; 4 | using MultiplayerMod.ModRuntime.Loader; 5 | 6 | namespace MultiplayerMod.Core.Scheduling; 7 | 8 | [UsedImplicitly] 9 | public class UnityTaskSchedulerConfigurer : IModComponentConfigurer { 10 | 11 | public void Configure(DependencyContainerBuilder builder) { 12 | builder.ContainerCreated += _ => UnityObject.CreateStaticWithComponent(); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Unity/MultiplayerKMonoBehaviour.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Core.Dependency; 2 | using MultiplayerMod.ModRuntime.StaticCompatibility; 3 | 4 | namespace MultiplayerMod.Core.Unity; 5 | 6 | public class MultiplayerKMonoBehaviour : KMonoBehaviour { 7 | 8 | private readonly IDependencyInjector injector = Dependencies.Get(); 9 | 10 | protected override void OnPrefabInit() => injector.Inject(this); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Core/Unity/MultiplayerMonoBehaviour.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Core.Dependency; 2 | using MultiplayerMod.ModRuntime; 3 | using UnityEngine; 4 | 5 | namespace MultiplayerMod.Core.Unity; 6 | 7 | public class MultiplayerMonoBehaviour : MonoBehaviour { 8 | 9 | private readonly IDependencyInjector injector = Runtime.Instance.Dependencies.Get(); 10 | 11 | protected virtual void Awake() => injector.Inject(this); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Exceptions/MultiplayerException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Exceptions; 4 | 5 | public class MultiplayerException : Exception { 6 | protected MultiplayerException(string message) : base(message) { } 7 | protected MultiplayerException(string message, Exception cause) : base(message, cause) { } 8 | } 9 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Game/Chores/States/ChoreTransitStateArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace MultiplayerMod.Game.Chores.States; 5 | 6 | [Serializable] 7 | public record ChoreTransitStateArgs( 8 | Chore Chore, 9 | string? TargetState, 10 | Dictionary Args 11 | ); 12 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Game/Chores/States/MoveToArgs.cs: -------------------------------------------------------------------------------- 1 | namespace MultiplayerMod.Game.Chores.States; 2 | 3 | public record MoveToArgs( 4 | Chore Chore, 5 | string? TargetState, 6 | int Cell, 7 | CellOffset[] Offsets 8 | ); 9 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Game/Chores/Types/ChoreSyncConfig.cs: -------------------------------------------------------------------------------- 1 | namespace MultiplayerMod.Game.Chores.Types; 2 | 3 | public record ChoreSyncConfig( 4 | CreationStatusEnum CreationSync, 5 | GlobalChoreProviderStatusEnum GlobalChoreProviderSync, 6 | StatesTransitionConfig StatesTransitionSync 7 | ) { 8 | public static ChoreSyncConfig FullyDeterminedByInput( 9 | GlobalChoreProviderStatusEnum globalChoreStatus = GlobalChoreProviderStatusEnum.Off 10 | ) { 11 | return new ChoreSyncConfig( 12 | CreationStatusEnum.On, 13 | globalChoreStatus, 14 | StatesTransitionConfig.Disabled() 15 | ); 16 | } 17 | 18 | public static ChoreSyncConfig Dynamic(StatesTransitionConfig statesTransitionConfig) { 19 | return new ChoreSyncConfig( 20 | CreationStatusEnum.On, 21 | GlobalChoreProviderStatusEnum.Off, 22 | statesTransitionConfig 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Game/Chores/Types/CreationStatusEnum.cs: -------------------------------------------------------------------------------- 1 | namespace MultiplayerMod.Game.Chores.Types; 2 | 3 | public enum CreationStatusEnum { 4 | On, 5 | Off 6 | } 7 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Game/Chores/Types/GlobalChoreProviderStatusEnum.cs: -------------------------------------------------------------------------------- 1 | namespace MultiplayerMod.Game.Chores.Types; 2 | 3 | public enum GlobalChoreProviderStatusEnum { 4 | // TODO implement sync using this value. 5 | OnTodo, 6 | Off 7 | } 8 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Game/Chores/Types/StatesTransitionConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Game.Chores.Types; 4 | 5 | public record StatesTransitionConfig( 6 | StatesTransitionStatus Status, 7 | Type StateType, 8 | StateTransitionConfig[] StateTransitionConfigs 9 | ) { 10 | 11 | public static StatesTransitionConfig Enabled( 12 | params StateTransitionConfig[] stateTransitionConfigs 13 | ) where T : StateMachine { 14 | return new StatesTransitionConfig(StatesTransitionStatus.On, typeof(T), stateTransitionConfigs); 15 | } 16 | 17 | public static StatesTransitionConfig Disabled() { 18 | return new StatesTransitionConfig(StatesTransitionStatus.Off, null!, Array.Empty()); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Game/Chores/Types/StatesTransitionStatus.cs: -------------------------------------------------------------------------------- 1 | namespace MultiplayerMod.Game.Chores.Types; 2 | 3 | public enum StatesTransitionStatus { 4 | On, 5 | Off 6 | } 7 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Game/Chores/Types/TransitionTypeEnum.cs: -------------------------------------------------------------------------------- 1 | namespace MultiplayerMod.Game.Chores.Types; 2 | 3 | public enum TransitionTypeEnum { 4 | // names are aligned with methods in GameStateMachine`4.cs 5 | Enter, 6 | Exit, 7 | MoveTo, 8 | Update, 9 | EventHandler, 10 | Transition 11 | } 12 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Game/Context/GameContext.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | 3 | namespace MultiplayerMod.Game.Context; 4 | 5 | public static class GameContext { 6 | 7 | [SuppressMessage("ReSharper", "SuspiciousTypeConversion.Global")] 8 | public static void Override(IGameContext context, System.Action action) { 9 | try { 10 | context.Apply(); 11 | action(); 12 | } finally { 13 | context.Restore(); 14 | } 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Game/Context/GameContextComposite.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Core.Extensions; 2 | 3 | namespace MultiplayerMod.Game.Context; 4 | 5 | public class GameContextComposite : IGameContext { 6 | 7 | private readonly IGameContext[] contexts; 8 | 9 | public GameContextComposite(params IGameContext[] contexts) { 10 | this.contexts = contexts; 11 | } 12 | 13 | public void Apply() { 14 | contexts.ForEach(it => it.Apply()); 15 | } 16 | 17 | public void Restore() { 18 | contexts.ForEach(it => it.Restore()); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Game/Context/IGameContext.cs: -------------------------------------------------------------------------------- 1 | namespace MultiplayerMod.Game.Context; 2 | 3 | public interface IGameContext { 4 | void Apply(); 5 | void Restore(); 6 | } 7 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Game/Debug/GameDebugEvents.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | using JetBrains.Annotations; 3 | using MultiplayerMod.ModRuntime.Context; 4 | 5 | namespace MultiplayerMod.Game.Debug; 6 | 7 | [HarmonyPatch] 8 | public static class GameDebugEvents { 9 | 10 | public static event System.Action? GameFrameStepping; 11 | public static event System.Action? SimulationStepping; 12 | 13 | [HarmonyPrefix, UsedImplicitly] 14 | [HarmonyPatch(typeof(SpeedControlScreen), nameof(SpeedControlScreen.DebugStepFrame))] 15 | [RequireExecutionLevel(ExecutionLevel.Game)] 16 | private static void DebugStepFramePrefix() => GameFrameStepping?.Invoke(); 17 | 18 | [HarmonyPrefix, UsedImplicitly] 19 | [HarmonyPatch(typeof(global::Game), nameof(global::Game.ForceSimStep))] 20 | [RequireExecutionLevel(ExecutionLevel.Game)] 21 | private static void ForceSimStepPrefix() => SimulationStepping?.Invoke(); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Game/Dev/DevToolSceneInspectorPatch.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using HarmonyLib; 3 | using ImGuiNET; 4 | using MultiplayerMod.Core.Patch; 5 | 6 | namespace MultiplayerMod.Game.Dev; 7 | 8 | // ReSharper disable once UnusedType.Global 9 | [HarmonyOptional] 10 | [HarmonyPatch(typeof(DevToolSceneInspector))] 11 | public class DevToolSceneInspectorPatch { 12 | 13 | // ReSharper disable once UnusedMember.Local 14 | [HarmonyPrefix] 15 | [HarmonyPatch(nameof(DevToolSceneInspector.DisplayField))] 16 | private static bool DisplayFieldPrefix(ref bool __result, string name, Type ft, ref object obj) { 17 | var longType = ft == typeof(long) || ft == typeof(ulong); 18 | if (!longType) 19 | return true; 20 | 21 | ImGui.LabelText(name, obj.ToString()); 22 | __result = true; 23 | return false; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Game/Effects/DisablePopUpEffects.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Reflection; 4 | using HarmonyLib; 5 | using MultiplayerMod.Game.Context; 6 | 7 | namespace MultiplayerMod.Game.Effects; 8 | 9 | [HarmonyPatch] 10 | public class DisablePopUpEffects : IGameContext { 11 | 12 | private static bool enabled = true; 13 | 14 | // ReSharper disable once UnusedMember.Local 15 | private static IEnumerable TargetMethods() => 16 | typeof(PopFXManager).GetMethods() 17 | .Where(it => it.Name.StartsWith("SpawnFX")); 18 | 19 | [HarmonyPrefix] 20 | // ReSharper disable once UnusedMember.Local 21 | private static bool SpawnFxPrefix() => enabled; 22 | 23 | public void Apply() { 24 | enabled = false; 25 | } 26 | 27 | public void Restore() { 28 | enabled = true; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Game/Effects/DisablePriorityConfirmSound.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | using MultiplayerMod.Game.Context; 3 | 4 | namespace MultiplayerMod.Game.Effects; 5 | 6 | [HarmonyPatch(typeof(PriorityScreen))] 7 | public class DisablePriorityConfirmSound : IGameContext { 8 | 9 | private static bool enabled = true; 10 | 11 | [HarmonyPrefix] 12 | [HarmonyPatch(nameof(PriorityScreen.PlayPriorityConfirmSound))] 13 | // ReSharper disable once UnusedMember.Local 14 | private static bool PlayPriorityConfirmSoundPrefix() => enabled; 15 | 16 | public void Apply() => enabled = false; 17 | 18 | public void Restore() => enabled = true; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Game/Extension/GameExtensionsConfigurator.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | using MultiplayerMod.Core.Dependency; 3 | 4 | namespace MultiplayerMod.Game.Extension; 5 | 6 | [Dependency, UsedImplicitly] 7 | public class GameExtensionsConfigurator { 8 | 9 | public GameExtensionsConfigurator() { 10 | GameEvents.GameObjectCreated += it => it.AddComponent(); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Game/Extension/GameObjectExtension.cs: -------------------------------------------------------------------------------- 1 | namespace MultiplayerMod.Game.Extension; 2 | 3 | public class GameObjectExtension : KMonoBehaviour { 4 | 5 | public int GridLayer { get; set; } 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Game/KEventSystemExtensions.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace MultiplayerMod.Game; 4 | 5 | public static class KEventSystemExtensions { 6 | 7 | public static void Trigger(this GameObject go, GameHashes hash, object? data = null) { 8 | var kObject = KObjectManager.Instance.Get(go); 9 | if (kObject == null || !kObject.hasEventSystem) 10 | return; 11 | 12 | kObject.GetEventSystem().Trigger(go, (int) hash, data); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Game/Location/GridObjectLayerIndexerPatch.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | using MultiplayerMod.Game.Extension; 3 | using UnityEngine; 4 | 5 | namespace MultiplayerMod.Game.Location; 6 | 7 | // ReSharper disable once UnusedType.Global 8 | [HarmonyPatch(typeof(Grid.ObjectLayerIndexer))] 9 | public static class GridObjectLayerIndexerPatch { 10 | 11 | // ReSharper disable once UnusedMember.Local 12 | [HarmonyPostfix] 13 | [HarmonyPatch("set_Item")] 14 | private static void SetItemPostfix(int layer, GameObject value) { 15 | if (value == null) 16 | return; 17 | 18 | var extension = value.GetComponent(); 19 | if (extension == null) 20 | return; 21 | 22 | extension.GridLayer = layer; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Game/MainMenuExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace MultiplayerMod.Game; 2 | 3 | public static class MainMenuExtensions { 4 | 5 | private const int defaultButtonFontSize = 22; 6 | 7 | public static void AddButton(this MainMenu menu, string text, bool highlight, System.Action action) { 8 | var buttonInfo = new MainMenu.ButtonInfo( 9 | new LocString(text), 10 | action, 11 | defaultButtonFontSize, 12 | highlight ? menu.topButtonStyle : menu.normalButtonStyle 13 | ); 14 | menu.MakeButton(buttonInfo); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Game/Mechanics/Minions/MinionIdentityExtensions.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Multiplayer.Objects; 2 | 3 | namespace MultiplayerMod.Game.Mechanics.Minions; 4 | 5 | public static class MinionIdentityExtensions { 6 | 7 | public static MultiplayerInstance GetMultiplayerInstance(this MinionIdentity identity) { 8 | identity.ValidateProxy(); 9 | var proxy = identity.assignableProxy.Get(); 10 | return proxy.GetComponent(); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Game/Mechanics/Objects/ComponentEventsArgs.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace MultiplayerMod.Game.Mechanics.Objects; 4 | 5 | public record ComponentEventsArgs( 6 | KMonoBehaviour Component, 7 | MethodBase Method, 8 | object[] Args 9 | ); 10 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Game/Mechanics/Objects/StateMachineEventsArgs.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace MultiplayerMod.Game.Mechanics.Objects; 4 | 5 | public record StateMachineEventsArgs(StateMachine.Instance StateMachineInstance, MethodBase Method, object[] Args); 6 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Game/Mechanics/Printing/TelepadAcceptDeliveryCapture.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Core.Patch.Capture; 2 | using UnityEngine; 3 | 4 | namespace MultiplayerMod.Game.Mechanics.Printing; 5 | 6 | [LocalVariableCapture(typeof(Telepad), nameof(Telepad.OnAcceptDelivery))] 7 | public class TelepadAcceptDeliveryCapture : ILocalCapture { 8 | 9 | [LocalVariable(1)] 10 | public GameObject Instance { get; private set; } = null!; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Game/Mechanics/Schedules/ScheduleConditionalUpdatePatch.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | 3 | namespace MultiplayerMod.Game.Mechanics.Schedules; 4 | 5 | [HarmonyPatch(typeof(Schedule))] 6 | public static class ScheduleConditionalUpdatePatch { 7 | 8 | [HarmonyPrefix] 9 | [HarmonyPatch(nameof(Schedule.SetGroup))] 10 | private static bool SetGroupPrefix(Schedule __instance, int idx, ScheduleGroup group) { 11 | if (idx < 0 || idx >= __instance.blocks.Count) 12 | return false; 13 | 14 | return __instance.blocks[idx].GroupId != group.Id; 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Game/UI/MainMenuEvents.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | 3 | namespace MultiplayerMod.Game.UI; 4 | 5 | [HarmonyPatch(typeof(MainMenu))] 6 | // ReSharper disable once UnusedType.Global 7 | public static class MainMenuEvents { 8 | 9 | public static event System.Action? Initialized; 10 | 11 | // ReSharper disable once UnusedMember.Local 12 | [HarmonyPostfix] 13 | [HarmonyPatch(nameof(MainMenu.OnSpawn))] 14 | private static void AfterSpawn() => Initialized?.Invoke(); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Game/UI/Screens/Events/MeterScreenEvents.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using HarmonyLib; 3 | using JetBrains.Annotations; 4 | using MultiplayerMod.ModRuntime.Context; 5 | 6 | namespace MultiplayerMod.Game.UI.Screens.Events; 7 | 8 | [HarmonyPatch(typeof(MeterScreen))] 9 | public class MeterScreenEvents { 10 | 11 | public static event Action? RedAlertToggling; 12 | 13 | [HarmonyPrefix, UsedImplicitly] 14 | [HarmonyPatch(nameof(MeterScreen.OnRedAlertClick))] 15 | [RequireExecutionLevel(ExecutionLevel.Game)] 16 | private static void BeforeRedAlertClick() => RedAlertToggling?.Invoke( 17 | !ClusterManager.Instance.activeWorld.AlertManager.IsRedAlertToggledOn() 18 | ); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Game/UI/Screens/Events/PauseScreenEvents.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | using MultiplayerMod.ModRuntime.Context; 3 | 4 | namespace MultiplayerMod.Game.UI.Screens.Events; 5 | 6 | [HarmonyPatch(typeof(PauseScreen))] 7 | public static class PauseScreenEvents { 8 | 9 | public static event System.Action? GameQuit; 10 | 11 | [HarmonyPostfix] 12 | [HarmonyPatch(nameof(PauseScreen.TriggerQuitGame))] 13 | [RequireExecutionLevel(ExecutionLevel.Game)] 14 | // ReSharper disable once UnusedMember.Local 15 | private static void TriggerQuitGame() => GameQuit?.Invoke(); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Game/UI/Screens/Events/ResearchScreenEvents.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using HarmonyLib; 3 | using MultiplayerMod.ModRuntime.Context; 4 | 5 | namespace MultiplayerMod.Game.UI.Screens.Events; 6 | 7 | [HarmonyPatch(typeof(ResearchEntry))] 8 | public static class ResearchScreenEvents { 9 | 10 | public static event Action? Cancel; 11 | public static event Action? Select; 12 | 13 | [HarmonyPostfix] 14 | [HarmonyPatch(nameof(ResearchEntry.OnResearchCanceled))] 15 | [RequireExecutionLevel(ExecutionLevel.Game)] 16 | // ReSharper disable once UnusedMember.Local 17 | private static void OnResearchCanceledPatch(ResearchEntry __instance) => 18 | Cancel?.Invoke(__instance.targetTech.Id); 19 | 20 | [HarmonyPostfix] 21 | [HarmonyPatch(nameof(ResearchEntry.OnResearchClicked))] 22 | [RequireExecutionLevel(ExecutionLevel.Game)] 23 | // ReSharper disable once UnusedMember.Local 24 | private static void OnResearchClickedPatch(ResearchEntry __instance) => 25 | Select?.Invoke(__instance.targetTech.Id); 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Game/UI/Screens/Events/UserMenuScreenEvents.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using HarmonyLib; 3 | using JetBrains.Annotations; 4 | using MultiplayerMod.ModRuntime.Context; 5 | using MultiplayerMod.Multiplayer.Objects.Extensions; 6 | using MultiplayerMod.Multiplayer.Objects.Reference; 7 | 8 | namespace MultiplayerMod.Game.UI.Screens.Events; 9 | 10 | [HarmonyPatch(typeof(UserMenuScreen))] 11 | public static class UserMenuScreenEvents { 12 | 13 | public static event Action, PrioritySetting>? PriorityChanged; 14 | 15 | [UsedImplicitly] 16 | [HarmonyPostfix] 17 | [HarmonyPatch(nameof(UserMenuScreen.OnPriorityClicked))] 18 | [RequireExecutionLevel(ExecutionLevel.Game)] 19 | private static void OnPriorityClicked(UserMenuScreen __instance, PrioritySetting priority) { 20 | if (__instance.selected == null) 21 | return; 22 | 23 | var component = __instance.selected.GetComponent(); 24 | if (component == null) 25 | return; 26 | 27 | PriorityChanged?.Invoke(component.GetReference(), priority); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Game/UI/SideScreens/RailGunSideScreenEvents.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using HarmonyLib; 3 | using MultiplayerMod.ModRuntime.Context; 4 | using MultiplayerMod.Multiplayer.Objects.Extensions; 5 | using MultiplayerMod.Multiplayer.Objects.Reference; 6 | 7 | namespace MultiplayerMod.Game.UI.SideScreens; 8 | 9 | [HarmonyPatch(typeof(RailGunSideScreen))] 10 | public static class RailGunSideScreenEvents { 11 | 12 | public static event Action? UpdateRailGunCapacity; 13 | 14 | [HarmonyPostfix] 15 | [HarmonyPatch(nameof(RailGunSideScreen.UpdateMaxCapacity))] 16 | [RequireExecutionLevel(ExecutionLevel.Game)] 17 | // ReSharper disable once InconsistentNaming, UnusedMember.Local 18 | private static void UpdateMaxCapacity(RailGunSideScreen __instance, float newValue) => 19 | UpdateRailGunCapacity?.Invoke(new RailGunSideScreenEventArgs(__instance.selectedGun.GetReference(), newValue)); 20 | 21 | [Serializable] 22 | public record RailGunSideScreenEventArgs(ComponentReference Target, float LaunchMass); 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Game/UI/Tools/Context/DisableBuildingValidation.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Reflection; 4 | using HarmonyLib; 5 | using MultiplayerMod.Game.Context; 6 | 7 | namespace MultiplayerMod.Game.UI.Tools.Context; 8 | 9 | [HarmonyPatch] 10 | public class DisableBuildingValidation : IGameContext { 11 | 12 | private static bool validationEnabled = true; 13 | 14 | // ReSharper disable once UnusedMember.Local 15 | private static IEnumerable TargetMethods() => 16 | typeof(BuildingDef).GetMethods() 17 | .Where(it => it.Name.StartsWith("IsValidPlaceLocation")); 18 | 19 | [HarmonyPrefix] 20 | // ReSharper disable once UnusedMember.Local 21 | private static bool IsValidPlaceLocationPrefix(ref bool __result) { 22 | if (validationEnabled) 23 | return true; 24 | 25 | __result = true; 26 | return false; 27 | } 28 | 29 | public void Apply() { 30 | validationEnabled = false; 31 | } 32 | 33 | public void Restore() { 34 | validationEnabled = true; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Game/UI/Tools/Context/PrioritySettingsContext.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Game.Context; 2 | 3 | namespace MultiplayerMod.Game.UI.Tools.Context; 4 | 5 | public class PrioritySettingsContext : IGameContext { 6 | 7 | private readonly PrioritySetting priority; 8 | 9 | public PrioritySettingsContext(PrioritySetting priority) { 10 | this.priority = priority; 11 | } 12 | 13 | public void Apply() { 14 | GameStaticContext.Override(); 15 | if (GameStaticContext.Current.PriorityScreen != null) 16 | GameStaticContext.Current.PriorityScreen.lastSelectedPriority = priority; 17 | } 18 | 19 | public void Restore() { 20 | GameStaticContext.Restore(); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Game/UI/Tools/Events/BuildEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Game.UI.Tools.Events; 4 | 5 | [Serializable] 6 | public record BuildEventArgs( 7 | int Cell, 8 | string PrefabId, 9 | bool InstantBuild, 10 | bool Upgrade, 11 | Orientation Orientation, 12 | Tag[] Materials, 13 | string FacadeId, 14 | PrioritySetting Priority 15 | ); 16 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Game/UI/Tools/Events/CopySettingsEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Game.UI.Tools.Events; 4 | 5 | [Serializable] 6 | public record CopySettingsEventArgs( 7 | DragCompleteEventArgs DragEvent, 8 | int SourceCell, 9 | ObjectLayer SourceLayer 10 | ); 11 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Game/UI/Tools/Events/DragCompleteEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | namespace MultiplayerMod.Game.UI.Tools.Events; 6 | 7 | [Serializable] 8 | public record DragCompleteEventArgs( 9 | List Cells, 10 | Vector3 CursorDown, 11 | Vector3 CursorUp, 12 | PrioritySetting Priority, 13 | string[]? Parameters 14 | ); 15 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Game/UI/Tools/Events/ModifyEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Game.UI.Tools.Context; 3 | 4 | namespace MultiplayerMod.Game.UI.Tools.Events; 5 | 6 | [Serializable] 7 | public record ModifyEventArgs( 8 | DragCompleteEventArgs DragEventArgs, 9 | DebugTool.Type Type, 10 | DebugToolContext ToolContext 11 | ); 12 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Game/UI/Tools/Events/MoveToLocationToolEvents.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using HarmonyLib; 3 | using JetBrains.Annotations; 4 | using MultiplayerMod.ModRuntime.Context; 5 | 6 | namespace MultiplayerMod.Game.UI.Tools.Events; 7 | 8 | [HarmonyPatch(typeof(MoveToLocationTool))] 9 | public static class MoveToLocationToolEvents { 10 | 11 | public static event Action? LocationSet; 12 | 13 | [UsedImplicitly] 14 | [HarmonyPostfix] 15 | [HarmonyPatch(nameof(MoveToLocationTool.SetMoveToLocation))] 16 | [RequireExecutionLevel(ExecutionLevel.Multiplayer)] 17 | private static void SetMoveToLocationPostfix(MoveToLocationTool __instance, int target_cell) => 18 | LocationSet?.Invoke(__instance.targetNavigator, __instance.targetMovable, target_cell); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Game/UI/Tools/Events/StampEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | namespace MultiplayerMod.Game.UI.Tools.Events; 5 | 6 | [Serializable] 7 | public record StampEventArgs( 8 | TemplateContainer Template, 9 | Vector2 Location 10 | ); 11 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Game/UI/Tools/Events/UtilityBuildEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace MultiplayerMod.Game.UI.Tools.Events; 5 | 6 | [Serializable] 7 | public record UtilityBuildEventArgs( 8 | string PrefabId, 9 | Tag[] Materials, 10 | List Path, 11 | PrioritySetting Priority 12 | ); 13 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Game/UI/Tools/Events/UtilityBuildEvents.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using HarmonyLib; 4 | using MultiplayerMod.ModRuntime.Context; 5 | 6 | namespace MultiplayerMod.Game.UI.Tools.Events; 7 | 8 | [HarmonyPatch(typeof(BaseUtilityBuildTool))] 9 | public static class UtilityBuildEvents { 10 | 11 | public static event EventHandler? Build; 12 | 13 | [HarmonyPrefix] 14 | [HarmonyPatch(nameof(BaseUtilityBuildTool.BuildPath))] 15 | [RequireExecutionLevel(ExecutionLevel.Game)] 16 | // ReSharper disable once UnusedMember.Local 17 | private static void BuildPathPrefix(BaseUtilityBuildTool __instance) => Build?.Invoke( 18 | __instance, 19 | new UtilityBuildEventArgs( 20 | __instance.def.PrefabID, 21 | __instance.selectedElements.ToArray(), 22 | __instance.path, 23 | GameState.BuildToolPriority 24 | ) 25 | ); 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Game/World/SaveLoaderEvents.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | using JetBrains.Annotations; 3 | using MultiplayerMod.ModRuntime.Context; 4 | 5 | namespace MultiplayerMod.Game.World; 6 | 7 | [HarmonyPatch(typeof(SaveLoader))] 8 | public static class SaveLoaderEvents { 9 | 10 | public static event System.Action? WorldSaved; 11 | public static event System.Action? WorldLoading; 12 | 13 | [HarmonyPostfix, UsedImplicitly] 14 | [HarmonyPatch(nameof(SaveLoader.Save), typeof(string), typeof(bool), typeof(bool))] 15 | [RequireExecutionLevel(ExecutionLevel.Game)] 16 | public static void SavePostfix() => WorldSaved?.Invoke(); 17 | 18 | [HarmonyPrefix, UsedImplicitly] 19 | [HarmonyPatch(nameof(SaveLoader.Load), typeof(string))] 20 | [RequireExecutionLevel(ExecutionLevel.Multiplayer)] 21 | public static void LoadPrefix() => WorldLoading?.Invoke(); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/MultiplayerMod/ModRuntime/Context/ExecutionContext.cs: -------------------------------------------------------------------------------- 1 | namespace MultiplayerMod.ModRuntime.Context; 2 | 3 | public class ExecutionContext { 4 | 5 | public ExecutionLevel Level { get; } 6 | 7 | public ExecutionContext(ExecutionLevel level) { 8 | Level = level; 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/MultiplayerMod/ModRuntime/Context/ExecutionContextIntegrityFailureException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.ModRuntime.Context; 4 | 5 | public class ExecutionContextIntegrityFailureException : Exception { 6 | public ExecutionContextIntegrityFailureException(string message) : base(message) { } 7 | } 8 | -------------------------------------------------------------------------------- /src/MultiplayerMod/ModRuntime/Context/ExecutionContextManager.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using JetBrains.Annotations; 3 | using MultiplayerMod.Core.Dependency; 4 | 5 | namespace MultiplayerMod.ModRuntime.Context; 6 | 7 | [Dependency, UsedImplicitly] 8 | public class ExecutionContextManager { 9 | 10 | public ExecutionContext BaseContext { get; set; } = new(ExecutionLevel.System); 11 | 12 | public ExecutionContext Context => stack.Value.Current ?? BaseContext; 13 | 14 | private readonly ThreadLocal stack = new(() => new ExecutionContextStack()); 15 | 16 | public void EnterOverrideSection(ExecutionContext context) => stack.Value.Push(context); 17 | 18 | public void LeaveOverrideSection() => stack.Value.Pop(); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/MultiplayerMod/ModRuntime/Context/RequireDebugBuildAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AttributeProcessor.Annotations; 3 | 4 | namespace MultiplayerMod.ModRuntime.Context; 5 | 6 | [AttributeUsage(AttributeTargets.Method)] 7 | [ConditionalInvocation(typeof(RequireDebugBuildAttribute), nameof(DebugEnabled))] 8 | public class RequireDebugBuildAttribute : Attribute { 9 | 10 | private static bool DebugEnabled() { 11 | #if DEBUG 12 | return true; 13 | #else 14 | return false; 15 | #endif 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/MultiplayerMod/ModRuntime/Context/RequireExecutionLevelAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AttributeProcessor.Annotations; 3 | using JetBrains.Annotations; 4 | using MultiplayerMod.Core.Dependency; 5 | 6 | namespace MultiplayerMod.ModRuntime.Context; 7 | 8 | [DependenciesStaticTarget] 9 | [AttributeUsage(AttributeTargets.Method)] 10 | [ConditionalInvocation(typeof(RequireExecutionLevelAttribute), nameof(CheckExecutionLevel))] 11 | public class RequireExecutionLevelAttribute(ExecutionLevel level) : Attribute { 12 | 13 | [InjectDependency] 14 | private static readonly ExecutionLevelManager manager = null!; 15 | 16 | [UsedImplicitly] 17 | public ExecutionLevel Level { get; } = level; 18 | 19 | private static bool CheckExecutionLevel(ExecutionLevel level) => manager.LevelIsActive(level); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/MultiplayerMod/ModRuntime/DependenciesStaticTargetAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.ModRuntime; 4 | 5 | [AttributeUsage(AttributeTargets.Class)] 6 | public class DependenciesStaticTargetAttribute : Attribute; 7 | -------------------------------------------------------------------------------- /src/MultiplayerMod/ModRuntime/EventDispatcherMonitor.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | using MultiplayerMod.Core.Dependency; 3 | using MultiplayerMod.Core.Events; 4 | using MultiplayerMod.Core.Logging; 5 | 6 | namespace MultiplayerMod.ModRuntime; 7 | 8 | [Dependency, UsedImplicitly] 9 | public class EventDispatcherMonitor { 10 | 11 | private readonly Core.Logging.Logger log = LoggerFactory.GetLogger(); 12 | 13 | public EventDispatcherMonitor(EventDispatcher eventDispatcher) { 14 | if (log.Level == LogLevel.Trace) 15 | eventDispatcher.EventDispatching += OnEventDispatching; 16 | } 17 | 18 | private void OnEventDispatching(object @event) => log.Trace($"Event dispatched: {@event}"); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/MultiplayerMod/ModRuntime/Loader/IModComponentConfigurer.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Core.Dependency; 2 | 3 | namespace MultiplayerMod.ModRuntime.Loader; 4 | 5 | public interface IModComponentConfigurer { 6 | void Configure(DependencyContainerBuilder builder); 7 | } 8 | -------------------------------------------------------------------------------- /src/MultiplayerMod/ModRuntime/Loader/LaunchInitializerPatch.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | using MultiplayerMod.Core.Patch; 3 | 4 | namespace MultiplayerMod.ModRuntime.Loader; 5 | 6 | [HarmonyManual] 7 | [HarmonyPatch(typeof(LaunchInitializer))] 8 | public static class LaunchInitializerPatch { 9 | 10 | public static DelayedModLoader Loader { get; set; } = null!; 11 | 12 | // ReSharper disable once UnusedMember.Local 13 | [HarmonyPrefix] 14 | [HarmonyPatch(nameof(LaunchInitializer.DeleteLingeringFiles))] 15 | private static void LaunchInitializerBeforeDeleteLingeringFiles() { 16 | Loader.OnLoad(); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/MultiplayerMod/ModRuntime/Loader/ModComponentOrder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.ModRuntime.Loader; 4 | 5 | [AttributeUsage(AttributeTargets.Class)] 6 | public class ModComponentOrder : Attribute { 7 | 8 | public const int Runtime = 0; 9 | public const int Platform = 1; 10 | public const int Default = 2; 11 | public const int Configuration = 3; 12 | 13 | public int Order { get; } 14 | 15 | public ModComponentOrder(int order) { 16 | Order = order; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/MultiplayerMod/ModRuntime/Loader/ModLoader.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Reflection; 3 | using HarmonyLib; 4 | using KMod; 5 | using MultiplayerMod.Core.Logging; 6 | using MultiplayerMod.Core.Patch.Generics; 7 | 8 | namespace MultiplayerMod.ModRuntime.Loader; 9 | 10 | // ReSharper disable once UnusedType.Global 11 | public class ModLoader : UserMod2 { 12 | 13 | private readonly Core.Logging.Logger log = LoggerFactory.GetLogger(); 14 | 15 | public override void OnLoad(Harmony harmony) { 16 | var version = assembly.GetCustomAttribute().InformationalVersion; 17 | log.Info($"Multiplayer mod version: {version}"); 18 | harmony.CreateClassProcessor(typeof(LaunchInitializerPatch)).Patch(); 19 | harmony.CreateClassProcessor(typeof(HarmonyGenericsRouter)).Patch(); 20 | } 21 | 22 | public override void OnAllModsLoaded(Harmony harmony, IReadOnlyList mods) { 23 | LaunchInitializerPatch.Loader = new DelayedModLoader(harmony, assembly, mods); 24 | log.Info("Delayed loader initialized"); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/MultiplayerMod/ModRuntime/Runtime.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | using MultiplayerMod.Core.Dependency; 3 | 4 | namespace MultiplayerMod.ModRuntime; 5 | 6 | [Dependency, UsedImplicitly] 7 | public class Runtime { 8 | 9 | public static Runtime Instance { get; private set; } = null!; 10 | 11 | public IDependencyContainer Dependencies { get; } 12 | 13 | public Runtime(IDependencyContainer container) { 14 | Dependencies = container; 15 | Instance = this; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/MultiplayerMod/ModRuntime/RuntimeReadyEvent.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Core.Events; 2 | 3 | namespace MultiplayerMod.ModRuntime; 4 | 5 | public record RuntimeReadyEvent(Runtime Runtime) : IDispatchableEvent; 6 | -------------------------------------------------------------------------------- /src/MultiplayerMod/ModRuntime/StaticCompatibility/Dependencies.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Core.Collections; 2 | using MultiplayerMod.Core.Dependency; 3 | 4 | namespace MultiplayerMod.ModRuntime.StaticCompatibility; 5 | 6 | public static class Dependencies { 7 | 8 | private static IDependencyContainer Container => Runtime.Instance.Dependencies; 9 | 10 | public static T Get() where T : notnull => Container.Get(); 11 | 12 | public static Deconstructable Get() 13 | where T1 : notnull 14 | where T2 : notnull 15 | => Container.Get(); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/MultiplayerMod/ModRuntime/StaticCompatibility/Execution.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using MultiplayerMod.ModRuntime.Context; 3 | 4 | namespace MultiplayerMod.ModRuntime.StaticCompatibility; 5 | 6 | public static class Execution { 7 | 8 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 9 | public static void EnterLevelSection(ExecutionLevel level) => 10 | Dependencies.Get().EnterOverrideSection(level); 11 | 12 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 13 | public static void LeaveLevelSection() => 14 | Dependencies.Get().LeaveOverrideSection(); 15 | 16 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 17 | public static void RunUsingLevel(ExecutionLevel level, System.Action action) => 18 | Dependencies.Get().RunUsingLevel(level, action); 19 | 20 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 21 | public static void RunIfLevelIsActive(ExecutionLevel requiredLevel, System.Action action) => 22 | Dependencies.Get().RunIfLevelIsActive(requiredLevel, action); 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Chores/ChoreConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Multiplayer.StateMachines.Configuration; 3 | 4 | namespace MultiplayerMod.Multiplayer.Chores; 5 | 6 | public record ChoreConfiguration(Type? ChoreType, StateMachineConfigurer? StatesConfigurer); 7 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Chores/Commands/States/TransitToState.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Multiplayer.Commands; 3 | using MultiplayerMod.Multiplayer.Objects; 4 | using MultiplayerMod.Multiplayer.Objects.Extensions; 5 | using MultiplayerMod.Multiplayer.States; 6 | 7 | namespace MultiplayerMod.Multiplayer.Chores.Commands.States; 8 | 9 | [Serializable] 10 | public class TransitToState : MultiplayerCommand { 11 | public readonly MultiplayerId ChoreId; 12 | public readonly string? State; 13 | 14 | public TransitToState(Chore chore, string? state) { 15 | ChoreId = chore.MultiplayerId(); 16 | State = state; 17 | } 18 | 19 | public override void Execute(MultiplayerCommandContext context) { 20 | var chore = context.Multiplayer.Objects.Get(ChoreId)!; 21 | var smi = context.Runtime.Dependencies.Get().GetSmi(chore); 22 | smi.GoTo(State); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Chores/Driver/Commands/ReleaseChoreDriver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Multiplayer.Commands; 3 | using MultiplayerMod.Multiplayer.Objects.Extensions; 4 | using MultiplayerMod.Multiplayer.Objects.Reference; 5 | 6 | namespace MultiplayerMod.Multiplayer.Chores.Driver.Commands; 7 | 8 | [Serializable] 9 | public class ReleaseChoreDriver(ChoreDriver driver) : MultiplayerCommand { 10 | 11 | private readonly ComponentReference driverReference = driver.GetReference(); 12 | 13 | public override void Execute(MultiplayerCommandContext context) { 14 | var driver = driverReference.Resolve(); 15 | context.Dependencies.Get().Release(driver); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Chores/Events/ChoreCleanupEvent.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Core.Events; 2 | 3 | namespace MultiplayerMod.Multiplayer.Chores.Events; 4 | 5 | public record ChoreCleanupEvent(Chore Chore) : IDispatchableEvent; 6 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Chores/Events/ChoreCreatedEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Core.Events; 3 | using MultiplayerMod.Multiplayer.Objects; 4 | 5 | namespace MultiplayerMod.Multiplayer.Chores.Events; 6 | 7 | public record ChoreCreatedEvent(Chore Chore, MultiplayerId Id, Type Type, object?[] Arguments) : IDispatchableEvent; 8 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/Alerts/ChangeRedAlertState.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Multiplayer.Commands.Alerts; 4 | 5 | [Serializable] 6 | public class ChangeRedAlertState : MultiplayerCommand { 7 | 8 | private bool enabled; 9 | 10 | public ChangeRedAlertState(bool enabled) { 11 | this.enabled = enabled; 12 | } 13 | 14 | public override void Execute(MultiplayerCommandContext context) { 15 | ClusterManager.Instance.activeWorld.AlertManager.ToggleRedAlert(enabled); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/Debug/DebugGameFrameStep.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Multiplayer.Commands.Debug; 4 | 5 | [Serializable] 6 | public class DebugGameFrameStep : MultiplayerCommand { 7 | 8 | public override void Execute(MultiplayerCommandContext context) => SpeedControlScreen.Instance.DebugStepFrame(); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/Debug/DebugSimulationStep.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Multiplayer.Commands.Debug; 4 | 5 | [Serializable] 6 | public class DebugSimulationStep : MultiplayerCommand { 7 | 8 | public override void Execute(MultiplayerCommandContext context) => global::Game.Instance.ForceSimStep(); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/Debug/SyncWorldDebugSnapshot.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Multiplayer.World.Debug; 3 | 4 | namespace MultiplayerMod.Multiplayer.Commands.Debug; 5 | 6 | [Serializable] 7 | public class SyncWorldDebugSnapshot : MultiplayerCommand { 8 | 9 | private WorldDebugSnapshot snapshot; 10 | 11 | public SyncWorldDebugSnapshot(WorldDebugSnapshot snapshot) { 12 | this.snapshot = snapshot; 13 | } 14 | 15 | public override void Execute(MultiplayerCommandContext context) { 16 | WorldDebugSnapshotRunner.LastServerInfo = snapshot; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/Gameplay/ChangePriority.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Multiplayer.Objects.Reference; 3 | 4 | namespace MultiplayerMod.Multiplayer.Commands.Gameplay; 5 | 6 | [Serializable] 7 | public class ChangePriority : MultiplayerCommand { 8 | 9 | private ComponentReference target; 10 | private PrioritySetting priority; 11 | 12 | public ChangePriority(ComponentReference target, PrioritySetting priority) { 13 | this.target = target; 14 | this.priority = priority; 15 | } 16 | 17 | public override void Execute(MultiplayerCommandContext context) => target.Resolve().SetMasterPriority(priority); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/Gameplay/RejectDelivery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Game.UI.Screens; 3 | using MultiplayerMod.Multiplayer.Objects.Reference; 4 | 5 | namespace MultiplayerMod.Multiplayer.Commands.Gameplay; 6 | 7 | [Serializable] 8 | public class RejectDelivery : MultiplayerCommand { 9 | 10 | private ComponentReference reference; 11 | 12 | public RejectDelivery(ComponentReference reference) { 13 | this.reference = reference; 14 | } 15 | 16 | public override void Execute(MultiplayerCommandContext context) { 17 | reference.Resolve().RejectAll(); 18 | ImmigrantScreenPatch.Deliverables = null; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/IMultiplayerCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Multiplayer.Commands; 4 | 5 | public interface IMultiplayerCommand { 6 | Guid Id { get; } 7 | void Execute(MultiplayerCommandContext context); 8 | } 9 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/MultiplayerCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Multiplayer.Commands; 4 | 5 | [Serializable] 6 | public abstract class MultiplayerCommand : IMultiplayerCommand { 7 | 8 | public Guid Id { get; } = Guid.NewGuid(); 9 | 10 | public abstract void Execute(MultiplayerCommandContext context); 11 | 12 | public override string ToString() => $"Command [{Id:N}] {GetType().Name}"; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/MultiplayerCommandAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Multiplayer.Commands; 4 | 5 | [AttributeUsage(AttributeTargets.Class)] 6 | public class MultiplayerCommandAttribute : Attribute { 7 | public MultiplayerCommandType Type { get; set; } = MultiplayerCommandType.Game; 8 | public bool ExecuteOnServer { get; set; } 9 | } 10 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/MultiplayerCommandContext.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Core.Dependency; 2 | using MultiplayerMod.Core.Events; 3 | using MultiplayerMod.ModRuntime; 4 | using MultiplayerMod.Network; 5 | 6 | namespace MultiplayerMod.Multiplayer.Commands; 7 | 8 | public class MultiplayerCommandContext(IMultiplayerClientId? clientId, MultiplayerCommandRuntimeAccessor accessor) { 9 | public IMultiplayerClientId? ClientId { get; } = clientId; 10 | public Runtime Runtime => accessor.Runtime; 11 | public IDependencyContainer Dependencies => accessor.Dependencies; 12 | public EventDispatcher EventDispatcher => accessor.EventDispatcher; 13 | public MultiplayerGame Multiplayer => accessor.Multiplayer; 14 | } 15 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/MultiplayerCommandRuntimeAccessor.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Core.Dependency; 2 | using MultiplayerMod.Core.Events; 3 | using MultiplayerMod.ModRuntime; 4 | 5 | namespace MultiplayerMod.Multiplayer.Commands; 6 | 7 | public class MultiplayerCommandRuntimeAccessor(Runtime runtime) { 8 | public Runtime Runtime { get; } = runtime; 9 | public IDependencyContainer Dependencies { get; } = runtime.Dependencies; 10 | public EventDispatcher EventDispatcher { get; } = runtime.Dependencies.Get(); 11 | public MultiplayerGame Multiplayer { get; } = runtime.Dependencies.Get(); 12 | } 13 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/MultiplayerCommandType.cs: -------------------------------------------------------------------------------- 1 | namespace MultiplayerMod.Multiplayer.Commands; 2 | 3 | public enum MultiplayerCommandType { 4 | 5 | /// 6 | /// A command will be always executed. 7 | /// 8 | System, 9 | 10 | /// 11 | /// A command is related to the game and will be executed if possible. 12 | /// 13 | Game 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/MultiplayerCommandsConfigurer.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using JetBrains.Annotations; 3 | using MultiplayerMod.Core.Dependency; 4 | using MultiplayerMod.Core.Extensions; 5 | using MultiplayerMod.ModRuntime.Loader; 6 | using MultiplayerMod.Multiplayer.Commands.Registry; 7 | 8 | namespace MultiplayerMod.Multiplayer.Commands; 9 | 10 | [UsedImplicitly] 11 | public class MultiplayerCommandsConfigurer : IModComponentConfigurer { 12 | 13 | public void Configure(DependencyContainerBuilder builder) { 14 | builder.ContainerCreated += RegisterCommands; 15 | } 16 | 17 | private void RegisterCommands(DependencyContainer container) { 18 | var registry = container.Get(); 19 | GetType().Assembly.DefinedTypes 20 | .Where(it => typeof(IMultiplayerCommand).IsAssignableFrom(it)) 21 | .Where(it => !it.IsAbstract) 22 | .ForEach(it => registry.Register(it)); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/Overlay/SetDisinfectSettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Multiplayer.Commands.Overlay; 4 | 5 | [Serializable] 6 | public class SetDisinfectSettings : MultiplayerCommand { 7 | private readonly int minGerm; 8 | private readonly bool enableAutoDisinfect; 9 | 10 | public SetDisinfectSettings(int minGerm, bool enableAutoDisinfect) { 11 | this.minGerm = minGerm; 12 | this.enableAutoDisinfect = enableAutoDisinfect; 13 | } 14 | 15 | public override void Execute(MultiplayerCommandContext context) { 16 | SaveGame.Instance.enableAutoDisinfect = enableAutoDisinfect; 17 | SaveGame.Instance.minGermCountForDisinfect = minGerm; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/Player/UpdatePlayerCursorPosition.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Game.UI.Tools.Events; 3 | using MultiplayerMod.Multiplayer.Players; 4 | using MultiplayerMod.Multiplayer.Players.Events; 5 | 6 | namespace MultiplayerMod.Multiplayer.Commands.Player; 7 | 8 | [Serializable] 9 | public class UpdatePlayerCursorPosition : MultiplayerCommand { 10 | 11 | private PlayerIdentity playerId; 12 | private InterfaceToolEvents.MouseMovedEventArgs mouseMovedEventArgs; 13 | 14 | public UpdatePlayerCursorPosition( 15 | PlayerIdentity playerId, 16 | InterfaceToolEvents.MouseMovedEventArgs mouseMovedEventArgs 17 | ) { 18 | this.playerId = playerId; 19 | this.mouseMovedEventArgs = mouseMovedEventArgs; 20 | } 21 | 22 | public override void Execute(MultiplayerCommandContext context) { 23 | var player = context.Multiplayer.Players[playerId]; 24 | context.EventDispatcher.Dispatch(new PlayerCursorPositionUpdatedEvent(player, mouseMovedEventArgs)); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/Registry/MultiplayerCommandAlreadyRegisteredException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Exceptions; 3 | 4 | namespace MultiplayerMod.Multiplayer.Commands.Registry; 5 | 6 | public class MultiplayerCommandAlreadyRegisteredException : MultiplayerException { 7 | public MultiplayerCommandAlreadyRegisteredException(Type type) : base( 8 | $"Command \"{type}\" already registered" 9 | ) { } 10 | } 11 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/Registry/MultiplayerCommandConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Multiplayer.Commands.Registry; 4 | 5 | public class MultiplayerCommandConfiguration { 6 | 7 | public Type Type { get;} 8 | public MultiplayerCommandType CommandType { get;} 9 | public bool ExecuteOnServer { get;} 10 | 11 | public MultiplayerCommandConfiguration(Type type, MultiplayerCommandType commandType, bool executeOnServer) { 12 | Type = type; 13 | CommandType = commandType; 14 | ExecuteOnServer = executeOnServer; 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/Registry/MultiplayerCommandInvalidInterfaceException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Exceptions; 3 | 4 | namespace MultiplayerMod.Multiplayer.Commands.Registry; 5 | 6 | public class MultiplayerCommandInvalidInterfaceException : MultiplayerException { 7 | public MultiplayerCommandInvalidInterfaceException(Type type) : base( 8 | $"Type \"{type}\" doesn't implement \"{typeof(IMultiplayerCommand)}\" interface" 9 | ) { } 10 | } 11 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/Registry/MultiplayerCommandNotFoundException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Exceptions; 3 | 4 | namespace MultiplayerMod.Multiplayer.Commands.Registry; 5 | 6 | public class MultiplayerCommandNotFoundException : MultiplayerException { 7 | public MultiplayerCommandNotFoundException(Type type) : base( 8 | $"Command \"{type}\" not found" 9 | ) { } 10 | } 11 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/Screens/Immigration/InitializeImmigration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using MultiplayerMod.Game.UI.Screens; 4 | 5 | namespace MultiplayerMod.Multiplayer.Commands.Screens.Immigration; 6 | 7 | [Serializable] 8 | public class InitializeImmigration : MultiplayerCommand { 9 | 10 | private List? deliverables; 11 | 12 | public InitializeImmigration(List? deliverables) { 13 | this.deliverables = deliverables; 14 | } 15 | 16 | public override void Execute(MultiplayerCommandContext context) { 17 | ImmigrantScreenPatch.Deliverables = deliverables; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/Screens/Priorities/SetPersonalPrioritiesAdvanced.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Multiplayer.Commands.Screens.Priorities; 4 | 5 | [Serializable] 6 | public class SetPersonalPrioritiesAdvanced : MultiplayerCommand { 7 | private readonly bool value; 8 | 9 | public SetPersonalPrioritiesAdvanced(bool value) { 10 | this.value = value; 11 | } 12 | 13 | public override void Execute(MultiplayerCommandContext context) { 14 | global::Game.Instance.advancedPersonalPriorities = value; 15 | ManagementMenu.Instance.jobsScreen.toggleAdvancedModeButton.fgImage.gameObject.SetActive(value); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/Screens/Research/AbstractResearchEntryCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using MultiplayerMod.Core.Logging; 4 | 5 | namespace MultiplayerMod.Multiplayer.Commands.Screens.Research; 6 | 7 | [Serializable] 8 | public abstract class AbstractResearchEntryCommand : MultiplayerCommand { 9 | 10 | private static Core.Logging.Logger log = LoggerFactory.GetLogger(); 11 | 12 | private string techId; 13 | 14 | protected AbstractResearchEntryCommand(string techId) { 15 | this.techId = techId; 16 | } 17 | 18 | protected abstract void Execute(ResearchEntry researchEntry); 19 | 20 | public override void Execute(MultiplayerCommandContext context) { 21 | var screen = ManagementMenu.Instance.researchScreen; 22 | var entry = screen.entryMap.Values.FirstOrDefault(entry => entry.targetTech.Id == techId); 23 | if (entry == null) { 24 | log.Warning($"Tech {techId} is not found."); 25 | return; 26 | } 27 | Execute(entry); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/Screens/Research/CancelResearch.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Multiplayer.Commands.Screens.Research; 4 | 5 | [Serializable] 6 | public class CancelResearch : AbstractResearchEntryCommand { 7 | public CancelResearch(string techId) : base(techId) { } 8 | 9 | protected override void Execute(ResearchEntry researchEntry) { 10 | researchEntry.OnResearchCanceled(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/Screens/Research/SelectResearch.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Multiplayer.Commands.Screens.Research; 4 | 5 | [Serializable] 6 | public class SelectResearch : AbstractResearchEntryCommand { 7 | 8 | public SelectResearch(string techId) : base(techId) { } 9 | 10 | protected override void Execute(ResearchEntry researchEntry) { 11 | researchEntry.OnResearchClicked(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/Screens/SideScreen/UpdateAlarm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using static MultiplayerMod.Game.UI.SideScreens.AlarmSideScreenEvents; 3 | 4 | namespace MultiplayerMod.Multiplayer.Commands.Screens.SideScreen; 5 | 6 | [Serializable] 7 | public class UpdateAlarm : MultiplayerCommand { 8 | 9 | private readonly AlarmSideScreenEventArgs args; 10 | 11 | public UpdateAlarm(AlarmSideScreenEventArgs args) { 12 | this.args = args; 13 | } 14 | 15 | public override void Execute(MultiplayerCommandContext context) { 16 | var alarm = args.Target.Resolve(); 17 | alarm.notificationName = args.NotificationName; 18 | alarm.notificationTooltip = args.NotificationTooltip; 19 | alarm.pauseOnNotify = args.PauseOnNotify; 20 | alarm.zoomOnNotify = args.ZoomOnNotify; 21 | alarm.notificationType = args.NotificationType; 22 | 23 | alarm.UpdateNotification(true); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/Screens/SideScreen/UpdateCritterSensor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using static MultiplayerMod.Game.UI.SideScreens.CritterSensorSideScreenEvents; 3 | 4 | namespace MultiplayerMod.Multiplayer.Commands.Screens.SideScreen; 5 | 6 | [Serializable] 7 | public class UpdateCritterSensor : MultiplayerCommand { 8 | 9 | private readonly CritterSensorSideScreenEventArgs args; 10 | 11 | public UpdateCritterSensor(CritterSensorSideScreenEventArgs args) { 12 | this.args = args; 13 | } 14 | 15 | public override void Execute(MultiplayerCommandContext context) { 16 | var logicCritterCountSensor = args.Target.Resolve(); 17 | logicCritterCountSensor.countCritters = args.CountCritters; 18 | logicCritterCountSensor.countEggs = args.CountEggs; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/Screens/SideScreen/UpdateLogicCounter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using static MultiplayerMod.Game.UI.SideScreens.CounterSideScreenEvents; 3 | 4 | namespace MultiplayerMod.Multiplayer.Commands.Screens.SideScreen; 5 | 6 | [Serializable] 7 | public class UpdateLogicCounter : MultiplayerCommand { 8 | 9 | private readonly CounterSideScreenEventArgs args; 10 | 11 | public UpdateLogicCounter(CounterSideScreenEventArgs args) { 12 | this.args = args; 13 | } 14 | 15 | public override void Execute(MultiplayerCommandContext context) { 16 | var logicCounter = args.Target.Resolve(); 17 | logicCounter.maxCount = args.MaxCount; 18 | logicCounter.currentCount = args.CurrentCount; 19 | logicCounter.advancedMode = args.AdvancedMode; 20 | 21 | logicCounter.SetCounterState(); 22 | logicCounter.UpdateLogicCircuit(); 23 | logicCounter.UpdateVisualState(true); 24 | logicCounter.UpdateMeter(); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/Screens/SideScreen/UpdateLogicTimeOfDaySensor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using static MultiplayerMod.Game.UI.SideScreens.TimeRangeSideScreenEvents; 3 | 4 | namespace MultiplayerMod.Multiplayer.Commands.Screens.SideScreen; 5 | 6 | [Serializable] 7 | public class UpdateLogicTimeOfDaySensor : MultiplayerCommand { 8 | 9 | private readonly TimeRangeSideScreenEventArgs args; 10 | 11 | public UpdateLogicTimeOfDaySensor(TimeRangeSideScreenEventArgs args) { 12 | this.args = args; 13 | } 14 | 15 | public override void Execute(MultiplayerCommandContext context) { 16 | var sensor = args.Target.Resolve(); 17 | sensor.startTime = args.StartTime; 18 | sensor.duration = args.Duration; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/Screens/SideScreen/UpdateLogicTimeSensor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using static MultiplayerMod.Game.UI.SideScreens.TimerSideScreenEvents; 3 | 4 | namespace MultiplayerMod.Multiplayer.Commands.Screens.SideScreen; 5 | 6 | [Serializable] 7 | public class UpdateLogicTimeSensor : MultiplayerCommand { 8 | 9 | private readonly TimerSideScreenEventArgs args; 10 | 11 | public UpdateLogicTimeSensor(TimerSideScreenEventArgs args) { 12 | this.args = args; 13 | } 14 | 15 | public override void Execute(MultiplayerCommandContext context) { 16 | var sensor = args.Target.Resolve(); 17 | sensor.displayCyclesMode = args.DisplayCyclesMode; 18 | sensor.onDuration = args.OnDuration; 19 | sensor.offDuration = args.OffDuration; 20 | sensor.timeElapsedInCurrentState = args.TimeElapsedInCurrentState; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/Screens/SideScreen/UpdateRailGunCapacity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using static MultiplayerMod.Game.UI.SideScreens.RailGunSideScreenEvents; 3 | 4 | namespace MultiplayerMod.Multiplayer.Commands.Screens.SideScreen; 5 | 6 | [Serializable] 7 | public class UpdateRailGunCapacity : MultiplayerCommand { 8 | 9 | private readonly RailGunSideScreenEventArgs args; 10 | 11 | public UpdateRailGunCapacity(RailGunSideScreenEventArgs args) { 12 | this.args = args; 13 | } 14 | 15 | public override void Execute(MultiplayerCommandContext context) { 16 | var railGun = args.Target.Resolve(); 17 | railGun.launchMass = args.LaunchMass; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/Screens/SideScreen/UpdateTemperatureSwitch.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using static MultiplayerMod.Game.UI.SideScreens.TemperatureSwitchSideScreenEvents; 3 | 4 | namespace MultiplayerMod.Multiplayer.Commands.Screens.SideScreen; 5 | 6 | [Serializable] 7 | public class UpdateTemperatureSwitch : MultiplayerCommand { 8 | 9 | private readonly TemperatureSwitchSideScreenEventArgs args; 10 | 11 | public UpdateTemperatureSwitch(TemperatureSwitchSideScreenEventArgs args) { 12 | this.args = args; 13 | } 14 | 15 | public override void Execute(MultiplayerCommandContext context) { 16 | var temperatureControlledSwitch = args.Target.Resolve(); 17 | temperatureControlledSwitch.thresholdTemperature = args.ThresholdTemperature; 18 | temperatureControlledSwitch.activateOnWarmerThan = args.ActivateOnWarmerThan; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/Speed/ChangeGameSpeed.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Multiplayer.Commands.Speed; 4 | 5 | [Serializable] 6 | public class ChangeGameSpeed : MultiplayerCommand { 7 | 8 | private int speed; 9 | 10 | public ChangeGameSpeed(int speed) { 11 | this.speed = speed; 12 | } 13 | 14 | public override void Execute(MultiplayerCommandContext context) { 15 | SpeedControlScreen.Instance.SetSpeed(speed); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/Speed/PauseGame.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Multiplayer.Commands.Speed; 4 | 5 | [Serializable] 6 | public class PauseGame : MultiplayerCommand { 7 | 8 | public override void Execute(MultiplayerCommandContext context) { 9 | if (!SpeedControlScreen.Instance.IsPaused) 10 | SpeedControlScreen.Instance.Pause(); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/Speed/ResumeGame.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Core.Events; 3 | using MultiplayerMod.Multiplayer.Players.Events; 4 | 5 | namespace MultiplayerMod.Multiplayer.Commands.Speed; 6 | 7 | [Serializable] 8 | public class ResumeGame : MultiplayerCommand { 9 | 10 | public override void Execute(MultiplayerCommandContext context) { 11 | if (TryResume(context.Multiplayer)) 12 | return; 13 | context.EventDispatcher.Subscribe(OnPlayersReady); 14 | } 15 | 16 | private void OnPlayersReady(PlayersReadyEvent @event, EventSubscription subscription) { 17 | Resume(); 18 | subscription.Cancel(); 19 | } 20 | 21 | private bool TryResume(MultiplayerGame multiplayer) { 22 | if (!multiplayer.Players.Ready) 23 | return false; 24 | Resume(); 25 | return true; 26 | } 27 | 28 | private void Resume() { 29 | if (SpeedControlScreen.Instance.IsPaused) 30 | SpeedControlScreen.Instance.Unpause(); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/Tools/Attack.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Game.UI.Tools.Events; 3 | 4 | namespace MultiplayerMod.Multiplayer.Commands.Tools; 5 | 6 | [Serializable] 7 | public class Attack : AbstractDragToolCommand { 8 | public Attack(DragCompleteEventArgs arguments) : base(arguments) { } 9 | 10 | protected override void InvokeTool(AttackTool tool) => 11 | tool.OnDragComplete(Arguments.CursorDown, Arguments.CursorUp); 12 | } 13 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/Tools/BuildUtility.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Game.UI.Tools.Events; 3 | 4 | namespace MultiplayerMod.Multiplayer.Commands.Tools; 5 | 6 | [Serializable] 7 | public class BuildUtility : AbstractBuildUtilityCommand { 8 | public BuildUtility(UtilityBuildEventArgs arguments) : base(arguments) { } 9 | } 10 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/Tools/BuildWire.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Game.UI.Tools.Events; 3 | 4 | namespace MultiplayerMod.Multiplayer.Commands.Tools; 5 | 6 | [Serializable] 7 | public class BuildWire : AbstractBuildUtilityCommand { 8 | public BuildWire(UtilityBuildEventArgs arguments) : base(arguments) { } 9 | } 10 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/Tools/Cancel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Game.UI.Tools.Events; 3 | 4 | namespace MultiplayerMod.Multiplayer.Commands.Tools; 5 | 6 | [Serializable] 7 | public class Cancel : AbstractDragToolCommand { 8 | public Cancel(DragCompleteEventArgs arguments) : base(arguments) { } 9 | } 10 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/Tools/Deconstruct.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Game.UI.Tools.Events; 3 | 4 | namespace MultiplayerMod.Multiplayer.Commands.Tools; 5 | 6 | [Serializable] 7 | public class Deconstruct : AbstractDragToolCommand { 8 | public Deconstruct(DragCompleteEventArgs arguments) : base(arguments) { } 9 | } 10 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/Tools/Dig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Game.UI.Tools.Events; 3 | 4 | namespace MultiplayerMod.Multiplayer.Commands.Tools; 5 | 6 | [Serializable] 7 | public class Dig : AbstractDragToolCommand { 8 | public Dig(DragCompleteEventArgs arguments) : base(arguments) { } 9 | } 10 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/Tools/Disconnect.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Game.UI.Tools.Events; 3 | 4 | namespace MultiplayerMod.Multiplayer.Commands.Tools; 5 | 6 | [Serializable] 7 | public class Disconnect : AbstractDragToolCommand { 8 | 9 | public Disconnect(DragCompleteEventArgs arguments) : base(arguments) { } 10 | 11 | protected override void InvokeTool(DisconnectTool tool) { 12 | tool.OnDragComplete(Arguments.CursorDown, Arguments.CursorUp); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/Tools/Disinfect.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Game.UI.Tools.Events; 3 | 4 | namespace MultiplayerMod.Multiplayer.Commands.Tools; 5 | 6 | [Serializable] 7 | public class Disinfect : AbstractDragToolCommand { 8 | public Disinfect(DragCompleteEventArgs arguments) : base(arguments) { } 9 | } 10 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/Tools/EmptyPipe.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Game.UI.Tools.Events; 3 | 4 | namespace MultiplayerMod.Multiplayer.Commands.Tools; 5 | 6 | [Serializable] 7 | public class EmptyPipe : AbstractDragToolCommand { 8 | public EmptyPipe(DragCompleteEventArgs arguments) : base(arguments) { } 9 | } 10 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/Tools/Harvest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using MultiplayerMod.Core.Extensions; 4 | using MultiplayerMod.Game.UI.Tools.Events; 5 | 6 | namespace MultiplayerMod.Multiplayer.Commands.Tools; 7 | 8 | [Serializable] 9 | public class Harvest : AbstractDragToolCommand { 10 | 11 | public Harvest(DragCompleteEventArgs arguments) : base(arguments) { } 12 | 13 | protected override void InitializeTool(HarvestTool tool) { 14 | base.InitializeTool(tool); 15 | tool.options = new Dictionary { 16 | ["HARVEST_WHEN_READY"] = ToolParameterMenu.ToggleState.Off, 17 | ["DO_NOT_HARVEST"] = ToolParameterMenu.ToggleState.Off 18 | }; 19 | Arguments.Parameters?.ForEach(it => tool.options[it] = ToolParameterMenu.ToggleState.On); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/Tools/Modify.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Game.Context; 3 | using MultiplayerMod.Game.UI.Tools.Events; 4 | 5 | namespace MultiplayerMod.Multiplayer.Commands.Tools; 6 | 7 | [Serializable] 8 | public class Modify : MultiplayerCommand { 9 | 10 | private ModifyEventArgs arguments; 11 | 12 | public Modify(ModifyEventArgs arguments) { 13 | this.arguments = arguments; 14 | } 15 | 16 | // ReSharper disable once Unity.IncorrectMonoBehaviourInstantiation 17 | public override void Execute(MultiplayerCommandContext context) { 18 | var tool = new DebugTool { 19 | type = arguments.Type 20 | }; 21 | GameContext.Override( 22 | arguments.ToolContext, 23 | () => { arguments.DragEventArgs.Cells.ForEach(it => tool.OnDragTool(it, 0)); } 24 | ); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/Tools/Mop.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Game.UI.Tools.Events; 3 | 4 | namespace MultiplayerMod.Multiplayer.Commands.Tools; 5 | 6 | [Serializable] 7 | public class Mop : AbstractDragToolCommand { 8 | public Mop(DragCompleteEventArgs arguments) : base(arguments) { } 9 | 10 | protected override void InitializeTool(MopTool tool) { 11 | base.InitializeTool(tool); 12 | tool.Placer = Assets.GetPrefab(new Tag("MopPlacer")); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/Tools/MoveToLocation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Multiplayer.Objects.Extensions; 3 | using MultiplayerMod.Multiplayer.Objects.Reference; 4 | 5 | namespace MultiplayerMod.Multiplayer.Commands.Tools; 6 | 7 | [Serializable] 8 | public class MoveToLocation(Navigator? navigator, Movable? movable, int cell) : MultiplayerCommand { 9 | 10 | private readonly ComponentReference? navigatorReference = navigator?.GetReference(); 11 | private readonly ComponentReference? movableReference = movable?.GetReference(); 12 | 13 | public override void Execute(MultiplayerCommandContext context) { 14 | var navigator = navigatorReference?.Resolve(); 15 | var movable = movableReference?.Resolve(); 16 | 17 | if (navigator != null) 18 | navigator.GetSMI()?.MoveToLocation(cell); 19 | else if (movable != null) 20 | movable.MoveToLocation(cell); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/Tools/Prioritize.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Game.Context; 3 | using MultiplayerMod.Game.Effects; 4 | using MultiplayerMod.Game.UI.Tools.Events; 5 | 6 | namespace MultiplayerMod.Multiplayer.Commands.Tools; 7 | 8 | [Serializable] 9 | public class Prioritize : AbstractDragToolCommand { 10 | 11 | public Prioritize(DragCompleteEventArgs arguments) : base(arguments) { } 12 | 13 | protected override IGameContext CreateContext() => new GameContextComposite( 14 | base.CreateContext(), 15 | new DisablePriorityConfirmSound() 16 | ); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/Tools/Stamp.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Game.Context; 3 | using MultiplayerMod.Game.UI.Tools.Context; 4 | using MultiplayerMod.Game.UI.Tools.Events; 5 | 6 | namespace MultiplayerMod.Multiplayer.Commands.Tools; 7 | 8 | [Serializable] 9 | public class Stamp : MultiplayerCommand { 10 | 11 | private StampEventArgs arguments; 12 | 13 | public Stamp(StampEventArgs arguments) { 14 | this.arguments = arguments; 15 | } 16 | 17 | // ReSharper disable once Unity.IncorrectMonoBehaviourInstantiation 18 | public override void Execute(MultiplayerCommandContext context) { 19 | var tool = new StampTool { 20 | stampTemplate = arguments.Template, 21 | ready = true, 22 | selectAffected = false, 23 | deactivateOnStamp = false 24 | }; 25 | GameContext.Override(new StampCompletionOverride(), () => tool.Stamp(arguments.Location)); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/Tools/Sweep.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Game.UI.Tools.Events; 3 | 4 | namespace MultiplayerMod.Multiplayer.Commands.Tools; 5 | 6 | [Serializable] 7 | public class Sweep : AbstractDragToolCommand { 8 | public Sweep(DragCompleteEventArgs arguments) : base(arguments) { } 9 | } 10 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Commands/Tools/Wrangle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Game.UI.Tools.Events; 3 | 4 | namespace MultiplayerMod.Multiplayer.Commands.Tools; 5 | 6 | [Serializable] 7 | public class Wrangle : AbstractDragToolCommand { 8 | public Wrangle(DragCompleteEventArgs arguments) : base(arguments) { } 9 | 10 | protected override void InvokeTool(CaptureTool tool) => 11 | tool.OnDragComplete(Arguments.CursorDown, Arguments.CursorUp); 12 | } 13 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Components/AssignedMultiplayerPlayer.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Multiplayer.Players; 2 | using UnityEngine; 3 | 4 | namespace MultiplayerMod.Multiplayer.Components; 5 | 6 | public class AssignedMultiplayerPlayer : MonoBehaviour { 7 | 8 | public MultiplayerPlayer Player { get; set; } = null!; 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Components/DestroyOnPlayerLeave.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Core.Dependency; 2 | using MultiplayerMod.Core.Events; 3 | using MultiplayerMod.Core.Unity; 4 | using MultiplayerMod.Multiplayer.Players.Events; 5 | 6 | namespace MultiplayerMod.Multiplayer.Components; 7 | 8 | public class DestroyOnPlayerLeave : MultiplayerKMonoBehaviour { 9 | 10 | [InjectDependency] 11 | private readonly EventDispatcher events = null!; 12 | 13 | [MyCmpReq] 14 | private readonly AssignedMultiplayerPlayer playerComponent = null!; 15 | 16 | private EventSubscription subscription = null!; 17 | 18 | protected override void OnSpawn() { 19 | var player = playerComponent.Player; 20 | subscription = events.Subscribe(@event => { 21 | if (@event.Player == player) 22 | DestroyImmediate(gameObject); 23 | }); 24 | } 25 | 26 | protected override void OnForcedCleanUp() => subscription.Cancel(); 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/CoreOperations/CommandExecution/CommandConfigurationException.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Exceptions; 2 | 3 | namespace MultiplayerMod.Multiplayer.CoreOperations.CommandExecution; 4 | 5 | public class CommandConfigurationException : MultiplayerException { 6 | public CommandConfigurationException(string message) : base(message) { } 7 | } 8 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/CoreOperations/CommandExecution/CommandExceptionHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.ExceptionServices; 3 | using MultiplayerMod.Core.Logging; 4 | using MultiplayerMod.Multiplayer.Commands; 5 | using MultiplayerMod.Multiplayer.Objects; 6 | using MultiplayerMod.Multiplayer.Objects.Reference; 7 | 8 | namespace MultiplayerMod.Multiplayer.CoreOperations.CommandExecution; 9 | 10 | public class CommandExceptionHandler { 11 | 12 | private static readonly Core.Logging.Logger log = LoggerFactory.GetLogger(); 13 | 14 | public void Handle(IMultiplayerCommand command, Exception exception) { 15 | MultiplayerObjectsDebugHelper.LogDump(); 16 | switch (exception) { 17 | case ObjectNotFoundException e: 18 | log.Warning($"Multiplayer object {e.Reference} not found in command {command.GetType().FullName}"); 19 | return; 20 | default: 21 | ExceptionDispatchInfo.Capture(exception).Throw(); 22 | return; 23 | } 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/CoreOperations/Events/ConnectionLostEvent.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Core.Events; 2 | 3 | namespace MultiplayerMod.Multiplayer.CoreOperations.Events; 4 | 5 | public record ConnectionLostEvent : IDispatchableEvent; 6 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/CoreOperations/Events/GameQuitEvent.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Core.Events; 2 | 3 | namespace MultiplayerMod.Multiplayer.CoreOperations.Events; 4 | 5 | public record GameQuitEvent : IDispatchableEvent; 6 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/CoreOperations/Events/GameReadyEvent.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Core.Events; 2 | 3 | namespace MultiplayerMod.Multiplayer.CoreOperations.Events; 4 | 5 | public record GameReadyEvent : IDispatchableEvent; 6 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/CoreOperations/Events/GameStartedEvent.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Core.Events; 2 | 3 | namespace MultiplayerMod.Multiplayer.CoreOperations.Events; 4 | 5 | public record GameStartedEvent(MultiplayerGame Multiplayer) : IDispatchableEvent; 6 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/CoreOperations/Events/MultiplayerModeSelectedEvent.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Core.Events; 2 | 3 | namespace MultiplayerMod.Multiplayer.CoreOperations.Events; 4 | 5 | public record MultiplayerModeSelectedEvent(MultiplayerMode Mode) : IDispatchableEvent; 6 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/CoreOperations/Events/SinglePlayerModeSelectedEvent.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Core.Events; 2 | 3 | namespace MultiplayerMod.Multiplayer.CoreOperations.Events; 4 | 5 | public record SinglePlayerModeSelectedEvent : IDispatchableEvent; 6 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/CoreOperations/Events/StopMultiplayerEvent.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Core.Events; 2 | 3 | namespace MultiplayerMod.Multiplayer.CoreOperations.Events; 4 | 5 | public record StopMultiplayerEvent(MultiplayerGame Multiplayer) : IDispatchableEvent; 6 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/CoreOperations/Events/WorldLoadingEvent.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Core.Events; 2 | 3 | namespace MultiplayerMod.Multiplayer.CoreOperations.Events; 4 | 5 | public class WorldLoadingEvent : IDispatchableEvent; 6 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/CoreOperations/Events/WorldSavedEvent.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Core.Events; 2 | 3 | namespace MultiplayerMod.Multiplayer.CoreOperations.Events; 4 | 5 | public record WorldSavedEvent : IDispatchableEvent; 6 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/CoreOperations/Events/WorldStateInitializingEvent.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Core.Events; 2 | 3 | namespace MultiplayerMod.Multiplayer.CoreOperations.Events; 4 | 5 | public record WorldStateInitializingEvent : IDispatchableEvent; 6 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/CoreOperations/MultiplayerGameObjectsSpawner.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | using MultiplayerMod.Core.Dependency; 3 | using MultiplayerMod.Core.Events; 4 | using MultiplayerMod.Multiplayer.Components; 5 | using MultiplayerMod.Multiplayer.CoreOperations.Events; 6 | using MultiplayerMod.Multiplayer.World.Debug; 7 | using UnityEngine; 8 | 9 | namespace MultiplayerMod.Multiplayer.CoreOperations; 10 | 11 | [Dependency, UsedImplicitly] 12 | public class MultiplayerGameObjectsSpawner { 13 | 14 | public MultiplayerGameObjectsSpawner(EventDispatcher events) { 15 | events.Subscribe(OnGameStarted); 16 | } 17 | 18 | // ReSharper disable once ObjectCreationAsStatement 19 | private void OnGameStarted(GameStartedEvent _) { 20 | var components = new[] { 21 | #if DEBUG 22 | typeof(WorldDebugSnapshotRunner), 23 | #endif 24 | typeof(CursorManager), 25 | typeof(MultiplayerPlayerNotifier) 26 | }; 27 | new GameObject("Multiplayer", components); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/CoreOperations/PlayersManagement/ClientInitializationRequestEvent.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Core.Events; 2 | using MultiplayerMod.Multiplayer.Players; 3 | using MultiplayerMod.Network; 4 | 5 | namespace MultiplayerMod.Multiplayer.CoreOperations.PlayersManagement; 6 | 7 | public record ClientInitializationRequestEvent( 8 | IMultiplayerClientId ClientId, 9 | PlayerProfile Profile 10 | ) : IDispatchableEvent; 11 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/CoreOperations/PlayersManagement/Commands/AddPlayerCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Multiplayer.Commands; 3 | using MultiplayerMod.Multiplayer.Players; 4 | using MultiplayerMod.Multiplayer.Players.Events; 5 | 6 | namespace MultiplayerMod.Multiplayer.CoreOperations.PlayersManagement.Commands; 7 | 8 | [Serializable] 9 | [MultiplayerCommand(Type = MultiplayerCommandType.System)] 10 | public class AddPlayerCommand : MultiplayerCommand { 11 | 12 | private readonly MultiplayerPlayer player; 13 | private readonly bool current; 14 | 15 | public AddPlayerCommand(MultiplayerPlayer player, bool current) { 16 | this.player = player; 17 | this.current = current; 18 | } 19 | 20 | public override void Execute(MultiplayerCommandContext context) { 21 | context.Multiplayer.Players.Add(player); 22 | if (current) { 23 | context.Multiplayer.Players.SetCurrentPlayerId(player.Id); 24 | context.EventDispatcher.Dispatch(new CurrentPlayerInitializedEvent(player)); 25 | } 26 | context.EventDispatcher.Dispatch(new PlayerJoinedEvent(player)); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/CoreOperations/PlayersManagement/Commands/InitializeClientCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Core.Logging; 3 | using MultiplayerMod.Multiplayer.Commands; 4 | using MultiplayerMod.Multiplayer.Players; 5 | 6 | namespace MultiplayerMod.Multiplayer.CoreOperations.PlayersManagement.Commands; 7 | 8 | [Serializable] 9 | [MultiplayerCommand(Type = MultiplayerCommandType.System, ExecuteOnServer = true)] 10 | public class InitializeClientCommand : MultiplayerCommand { 11 | 12 | private static Core.Logging.Logger log = LoggerFactory.GetLogger(); 13 | 14 | private PlayerProfile profile; 15 | 16 | public InitializeClientCommand(PlayerProfile profile) { 17 | this.profile = profile; 18 | } 19 | 20 | public override void Execute(MultiplayerCommandContext context) { 21 | if (context.ClientId == null) { 22 | log.Error("Missing client id. Unable to initialize a player."); 23 | return; 24 | } 25 | context.EventDispatcher.Dispatch(new ClientInitializationRequestEvent(context.ClientId, profile)); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/CoreOperations/PlayersManagement/Commands/RemovePlayerCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Multiplayer.Commands; 3 | using MultiplayerMod.Multiplayer.Players; 4 | using MultiplayerMod.Multiplayer.Players.Events; 5 | 6 | namespace MultiplayerMod.Multiplayer.CoreOperations.PlayersManagement.Commands; 7 | 8 | [Serializable] 9 | [MultiplayerCommand(Type = MultiplayerCommandType.System)] 10 | public class RemovePlayerCommand : MultiplayerCommand { 11 | 12 | private PlayerIdentity playerId; 13 | 14 | public RemovePlayerCommand(PlayerIdentity playerId) { 15 | this.playerId = playerId; 16 | } 17 | 18 | public override void Execute(MultiplayerCommandContext context) { 19 | var player = context.Multiplayer.Players[playerId]; 20 | context.Multiplayer.Players.Remove(playerId); 21 | context.EventDispatcher.Dispatch(new PlayerLeftEvent(player, player.State == PlayerState.Leaving)); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/CoreOperations/PlayersManagement/Commands/RequestPlayerStateChangeCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Multiplayer.Commands; 3 | using MultiplayerMod.Multiplayer.Players; 4 | using MultiplayerMod.Network; 5 | 6 | namespace MultiplayerMod.Multiplayer.CoreOperations.PlayersManagement.Commands; 7 | 8 | [Serializable] 9 | [MultiplayerCommand(Type = MultiplayerCommandType.System, ExecuteOnServer = true)] 10 | public class RequestPlayerStateChangeCommand : MultiplayerCommand { 11 | 12 | private PlayerIdentity playerId; 13 | private PlayerState state; 14 | 15 | public RequestPlayerStateChangeCommand(PlayerIdentity playerId, PlayerState state) { 16 | this.playerId = playerId; 17 | this.state = state; 18 | } 19 | 20 | public override void Execute(MultiplayerCommandContext context) { 21 | var server = context.Runtime.Dependencies.Get(); 22 | server.Send(new ChangePlayerStateCommand(playerId, state)); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/CoreOperations/PlayersManagement/Commands/RequestWorldSyncCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Multiplayer.Commands; 3 | 4 | namespace MultiplayerMod.Multiplayer.CoreOperations.PlayersManagement.Commands; 5 | 6 | [Serializable] 7 | [MultiplayerCommand(ExecuteOnServer = true)] 8 | public class RequestWorldSyncCommand : MultiplayerCommand { 9 | 10 | public override void Execute(MultiplayerCommandContext context) { 11 | context.EventDispatcher.Dispatch(new WorldSyncRequestedEvent()); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/CoreOperations/PlayersManagement/Commands/SyncPlayersCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Multiplayer.Commands; 3 | using MultiplayerMod.Multiplayer.Players; 4 | using MultiplayerMod.Multiplayer.Players.Events; 5 | 6 | namespace MultiplayerMod.Multiplayer.CoreOperations.PlayersManagement.Commands; 7 | 8 | [Serializable] 9 | [MultiplayerCommand(Type = MultiplayerCommandType.System)] 10 | public class SyncPlayersCommand : MultiplayerCommand { 11 | 12 | private MultiplayerPlayer[] players; 13 | 14 | public SyncPlayersCommand(MultiplayerPlayer[] players) { 15 | this.players = players; 16 | } 17 | 18 | public override void Execute(MultiplayerCommandContext context) { 19 | context.Multiplayer.Players.Synchronize(players); 20 | context.EventDispatcher.Dispatch(new PlayersUpdatedEvent(context.Multiplayer.Players)); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/CoreOperations/PlayersManagement/CurrentPlayerInitializedEvent.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Core.Events; 2 | using MultiplayerMod.Multiplayer.Players; 3 | 4 | namespace MultiplayerMod.Multiplayer.CoreOperations.PlayersManagement; 5 | 6 | public record CurrentPlayerInitializedEvent(MultiplayerPlayer Player) : IDispatchableEvent; 7 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/CoreOperations/PlayersManagement/PlayersManagementException.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Exceptions; 2 | 3 | namespace MultiplayerMod.Multiplayer.CoreOperations.PlayersManagement; 4 | 5 | public class PlayersManagementException : MultiplayerException { 6 | public PlayersManagementException(string message) : base(message) { } 7 | } 8 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/CoreOperations/PlayersManagement/WorldSyncRequestedEvent.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Core.Events; 2 | 3 | namespace MultiplayerMod.Multiplayer.CoreOperations.PlayersManagement; 4 | 5 | public record WorldSyncRequestedEvent : IDispatchableEvent; 6 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/CoreOperations/RequireMultiplayerModeAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AttributeProcessor.Annotations; 3 | using JetBrains.Annotations; 4 | using MultiplayerMod.Core.Dependency; 5 | using MultiplayerMod.ModRuntime; 6 | 7 | namespace MultiplayerMod.Multiplayer.CoreOperations; 8 | 9 | [DependenciesStaticTarget] 10 | [AttributeUsage(AttributeTargets.Method)] 11 | [ConditionalInvocation(typeof(RequireMultiplayerModeAttribute), nameof(CheckMultiplayerGameMode))] 12 | public class RequireMultiplayerModeAttribute(MultiplayerMode mode) : Attribute { 13 | 14 | [InjectDependency] 15 | private static readonly MultiplayerGame multiplayer = null!; 16 | 17 | [UsedImplicitly] 18 | public MultiplayerMode Mode { get; } = mode; 19 | 20 | private static bool CheckMultiplayerGameMode(MultiplayerMode mode) => multiplayer.Mode == mode; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/IMultiplayerOperations.cs: -------------------------------------------------------------------------------- 1 | namespace MultiplayerMod.Multiplayer; 2 | 3 | public interface IMultiplayerOperations { 4 | void Join(); 5 | } 6 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/MultiplayerGame.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | using MultiplayerMod.Core.Dependency; 3 | using MultiplayerMod.Multiplayer.Objects; 4 | using MultiplayerMod.Multiplayer.Players; 5 | 6 | namespace MultiplayerMod.Multiplayer; 7 | 8 | [Dependency, UsedImplicitly] 9 | public class MultiplayerGame { 10 | 11 | public MultiplayerMode Mode { get; private set; } 12 | public MultiplayerPlayers Players { get; private set; } = null!; 13 | public MultiplayerObjects Objects { get; private set; } 14 | 15 | public MultiplayerGame(MultiplayerObjects multiplayerObjects) { 16 | Objects = multiplayerObjects; 17 | Refresh(MultiplayerMode.Client); 18 | } 19 | 20 | public void Refresh(MultiplayerMode mode) { 21 | Mode = mode; 22 | Players = new MultiplayerPlayers(); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/MultiplayerMode.cs: -------------------------------------------------------------------------------- 1 | namespace MultiplayerMod.Multiplayer; 2 | 3 | public enum MultiplayerMode { 4 | Host, 5 | Client 6 | } 7 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Objects/Extensions/ChoreExtensions.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Core.Dependency; 2 | using MultiplayerMod.ModRuntime; 3 | using MultiplayerMod.Multiplayer.Objects.Reference; 4 | 5 | namespace MultiplayerMod.Multiplayer.Objects.Extensions; 6 | 7 | [DependenciesStaticTarget] 8 | public static class ChoreExtensions { 9 | 10 | [InjectDependency] 11 | private static readonly MultiplayerObjects objects = null!; 12 | 13 | public static MultiplayerId Register( 14 | this Chore chore, 15 | MultiplayerId? multiplayerId = null, 16 | bool persistent = false 17 | ) => objects.Register(chore, multiplayerId, persistent).Id; 18 | 19 | public static MultiplayerId MultiplayerId(this Chore chore) => objects.Get(chore)!.Id; 20 | 21 | public static ChoreReference GetReference(this Chore chore) => new(chore); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Objects/Extensions/ComponentReferenceExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using MultiplayerMod.Multiplayer.Objects.Reference; 3 | 4 | namespace MultiplayerMod.Multiplayer.Objects.Extensions; 5 | 6 | public static class ComponentReferenceExtensions { 7 | 8 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 9 | public static ComponentReference GetReference(this KMonoBehaviour component) => new( 10 | component.gameObject.GetReference(), 11 | component.GetType() 12 | ); 13 | 14 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 15 | public static ComponentReference GetReference(this T component) where T : KMonoBehaviour => 16 | new(component.gameObject.GetReference()); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Objects/Extensions/GameObjectExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using MultiplayerMod.Multiplayer.Objects.Reference; 3 | using UnityEngine; 4 | 5 | namespace MultiplayerMod.Multiplayer.Objects.Extensions; 6 | 7 | public static class GameObjectExtensions { 8 | 9 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 10 | public static GameObjectReference GetReference(this GameObject gameObject) { 11 | var multiplayerId = gameObject.GetComponent().Id; 12 | if (multiplayerId != null) 13 | return new MultiplayerIdReference(multiplayerId); 14 | 15 | return new GridReference(gameObject); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Objects/Extensions/StateMachineReferenceExtensions.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Core.Reflection; 2 | using MultiplayerMod.Multiplayer.Objects.Reference; 3 | 4 | namespace MultiplayerMod.Multiplayer.Objects.Extensions; 5 | 6 | public static class StateMachineReferenceExtensions { 7 | 8 | public static StateMachineReference GetReference(this StateMachine.Instance instance) => new( 9 | GetStateMachineController(instance).GetReference(), 10 | instance.GetType() 11 | ); 12 | 13 | // `controller` field is defined in StateMachine<,,,>.GenericInstance. However cast is impossible due to unknown 14 | // generic argument types. So reflection is the most handy way to get its value :( 15 | private static StateMachineController GetStateMachineController(StateMachine.Instance instance) => 16 | instance.GetFieldValue("controller"); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Objects/InternalMultiplayerIdType.cs: -------------------------------------------------------------------------------- 1 | namespace MultiplayerMod.Multiplayer.Objects; 2 | 3 | public enum InternalMultiplayerIdType : byte { 4 | KPrefabId = 0, 5 | Chore = 1 6 | } 7 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Objects/MultiplayerIdType.cs: -------------------------------------------------------------------------------- 1 | namespace MultiplayerMod.Multiplayer.Objects; 2 | 3 | public enum MultiplayerIdType : byte { 4 | Internal = 0, 5 | Generated = 1 6 | } 7 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Objects/MultiplayerObject.cs: -------------------------------------------------------------------------------- 1 | namespace MultiplayerMod.Multiplayer.Objects; 2 | 3 | public class MultiplayerObject(MultiplayerId id, int generation, bool persistent = true) { 4 | 5 | public MultiplayerId Id { get; } = id; 6 | 7 | public bool Persistent { get; } = persistent; 8 | public int Generation { get; } = generation; 9 | 10 | protected bool Equals(MultiplayerObject other) => Id.Equals(other.Id); 11 | 12 | public override bool Equals(object? other) { 13 | if (ReferenceEquals(null, other)) 14 | return false; 15 | if (ReferenceEquals(this, other)) 16 | return true; 17 | 18 | return other.GetType() == GetType() && Equals((MultiplayerObject) other); 19 | } 20 | 21 | public override int GetHashCode() => Id.GetHashCode(); 22 | 23 | public static bool operator ==(MultiplayerObject? left, MultiplayerObject? right) => Equals(left, right); 24 | 25 | public static bool operator !=(MultiplayerObject? left, MultiplayerObject? right) => !Equals(left, right); 26 | 27 | public override string ToString() => $"{(Persistent ? "[P] " : "")}{Id}"; 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Objects/Reference/ChoreReference.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Core.Dependency; 3 | using MultiplayerMod.ModRuntime; 4 | using MultiplayerMod.Multiplayer.Objects.Extensions; 5 | 6 | namespace MultiplayerMod.Multiplayer.Objects.Reference; 7 | 8 | [Serializable] 9 | [DependenciesStaticTarget] 10 | public class ChoreReference(Chore chore) : TypedReference { 11 | 12 | [InjectDependency] 13 | private static MultiplayerObjects objects = null!; 14 | 15 | private MultiplayerId id = chore.MultiplayerId(); 16 | 17 | public override Chore Resolve() => objects.Get(id)!; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Objects/Reference/GameObjectReference.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | namespace MultiplayerMod.Multiplayer.Objects.Reference; 5 | 6 | [Serializable] 7 | public abstract class GameObjectReference : TypedReference { 8 | 9 | protected abstract GameObject? ResolveGameObject(); 10 | 11 | public override GameObject Resolve() => ResolveGameObject() ?? throw new ObjectNotFoundException(this); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Objects/Reference/ObjectNotFoundException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Multiplayer.Objects.Reference; 4 | 5 | public class ObjectNotFoundException(Reference reference) : Exception { 6 | 7 | public Reference Reference { get; } = reference; 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Objects/Reference/Reference.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace MultiplayerMod.Multiplayer.Objects.Reference; 5 | 6 | public interface Reference { 7 | public abstract T Resolve(); 8 | } 9 | 10 | public interface Reference { 11 | public abstract object? Resolve(); 12 | } 13 | 14 | [Serializable] 15 | public abstract class TypedReference : Reference, Reference { 16 | public abstract T Resolve(); 17 | object? Reference.Resolve() => Resolve(); 18 | } 19 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Objects/Support/MinionStorageRedirectionSupport.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | using JetBrains.Annotations; 3 | using MultiplayerMod.ModRuntime.Context; 4 | using UnityEngine; 5 | 6 | namespace MultiplayerMod.Multiplayer.Objects.Support; 7 | 8 | [UsedImplicitly] 9 | [HarmonyPatch(typeof(MinionStorage))] 10 | public static class MinionStorageRedirectionSupport { 11 | 12 | [HarmonyPostfix, UsedImplicitly] 13 | [HarmonyPatch(nameof(MinionStorage.RedirectInstanceTracker))] 14 | [RequireExecutionLevel(ExecutionLevel.Multiplayer)] 15 | private static void RedirectInstanceTracker(GameObject src_minion, GameObject dest_minion) { 16 | src_minion.GetComponent().Redirect(dest_minion.GetComponent()); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Players/Events/MultiplayerJoinRequestedEvent.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Core.Events; 2 | using MultiplayerMod.Network; 3 | 4 | namespace MultiplayerMod.Multiplayer.Players.Events; 5 | 6 | public record MultiplayerJoinRequestedEvent(IMultiplayerEndpoint Endpoint, string HostName) : IDispatchableEvent; 7 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Players/Events/PlayerCursorPositionUpdatedEvent.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Core.Events; 2 | using MultiplayerMod.Game.UI.Tools.Events; 3 | 4 | namespace MultiplayerMod.Multiplayer.Players.Events; 5 | 6 | public record PlayerCursorPositionUpdatedEvent( 7 | MultiplayerPlayer Player, 8 | InterfaceToolEvents.MouseMovedEventArgs MouseMovedEventArgs 9 | ) : IDispatchableEvent; 10 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Players/Events/PlayerJoinedEvent.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Core.Events; 2 | 3 | namespace MultiplayerMod.Multiplayer.Players.Events; 4 | 5 | public record PlayerJoinedEvent(MultiplayerPlayer Player) : IDispatchableEvent; 6 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Players/Events/PlayerLeftEvent.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Core.Events; 2 | 3 | namespace MultiplayerMod.Multiplayer.Players.Events; 4 | 5 | public record PlayerLeftEvent(MultiplayerPlayer Player, bool Gracefully) : IDispatchableEvent; 6 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Players/Events/PlayerStateChangedEvent.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Core.Events; 2 | 3 | namespace MultiplayerMod.Multiplayer.Players.Events; 4 | 5 | public record PlayerStateChangedEvent(MultiplayerPlayer Player, PlayerState State) : IDispatchableEvent; 6 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Players/Events/PlayersReadyEvent.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Core.Events; 2 | 3 | namespace MultiplayerMod.Multiplayer.Players.Events; 4 | 5 | public record PlayersReadyEvent(MultiplayerGame Multiplayer) : IDispatchableEvent; 6 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Players/Events/PlayersUpdatedEvent.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Core.Events; 2 | 3 | namespace MultiplayerMod.Multiplayer.Players.Events; 4 | 5 | public record PlayersUpdatedEvent(MultiplayerPlayers Players) : IDispatchableEvent; 6 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Players/IPlayerProfileProvider.cs: -------------------------------------------------------------------------------- 1 | namespace MultiplayerMod.Multiplayer.Players; 2 | 3 | public interface IPlayerProfileProvider { 4 | PlayerProfile GetPlayerProfile(); 5 | } 6 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Players/PlayerIdentity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Multiplayer.Players; 4 | 5 | [Serializable] 6 | public record PlayerIdentity { 7 | public Guid Value { get; } = Guid.NewGuid(); 8 | 9 | public override string ToString() => Value.ToString(); 10 | } 11 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Players/PlayerProfile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Multiplayer.Players; 4 | 5 | [Serializable] 6 | public record PlayerProfile(string PlayerName); 7 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Players/PlayerRole.cs: -------------------------------------------------------------------------------- 1 | namespace MultiplayerMod.Multiplayer.Players; 2 | 3 | public enum PlayerRole { 4 | Host, 5 | Client 6 | } 7 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Players/PlayerState.cs: -------------------------------------------------------------------------------- 1 | namespace MultiplayerMod.Multiplayer.Players; 2 | 3 | public enum PlayerState { 4 | Initializing, 5 | Loading, 6 | Ready, 7 | Leaving 8 | } 9 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/StateMachines/Commands/GoToState.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Multiplayer.Commands; 3 | using MultiplayerMod.Multiplayer.Objects.Reference; 4 | using MultiplayerMod.Multiplayer.StateMachines.RuntimeTools; 5 | 6 | namespace MultiplayerMod.Multiplayer.StateMachines.Commands; 7 | 8 | [Serializable] 9 | public class GoToState : MultiplayerCommand { 10 | 11 | private readonly Reference reference; 12 | private readonly string? stateName; 13 | 14 | public GoToState(Reference reference, StateMachine.BaseState? state) : this( 15 | reference, 16 | state?.name 17 | ) { } 18 | 19 | public GoToState(Reference reference, string? stateName) { 20 | this.stateName = stateName; 21 | this.reference = reference; 22 | } 23 | 24 | public override void Execute(MultiplayerCommandContext context) { 25 | StateMachineRuntimeTools.Get(reference.Resolve()).GoToState(stateName); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/StateMachines/Configuration/Configurers/InvalidStateExpressionException.cs: -------------------------------------------------------------------------------- 1 | namespace MultiplayerMod.Multiplayer.StateMachines.Configuration.Configurers; 2 | 3 | public class InvalidStateExpressionException(string message) : StateMachineConfigurationException(message); 4 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/StateMachines/Configuration/Configurers/StateMachineConfigurerDelegate.cs: -------------------------------------------------------------------------------- 1 | namespace MultiplayerMod.Multiplayer.StateMachines.Configuration.Configurers; 2 | 3 | public delegate void StateMachineConfigurerDelegate( 4 | IStateMachineRootConfigurer root 5 | ) 6 | where TStateMachine : GameStateMachine 7 | where TStateMachineInstance : GameStateMachine.GameInstance 8 | where TMaster : IStateMachineTarget; 9 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/StateMachines/Configuration/Configurers/StateMachinePostConfigurer.cs: -------------------------------------------------------------------------------- 1 | namespace MultiplayerMod.Multiplayer.StateMachines.Configuration.Configurers; 2 | 3 | public class StateMachinePostConfigurer( 4 | StateMachineRootConfigurer root, 5 | StateMachineConfigurationContext context, 6 | TStateMachine stateMachine 7 | ) : StateMachineBaseConfigurer(root, context, stateMachine) 8 | where TStateMachine : GameStateMachine 9 | where TStateMachineInstance : GameStateMachine.GameInstance 10 | where TMaster : IStateMachineTarget; 11 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/StateMachines/Configuration/InvalidConfigurationPhaseException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Core.Extensions; 3 | 4 | namespace MultiplayerMod.Multiplayer.StateMachines.Configuration; 5 | 6 | public class InvalidConfigurationPhaseException( 7 | Type stateMachineType, 8 | StateMachineConfigurationPhase targetPhase, 9 | StateMachineConfigurationPhase currentPhase 10 | ) : StateMachineConfigurationException( 11 | $"Configuration for {stateMachineType.GetSignature()} " + 12 | $"produced an action targeting \"{targetPhase}\" phase " + 13 | $"that wouldn't be run after the current \"{currentPhase}\" phase" 14 | ); 15 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/StateMachines/Configuration/InvalidStateExpressionException.cs: -------------------------------------------------------------------------------- 1 | namespace MultiplayerMod.Multiplayer.StateMachines.Configuration; 2 | 3 | public class ConfigurationContextLockedException(string message) : StateMachineConfigurationException(message); 4 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/StateMachines/Configuration/Parameters/StateMachineMultiplayerParameterInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Multiplayer.StateMachines.Configuration.Parameters; 4 | 5 | public class StateMachineMultiplayerParameterInfo(string name, bool shared = true, T? defaultValue = default) { 6 | public string Name { get; } = name; 7 | public Type ValueType { get; } = typeof(T); 8 | public bool Shared { get; } = shared; 9 | public object? DefaultValue { get; } = defaultValue; 10 | } 11 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/StateMachines/Configuration/StateMachineConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace MultiplayerMod.Multiplayer.StateMachines.Configuration; 5 | 6 | public record StateMachineConfiguration( 7 | Type MasterType, 8 | Type StateMachineType, 9 | List Actions 10 | ); 11 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/StateMachines/Configuration/StateMachineConfigurationAction.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Multiplayer.StateMachines.Configuration; 4 | 5 | public record StateMachineConfigurationAction( 6 | StateMachineConfigurationPhase Phase, 7 | Action Configure 8 | ); 9 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/StateMachines/Configuration/StateMachineConfigurationException.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Exceptions; 2 | 3 | namespace MultiplayerMod.Multiplayer.StateMachines.Configuration; 4 | 5 | public class StateMachineConfigurationException(string message) : MultiplayerException(message); 6 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/StateMachines/Configuration/StateMachineConfigurationPhase.cs: -------------------------------------------------------------------------------- 1 | namespace MultiplayerMod.Multiplayer.StateMachines.Configuration; 2 | 3 | public enum StateMachineConfigurationPhase { 4 | PreConfiguration, 5 | ControlFlowApply, 6 | ControlFlowReset, 7 | PostConfiguration 8 | } 9 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/StateMachines/Configuration/States/StateMachineMultiplayerStateInfo.cs: -------------------------------------------------------------------------------- 1 | namespace MultiplayerMod.Multiplayer.StateMachines.Configuration.States; 2 | 3 | public class StateMachineMultiplayerStateInfo(string name) { 4 | public string Name { get; } = name; 5 | public string ReferenceName { get; } = $"root.{name}"; 6 | } 7 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/StateMachines/RuntimeTools/StateMachineRuntimeParameter.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using MultiplayerMod.Game.NameOf; 3 | 4 | namespace MultiplayerMod.Multiplayer.StateMachines.RuntimeTools; 5 | 6 | public class StateMachineRuntimeParameter(StateMachine.Instance instance, StateMachine.Parameter parameter) { 7 | 8 | private readonly MethodBase setter = parameter.GetType() 9 | .GetMethod(nameof(StateMachineMemberReference.Parameter.Set))!; 10 | 11 | public void Set(T value, bool silenceEvents = false) => setter.Invoke(parameter, [value, instance, silenceEvents]); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/StateMachines/RuntimeTools/StateMachineStateNotFoundException.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Exceptions; 2 | 3 | namespace MultiplayerMod.Multiplayer.StateMachines.RuntimeTools; 4 | 5 | public class StateMachineStateNotFoundException(StateMachine stateMachine, string name) : MultiplayerException( 6 | $"State \"{name}\" not found in \"{stateMachine.name}\"" 7 | ); 8 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/States/ContinuationState.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Game.NameOf; 2 | 3 | namespace MultiplayerMod.Multiplayer.States; 4 | 5 | public class 6 | ContinuationState : StateMachine.State 8 | where StateMachineInstanceType : StateMachine.Instance 9 | where MasterType : IStateMachineTarget { 10 | 11 | public ContinuationState(StateMachine sm, StateMachine.BaseState original) { 12 | name = StatesManager.ContinuationName; 13 | 14 | // enter and update actions must be synced differently. 15 | exitActions = original.exitActions; 16 | defaultState = original.defaultState; 17 | 18 | var stateMachineType = sm.GetType(); 19 | var root = stateMachineType.GetField(nameof(StateMachineMemberReference.root)).GetValue(sm); 20 | stateMachineType.GetMethod(nameof(StateMachineMemberReference.BindState))! 21 | .Invoke(sm, new[] { root, this, name }); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/States/IWaitHostState.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace MultiplayerMod.Multiplayer.States; 4 | 5 | public interface IWaitHostState { 6 | void AllowTransition(StateMachine.Instance smi, string? target, Dictionary args); 7 | } 8 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/Tools/CommandRateThrottle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using MultiplayerMod.Multiplayer.Commands; 4 | 5 | namespace MultiplayerMod.Multiplayer.Tools; 6 | 7 | public class CommandRateThrottle { 8 | 9 | private readonly TimeSpan period; 10 | private readonly Dictionary lastInvokedByType = new(); 11 | 12 | public CommandRateThrottle(int rate) { 13 | period = new TimeSpan(10000000 / rate); 14 | } 15 | 16 | public void Run(System.Action action) where T : IMultiplayerCommand { 17 | lastInvokedByType.TryGetValue(typeof(T), out var lastInvoked); 18 | if (System.DateTime.Now - lastInvoked < period) 19 | return; 20 | action(); 21 | lastInvokedByType[typeof(T)] = System.DateTime.Now; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/UI/Dev/DevToolsConfigurer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JetBrains.Annotations; 3 | using MultiplayerMod.Core.Dependency; 4 | using MultiplayerMod.Core.Events; 5 | using MultiplayerMod.Core.Logging; 6 | using MultiplayerMod.ModRuntime; 7 | 8 | namespace MultiplayerMod.Multiplayer.UI.Dev; 9 | 10 | [UsedImplicitly] 11 | [Dependency] 12 | public class DevToolsConfigurer { 13 | 14 | private readonly Core.Logging.Logger log = LoggerFactory.GetLogger(); 15 | 16 | public DevToolsConfigurer(EventDispatcher events) { 17 | if (Type.GetType("DevTool, Assembly-CSharp") == null) { 18 | log.Info("Dev tools are unavailable on this platform"); 19 | return; 20 | } 21 | 22 | events.Subscribe(OnRuntimeReady); 23 | } 24 | 25 | private void OnRuntimeReady(RuntimeReadyEvent @event) { 26 | DevToolManager.Instance.RegisterDevTool("Multiplayer/Objects"); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/UI/Diagnostics/ColonyDiagnosticScreenPatch.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | using JetBrains.Annotations; 3 | using MultiplayerMod.ModRuntime.Context; 4 | 5 | namespace MultiplayerMod.Multiplayer.UI.Diagnostics; 6 | 7 | [UsedImplicitly] 8 | [HarmonyPatch(typeof(ColonyDiagnosticScreen))] 9 | public class ColonyDiagnosticScreenPatch { 10 | 11 | [UsedImplicitly] 12 | [HarmonyPrefix] 13 | [HarmonyPatch(nameof(ColonyDiagnosticScreen.SpawnTrackerLines))] 14 | [RequireExecutionLevel(ExecutionLevel.Multiplayer)] 15 | [RequireDebugBuild] 16 | public static void SpawnTrackerLines(ColonyDiagnosticScreen __instance, int world) { 17 | __instance.AddDiagnostic( 18 | world, 19 | __instance.contentContainer, 20 | __instance.diagnosticRows 21 | ); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/UI/Notifications.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | using MultiplayerMod.Core.Dependency; 3 | using MultiplayerMod.Core.Events; 4 | using MultiplayerMod.Multiplayer.CoreOperations.Events; 5 | 6 | namespace MultiplayerMod.Multiplayer.UI; 7 | 8 | [Dependency, UsedImplicitly] 9 | public class Notifications { 10 | 11 | public Notifications(EventDispatcher events) { 12 | events.Subscribe(OnConnectionLost); 13 | } 14 | 15 | private void OnConnectionLost(ConnectionLostEvent @event) { 16 | var screen = (InfoDialogScreen) GameScreenManager.Instance.StartScreen( 17 | ScreenPrefabs.Instance.InfoDialogScreen.gameObject, 18 | GameScreenManager.Instance.ssOverlayCanvas.gameObject 19 | ); 20 | screen.SetHeader("Multiplayer"); 21 | screen.AddPlainText("Connection has been lost. Further play can not be synced"); 22 | screen.AddOption( 23 | "OK", 24 | _ => PauseScreen.Instance.OnQuitConfirm() 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/UI/WorldTrackers/MultiplayerErrorsTracker.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Multiplayer.World.Debug; 2 | 3 | namespace MultiplayerMod.Multiplayer.UI.WorldTrackers; 4 | 5 | public class MultiplayerErrorsTracker : WorldTracker { 6 | 7 | public MultiplayerErrorsTracker(int worldId) : base(worldId) { } 8 | 9 | public override void UpdateData() { 10 | AddPoint(WorldDebugSnapshotRunner.ErrorsCount); 11 | } 12 | 13 | public override string FormatValueString(float value) => value.ToString(); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/UI/WorldTrackers/TrackerToolPatch.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | 3 | namespace MultiplayerMod.Multiplayer.UI.WorldTrackers; 4 | 5 | [HarmonyPatch(typeof(TrackerTool))] 6 | // ReSharper disable once UnusedType.Global 7 | internal static class TrackerToolPatch { 8 | 9 | [HarmonyPostfix] 10 | [HarmonyPatch(nameof(TrackerTool.AddNewWorldTrackers))] 11 | // ReSharper disable once UnusedMember.Local 12 | private static void AddNewWorldTrackersPostfix(TrackerTool __instance, int worldID) { 13 | __instance.worldTrackers.Add(new MultiplayerErrorsTracker(worldID)); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/World/Commands/LoadWorld.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Multiplayer.Commands; 3 | using MultiplayerMod.Multiplayer.World.Data; 4 | 5 | namespace MultiplayerMod.Multiplayer.World.Commands; 6 | 7 | [Serializable] 8 | [MultiplayerCommand(Type = MultiplayerCommandType.System)] 9 | public class LoadWorld(WorldSave world) : MultiplayerCommand { 10 | 11 | public override void Execute(MultiplayerCommandContext context) { 12 | context.Runtime.Dependencies.Get().RequestWorldLoad(world); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/World/Commands/NotifyWorldSavePreparing.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Multiplayer.Commands; 3 | using MultiplayerMod.Multiplayer.UI.Overlays; 4 | 5 | namespace MultiplayerMod.Multiplayer.World.Commands; 6 | 7 | [Serializable] 8 | [MultiplayerCommand(Type = MultiplayerCommandType.System)] 9 | public class NotifyWorldSavePreparing : MultiplayerCommand { 10 | 11 | public override void Execute(MultiplayerCommandContext context) { 12 | MultiplayerStatusOverlay.Show("Waiting for the world..."); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/World/Data/WorldSave.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Multiplayer.World.Data; 4 | 5 | [Serializable] 6 | public record WorldSave(string Name, byte[] Data, WorldState State); 7 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/World/Data/WorldState.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace MultiplayerMod.Multiplayer.World.Data; 5 | 6 | [Serializable] 7 | public class WorldState { 8 | public Dictionary Entries { get; } = new(); 9 | } 10 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/World/Debug/DebugSnapshotAvailableEvent.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Core.Events; 2 | 3 | namespace MultiplayerMod.Multiplayer.World.Debug; 4 | 5 | public record DebugSnapshotAvailableEvent(WorldDebugSnapshot Snapshot) : IDispatchableEvent; 6 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/World/IWorldStateManager.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Multiplayer.World.Data; 2 | 3 | namespace MultiplayerMod.Multiplayer.World; 4 | 5 | public interface IWorldStateManager { 6 | void SaveState(WorldState data); 7 | void LoadState(WorldState data); 8 | } 9 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Multiplayer/World/WorldSyncEvent.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Core.Events; 2 | 3 | namespace MultiplayerMod.Multiplayer.World; 4 | 5 | public record WorldSyncEvent : IDispatchableEvent; 6 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Network/IMultiplayerClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Multiplayer.Commands; 3 | 4 | namespace MultiplayerMod.Network; 5 | 6 | public interface IMultiplayerClient { 7 | MultiplayerClientState State { get; } 8 | IMultiplayerClientId Id { get; } 9 | 10 | void Connect(IMultiplayerEndpoint endpoint); 11 | void Disconnect(); 12 | 13 | void Send(IMultiplayerCommand command, MultiplayerCommandOptions options = MultiplayerCommandOptions.None); 14 | 15 | event Action StateChanged; 16 | event Action CommandReceived; 17 | } 18 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Network/IMultiplayerClientId.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Network; 4 | 5 | public interface IMultiplayerClientId : IEquatable { } 6 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Network/IMultiplayerEndpoint.cs: -------------------------------------------------------------------------------- 1 | namespace MultiplayerMod.Network; 2 | 3 | public interface IMultiplayerEndpoint { } 4 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Network/IMultiplayerServer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using MultiplayerMod.Multiplayer.Commands; 4 | 5 | namespace MultiplayerMod.Network; 6 | 7 | public interface IMultiplayerServer { 8 | void Start(); 9 | void Stop(); 10 | 11 | MultiplayerServerState State { get; } 12 | IMultiplayerEndpoint Endpoint { get; } 13 | List Clients { get; } 14 | 15 | void Send(IMultiplayerClientId clientId, IMultiplayerCommand command); 16 | void Send(IMultiplayerCommand command, MultiplayerCommandOptions options = MultiplayerCommandOptions.None); 17 | 18 | event Action StateChanged; 19 | event Action ClientConnected; 20 | event Action ClientDisconnected; 21 | event Action CommandReceived; 22 | } 23 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Network/MultiplayerClientState.cs: -------------------------------------------------------------------------------- 1 | namespace MultiplayerMod.Network; 2 | 3 | public enum MultiplayerClientState { 4 | Error = -1, 5 | Disconnected, 6 | Connecting, 7 | Connected 8 | } 9 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Network/MultiplayerCommandOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Network; 4 | 5 | [Flags] 6 | public enum MultiplayerCommandOptions { 7 | 8 | /// 9 | /// Default command behavior. 10 | /// 11 | None = 0, 12 | 13 | /// 14 | /// A command will not be sent to host client. 15 | /// 16 | SkipHost = 1 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Network/MultiplayerServerState.cs: -------------------------------------------------------------------------------- 1 | namespace MultiplayerMod.Network; 2 | 3 | public enum MultiplayerServerState { 4 | Error = -1, 5 | Stopped, 6 | Preparing, 7 | Starting, 8 | Started 9 | } 10 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Platform/PlatformException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Exceptions; 3 | 4 | namespace MultiplayerMod.Platform; 5 | 6 | public class PlatformException : MultiplayerException { 7 | public PlatformException(string message) : base(message) { } 8 | public PlatformException(string message, Exception cause) : base(message, cause) { } 9 | } 10 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Platform/Steam/Network/Components/SteamClientComponent.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Core.Dependency; 2 | using MultiplayerMod.Core.Unity; 3 | 4 | // ReSharper disable FieldCanBeMadeReadOnly.Local 5 | 6 | namespace MultiplayerMod.Platform.Steam.Network.Components; 7 | 8 | public class SteamClientComponent : MultiplayerMonoBehaviour { 9 | 10 | [InjectDependency] 11 | private SteamClient client = null!; 12 | 13 | private void Update() => client.Tick(); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Platform/Steam/Network/Components/SteamServerComponent.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Core.Dependency; 2 | using MultiplayerMod.Core.Unity; 3 | 4 | // ReSharper disable FieldCanBeMadeReadOnly.Local 5 | 6 | namespace MultiplayerMod.Platform.Steam.Network.Components; 7 | 8 | public class SteamServerComponent : MultiplayerMonoBehaviour { 9 | 10 | [InjectDependency] 11 | private SteamServer server = null!; 12 | 13 | private void Update() => server.Tick(); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Platform/Steam/Network/Configuration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Platform.Steam.Network.Messaging; 3 | using Steamworks; 4 | 5 | namespace MultiplayerMod.Platform.Steam.Network; 6 | 7 | public static class Configuration { 8 | 9 | private const int defaultBufferSize = 10485760; // 10 MiB 10 | 11 | public static SteamNetworkingConfigValue_t SendBufferSize(int size = defaultBufferSize) => new() { 12 | m_eValue = ESteamNetworkingConfigValue.k_ESteamNetworkingConfig_SendBufferSize, 13 | m_eDataType = ESteamNetworkingConfigDataType.k_ESteamNetworkingConfig_Int32, 14 | m_val = new SteamNetworkingConfigValue_t.OptionValue { m_int32 = size } 15 | }; 16 | 17 | public const int MaxMessageSize = 524288; // 512 KiB 18 | public static readonly int MaxFragmentDataSize = GetFragmentDataSize(); 19 | 20 | private static int GetFragmentDataSize() { 21 | using var serialized = NetworkSerializer.Serialize(new NetworkMessageFragment(0, Array.Empty())); 22 | return MaxMessageSize - (int) serialized.Size; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Platform/Steam/Network/Extensions.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Platform.Steam.Network.Messaging; 2 | using Steamworks; 3 | 4 | namespace MultiplayerMod.Platform.Steam.Network; 5 | 6 | public static class SteamExtensions { 7 | 8 | public static INetworkMessageHandle GetNetworkMessageHandle(this SteamNetworkingMessage_t message) => 9 | new NetworkMessageHandle(message.m_pData, (uint) message.m_cbSize); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Platform/Steam/Network/Messaging/INetworkMessage.cs: -------------------------------------------------------------------------------- 1 | namespace MultiplayerMod.Platform.Steam.Network.Messaging; 2 | 3 | public interface INetworkMessage { } 4 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Platform/Steam/Network/Messaging/INetworkMessageHandle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Platform.Steam.Network.Messaging; 4 | 5 | public interface INetworkMessageHandle : IDisposable { 6 | public IntPtr Pointer { get; } 7 | public uint Size { get; } 8 | } 9 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Platform/Steam/Network/Messaging/NetworkMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Multiplayer.Commands; 3 | using MultiplayerMod.Network; 4 | 5 | namespace MultiplayerMod.Platform.Steam.Network.Messaging; 6 | 7 | [Serializable] 8 | public class NetworkMessage : INetworkMessage { 9 | 10 | public IMultiplayerCommand Command { get; } 11 | public MultiplayerCommandOptions Options { get; } 12 | 13 | public NetworkMessage(IMultiplayerCommand command, MultiplayerCommandOptions options) { 14 | Command = command; 15 | Options = options; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Platform/Steam/Network/Messaging/NetworkMessageFragment.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Platform.Steam.Network.Messaging; 4 | 5 | [Serializable] 6 | public class NetworkMessageFragment : INetworkMessage { 7 | 8 | public int MessageId { get; } 9 | public byte[] Data { get; } 10 | 11 | public NetworkMessageFragment(int messageId, byte[] data) { 12 | MessageId = messageId; 13 | Data = data; 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Platform/Steam/Network/Messaging/NetworkMessageFragmentsHeader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | 4 | namespace MultiplayerMod.Platform.Steam.Network.Messaging; 5 | 6 | [Serializable] 7 | public class NetworkMessageFragmentsHeader : INetworkMessage { 8 | 9 | public int MessageId { get; } 10 | public int FragmentsCount { get; } 11 | 12 | private static int uniqueMessageId; 13 | 14 | public NetworkMessageFragmentsHeader(int fragmentsCount) { 15 | MessageId = Interlocked.Increment(ref uniqueMessageId); 16 | FragmentsCount = fragmentsCount; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Platform/Steam/Network/Messaging/NetworkMessageHandle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Platform.Steam.Network.Messaging; 4 | 5 | public class NetworkMessageHandle : INetworkMessageHandle { 6 | 7 | public IntPtr Pointer { get; } 8 | public uint Size { get; } 9 | 10 | public NetworkMessageHandle(IntPtr pointer, uint size) { 11 | Pointer = pointer; 12 | Size = size; 13 | } 14 | 15 | public void Dispose() { 16 | // No disposal required 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Platform/Steam/Network/Messaging/NetworkSerializer.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Runtime.Serialization.Formatters.Binary; 3 | using MultiplayerMod.Platform.Steam.Network.Messaging.Surrogates; 4 | 5 | namespace MultiplayerMod.Platform.Steam.Network.Messaging; 6 | 7 | public static class NetworkSerializer { 8 | 9 | public static SerializedNetworkMessage Serialize(INetworkMessage message) { 10 | return new SerializedNetworkMessage(message); 11 | } 12 | 13 | public static unsafe INetworkMessage Deserialize(INetworkMessageHandle message) => 14 | (INetworkMessage) new BinaryFormatter { SurrogateSelector = SerializationSurrogates.Selector } 15 | .Deserialize( 16 | new UnmanagedMemoryStream((byte*) message.Pointer.ToPointer(), message.Size) 17 | ); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Platform/Steam/Network/Messaging/Surrogates/AssignmentGroupSurrogate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace MultiplayerMod.Platform.Steam.Network.Messaging.Surrogates; 5 | 6 | public class AssignmentGroupSurrogate : ISerializationSurrogate, ISurrogateType { 7 | 8 | public Type Type => typeof(AssignmentGroup); 9 | 10 | public void GetObjectData(object obj, SerializationInfo info, StreamingContext context) { 11 | var group = (AssignmentGroup) obj; 12 | info.AddValue("id", group.id); 13 | } 14 | 15 | public object? SetObjectData( 16 | object obj, 17 | SerializationInfo info, 18 | StreamingContext context, 19 | ISurrogateSelector selector 20 | ) { 21 | var id = info.GetString("id"); 22 | return global::Game.Instance.assignmentManager.assignment_groups[id]; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Platform/Steam/Network/Messaging/Surrogates/ChoreTypeSurrogate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace MultiplayerMod.Platform.Steam.Network.Messaging.Surrogates; 5 | 6 | public class ChoreTypeSurrogate : ISerializationSurrogate, ISurrogateType { 7 | 8 | public Type Type => typeof(ChoreType); 9 | 10 | public void GetObjectData(object obj, SerializationInfo info, StreamingContext context) { 11 | var choreType = (ChoreType) obj; 12 | info.AddValue("id", choreType.Id); 13 | } 14 | 15 | public object SetObjectData( 16 | object obj, 17 | SerializationInfo info, 18 | StreamingContext context, 19 | ISurrogateSelector selector 20 | ) { 21 | var id = info.GetString("id"); 22 | return Db.Get().ChoreTypes.Get(id); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Platform/Steam/Network/Messaging/Surrogates/ComplexRecipeSurrogate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Runtime.Serialization; 4 | 5 | namespace MultiplayerMod.Platform.Steam.Network.Messaging.Surrogates; 6 | 7 | public class ComplexRecipeSurrogate : ISerializationSurrogate, ISurrogateType { 8 | 9 | public Type Type => typeof(ComplexRecipe); 10 | 11 | public void GetObjectData(object obj, SerializationInfo info, StreamingContext context) { 12 | var recipe = (ComplexRecipe) obj; 13 | info.AddValue("id", recipe.id); 14 | } 15 | 16 | public object SetObjectData( 17 | object obj, 18 | SerializationInfo info, 19 | StreamingContext context, 20 | ISurrogateSelector selector 21 | ) { 22 | var id = info.GetString("id"); 23 | var recipe = ComplexRecipeManager.Get().recipes.Single(recipe => recipe.id == id); 24 | return recipe; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Platform/Steam/Network/Messaging/Surrogates/DeathSurrogate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace MultiplayerMod.Platform.Steam.Network.Messaging.Surrogates; 5 | 6 | public class DeathSurrogate : ISerializationSurrogate, ISurrogateType { 7 | 8 | public Type Type => typeof(Death); 9 | 10 | public void GetObjectData(object obj, SerializationInfo info, StreamingContext context) { 11 | var death = (Death) obj; 12 | info.AddValue("id", death.Id); 13 | } 14 | 15 | public object SetObjectData( 16 | object obj, 17 | SerializationInfo info, 18 | StreamingContext context, 19 | ISurrogateSelector selector 20 | ) { 21 | var id = info.GetString("id"); 22 | return Db.Get().Deaths.Get(id); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Platform/Steam/Network/Messaging/Surrogates/EmoteSurrogate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | using Klei.AI; 4 | 5 | namespace MultiplayerMod.Platform.Steam.Network.Messaging.Surrogates; 6 | 7 | public class EmoteSurrogate : ISerializationSurrogate, ISurrogateType { 8 | 9 | public Type Type => typeof(Emote); 10 | 11 | public void GetObjectData(object obj, SerializationInfo info, StreamingContext context) { 12 | var emote = (Emote) obj; 13 | info.AddValue("id", emote.Id); 14 | } 15 | 16 | public object SetObjectData( 17 | object obj, 18 | SerializationInfo info, 19 | StreamingContext context, 20 | ISurrogateSelector selector 21 | ) { 22 | var id = info.GetString("id"); 23 | return Db.Get().Emotes.Minion.Get(id); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Platform/Steam/Network/Messaging/Surrogates/ISurrogateType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Platform.Steam.Network.Messaging.Surrogates; 4 | 5 | public interface ISurrogateType { 6 | public Type Type { get; } 7 | } 8 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Platform/Steam/Network/Messaging/Surrogates/KAnimFileSurrogate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace MultiplayerMod.Platform.Steam.Network.Messaging.Surrogates; 5 | 6 | public class KAnimFileSurrogate : ISerializationSurrogate, ISurrogateType { 7 | 8 | public Type Type => typeof(KAnimFile); 9 | 10 | public void GetObjectData(object obj, SerializationInfo info, StreamingContext context) { 11 | var anim = (KAnimFile) obj; 12 | info.AddValue("name", anim.name); 13 | } 14 | 15 | public object SetObjectData( 16 | object obj, 17 | SerializationInfo info, 18 | StreamingContext context, 19 | ISurrogateSelector selector 20 | ) { 21 | var name = info.GetString("name"); 22 | return Assets.GetAnim(name); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Platform/Steam/Network/Messaging/Surrogates/PathNodeSurrogate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace MultiplayerMod.Platform.Steam.Network.Messaging.Surrogates; 5 | 6 | public class PathNodeSurrogate : ISerializationSurrogate, ISurrogateType { 7 | 8 | public Type Type => typeof(BaseUtilityBuildTool.PathNode); 9 | 10 | public void GetObjectData(object obj, SerializationInfo info, StreamingContext context) { 11 | var node = (BaseUtilityBuildTool.PathNode) obj; 12 | info.AddValue("cell", node.cell); 13 | info.AddValue("valid", node.valid); 14 | } 15 | 16 | public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector) { 17 | var node = (BaseUtilityBuildTool.PathNode) obj; 18 | node.cell = info.GetInt32("cell"); 19 | node.valid = info.GetBoolean("valid"); 20 | return node; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Platform/Steam/Network/Messaging/Surrogates/PrioritySettingSurrogate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace MultiplayerMod.Platform.Steam.Network.Messaging.Surrogates; 5 | 6 | public class PrioritySettingSurrogate : ISerializationSurrogate, ISurrogateType { 7 | 8 | public Type Type => typeof(PrioritySetting); 9 | 10 | public void GetObjectData(object obj, SerializationInfo info, StreamingContext context) { 11 | var priority = (PrioritySetting) obj; 12 | info.AddValue("class", priority.priority_class); 13 | info.AddValue("value", priority.priority_value); 14 | } 15 | 16 | public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector) { 17 | var priority = (PrioritySetting) obj; 18 | priority.priority_class = (PriorityScreen.PriorityClass) info.GetInt32("class"); 19 | priority.priority_value = info.GetInt32("value"); 20 | return priority; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Platform/Steam/Network/Messaging/Surrogates/ScheduleBlockTypeSurrogate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace MultiplayerMod.Platform.Steam.Network.Messaging.Surrogates; 5 | 6 | public class ScheduleBlockTypeSurrogate : ISerializationSurrogate, ISurrogateType { 7 | 8 | public Type Type => typeof(ScheduleBlockType); 9 | 10 | public void GetObjectData(object obj, SerializationInfo info, StreamingContext context) { 11 | var scheduleBlockType = (ScheduleBlockType) obj; 12 | info.AddValue("id", scheduleBlockType.Id); 13 | } 14 | 15 | public object? SetObjectData( 16 | object obj, 17 | SerializationInfo info, 18 | StreamingContext context, 19 | ISurrogateSelector selector 20 | ) { 21 | var id = info.GetString("id"); 22 | return Db.Get().ScheduleBlockTypes.Get(id); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Platform/Steam/Network/Messaging/Surrogates/SpaceDestinationSurrogate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace MultiplayerMod.Platform.Steam.Network.Messaging.Surrogates; 5 | 6 | public class SpaceDestinationSurrogate : ISerializationSurrogate, ISurrogateType { 7 | 8 | public Type Type => typeof(SpaceDestination); 9 | 10 | public void GetObjectData(object obj, SerializationInfo info, StreamingContext context) { 11 | var destination = (SpaceDestination) obj; 12 | info.AddValue("id", destination.id); 13 | } 14 | 15 | public object SetObjectData( 16 | object obj, 17 | SerializationInfo info, 18 | StreamingContext context, 19 | ISurrogateSelector selector 20 | ) { 21 | var id = info.GetString("id"); 22 | return SpacecraftManager.instance.GetDestination(int.Parse(id)); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Platform/Steam/Network/Messaging/Surrogates/SpiceGrinderSurrogate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace MultiplayerMod.Platform.Steam.Network.Messaging.Surrogates; 5 | 6 | public class SpiceGrinderSurrogate : ISerializationSurrogate, ISurrogateType { 7 | 8 | public Type Type => typeof(SpiceGrinder.Option); 9 | 10 | public void GetObjectData(object obj, SerializationInfo info, StreamingContext context) { 11 | var option = (SpiceGrinder.Option) obj; 12 | info.AddValue("id", option.Id); 13 | } 14 | 15 | public object SetObjectData( 16 | object obj, 17 | SerializationInfo info, 18 | StreamingContext context, 19 | ISurrogateSelector selector 20 | ) { 21 | var tag = (Tag) info.GetValue("id", typeof(Tag)); 22 | return SpiceGrinder.SettingOptions[tag]; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Platform/Steam/Network/Messaging/Surrogates/TagSurrogate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace MultiplayerMod.Platform.Steam.Network.Messaging.Surrogates; 5 | 6 | public class TagSurrogate : ISerializationSurrogate, ISurrogateType { 7 | 8 | public Type Type => typeof(Tag); 9 | 10 | public void GetObjectData(object obj, SerializationInfo info, StreamingContext context) { 11 | var tag = (Tag) obj; 12 | info.AddValue("hash", tag.hash); 13 | } 14 | 15 | public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector) { 16 | var tag = (Tag) obj; 17 | tag.hash = info.GetInt32("hash"); 18 | tag.name = TagManager.GetProperName(tag, stripLink: true); 19 | return tag; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Platform/Steam/Network/Messaging/Surrogates/Vector2SerializationSurrogate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | using UnityEngine; 4 | 5 | namespace MultiplayerMod.Platform.Steam.Network.Messaging.Surrogates; 6 | 7 | public class Vector2SerializationSurrogate : ISerializationSurrogate, ISurrogateType { 8 | 9 | public Type Type => typeof(Vector2); 10 | 11 | public void GetObjectData(object obj, SerializationInfo info, StreamingContext context) { 12 | var vector = (Vector2) obj; 13 | info.AddValue("x", vector.x); 14 | info.AddValue("y", vector.y); 15 | } 16 | 17 | public object SetObjectData( 18 | object obj, 19 | SerializationInfo info, 20 | StreamingContext context, 21 | ISurrogateSelector selector 22 | ) { 23 | var vector = (Vector2) obj; 24 | vector.x = info.GetSingle("x"); 25 | vector.y = info.GetSingle("y"); 26 | return vector; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Platform/Steam/Network/Messaging/Surrogates/Vector2fSerializationSurrogate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace MultiplayerMod.Platform.Steam.Network.Messaging.Surrogates; 5 | 6 | // ReSharper disable once InconsistentNaming 7 | public class Vector2fSerializationSurrogate : ISerializationSurrogate, ISurrogateType { 8 | 9 | public Type Type => typeof(Vector2f); 10 | 11 | public void GetObjectData(object obj, SerializationInfo info, StreamingContext context) { 12 | var vector = (Vector2f) obj; 13 | info.AddValue("x", vector.x); 14 | info.AddValue("y", vector.y); 15 | } 16 | 17 | public object SetObjectData( 18 | object obj, 19 | SerializationInfo info, 20 | StreamingContext context, 21 | ISurrogateSelector selector 22 | ) { 23 | var vector = (Vector2f) obj; 24 | vector.x = info.GetSingle("x"); 25 | vector.y = info.GetSingle("y"); 26 | return vector; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Platform/Steam/Network/Messaging/Surrogates/Vector3SerializationSurrogate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | using UnityEngine; 4 | 5 | namespace MultiplayerMod.Platform.Steam.Network.Messaging.Surrogates; 6 | 7 | public class Vector3SerializationSurrogate : ISerializationSurrogate, ISurrogateType { 8 | 9 | public Type Type => typeof(Vector3); 10 | 11 | public void GetObjectData(object obj, SerializationInfo info, StreamingContext context) { 12 | var vector = (Vector3) obj; 13 | info.AddValue("x", vector.x); 14 | info.AddValue("y", vector.y); 15 | info.AddValue("z", vector.z); 16 | } 17 | 18 | public object SetObjectData( 19 | object obj, 20 | SerializationInfo info, 21 | StreamingContext context, 22 | ISurrogateSelector selector 23 | ) { 24 | var vector = (Vector3) obj; 25 | vector.x = info.GetSingle("x"); 26 | vector.y = info.GetSingle("y"); 27 | vector.z = info.GetSingle("z"); 28 | return vector; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Platform/Steam/Network/NetworkPlatformException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiplayerMod.Platform.Steam.Network; 4 | 5 | public class NetworkPlatformException : PlatformException { 6 | public NetworkPlatformException(string message) : base(message) { } 7 | public NetworkPlatformException(string message, Exception cause) : base(message, cause) { } 8 | } 9 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Platform/Steam/Network/SteamMultiplayerClientId.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MultiplayerMod.Network; 3 | using Steamworks; 4 | 5 | namespace MultiplayerMod.Platform.Steam.Network; 6 | 7 | [Serializable] 8 | public record SteamMultiplayerClientId(CSteamID Id) : IMultiplayerClientId { 9 | 10 | public bool Equals(IMultiplayerClientId other) { 11 | return other is SteamMultiplayerClientId player && player.Equals(this); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Platform/Steam/Network/SteamServerEndpoint.cs: -------------------------------------------------------------------------------- 1 | using MultiplayerMod.Network; 2 | using Steamworks; 3 | 4 | namespace MultiplayerMod.Platform.Steam.Network; 5 | 6 | public record SteamServerEndpoint(CSteamID LobbyID) : IMultiplayerEndpoint; 7 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Platform/Steam/SteamMultiplayerOperations.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | using MultiplayerMod.Core.Dependency; 3 | using MultiplayerMod.Multiplayer; 4 | using Steamworks; 5 | 6 | namespace MultiplayerMod.Platform.Steam; 7 | 8 | [Dependency, UsedImplicitly] 9 | public class SteamMultiplayerOperations : IMultiplayerOperations { 10 | 11 | public void Join() => SteamFriends.ActivateGameOverlay("friends"); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Platform/Steam/SteamPlatformConfigurer.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | using MultiplayerMod.Core.Dependency; 3 | using MultiplayerMod.Core.Logging; 4 | using MultiplayerMod.Core.Unity; 5 | using MultiplayerMod.ModRuntime.Loader; 6 | using MultiplayerMod.Platform.Steam.Network.Components; 7 | 8 | namespace MultiplayerMod.Platform.Steam; 9 | 10 | [UsedImplicitly] 11 | [ModComponentOrder(ModComponentOrder.Platform)] 12 | public class SteamPlatformConfigurer : IModComponentConfigurer { 13 | 14 | private readonly Core.Logging.Logger log = LoggerFactory.GetLogger(); 15 | 16 | public void Configure(DependencyContainerBuilder builder) { 17 | var steam = DistributionPlatform.Inst.Platform == "Steam"; 18 | if (!steam) 19 | return; 20 | 21 | log.Info("Steam platform detected"); 22 | 23 | builder.ContainerCreated += _ => UnityObject.CreateStaticWithComponent(); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/MultiplayerMod/Platform/Steam/SteamPlayerProfileProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JetBrains.Annotations; 3 | using MultiplayerMod.Core.Dependency; 4 | using MultiplayerMod.Multiplayer.Players; 5 | using Steamworks; 6 | 7 | namespace MultiplayerMod.Platform.Steam; 8 | 9 | [Dependency, UsedImplicitly] 10 | public class SteamPlayerProfileProvider : IPlayerProfileProvider { 11 | 12 | private readonly Lazy profile = new( 13 | () => new PlayerProfile( 14 | SteamFriends.GetPersonaName() 15 | ) 16 | ); 17 | 18 | public PlayerProfile GetPlayerProfile() => profile.Value; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/MultiplayerMod/mod.yaml: -------------------------------------------------------------------------------- 1 | title: "Multiplayer mod" 2 | description: "Play Oxygen Not Included together!" 3 | staticID: "multiplayerMod" 4 | -------------------------------------------------------------------------------- /steamworkshop/loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onimp/oni_multiplayer/926abd3b5f177b12ab4cc6d6aa537dcdb09684b7/steamworkshop/loading.png -------------------------------------------------------------------------------- /steamworkshop/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onimp/oni_multiplayer/926abd3b5f177b12ab4cc6d6aa537dcdb09684b7/steamworkshop/menu.png -------------------------------------------------------------------------------- /steamworkshop/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onimp/oni_multiplayer/926abd3b5f177b12ab4cc6d6aa537dcdb09684b7/steamworkshop/preview.png --------------------------------------------------------------------------------