From abbd440bf105f1a9e46b734d3db66e2534e30480 Mon Sep 17 00:00:00 2001 From: Marvin <52848568+mleem97@users.noreply.github.com> Date: Mon, 20 Apr 2026 04:25:54 +0200 Subject: [PATCH] feat: Implement core performance and automation modules - Added RepairEngine for device repair automation. - Introduced GregFrameRateLimiter to manage frame rates based on performance profiles. - Implemented GregMemoryPressureHandler to handle memory pressure events and optimize garbage collection. - Created GregOperationQueue for managing and throttling operations with priority. - Developed GregPerformanceGovernor to oversee performance management and resource monitoring. - Added GregRequestThrottler to limit concurrent operations and requests. - Implemented GregResourceMonitor to track system resource usage and publish metrics. - Introduced IAssemblyScanner interface for plugin assembly scanning. - Added various public API modules including GregAutomationModule, GregEconomyModule, GregFacilityModule, GregNetworkModule, GregNpcModule, GregPerformanceModule, GregPlayerModule, GregSaveModule, GregServerModule, GregTimeModule, and GregUIModule for enhanced game functionality. - Created event handling and resource snapshot types for better event management and performance tracking. --- publish/gregCore-v1.0.0.32.zip | Bin 0 -> 76096 bytes scripts/Build-Release.ps1 | 7 +- src/AssemblyInfo.cs | 4 - .../Abstractions/IGregPerformanceGovernor.cs | 13 + .../Abstractions/IGregPersistenceService.cs | 15 +- src/Core/Events/GregEventBus.cs | 24 + src/Core/Models/AutomationProgress.cs | 9 + src/Core/Models/AutomationResult.cs | 18 + src/Core/Models/AutomationTask.cs | 35 + src/Core/Models/CableType.cs | 12 + src/Core/Models/EventPayload.cs | 2 +- src/Core/Models/HookName.cs | 7 +- src/Core/Models/NetworkSegmentConfig.cs | 11 + src/Core/Models/OperationPriority.cs | 10 + src/Core/Models/PerformanceProfile.cs | 43 + src/Core/Models/PerformanceStats.cs | 9 + src/Core/Models/RackBuildConfig.cs | 17 + src/Core/Models/ResourceSnapshot.cs | 24 + src/Core/Models/ThrottleMetrics.cs | 10 + src/Core/NullableAttributes.cs | 21 + src/Diagnostic/FrameLimiterConfig.cs | 24 - src/Diagnostic/GregDiagnosticTools.cs | 70 - src/Diagnostic/GregFrameLimiterService.cs | 150 -- src/Diagnostic/GregPerfConfig.cs | 46 - src/Diagnostic/GregPerformanceHud.cs | 48 - src/Diagnostic/GregPerformanceOptimizer.cs | 461 ----- src/Diagnostic/GregRenderOptimizer.cs | 77 - src/Diagnostic/GregSessionLogger.cs | 53 - src/Diagnostic/GregTelemetryService.cs | 157 -- src/Diagnostic/RenderOptimizerConfig.cs | 18 - src/Diagnostic/TelemetryModels.cs | 41 - src/GameLayer/Bootstrap/GregBootstrapper.cs | 38 +- src/GameLayer/Bootstrap/GregCoreLoader.cs | 6 + .../Automation/AutomationEngine.cs | 22 + .../Automation/CableLayingEngine.cs | 49 + .../Automation/DeliveryZoneEngine.cs | 57 + .../Automation/NetworkConfigEngine.cs | 17 + .../Automation/RackBuildEngine.cs | 26 + src/Infrastructure/Automation/RepairEngine.cs | 17 + .../Config/GregPersistenceService.cs | 45 +- .../Performance/GregFrameRateLimiter.cs | 64 + .../Performance/GregMemoryPressureHandler.cs | 63 + .../Performance/GregOperationQueue.cs | 47 + .../Performance/GregPerformanceGovernor.cs | 70 + .../Performance/GregRequestThrottler.cs | 62 + .../Performance/GregResourceMonitor.cs | 91 + src/Infrastructure/Plugins/AssemblyScanner.cs | 2 +- .../Plugins/IAssemblyScanner.cs | 12 + src/PublicApi/MODDER_GUIDE.md | 27 + src/PublicApi/Modules/GregAutomationModule.cs | 69 + src/PublicApi/Modules/GregEconomyModule.cs | 53 + src/PublicApi/Modules/GregFacilityModule.cs | 9 + src/PublicApi/Modules/GregNetworkModule.cs | 34 + src/PublicApi/Modules/GregNpcModule.cs | 8 + .../Modules/GregPerformanceModule.cs | 39 + src/PublicApi/Modules/GregPlayerModule.cs | 12 + src/PublicApi/Modules/GregSaveModule.cs | 13 + src/PublicApi/Modules/GregServerModule.cs | 9 + src/PublicApi/Modules/GregTimeModule.cs | 25 + src/PublicApi/Modules/GregUIModule.cs | 15 + src/PublicApi/Types/DeviceInfo.cs | 2 + src/PublicApi/Types/GregEventHandle.cs | 16 + src/PublicApi/Types/MoneyResult.cs | 2 + src/PublicApi/Types/ServerInfo.cs | 2 + src/PublicApi/greg.cs | 38 + src/Scripting/Go/GoLanguageBridge.cs | 64 - src/Scripting/GregHookBus.cs | 77 - src/Scripting/IGregLanguageBridge.cs | 32 - .../JS/TypeScriptJavaScriptLanguageBridge.cs | 258 --- src/Scripting/Lua/IGregLuaModule.cs | 18 - src/Scripting/Lua/LuaLanguageBridge.cs | 300 --- .../Lua/LuaModules/gregHooksLuaModule.cs | 269 --- .../Lua/LuaModules/gregInputLuaModule.cs | 156 -- .../Lua/LuaModules/gregIoLuaModule.cs | 120 -- .../LuaModules/gregLuaObjectHandleRegistry.cs | 70 - .../Lua/LuaModules/gregSdkLuaModule.cs | 101 -- .../Lua/LuaModules/gregUnityLuaModule.cs | 858 --------- .../Rust/RustLanguageBridgeAdapter.cs | 126 -- src/Scripting/gregLanguageBridgeHost.cs | 250 --- src/Scripting/gregRuntimeUnit.cs | 12 - src/Tests/Core/DependencyResolverTests.cs | 32 - src/Tests/Events/GregEventBusTests.cs | 53 - src/Tests/Mocks/MockLogger.cs | 23 - src/Tests/gregCore.Tests.csproj | 18 - src/UI/Components/GregBadge.cs | 38 - src/UI/Components/GregButton.cs | 55 - src/UI/Components/GregLabel.cs | 28 - src/UI/Components/GregMainMenuReplacement.cs | 498 ----- src/UI/Components/GregPanel.cs | 162 -- src/UI/Components/GregPauseMenuReplacement.cs | 91 - src/UI/Components/GregSeparator.cs | 22 - src/UI/Components/GregShopReplacement.cs | 76 - src/UI/Components/GregSlider.cs | 87 - src/UI/Components/GregToggle.cs | 55 - src/UI/Components/GregUIBuilder.cs | 68 - src/UI/Components/IGregUIComponent.cs | 12 - src/UI/FixedTableUI.cs | 36 - src/UI/GameMethodInvoker.cs | 271 --- src/UI/GregComponents.cs | 51 - src/UI/GregUIManager.cs | 78 - src/UI/GregUITheme.cs | 62 - src/UI/GregUiBuilder.cs | 151 -- src/UI/GregUiFocusManager.cs | 47 - src/UI/IGregUIElement.cs | 18 - src/UI/Il2CppSafe.cs | 53 - src/UI/Patches/MainMenuPatch.cs | 29 - src/UI/Patches/PauseMenuPatch.cs | 49 - src/UI/Patches/StaticUIElementsPatch.cs | 30 - src/UI/UIPathConfig.md | 69 - src/UI/UIRouter.cs | 253 --- src/gregCore.csproj | 8 +- src/gregCore/Patches/FrameCapPatches.cs | 59 - src/gregCore/Services/GregFrameCapService.cs | 89 - src/gregCore/Services/GregMemoryService.cs | 76 - src/gregCore/Services/GregRenderingService.cs | 125 -- src/gregCore/Services/GregThreadingService.cs | 50 - src/gregHarmony/GregEmployeeAIPatch.cs | 15 - src/gregHarmony/GregEngineBridgesPatch.cs | 33 - src/gregHarmony/GregGameVersionPatch.cs | 20 - src/gregHarmony/GregHexViewerPatch.cs | 116 -- src/gregHarmony/GregLifecyclePatch.cs | 24 - src/gregHarmony/GregModelOverridePatch.cs | 20 - src/gregHarmony/GregNetworkTopologyPatch.cs | 15 - src/gregHarmony/GregShopPatch.cs | 61 - src/gregHarmony/GregVersionPatch.cs | 29 - src/gregHarmony/InputHook.cs | 60 - src/gregHarmony/MainMenuPatch.cs | 206 --- src/gregHarmony/Patches/MainMenuPatch.cs | 62 - src/gregHarmony/Patches/SaveSystemPatch.cs | 50 - src/gregHarmony/PauseMenuPatch.cs | 105 -- src/gregHarmony/PerformancePatches.cs | 97 - src/gregHarmony/README.md | 2 - src/gregHarmony/UIRouterHooks.cs | 64 - src/gregHarmony/greg_api.h | 117 -- src/gregModLoader/Events/ErrorEvents.cs | 20 - src/gregModLoader/Events/ExportEvents.cs | 32 - src/gregModLoader/Events/HookEvents.cs | 46 - src/gregModLoader/Events/IModEvent.cs | 11 - .../Events/Il2CppCatalogEvents.cs | 32 - .../Events/ModLifecycleEvents.cs | 32 - src/gregModLoader/Events/ToggleEvents.cs | 20 - src/gregModLoader/Hooks/HookBinder.cs | 291 --- src/gregModLoader/Hooks/HookContext.cs | 63 - .../Plugins/gregDependencyResolver.cs | 116 -- src/gregModLoader/Plugins/gregModBase.cs | 71 - src/gregModLoader/Plugins/gregPluginBase.cs | 46 - src/gregModLoader/Plugins/gregRegistry.cs | 94 - .../References/ReferenceScanner.cs | 149 -- .../UI/GregUiReplacementManager.cs | 86 - .../UI/gregMainHudReplacement.cs | 141 -- src/gregModLoader/UI/gregModConfigManager.cs | 215 --- src/gregModLoader/UI/gregUiStyler.cs | 58 - .../gregAssemblyHookDumpService.cs | 103 -- src/gregModLoader/gregCore.cs | 561 ------ .../gregCustomEmployeeManager.cs | 810 --------- src/gregModLoader/gregEventDispatcher.cs | 726 -------- src/gregModLoader/gregFfiBridge.cs | 501 ------ src/gregModLoader/gregFrameworkLog.cs | 66 - src/gregModLoader/gregGameApi.cs | 1174 ------------ src/gregModLoader/gregGameFolderLayout.cs | 63 - src/gregModLoader/gregGameHooks.cs | 666 ------- .../gregGameSignalSnapshotService.cs | 60 - src/gregModLoader/gregHarmonyPatches.cs | 942 ---------- src/gregModLoader/gregHireRosterService.cs | 99 - src/gregModLoader/gregHookIntegration.cs | 127 -- src/gregModLoader/gregHooker.cs | 334 ---- .../gregIl2CppEventCatalogService.cs | 268 --- .../gregIl2CppGameplayIndexService.cs | 99 - src/gregModLoader/gregLangserverCompat.cs | 441 ----- src/gregModLoader/gregLocalisationBridge.cs | 101 -- src/gregModLoader/gregModActivationService.cs | 115 -- src/gregModLoader/gregModEventFramework.cs | 91 - .../gregModSaveCompatibilityService.cs | 141 -- src/gregModLoader/gregModigApi.cs | 688 ------- src/gregModLoader/gregMultiplayerBridge.cs | 1603 ----------------- src/gregModLoader/gregPluginSyncService.cs | 231 --- src/gregModLoader/gregReleaseVersion.cs | 7 - src/gregModLoader/gregRuntimeHookService.cs | 413 ----- src/gregModLoader/gregTechnicianHiring.cs | 286 --- src/gregModLoader/gregUiExtensionBridge.cs | 371 ---- src/gregSdk/Definitions/CableDefinition.cs | 12 - src/gregSdk/Definitions/CustomerDefinition.cs | 12 - src/gregSdk/Definitions/EmployeeDefinition.cs | 13 - .../Definitions/FurnitureDefinition.cs | 12 - .../Definitions/GregUiReplacementManifest.cs | 39 - src/gregSdk/Definitions/ItemDefinition.cs | 10 - .../Definitions/ModelOverrideManifest.cs | 12 - .../Definitions/PlacementRuleDefinition.cs | 11 - src/gregSdk/Definitions/ServerDefinition.cs | 15 - .../Definitions/ServerTypeDefinition.cs | 10 - src/gregSdk/Definitions/SfpDefinition.cs | 10 - src/gregSdk/Definitions/SwitchDefinition.cs | 16 - src/gregSdk/Events/GregEvents.cs | 33 - src/gregSdk/Exporter/DataExporter.cs | 122 -- src/gregSdk/Extensions/IGregHexviewerApi.cs | 16 - src/gregSdk/GregContentRegistry.cs | 64 - src/gregSdk/GregCustomerPolicyEngine.cs | 43 - src/gregSdk/GregEmployeeStateService.cs | 8 - src/gregSdk/GregIncidentOrchestrator.cs | 8 - src/gregSdk/GregModelOverrideService.cs | 10 - .../GregNetworkCompatibilityService.cs | 44 - src/gregSdk/GregSignalPayload.cs | 29 - src/gregSdk/IGregContentRegistry.cs | 13 - src/gregSdk/IGregModelOverrideService.cs | 7 - src/gregSdk/Interfaces/IContentMigration.cs | 8 - src/gregSdk/Interfaces/IContentValidator.cs | 7 - .../Interfaces/IGregUiReplacementService.cs | 26 - src/gregSdk/Internal/GregUiHijacker.cs | 54 - src/gregSdk/NullableAttributes.cs | 27 - src/gregSdk/Registries/GregCableRegistry.cs | 9 - .../Registries/GregCustomerRegistry.cs | 9 - .../Registries/GregEmployeeRegistry.cs | 9 - .../Registries/GregFurnitureRegistry.cs | 9 - src/gregSdk/Registries/GregIl2CppRegistry.cs | 40 - src/gregSdk/Registries/GregItemRegistry.cs | 9 - .../Registries/GregPlacementRegistry.cs | 9 - src/gregSdk/Registries/GregServerRegistry.cs | 9 - .../Registries/GregServerTypeRegistry.cs | 9 - src/gregSdk/Registries/GregSfpRegistry.cs | 9 - src/gregSdk/Registries/GregSwitchRegistry.cs | 9 - src/gregSdk/Services/GregBalanceService.cs | 66 - src/gregSdk/Services/GregCohousingService.cs | 77 - .../Services/GregCompatBridgeService.cs | 33 - .../Services/GregComponentMetadataService.cs | 123 -- src/gregSdk/Services/GregConfigService.cs | 75 - src/gregSdk/Services/GregContentRegistry.cs | 28 - .../Services/GregCustomerBaseService.cs | 52 - src/gregSdk/Services/GregCustomerService.cs | 231 --- src/gregSdk/Services/GregDDoSService.cs | 116 -- src/gregSdk/Services/GregDiagnostics.cs | 17 - src/gregSdk/Services/GregEmployeeAIBridge.cs | 8 - src/gregSdk/Services/GregEventBus.cs | 92 - .../Services/GregGameManagerService.cs | 56 - src/gregSdk/Services/GregHarmonyService.cs | 139 -- src/gregSdk/Services/GregHudService.cs | 121 -- .../Services/GregIPAllocationService.cs | 27 - src/gregSdk/Services/GregInputGuardService.cs | 47 - .../Services/GregInputManagerService.cs | 103 -- src/gregSdk/Services/GregInventoryService.cs | 8 - src/gregSdk/Services/GregIpService.cs | 178 -- .../Services/GregLocalisationService.cs | 101 -- src/gregSdk/Services/GregMetadataService.cs | 23 - src/gregSdk/Services/GregModRegistry.cs | 67 - .../Services/GregModelOverrideService.cs | 48 - .../Services/GregNetworkMaintenanceService.cs | 106 -- .../Services/GregNetworkSanityService.cs | 71 - .../Services/GregNotificationService.cs | 147 -- src/gregSdk/Services/GregOrbRenderService.cs | 54 - src/gregSdk/Services/GregPacketService.cs | 12 - .../Services/GregPathRedirectorService.cs | 105 -- .../GregPerformanceProfilerService.cs | 31 - .../Services/GregPersistenceService.cs | 122 -- .../Services/GregPlayerManagerService.cs | 41 - src/gregSdk/Services/GregPowerService.cs | 30 - src/gregSdk/Services/GregPricingService.cs | 12 - .../Services/GregRackInteractionService.cs | 45 - src/gregSdk/Services/GregRackService.cs | 43 - .../Services/GregResetSwitchService.cs | 391 ---- src/gregSdk/Services/GregRopeService.cs | 12 - .../Services/GregRouteEvaluationService.cs | 46 - src/gregSdk/Services/GregSaveService.cs | 132 -- .../Services/GregServerDiscoveryService.cs | 190 -- .../Services/GregServerInteractionService.cs | 60 - src/gregSdk/Services/GregShopService.cs | 46 - .../Services/GregSwitchConfigService.cs | 41 - .../Services/GregSwitchDiscoveryService.cs | 60 - src/gregSdk/Services/GregTargetingService.cs | 131 -- src/gregSdk/Services/GregTaskQueueService.cs | 72 - src/gregSdk/Services/GregTechnicianService.cs | 46 - src/gregSdk/Services/GregTimeService.cs | 41 - src/gregSdk/Services/GregTopologyService.cs | 18 - .../Services/GregUiReplacementService.cs | 131 -- src/gregSdk/Services/GregUiService.cs | 198 -- src/gregSdk/Services/GregUxmlService.cs | 84 - src/gregSdk/Services/GregVlanService.cs | 232 --- src/gregSdk/Services/GregWaypointService.cs | 17 - src/gregSdk/Services/MCP/GregMCPServer.cs | 147 -- .../Services/Models/GregModelLoaderService.cs | 92 - .../Models/GregModelRegistryService.cs | 112 -- .../Models/GregModelSwapperService.cs | 139 -- .../Multiplayer/GregMultiplayerService.cs | 205 --- .../Multiplayer/GregStateSyncService.cs | 55 - src/gregSdk/Validators/ServerValidator.cs | 17 - src/gregSdk/Validators/SwitchValidator.cs | 17 - src/gregSdk/gregCompatBridge.cs | 191 -- src/gregSdk/gregDomain.cs | 21 - src/gregSdk/gregEventDispatcher.cs | 278 --- src/gregSdk/gregHookName.cs | 61 - src/gregSdk/gregNativeEventHooks.cs | 295 --- src/gregSdk/gregPayload.cs | 70 - tests/ContentTests.cs | 67 - tests/ProjectSanityTests.cs | 25 - 292 files changed, 1291 insertions(+), 27761 deletions(-) create mode 100644 publish/gregCore-v1.0.0.32.zip delete mode 100644 src/AssemblyInfo.cs create mode 100644 src/Core/Abstractions/IGregPerformanceGovernor.cs create mode 100644 src/Core/Models/AutomationProgress.cs create mode 100644 src/Core/Models/AutomationResult.cs create mode 100644 src/Core/Models/AutomationTask.cs create mode 100644 src/Core/Models/CableType.cs create mode 100644 src/Core/Models/NetworkSegmentConfig.cs create mode 100644 src/Core/Models/OperationPriority.cs create mode 100644 src/Core/Models/PerformanceProfile.cs create mode 100644 src/Core/Models/PerformanceStats.cs create mode 100644 src/Core/Models/RackBuildConfig.cs create mode 100644 src/Core/Models/ResourceSnapshot.cs create mode 100644 src/Core/Models/ThrottleMetrics.cs create mode 100644 src/Core/NullableAttributes.cs delete mode 100644 src/Diagnostic/FrameLimiterConfig.cs delete mode 100644 src/Diagnostic/GregDiagnosticTools.cs delete mode 100644 src/Diagnostic/GregFrameLimiterService.cs delete mode 100644 src/Diagnostic/GregPerfConfig.cs delete mode 100644 src/Diagnostic/GregPerformanceHud.cs delete mode 100644 src/Diagnostic/GregPerformanceOptimizer.cs delete mode 100644 src/Diagnostic/GregRenderOptimizer.cs delete mode 100644 src/Diagnostic/GregSessionLogger.cs delete mode 100644 src/Diagnostic/GregTelemetryService.cs delete mode 100644 src/Diagnostic/RenderOptimizerConfig.cs delete mode 100644 src/Diagnostic/TelemetryModels.cs create mode 100644 src/Infrastructure/Automation/AutomationEngine.cs create mode 100644 src/Infrastructure/Automation/CableLayingEngine.cs create mode 100644 src/Infrastructure/Automation/DeliveryZoneEngine.cs create mode 100644 src/Infrastructure/Automation/NetworkConfigEngine.cs create mode 100644 src/Infrastructure/Automation/RackBuildEngine.cs create mode 100644 src/Infrastructure/Automation/RepairEngine.cs create mode 100644 src/Infrastructure/Performance/GregFrameRateLimiter.cs create mode 100644 src/Infrastructure/Performance/GregMemoryPressureHandler.cs create mode 100644 src/Infrastructure/Performance/GregOperationQueue.cs create mode 100644 src/Infrastructure/Performance/GregPerformanceGovernor.cs create mode 100644 src/Infrastructure/Performance/GregRequestThrottler.cs create mode 100644 src/Infrastructure/Performance/GregResourceMonitor.cs create mode 100644 src/Infrastructure/Plugins/IAssemblyScanner.cs create mode 100644 src/PublicApi/MODDER_GUIDE.md create mode 100644 src/PublicApi/Modules/GregAutomationModule.cs create mode 100644 src/PublicApi/Modules/GregEconomyModule.cs create mode 100644 src/PublicApi/Modules/GregFacilityModule.cs create mode 100644 src/PublicApi/Modules/GregNetworkModule.cs create mode 100644 src/PublicApi/Modules/GregNpcModule.cs create mode 100644 src/PublicApi/Modules/GregPerformanceModule.cs create mode 100644 src/PublicApi/Modules/GregPlayerModule.cs create mode 100644 src/PublicApi/Modules/GregSaveModule.cs create mode 100644 src/PublicApi/Modules/GregServerModule.cs create mode 100644 src/PublicApi/Modules/GregTimeModule.cs create mode 100644 src/PublicApi/Modules/GregUIModule.cs create mode 100644 src/PublicApi/Types/DeviceInfo.cs create mode 100644 src/PublicApi/Types/GregEventHandle.cs create mode 100644 src/PublicApi/Types/MoneyResult.cs create mode 100644 src/PublicApi/Types/ServerInfo.cs create mode 100644 src/PublicApi/greg.cs delete mode 100644 src/Scripting/Go/GoLanguageBridge.cs delete mode 100644 src/Scripting/GregHookBus.cs delete mode 100644 src/Scripting/IGregLanguageBridge.cs delete mode 100644 src/Scripting/JS/TypeScriptJavaScriptLanguageBridge.cs delete mode 100644 src/Scripting/Lua/IGregLuaModule.cs delete mode 100644 src/Scripting/Lua/LuaLanguageBridge.cs delete mode 100644 src/Scripting/Lua/LuaModules/gregHooksLuaModule.cs delete mode 100644 src/Scripting/Lua/LuaModules/gregInputLuaModule.cs delete mode 100644 src/Scripting/Lua/LuaModules/gregIoLuaModule.cs delete mode 100644 src/Scripting/Lua/LuaModules/gregLuaObjectHandleRegistry.cs delete mode 100644 src/Scripting/Lua/LuaModules/gregSdkLuaModule.cs delete mode 100644 src/Scripting/Lua/LuaModules/gregUnityLuaModule.cs delete mode 100644 src/Scripting/Rust/RustLanguageBridgeAdapter.cs delete mode 100644 src/Scripting/gregLanguageBridgeHost.cs delete mode 100644 src/Scripting/gregRuntimeUnit.cs delete mode 100644 src/Tests/Core/DependencyResolverTests.cs delete mode 100644 src/Tests/Events/GregEventBusTests.cs delete mode 100644 src/Tests/Mocks/MockLogger.cs delete mode 100644 src/Tests/gregCore.Tests.csproj delete mode 100644 src/UI/Components/GregBadge.cs delete mode 100644 src/UI/Components/GregButton.cs delete mode 100644 src/UI/Components/GregLabel.cs delete mode 100644 src/UI/Components/GregMainMenuReplacement.cs delete mode 100644 src/UI/Components/GregPanel.cs delete mode 100644 src/UI/Components/GregPauseMenuReplacement.cs delete mode 100644 src/UI/Components/GregSeparator.cs delete mode 100644 src/UI/Components/GregShopReplacement.cs delete mode 100644 src/UI/Components/GregSlider.cs delete mode 100644 src/UI/Components/GregToggle.cs delete mode 100644 src/UI/Components/GregUIBuilder.cs delete mode 100644 src/UI/Components/IGregUIComponent.cs delete mode 100644 src/UI/FixedTableUI.cs delete mode 100644 src/UI/GameMethodInvoker.cs delete mode 100644 src/UI/GregComponents.cs delete mode 100644 src/UI/GregUIManager.cs delete mode 100644 src/UI/GregUITheme.cs delete mode 100644 src/UI/GregUiBuilder.cs delete mode 100644 src/UI/GregUiFocusManager.cs delete mode 100644 src/UI/IGregUIElement.cs delete mode 100644 src/UI/Il2CppSafe.cs delete mode 100644 src/UI/Patches/MainMenuPatch.cs delete mode 100644 src/UI/Patches/PauseMenuPatch.cs delete mode 100644 src/UI/Patches/StaticUIElementsPatch.cs delete mode 100644 src/UI/UIPathConfig.md delete mode 100644 src/UI/UIRouter.cs delete mode 100644 src/gregCore/Patches/FrameCapPatches.cs delete mode 100644 src/gregCore/Services/GregFrameCapService.cs delete mode 100644 src/gregCore/Services/GregMemoryService.cs delete mode 100644 src/gregCore/Services/GregRenderingService.cs delete mode 100644 src/gregCore/Services/GregThreadingService.cs delete mode 100644 src/gregHarmony/GregEmployeeAIPatch.cs delete mode 100644 src/gregHarmony/GregEngineBridgesPatch.cs delete mode 100644 src/gregHarmony/GregGameVersionPatch.cs delete mode 100644 src/gregHarmony/GregHexViewerPatch.cs delete mode 100644 src/gregHarmony/GregLifecyclePatch.cs delete mode 100644 src/gregHarmony/GregModelOverridePatch.cs delete mode 100644 src/gregHarmony/GregNetworkTopologyPatch.cs delete mode 100644 src/gregHarmony/GregShopPatch.cs delete mode 100644 src/gregHarmony/GregVersionPatch.cs delete mode 100644 src/gregHarmony/InputHook.cs delete mode 100644 src/gregHarmony/MainMenuPatch.cs delete mode 100644 src/gregHarmony/Patches/MainMenuPatch.cs delete mode 100644 src/gregHarmony/Patches/SaveSystemPatch.cs delete mode 100644 src/gregHarmony/PauseMenuPatch.cs delete mode 100644 src/gregHarmony/PerformancePatches.cs delete mode 100644 src/gregHarmony/README.md delete mode 100644 src/gregHarmony/UIRouterHooks.cs delete mode 100644 src/gregHarmony/greg_api.h delete mode 100644 src/gregModLoader/Events/ErrorEvents.cs delete mode 100644 src/gregModLoader/Events/ExportEvents.cs delete mode 100644 src/gregModLoader/Events/HookEvents.cs delete mode 100644 src/gregModLoader/Events/IModEvent.cs delete mode 100644 src/gregModLoader/Events/Il2CppCatalogEvents.cs delete mode 100644 src/gregModLoader/Events/ModLifecycleEvents.cs delete mode 100644 src/gregModLoader/Events/ToggleEvents.cs delete mode 100644 src/gregModLoader/Hooks/HookBinder.cs delete mode 100644 src/gregModLoader/Hooks/HookContext.cs delete mode 100644 src/gregModLoader/Plugins/gregDependencyResolver.cs delete mode 100644 src/gregModLoader/Plugins/gregModBase.cs delete mode 100644 src/gregModLoader/Plugins/gregPluginBase.cs delete mode 100644 src/gregModLoader/Plugins/gregRegistry.cs delete mode 100644 src/gregModLoader/References/ReferenceScanner.cs delete mode 100644 src/gregModLoader/UI/GregUiReplacementManager.cs delete mode 100644 src/gregModLoader/UI/gregMainHudReplacement.cs delete mode 100644 src/gregModLoader/UI/gregModConfigManager.cs delete mode 100644 src/gregModLoader/UI/gregUiStyler.cs delete mode 100644 src/gregModLoader/gregAssemblyHookDumpService.cs delete mode 100644 src/gregModLoader/gregCore.cs delete mode 100644 src/gregModLoader/gregCustomEmployeeManager.cs delete mode 100644 src/gregModLoader/gregEventDispatcher.cs delete mode 100644 src/gregModLoader/gregFfiBridge.cs delete mode 100644 src/gregModLoader/gregFrameworkLog.cs delete mode 100644 src/gregModLoader/gregGameApi.cs delete mode 100644 src/gregModLoader/gregGameFolderLayout.cs delete mode 100644 src/gregModLoader/gregGameHooks.cs delete mode 100644 src/gregModLoader/gregGameSignalSnapshotService.cs delete mode 100644 src/gregModLoader/gregHarmonyPatches.cs delete mode 100644 src/gregModLoader/gregHireRosterService.cs delete mode 100644 src/gregModLoader/gregHookIntegration.cs delete mode 100644 src/gregModLoader/gregHooker.cs delete mode 100644 src/gregModLoader/gregIl2CppEventCatalogService.cs delete mode 100644 src/gregModLoader/gregIl2CppGameplayIndexService.cs delete mode 100644 src/gregModLoader/gregLangserverCompat.cs delete mode 100644 src/gregModLoader/gregLocalisationBridge.cs delete mode 100644 src/gregModLoader/gregModActivationService.cs delete mode 100644 src/gregModLoader/gregModEventFramework.cs delete mode 100644 src/gregModLoader/gregModSaveCompatibilityService.cs delete mode 100644 src/gregModLoader/gregModigApi.cs delete mode 100644 src/gregModLoader/gregMultiplayerBridge.cs delete mode 100644 src/gregModLoader/gregPluginSyncService.cs delete mode 100644 src/gregModLoader/gregReleaseVersion.cs delete mode 100644 src/gregModLoader/gregRuntimeHookService.cs delete mode 100644 src/gregModLoader/gregTechnicianHiring.cs delete mode 100644 src/gregModLoader/gregUiExtensionBridge.cs delete mode 100644 src/gregSdk/Definitions/CableDefinition.cs delete mode 100644 src/gregSdk/Definitions/CustomerDefinition.cs delete mode 100644 src/gregSdk/Definitions/EmployeeDefinition.cs delete mode 100644 src/gregSdk/Definitions/FurnitureDefinition.cs delete mode 100644 src/gregSdk/Definitions/GregUiReplacementManifest.cs delete mode 100644 src/gregSdk/Definitions/ItemDefinition.cs delete mode 100644 src/gregSdk/Definitions/ModelOverrideManifest.cs delete mode 100644 src/gregSdk/Definitions/PlacementRuleDefinition.cs delete mode 100644 src/gregSdk/Definitions/ServerDefinition.cs delete mode 100644 src/gregSdk/Definitions/ServerTypeDefinition.cs delete mode 100644 src/gregSdk/Definitions/SfpDefinition.cs delete mode 100644 src/gregSdk/Definitions/SwitchDefinition.cs delete mode 100644 src/gregSdk/Events/GregEvents.cs delete mode 100644 src/gregSdk/Exporter/DataExporter.cs delete mode 100644 src/gregSdk/Extensions/IGregHexviewerApi.cs delete mode 100644 src/gregSdk/GregContentRegistry.cs delete mode 100644 src/gregSdk/GregCustomerPolicyEngine.cs delete mode 100644 src/gregSdk/GregEmployeeStateService.cs delete mode 100644 src/gregSdk/GregIncidentOrchestrator.cs delete mode 100644 src/gregSdk/GregModelOverrideService.cs delete mode 100644 src/gregSdk/GregNetworkCompatibilityService.cs delete mode 100644 src/gregSdk/GregSignalPayload.cs delete mode 100644 src/gregSdk/IGregContentRegistry.cs delete mode 100644 src/gregSdk/IGregModelOverrideService.cs delete mode 100644 src/gregSdk/Interfaces/IContentMigration.cs delete mode 100644 src/gregSdk/Interfaces/IContentValidator.cs delete mode 100644 src/gregSdk/Interfaces/IGregUiReplacementService.cs delete mode 100644 src/gregSdk/Internal/GregUiHijacker.cs delete mode 100644 src/gregSdk/NullableAttributes.cs delete mode 100644 src/gregSdk/Registries/GregCableRegistry.cs delete mode 100644 src/gregSdk/Registries/GregCustomerRegistry.cs delete mode 100644 src/gregSdk/Registries/GregEmployeeRegistry.cs delete mode 100644 src/gregSdk/Registries/GregFurnitureRegistry.cs delete mode 100644 src/gregSdk/Registries/GregIl2CppRegistry.cs delete mode 100644 src/gregSdk/Registries/GregItemRegistry.cs delete mode 100644 src/gregSdk/Registries/GregPlacementRegistry.cs delete mode 100644 src/gregSdk/Registries/GregServerRegistry.cs delete mode 100644 src/gregSdk/Registries/GregServerTypeRegistry.cs delete mode 100644 src/gregSdk/Registries/GregSfpRegistry.cs delete mode 100644 src/gregSdk/Registries/GregSwitchRegistry.cs delete mode 100644 src/gregSdk/Services/GregBalanceService.cs delete mode 100644 src/gregSdk/Services/GregCohousingService.cs delete mode 100644 src/gregSdk/Services/GregCompatBridgeService.cs delete mode 100644 src/gregSdk/Services/GregComponentMetadataService.cs delete mode 100644 src/gregSdk/Services/GregConfigService.cs delete mode 100644 src/gregSdk/Services/GregContentRegistry.cs delete mode 100644 src/gregSdk/Services/GregCustomerBaseService.cs delete mode 100644 src/gregSdk/Services/GregCustomerService.cs delete mode 100644 src/gregSdk/Services/GregDDoSService.cs delete mode 100644 src/gregSdk/Services/GregDiagnostics.cs delete mode 100644 src/gregSdk/Services/GregEmployeeAIBridge.cs delete mode 100644 src/gregSdk/Services/GregEventBus.cs delete mode 100644 src/gregSdk/Services/GregGameManagerService.cs delete mode 100644 src/gregSdk/Services/GregHarmonyService.cs delete mode 100644 src/gregSdk/Services/GregHudService.cs delete mode 100644 src/gregSdk/Services/GregIPAllocationService.cs delete mode 100644 src/gregSdk/Services/GregInputGuardService.cs delete mode 100644 src/gregSdk/Services/GregInputManagerService.cs delete mode 100644 src/gregSdk/Services/GregInventoryService.cs delete mode 100644 src/gregSdk/Services/GregIpService.cs delete mode 100644 src/gregSdk/Services/GregLocalisationService.cs delete mode 100644 src/gregSdk/Services/GregMetadataService.cs delete mode 100644 src/gregSdk/Services/GregModRegistry.cs delete mode 100644 src/gregSdk/Services/GregModelOverrideService.cs delete mode 100644 src/gregSdk/Services/GregNetworkMaintenanceService.cs delete mode 100644 src/gregSdk/Services/GregNetworkSanityService.cs delete mode 100644 src/gregSdk/Services/GregNotificationService.cs delete mode 100644 src/gregSdk/Services/GregOrbRenderService.cs delete mode 100644 src/gregSdk/Services/GregPacketService.cs delete mode 100644 src/gregSdk/Services/GregPathRedirectorService.cs delete mode 100644 src/gregSdk/Services/GregPerformanceProfilerService.cs delete mode 100644 src/gregSdk/Services/GregPersistenceService.cs delete mode 100644 src/gregSdk/Services/GregPlayerManagerService.cs delete mode 100644 src/gregSdk/Services/GregPowerService.cs delete mode 100644 src/gregSdk/Services/GregPricingService.cs delete mode 100644 src/gregSdk/Services/GregRackInteractionService.cs delete mode 100644 src/gregSdk/Services/GregRackService.cs delete mode 100644 src/gregSdk/Services/GregResetSwitchService.cs delete mode 100644 src/gregSdk/Services/GregRopeService.cs delete mode 100644 src/gregSdk/Services/GregRouteEvaluationService.cs delete mode 100644 src/gregSdk/Services/GregSaveService.cs delete mode 100644 src/gregSdk/Services/GregServerDiscoveryService.cs delete mode 100644 src/gregSdk/Services/GregServerInteractionService.cs delete mode 100644 src/gregSdk/Services/GregShopService.cs delete mode 100644 src/gregSdk/Services/GregSwitchConfigService.cs delete mode 100644 src/gregSdk/Services/GregSwitchDiscoveryService.cs delete mode 100644 src/gregSdk/Services/GregTargetingService.cs delete mode 100644 src/gregSdk/Services/GregTaskQueueService.cs delete mode 100644 src/gregSdk/Services/GregTechnicianService.cs delete mode 100644 src/gregSdk/Services/GregTimeService.cs delete mode 100644 src/gregSdk/Services/GregTopologyService.cs delete mode 100644 src/gregSdk/Services/GregUiReplacementService.cs delete mode 100644 src/gregSdk/Services/GregUiService.cs delete mode 100644 src/gregSdk/Services/GregUxmlService.cs delete mode 100644 src/gregSdk/Services/GregVlanService.cs delete mode 100644 src/gregSdk/Services/GregWaypointService.cs delete mode 100644 src/gregSdk/Services/MCP/GregMCPServer.cs delete mode 100644 src/gregSdk/Services/Models/GregModelLoaderService.cs delete mode 100644 src/gregSdk/Services/Models/GregModelRegistryService.cs delete mode 100644 src/gregSdk/Services/Models/GregModelSwapperService.cs delete mode 100644 src/gregSdk/Services/Multiplayer/GregMultiplayerService.cs delete mode 100644 src/gregSdk/Services/Multiplayer/GregStateSyncService.cs delete mode 100644 src/gregSdk/Validators/ServerValidator.cs delete mode 100644 src/gregSdk/Validators/SwitchValidator.cs delete mode 100644 src/gregSdk/gregCompatBridge.cs delete mode 100644 src/gregSdk/gregDomain.cs delete mode 100644 src/gregSdk/gregEventDispatcher.cs delete mode 100644 src/gregSdk/gregHookName.cs delete mode 100644 src/gregSdk/gregNativeEventHooks.cs delete mode 100644 src/gregSdk/gregPayload.cs delete mode 100644 tests/ContentTests.cs delete mode 100644 tests/ProjectSanityTests.cs diff --git a/publish/gregCore-v1.0.0.32.zip b/publish/gregCore-v1.0.0.32.zip new file mode 100644 index 0000000000000000000000000000000000000000..d3008cebce65950de6ecd19d53070a377e59530a GIT binary patch literal 76096 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 AA zDau$S_9yY-*UmH=w>EK7E2Dd0#IX*y}UQuB@2wOh<^`R zi_|y;w}#y)D_X{Kx^{zmkUCaaKHWtT?PGlr-qc$l1qQ$1wQ=9OlBbvgr*S&?VX*7u zGzfS1T{V`+pfU_nGupy;l*Lj<+FrU7nnzvst*Gh!6fii7)zBQJP>k!AEK|CjB+B*| zUAt_(f5J}xqsAMbm#L=1Ixo|Nov|E6T~S^d;7p!iOH&Ol*I|DDgLYqI!0 zTV02{#hMcP%k_QJRbkbXI7`Ef7@8|sK^veDSDtzM`sIfuS-rb^wKOf%N-Vb-5y}w^ z!}Ya}035Y9Vz_UP@L{!jN8bGM-aUeH1PKw!5zLQjt#=0a6I;!h?wM1xctUZEt1c~+ z=9eU5lO)&@ao^qYJ1o@KprQIf8+$B|0BG2C)S*N@A-?-W$8px8%ICXkKvA`AGh)Qr z>ZpUY)+D(i!IDHIiqqkpP1{{Xo8$|Z_*c|+y~UOxD)ih?24p=uIX}liwW{yVyy5t*ve;zmrz${pWjz|Ba zzb6rKH#FOG9_A#1w45}OkWT|NO zu}c7!Kxw~~j8*PRbCcg#%4y}kEN6KpRWe?=wRbox6a+?*eNd)?pMRr?*zL^9+gxZP8{> zm=fN1y(<$aK&Ae;OA=ab(2w&)M2Nv$az$${aTxU|@<>!*sl2NrZvKI*wzKoV$U0PuMa%+d`&=!<4X{|9I8Za|aSuJ_5s5)hZUI zq%F+HtEYxIkeHvnh}eo)U|@<@!*sl&NrZvKx+2Lg3eH_Gu$8QggehDj>+#B0p$#~= zFx=?aXmEkL*Rd&m+@S`}j>i%evrUgH!;*B*<9Z-#m+7?eaf7>{?0sxPbl=CIl3l># z&of%7+-&Dt_c1WsHo01vmofM<$C!Pc1xxY+Md?P~sB#yEF$mk0j`<1OC3B#9$-5`^ zZI+Nt|7KbO;nI5a!|X&H=(CNy1vzjo*-NEJ^!1}ON;(TZyTOv*ea(;kNhet?y?9G+ z7Y8+`FLcDQ)nXxDx)~j254aTIMtI6`qH>3t852vukr$Ng`#gR2iuprObW- z_+qJ=w*XH3^p!mJ_uecuu-hWJrqyrQ;m*&^yE?TVJxbOT!Sp;4K^X*)tq_5kR zF~IB0uQX%$c4NE<|KTjwVN8N40Pzk}xkr`@cp|eOoU{$1bWDnJXY>qSLej5G;6I!bwrU1EP0VNp#BKpYI4qiRd|e?pBn5vm>fD$ zt@6qklb_{4jmDr?WW#tt#YSrvNRI~<9@V$mtAOy9Z|A{HI5ek>sx&nTFo>aA(Q6mB zdP!M3cQdn7w7{4Q*Bxo3kE$+aS3xleOlmP5@GJR|96z|8Z?=Y0GXkr^@Ddyeo_JBo zHvXt`%hmywYMo8iLZ^E`P!GBe6W8w}atQ+Gg+de0F}^g+nfBU-qCSqyXR%+HthTKS zB4-Gg?|8K=$0$>YuC++~Jh1D?U3_Ot47_9=?viwcmJ-S^7p)l)N5l?Rj8W7U`qm;} zY|{|brMLLr`Aw{N0jokFy@YMxIY!;ee8H;BB4%h&Yvv%h6{nu-80D%mh7ycTZu2cm zSPc}ac#XVAEMbMWwUEVzqt~Hj44?)oiv%1v%C50LD@bHw@K6y1W$0PI+3ax9rMk?4 zsxMf3Z&63EQ!?cvU{g2XsM(0WU7XWABaF^sDH+i&>n`1~xaJGPYzkL16yV^|xi~J9D8)Vdg z0Z44PM`TrE;LRbMIgMZ94@avdd3{7yE`Y=m7=Xm-CR)ea?G^dw&WreZ^7uF+=cKFh z^0skJu=Rcs5$YEIX5}8n_JDhhO=qdlring)+~TeKIrT7uCb(8z2?eK+*U=oOXq;!S zX#q7YYebHHzvKhm%OhU^R)>Yu{+ci!r--%G!Af2B)0{*PB$~40gpPydd`nFBa;&zo zt)SveO!muxBaWVl+0Tdt*3?4O7MolALi6T?ThA`opHEYfSz|F|g|J*w$4vK*WijJk z-R?37_YZfwWou5b{iE3eRDa+W4&@K3kv+EV*E_%!iAf@=C+=GYvHhw8R6uarfM%}a zto*B-0Ysa{vrF2gjfu(H|7y!ECC?7g@U|$5)e%K4mfp6@c*;aX*$jxmFG+5;s!1IN zz**u+c2P`+m%bb*BCzNv+x(P=ocV{}xzRXdXsv0ut+Hgf45H4dNJG8)1YV&^AMKif~rhW(tzy2uZ0Q$CQ>u=WAQXVT5$ zDd0&MMr@*_(RAY4A-NW>a4Hn{Xt!ZFUjY$VZxBQWe!De0=~dA;ZS~W{Tuk4Mlbe`R z#ObGNh@y%p8Kf)NAmLXEDnG`c56|-8unA*KK9RN>>6LhQ7ZA7&;(n@g)DPXbk&;JX z_zZu#NS!{fujf>ez=BGAxN;H5S9{Y(mfgx4D$kI-< z-B7BPi{b`a%BYer(L0a~iP;4rD(j;3cnQyvA)ivwPO^`V3~JEVw(0}>zOUsdKx%hB zy`n{Tv=c145J&L^n1E7=HMoj~+XNe{h`Lp97c=n7cV)|9V8w+^K#H@GB>TW#4UuHm zJ*p^V&lj4OvFd}2XzI6`gzf^35k(Mh(Ao&>61HxDS7&P(j<>ST=|TzAWoPb|8X9GH zZd1Y|mP}#{rkdIG@R~veJVo4q-I&$kWWj_np7~v;4fyPDGmc|cDC27<($tNX>|Iax z-B==)uy~J^{i^mxZZW$fjm*0W$H0txAGi43k7907zkO^@XofAr$6)SmwmzZWk}U`} z89J(o^D-zwfR#gIo~DATfN13~UtuBh?IqN1vyEw%7)Eol+i{~f4Wx{9NFmUpHq{52 zpD&uHwNkEYWTs9-tcZ*e(S6mme2yrzq-J57c1bbAGe)%W1V%Z*DWhR^dQ%OEm1}ev z8`il6#+a~`;J%h{`IIKB(tR2{oV(lOz}v7{GlmVWnh0eTV=FIHs z_pcX6M{kyWn&Nse0IAK^GM1&5r&z%*YHX#qABul?^?vbY`Fi;dLU9SKkK$+3%iBv5 z#U8C5f`#b{DZ>!&9> zhKQCCW2`wBJRMqWbBNxVK? zdMxC1?tbTAjkGGKPHW5zV%SZI(-QeDeH+s2K9x>g8-c`Qg!yvvmFepCe*cxmOcQGhF1h z8S+qQW6H)JQw21y=G7Ahz?$lw(^#7F=4*KDZ`bY1n%Y%Ok%ksmXTTn7oSFkEwP6sy zR$$;jz7fMA6zm<0XnJOcpuPqMtaSU{l0;%1s7=w9U?#ffh(Ej!knoe##nRRX*WbZ) z2D;ccgx@6f7*d8Pa6Cg;8v7-$*~yo34nVn$d04vEW#B{8raYf%m63<3F{=xbT!tRY z_p@-ZYu_5Ju`?he@m230ayOus`Vb!BWj%d>GA$bd*#Ss#t(yY{XToObpFXfCJ6pw$ zTHV(hFoeV$K(Q83Y~UK4+54INun;*#$T!RjwmYyXmZppcDY(oqsS-!3=5Iw}@w8@- zNOMA`je-K@D`#HdiGYKh$0#_1`At3hpW!_K6@qFIl*nQWl=EhOf+K*q~EkB&hJ8cUDo#6k;-@)Md&;2#q8tGX*TvK>t`2 zV;$tj-D*W5B4%qi`2)1Q3iaOf{Bxtg^*5SjVf4y*K#gN)b7#=ERjs-9LRYnP33apk z)7%7!ljL~@?xU=Cg6^m6A?Ch1tQ%G`V8pJd_|<^jWQ7suPD!+Ml$8uN?JMga1n%@l z1~a=XUH<4~uPf)Hp3}+puX_He#faW?yCm=5tX>_tAKsaksH2nq<*D?l?orRZ$zJ#bOTvFIKPcTdY^GE!L4T5$4D!%_}9%t3gWPtG#OJj=DB=JoAh3 zavAScAG}wf<8N7s|L*AxH-GG3q_3yWI{Vp8(CupZ`sLd@_vqzdy*=rPP{k`fSlNx} z7n@fO3J&V)hI+;$e$|lD?RA2Op?W~#HB5|kw*1eSM!B^aFhtcSvZJ{c8(aKQWEHd@ z&PihJggM4!wsG%C?rgaq9XNblWlWO47a#wDJR9?;yB5z3vw;}P8Fr23P5Ht<@(W;2 z1UaWulw)>Q)-EFU?NA0ZKtrVEJmQup3QL8S<8pP-}=L)+4Sb%CuptcFUzD1;{9tmeklZwxuNyUO{mXhBy&<@W`Xu)$ZaH z>BeJOzp;QD!3d|uHU#Hj`;`5h!LMp2oMZ5vaG=~G2mAR>p||UqxkOdmp0o*Zznuu8pubp*_I!j1P zeE`p+X1&PgYP?2ss}!F-ttDKV=7ZLWB$P3`jF?ip&gRy}`6SzC=|C)fb-HV;|M@}x zGo-6=^fmuK=Kou{oCXcb_2?t7L;7$l*UPG>3~Z>)fVJd@=u8jO4D!zlgGMjQ2d(o+ zDC2e-Q9nglMRlKjxO!&rK#y=L_m*@?{l?<^_%j8HNwTwnzPkbpC^%W(4zyGxl4zfE^D zr)N+r<{NPX)rd4R7T-Tbtk`AysdbT2K37`3n>!Y>Bn=>T_hL5)TjN^{z{gB5HxL~y1(j{ ztO663eVT@}s3%^~sesDWr~-l@X+23;K>?~xm8zXK7h4jrZ(U>&os={of)rX#qf;Sg zX(zzc4`b3xfjlLG1bSAZ)1aVfC&G2KW1AXmlox7-GDv9=gs5XSIw@qLQ2l_I6~)}dIoSpxmS z$~hzuj3%sx;{J9HC5vo3h9sHpqKJ+nzOe&-!+@>XjgodL%jxnM6-^DNp#eK%70X!o zYSuro!+@>=(o^PdY>%U}DBQ4sCW$U1vWm^c)}_Xh+>mJp;dO{Z$lv_jPEMeO>g5U- zC+8O@rU4p$Gs>%>S{}x_asYf8OONTGnn-COl5x~p(Fr_c-Na_E)yg8(_DY=)RxFoa zXl59TB}Y`G^w6@^Q3F&CV8|k~v??(tUfie1q{-mRjE>J<7n;-{h*HBF^-tmQJTxyg zMV~_?gjTGM)3}O@%+f*t@#l6a3SY09OND?|qk-VK1St{iqsiy1`BR*bjd`=5rNC> z1kZ-51Z%18Q6+c=T?qD+Nii(zFT6aq>+(dff(>RihSxw0-MiQW;WvhZ$y$%#6*}<9(tzVgc>sr67-dQz;|x-=t2;0qgetF-Qs588o2iiq*_$*b<*v2V3Cnoj?coGyI^{tbKO{t%26w zU^Y!wl^VD2Jaeq#87L|d|J5OFj&p$}bx z0ScvtGP4AtWL^X(gl2E})TeajiE-4@bKCb)np*{2H2F)=fGnrGC=R1l7~RY)ilqh( zN8~ozBP@p7Di%y$~kR|Hza6ZThvs)HsE5_vZkhtP~2wn)-v-h)fMku zS%@m3R9}PePH-0Lp0ShC4jgu<>^X_omr;20>1O6IuwBf4O<$Oh9}9oCBYbI`&0VzBhoO5%x01DLR#5X4i<=8ZrrTjZ zaQlkfeITVyxlM&$MZ=ygW@-q2StyqQfNHa35VH}W{$ztLIL$R^)y5oc82oc3FSW_= zibIW$uSiU)@5?)DEyI}f*idAzwPn{Yis0;JM4sO?SFI#vJfruC2PHT zi#8=pF)51p!&op}g&Oot;aU&ct8d_l=;9=W~rn{J=6M?!0UPWj}gCln~ z4yUxcc;~XMuc#3}5@(Ur*GCbd>i?Tv1gOm})QBH7uGIf1+Yd5>U1wuIj}Y5Y8NX5y zx0fcnLcdn2);5G&8;S723;}iu=j3mor@ncKu%cL z23rqT@t4XoY6>sQh{HCJKA!O>dHU=Hw5zfXAL-{i`kZs(8|;LTCd_s!D0clWx#5Ufpu2ygGjq6NVK1H{H6RL2~GQ z3ygU_OVRUPSus`r%k>gAwJ;)aOxq_L^-EB=UeLrF$r}Kg<|=O>A%(A)%nQ&7sBBxE zAz|vLr#oKV3cf&k@voo5$ont0om6{Qx3<`ofF?X<@6)@(9ayliBmV&^Fmw50Yb-Bp;0SS-n3*lqKV2hJBSPJX*4 zi*?{FJU3vAG1!GaoFxBE+$f$lPomu-IgTRt={LTehtKEoi|07Mi}tSW-nAEzW$~=y zeoo?wY+Wz<_wcUZ5M@98ZAW$_y&cDoTQVNksS824A;mZOCRGsruo~@@WU@eo*OawU zUv!pqGEAnbl@qM$3Ndz3wX#@IQgs87|Hr}d>*bX3{mV<%{Om59I=B%St8kWuG%aa( zMa(R3_qVj|L(!?#0JIdb0{Vr^NDHZL$x*;2=1mc9TcI}(*Le-AbD$bFw-C%V<|H}s zRx59@bN!@qjhantkRV`>PBA8nt1{#PDv|}gr$1EOpB=|-5WcPHn)1|KxR^N?=&DK+ zuDIeAHUX;0jQ}f!H-VjqQu;w;tmC_fLdvE91c2hFV6l$EAl$`9vpX9a+A`K4$~GFX zhw@hJfz|lNY!0uX$^vb0oi#@6xzedc_FB(kxx3X-=5&3Ugph4#5CHC0VM-WR(o7M! zO)6AN`boKPeJ^(TLS@s?e31kxGT+F_T%-bKfLN{WVO`?0QhIA4d2_92WTNSbCw4rF z_{i+Q5xMY(Q-wOYc zV~Vw?S0vd*0o!=(-zHC^#bD%A8RHV$_ zxD*)K8<&{>gr#OZZR`eF*wv*iHhS|SSp!p6!^f=jPu*x4Pu<|b?O#^4Xp`)>k8Vs( z!Z31fm-K(U65MVRw))&a22eH~n_<|Mu3{(yQt|<+8rE3};Tn7= zS~%aRgF`FnM*K%w+-tc*2z#+5!Ex|prKW;`CSg6(k?N)r1}lZOYN;S%-y|}V5S&H~r?{`8f%rGI!M)La@rBtjD*1-fT z#gsWqi3^CKX&8mO#535eprWy|zOD?1H~p%S_((g(Rnj2Q3zB5O zJkuTw_7<&)TvU-JE1H#wv+;smLSnQyMT-g8ML9K(L2!FtAe8f4j+{No%u%w;p!rjo zho!&)`%${ZyuBNxKw_{(#Fqh_NXH-Bhf?bxhgBtW`c9J8OkHDh88h_$v+ZOQ%!zij zY)4wpmnj1dlxTH8B6gtnSrl%#d7af$)^}9_`Rn|Ien1{QHCuvRT9Y(A=*lmRO@Pw# zcsZo>6~LjSZ?g>x zaz0B3pY>WsW6S%Tq*dl;`}lUv{45=O=4%;^E%O;c%g#DC_OrJn=iYMZ6QkqfOCqSL z;iYI`H#R{^RISTIpYkwa`yHHMz^>;^5_T4q*%ULm^PHwb=s)Wijja%JhBmJ!7|E&2UL$uLyof}oLzr4# zT~!c3gAHfOkLy0DNf7`JRfbg6!X*K;=Th%EOCk1%?Q5H&E^3r_*uSrd$o?3W&y~`WzO{1Y<&z!<6oa8 zVsrnFuUKpS?Swpdi(CPN^H6G>tc;T_HeT}I+1y`s@*~{D$8IJe`cPr2)L+*+8OR*R zOwzKsQ2ueeTngwO1F$Tqe9YW|Gi5U{zj(p-Lzc@IEB293sV}>R^SkrEjxfe=T9Yta zp?YV?X4>mAWiYRy83N!bV$XxV&LXGAIF`_a=x{Cop#7D=~j`-P5Ee&he8hfMzAC}5e`cW#_^)v~Q(@!;XTxc;S^bPEa8c!P|WZxzJm^%TXQqat}itvQ&nA3klVrY6X<5ZDV8Lv0=5 z6A9S%B58l%tI3>Y*%eY@t0J$T`wK2ZEZsPnV$THoTgUvmf3UZ;u0Rk>;sp*KEY z{Erv^@gl!SZ?0V8p)HnZs*W?o6`G>*yYeL7FzBD8Uc!9!(I6mZa7;+64Sl5|%cVfR zE+ntJnrm)U;!+PVx>xazni^hmlDgi2>MptrQ2eGghcXvwU{bvUqBg1X9Dv}E$UB?r zL<5p{Jn}6A@{(4B*Ki_3jrL%6LUL1Uyh9sTgm8|s#gNe;fVM^hBeyhY@u6|lvOs~N zk}*&bBY`uEL!aFt`YPSVq>{)b7>r3D?R-Z#1Ek6rO1?{4ha}pGLdIi;5(I{5O)N(! zSDCJ@NV9mhrB!l7;`n4Ic0lMhx=XhxsiI`9thYUIlvy3u;pvZszguE7v90DH)~(*Q zG2WA#&F%u62BI|?pzHN93cr(}@3fv00Q$V1Cc^tRu}3xprn^++PS43bC4hlv_cYPp z@990VS&%(ryTV(=$cIc~1c}!#{&|I1^on8Mpvu<3_KNL_a}`4!6XOd!XhF`5LKB$d zLkT>KFPVy$Zb1CY&|oRHraPu0ROd;dbiEpA4?QCarejqy$iC##fz|`LUPs~XeqDg~ z85z}KWm0by>Un&Ny_FWjJd^@%U>~9w(yNuhaVhlq@xbA4ZE3g#R6F5JATE?KWN9>gOGBRg-zRPq2Z zp6IqRS){?;@1>1iM?K4fz!pg@*D8yr-M(JEeEo)Q`yewpa+gQ$tJfb0fXosYfXtWSBZkox> z48!_lpZrCdWDJ0)*SjdVSgoitZNlJe7w|U6xaC&#)k7hbZUf%%@~dnq%m8YUAS(gTFP+LtN@%?T(S|s zCyK?Cyse7=T4yhKXEE(?_HvIFOr(aTizVaiAGTQlAopiCSo-8EdE`&{Ywh`@ugZET z#MrvI3Ap7QA-rB=6M*0!Bl4ZLW3%8g3h&vh$*x+<=dD-3kcx6M>2Vg`DrW%o#O^!u zMMOv-ABWMQn5qy+f;I3Qr&<|Rh@E}1er+;~#RREdIu*&W23~r8D3I> zjoAaSDwF{vyCU|Al`xP5YhpT1y~>OMzO5+F6}@_x(C6Wf1XZ z){8G`$Cpk2U*f)+ZL|S*@F&^$0`diI#Uv=HPopS|?1auCkjiWW&v6#g5@7(TE`k$6 zGcXG;wl!~vRZ8?a8;rb}A$Jb+xZsu&3EAbi?amxb*0)j$r#U#fcp zcz~Y#fVz7x0N<(?)_{wgFJT2xIma5JlCNRD2bH_T9KKS|mgGXT-5J&}ZMK&A9>|%} zhFC(X?bZgT!SZxT#|dms#&(xnlOF(8vKBVqO=gJhdaGRn*4A;CfuBh?U27bC3mwJ; zY8)ql4N`sAaNmOhFH?t>^lKPxcsxLK>i#5lgRrzbS5eWo7V2T#faM)+^C69iNCUTH z-=AhXo7lKWv29Eh_@|BB>Wq6X12@^ePX6)f`26(p%ke)BFZe^f2O>TgE~4FL9n8O=dkq45@9(VV=PST^NBb zefQ~RcDh>8FDD)^4;kj85_PeD-xi*C2Qo^k5CP-E} z-727pf+2b|(}9N%>tX7})) zvJ=PhU$eQ#;_e??(Jyd*cV3ELBC_2jt~ep3xROp3r5Or6Mbtd25bKRnPG)a2B@su@*I1HRpjG|9HFY=lI2@urkC%Jsw@Sth?Q2 z9}{mwGC%(3j?FfjU0lx5e6`j(L|?6*f-Ya>d7-<&az^iQMLuQM0ewozA#0z?KVDO7 zmPYReWS>gIfK?QXHn4^37+XLMWoyaO3Az@oUm0OiY32~rYov`>y0y&=h`1GttfdtL zQu8_Rva<5}^b*b>`ZjUjOL@x%*|*+@<6f%@fKkUbaU5d>TjmQh+PA1%)%4Yp z*EoRSgMwS0&F%yEww`8I4qaAz_4xBV%6sj=K?}-EVVjbu5^Qr&HKz~zJYo-g`d{5~ z2lq$zjQpXTFQ6xC!0p3rL7ccj;?W!OUyW8{R=lweiThjnr}HdTw7wo&sbjpg4Uj>d0KGoBuu_}hr9Srl6?Nm;m7cY$a>+Wtpb(O z|N5&)=ag{3lMC6>-v3%>IXU0t52hJU{_7_JTM&Yl@mISq!wED>VvGW6^+FX)GXqhQ=E5 z`>%0o6qv^Ro48;hvxG7^4H7STVh1m|!Sds7wIY#27Iz1#N_my!#Nr3hD@(u3f+hKJ z;4r88=!bX5QRF`TwxZ%Cum5l!-KT9xZw8CCbjgu`C? zvpI16TVdkuH>xiC@iK|BKbI%xw=etoO3XpUUXUFhI1yi*nE0y$C;Ni!QZ~7Q;BHB@ zQ@xF5MrsM=@Ivc|=`{!k?3x=t$vKaPcFv=HaL%Luc1|TX`UF}onKxa!0r3r2l@$qa z&jN2GX0CMxGQb$U`Y3G}M!WP#N^PlPpQA}-P0`OK?Z6dSW`G!)jRaF~vqXG_)XMc4BD7F~otPwJ_Fh(&>y)*-Nsy zYVy&b#r5KNP@IE7&1^t)msmOmv}(3hM=bY&{%3$zmF{(LPo;%MJ+-F`+wry0_Q&;9 zMKhmIR9}C?Pb_LBxX(>;aX^H1W4w#WISF>R*Y2G-?dR}@_Pw7@gL^L^x2F%ZGcE># z@$GC*e9BZD4;tNG1me4a?3IVmn%Rqzo$CwvyFPOA(Txeek=NQJ`vm7az}aH%Y?TL_ zN5!*TU_eR+K~(ge&Bu4gOBRkN5$}~2kC%T%bq(hj8MC$B)&CO>^vD!-<=6V>l=N;U!fLKRnznLfgYf>%d02^)KSSwRr*M zyk;AS>HQFXCowaILulWBtkUqzF2VgOjJ4#SqHwo8q-2={cP8&`%84`4Gl+?XC)M)q zj($u9!>u}7y#*1W*@X`&tQJ8^jc${ll-}XKrV1aVwwT&UHgjVTKRJb zoQ_=+Pb6gl#47k&31vKICcPhJBjzSHofR0aPi@tcwv>$>d&4GibXG1)h#;Pp(=*Ud zSOuxwi5K@2FHMP}o{rTskS{e0R6pZasTKXeU!XR#+F$+Tx2I6L@y|U}iS}!Do9-HkWC+Dd!v|Isn7$z6XqWMq;dbZKOJCB> z%t}SnOa>juMC^DMyAQO2_ylLL(B#*XjarER1t zC=<5h$2mJjI|^?;-OQZZg#G}pbGr$MLMQ32CM8=zLab$*`s^ZA5&UbI9Q%IwNS4Mwd$;TJrqKfYsheMUaa_5r(IRkU?3E znD|=|1)sHEYXV-O%Z!x^$GE^U^-Gu$@1L^I8 zlDlv7SwTQ-M4Q>}O;cJm!+zY-YnUQe(E-Z1v-cjH{i@!ezUsA~AS9CHk7?GJ8Gcxs z)?V+wykwnrpSwseAM8?s8O9mvcn|6q0A*OR17s-LFYr1<*mci?t`H&DbJq)cMf*p zB54_0wkJ`zAaP7Pm~qHV(sWH0-_Km%kFVEJxVvAUyW4J>=@9BAj0jcm-+C(9N)}L9o2oUFOh?y3 zE7r@Z_=;{8=Pb#4t_G$x#t~xszF|NYbO5H+RvBCK?^bB@JN>$`97K(HezG1a< zyP%(^Ykm^R@h02_NqU6nqnE71T{5RXEn80!wW?EVY(XlsRf8D_&kO}aw*Ljh>x;Y< zWsmQ1@QdmICo~_AeSgLlPh#z@=x6#IZ`C1t)P8?r`P4UR50kjCv2zyq1P z*@N2-w?_(+^f+d)p^MI7d$;(@^%ClC0`)pP^8znkcXbRo^sWW`DIU^p+OzW!y~*}o z^2zlAb^y6Wy`e%NwE+!0M_K8Y_;wO1Un4D?Y!)oN1uKE3!{Kr|oFTCRJ7ZZ?GgKG4AVd3l0rD5@XCB;g;XuJ7L&oiJAG z31z4W|K=<(U80fx5Kh5%9egAb%} z|3+R?d9dcaoieWASWP-3VcVF?82$VrUSIR+xKCpJk@1+nln(ijcLSQ{PEf)cpyby+ zu}eK)27%GL^ReP>%A=hw-iiA2@rKHgqua+69O+NnnUIN*(3rQ=3Q@0@WnNEcH2wcx%PyD$jRO6yDQIhjWr#aK3zHSeqxb z!4*d#3Q$LZpQJyAcmH|dC;+oy6*?aMkN%!S#NE*RWowaW2g+H9zX#4Ne~dVwh4yFT z^aGUBS!9=~7q&gZ*ePi)GCV;?O+_=3U8-cUa#xzK{KitAEB9r2%(JT^0Z0YfmM6Eu zNNRD#BxY+tzpZ@b(or2-o!R`Uol54_@$}C_cC5W&Z=T(X^2)A;gQ+85DFZ#E8k9Zc zcJc1b+qXwYN3ZYd^yPV~?U|PWdP-#$N0X$24WHF;7@t(XymVhL7Vlqvc-vDd381G` zvp9R_e*EBizEQ|q2*+2`Xp-~o*Sug!ejGULV@&DJf*VkA+gPJW&23_-yr5L)3k$PL zt>QP#b>YJ3tm`T7xf^}Y!Qz|PV2!v`EUT%95VwW%eru~S4o2EwXD2RC{}be`@_tqV z3-fwPEEW(R@0MQpE%Z%Jxsmj(B!+EYo4J8$Xsw6Du-9@%sj19cU$K+NRXKLwB8V=1 zvC-IWk?{SuexqLahvV}*GCu;_cg)NVlmb|?sK}zS?Evkk&cL3ci><}e_s%){S$r`rSjs&6_G3EBAg_`^(_#Dy0Ob8eV00XUiZ1>%(_wQf6KRSA4 zFSdkbFZLC=_u|Ch<-K0-Ect|8b|U&8miAhr*zz-j@*3B=&yt&9FWXHJ*$Gzw;7eCS zcE7}vjIfuF{rM7x?R!60#FM-K846l`tEGt{o3%PQQ+sW?bw;Zt3#VV&&Tvnf!VVo* zIaNPhPeS7b&e`p_bDSGUbT?!; z?a;RJ`|%ssI$B-y;uwPnR(evG_EjXc(XLMmt5p%RiEXF4ugdCLSqM?%#PxmS7fPw1 z@~~2Z5GQogymgc8TnsV&DWRE4e|d=^wcY?cnYno^DL?i?zLKPvAk&{1vDCQUGi{_S z3TSPRkghtj13uH$6D5>M?vMH8c7DJ!|L{Av4oU19OYj8G%*v@LRzu9PQLVCSV7FWU zDSPXG9e%kuXD#k=|1QsXzbaCLw6EzT;@54b95a-X&nP=4Ao3=U%PJQVU&Hht;1Xt zxn@V0kW$NMf5pn@r$Fe`<>i1X6~=3UCRY0^nEVpJUb89d-_t%BiOlEO$;y?|%U!4Y zuow=(%i2U7Pu%;kp{oQsA}ZV0PvmTdY=>A>mIOZ>)j+O`f*H&pCA9jmE^(iZ8ZeJBtO#)I;>|tbZZgxq(EFHXZT1NY-+0887aS2GSlkke%(T`o_%F@BhrDe3Q za%IK03Av=^#^6&1I$AzR`)b;_mUjlO2MvwwV_>L;))8lIb90C4u+T|aqf{C*}&!HO`es~qt6GtHb#rN~gQg~&;E z%l^}=Z>Lv>a}qrm@5w1{#+~S1OUO9eIRJX|CmwUVe7}5k=dM1ydR^NJij3CIC8&Z} zXlS<8W~L#+4x6<+#wp$1o8{Z1538dOFD;~#KrEya%gQKwo_7WlW)&67si6gJWCy6N zRb43Zvu&MKR2=P=hOyx8?(XiE#@*eaaVL0ihem<~cXxLP?(Xgq+=7K5nf_-k&Y3e) zRjYfg{%Ut$)ardv_3q~Z-~g~odsP(mX=@nu+_?3c-=r%QW|jfzledYI=BsZ`NwNsT z-M_T(S>U-+#UpL>%PE(m!bR9WV<%oUV&FC!-4yPbw-l1rBZM{?^}Qxs5vkH6o^#|Y zq(Uyb;?xliw3>fnG)1eBho=YdG!PdkT6W@6P9GfZ*-n@_<+HkOOAW+WASAk>eOCDS`aGOTcYe`^+XO=0 z&vm-dvcHfQdXpG(GFDIusYIVBvdzQ7O^03KwrMmL4`NpOzT^R!GR&xm1L+c1;6YK} z`b=@#81rBuJE0dR+zfxy6h1>{Hn~#{ zc`6`0p(oHCO>B2wQpEtZJavabn-KKa)~NGmQDa`BHTRr|3;_`ZK5<|4}z}WfoXDV?Q}0)-LDh2kDMP9(5{ey-xz#mFeb8KW(wY)l?Yn zo99ZZgV)<9bfU|{@qZE-T$oh#IXiF5Iv8?d6JNm=`>l-N-2~(uS*y8|8>DkU_P+4hA^CS2dG4S$u{dpBB~6Iz3~vF;u7MFFL?FM z)OdeJWHLYzBEC{bD(1A(ieE5fWj@lymhW;T%I3Hl+%U4 zkyiqx0)tBwDNO_Jti1{vQ~I<_M40&f^Qp&dL!w;F>%(fK!?F5G>}$G5DxnV%C}GIh zAHRptjbIf`I99vCm$HY186;Db8KY(5s>O3Q%~$qEg+#fYjYun4HQU_|dAn(L#A%`R zW0$(O_ZJ2(Qm9jJ!JvtiOW2Q2K{bRIDN2-6{nk9@=S*M}`OpC=H?zG4 z6f7(jXW$5&?amj&eLZuGFP^OfA0@NSLC5xu2-e*Lu>p7X9zuE37_U6rpH(_;E3XGG zI+2`cOol zrXdvJ;!NUpiTMF%&38nU)sF#ppJ%<(;JcBg-06=W)b#OpExugh_J3Zz31P9v6XN(3 z)siV3a~39QLTwP2(Elt8*Dt=3;y_F8vBlCOtZ5d&d6rwMR4A|~<7iR3Y6sQz2;EA1 zh2jK$nsE%X`8ClR1TTa{Dx30FRF`k-*1b@57)ePinzb%0MD2iUqrl^+-^mljTZuf`uAcfmKKxSa zbBw_DN!H5PR@E1sB>a{yt1}NB4-j^OUhIy6M^39dh4y~b3K5$fRVOQY8`yUIwoGW3 z^Bt4yrZBYQyPgjSBmgSQ?_XecO8;v3!HOtMrSNoqG`a?nbS&Xs=<849a{Wxwi`vF(`I2_fVVFQJ?Fu?4zHU; zlN?l{jIha&6XH@p_QSFf7fu%}waN}-Zkd5_FX>3!D8T0<(0*A0uyF-pYo zSbxO%AtY)%KP3vn552A>zv-F_iC=ZTFvpDv6_2gecMxCfb$(ejLt(nxIY-n58BCTJ zMzJ^*9>8re`iV1u{{X@HIG*u{+7^L#yv76e5JZVu*c88F{NvbZoc7|`T%Abs`)EQ< zVXkrhGLC}?|Js2sa0s=>9oyV(ly`v}pmqF@xyMh8U7(kL&AC>wJEq*!fUdQZJ1(o| zT`*MubwB4T!nB0GgTyn|uuB9rZT483?7Hd}ges4XBAhE;04Yycn`Y20V~o{|#Z|eM zp2Xt<|HklnmJF#Q!1|LsuO73XcwENvoHX0W66;(e0|68PAfs;E^0)WFygnWK22Y8y z9u7T@S4P0FW_v%Bdm`7X_;+oT5bRfB;LD%+U4okFT8vHzIjNmnGIpBU{W#XTdSuP5 z0c=?_l|Y9MSER-b-Em9x)nsC9*puaS#wUci$3fHzyuUf8$dB$HN4yZCAJ_}Ye{)lnYTDy zeQ)}GH0dY0>)2MXKi+RCwU`WngWt{A5`CjvwZdslwEy!#ERWjEv?n}26V+a*U)$4Y z8Ju}QBDKq1^N-PjgrediIjHxN#|!qBIF)iu+Hr5XD(ZP7Y(pTL=ZjSua3u)B+_njh zyWR!lRW7=o7t4kTx;Ccj=tRTJnM!rOwO=0#NI{@KW?v0sYvFs$X-msS=A*qKC2h6D zCTihH_eWp@HOPmyhIan2UN*fLZHHia3BsrBb;Hi_-Wyg z9PVV*zZ{JN=oIgz2c zdBnej*EX?B0d@!@eB_)Pfpq1amL;|YRNv35Hv{!&)6(WwYPSm}Ll^~xqPNoHFj9|D zo9O%BJ`AE%2VUTr8sQ$DD-mJn3ICR2DtDWqQl{RFQ^U@vBuetwh<@k5R@rgRut8^M zFfpl`cyT&>R^=x)ZxKu3h71}zVwmEa8eQ`WV5HK`g`Hm*$*sZZl`Ax>W8`k#8Ed@j zw(>T81`Y6wuKUTRR?5P0i(9Ji04%%^{o(1TRE^>T0kZp$PY5g z4kP*qU-_u!5Ayl`IhEjRYMAAvA+vK25<1+3_8_W@<7rrjM#a$T|ApI|@)uBzXfGV|@Bo zFMBZIlGjR0HX9}3+prQzQ(CGb9}B8s({dbVdm+Sv7Q-{*&;|EV*)Zv=XpI)~&+nR` z0x%r4@gF#tWI&$+$ykB+FcjB8aYjYLwhG8{8BBxKs=ciW}CfbwF&iayzz4S|cy5lzOOY+|_ zyc4xV((WcsJ3-IRcrr()E#G4?W5359pwA?9(H(xype{?oH(v2|%Krt`?9Qmc^traE z42)q?GM=k6KSH<&KlP#a;A&~GRjY4i#uov>=$qjOa+uDxZ%Kh#hzuT{B~{WC=qI#v zP>z~!0ZW`jS%ipcLL9&A53}%Y4D;qMxBROh$j27OTpc!Kg&iYh&Td1MjxHEMvY+b0 zBc^SHfHhR)R`R*7t&AQ7eT8Vo!LYT5NeZO(Pdd!5e8zjm!r#Wc@>oL_gMLs8O^G&7 zmMP_d7h%@*!RmoUO`4*XQVlO7}tPoZ0Ns;M+=W zOsW#_HNv+~r}50GY$lyvGCxL!QC!Fyi6Vp^8lfXaHae~nF1@^N#Tu8lQu(BR}#p@1HkcR4H8y1YAdZVh0U*X$B)Pd z!PUv)PG@Rv(i&lh_DBX0_}>v+sJa@vdc5t?34wvW39O@qsr^}uyp$^DgwAH~D`OgK znVTy)ruA-Avf%^`K5v(=I>wg7uCx_MU)jCsew`{GgpeT_H^7y8#K7ARzxd6FjW1dc zC&p=6$>@OAi{oKk{!ulAk))8q8a}e4-v{Y&>}kbt-{-L%%`$y|xYgIka^4a*3^GDl z;1Pp$&5pIpwUNV6s1$xxnYcu=+#f?Z~t?=Fvf))gAE zT+rqco6O)uT63j9FxYUHi_F{^pyS(o9W#1D4O^m;w~uy8zNE_Bib&mmR&>)?W`yYD zB2r4Mxoj`ehLxIRU;wNQEh-5p0jul-)aWlPI5m29(n|)Oym>Tx*g)_RRo#xX86m2X^on14>ISiz zOg04@Dc!@i2iM`F9Y~xpX+iwm$B{uHPSC?RlMz%0e8I;`p35l#I`A^XxI_!y@0lwI zb3c8Lx2_wUoU`a~#4)b~&m)@BIMr7S6TV#s(wxHMcrNI|V)>ghtwH?AM@6gqVgu~g z6^d#(0z1liKw6&^FqOR2i1oi;V`{Fb0O#44%(sD1zKIcmYWZ9UlGn34Nf6^T!2?YF zj@_gB-X7hMGUs^`fj$J`57DkS&!Sqzm%3p7%|6cv3v51~z7vTA#8+$PO(?QToTpwIk1wJoo<^w8cr#{wq`GaU)IFmbcXd*Kq`xYZh#;W_ zs`lG89jPnddW7)Xy??YhY2w&L@YQ6>ttl-r32ES22}sq7?)uD*(Olys+VP#p}R& zDAo7mDTo$lT)O-MLWI%XnjQlnp<&`^tm!LQ3f>rKpZ5Qg6e)6W+nz#ENADZ&?Tqxu zxMC`4!)+ckkJneiNOf!G@Z+7p1@V|_@fe#gQ#1W`tMZJ0Wvy<0K$KLoQuM9-%Y;vbt~5f^Yi+t?FRl;0uv%tXgTUsIvb znhGL`?^X^GtFMCglF4?WOXlxz^w4kmZSc1=k6O~Prk&fOu@b#c=++;WZ>JvLj9~C@ z&+Q*omm}-*C~WU#hQ}LEq@Fs7fDW~jh_ZM>NJVj3swYZYre zCB0HBDBlY{MrOO`>-+g}7axeP*mJg0c(sKDN(7u)wA${Esje64lNLQpeO!*@bKdQJ zJAOZ}X{p8pul_^v5KQ|0d>c;&V>|aYi8)EqiHI3-KJ6YX(pTy>ecw5;))VpH_~IxL zyLINX;`Wrdl)AB%M@rx6m)Tp?BFdEoY37zCWFE_5^{OYXv{H8D(o{Dcu!qGV z$Id8msjxt6B(8Z>m;15$iif)Ymtc0pXTVo)*yLurWerzVcl&%n>0#cyG6$pfPy3qXM?pj~hMi&72Fd~gwwE?Z>vY;1DNi?hica|$KX*Bdz zW#(pPa#H}r;|=cJH&c-4`jM=bBOOlAS!=^?zRLtk zvQ=%&gT?lg;fy@5`HEou8}AD(?EY(?D%%C;J^kKpdWXwnpCvY{EU`7(-l=~#AinRE z9pme_8rr?zkW=M)qa^a1ddwrcqXCjz$I0G@o3^*vlaq8pD|>N;Ri@?_z2bQVtVDaA zC>$*N)K!tpe^3x-jzD;Hy~p2wf>{N0P6r?RLl28dA5gq1jahQPNt21jOzqqtD`g5c z5RNgejU^!>$QY+29VoK>!#7VVu>MjAC9-!3*}4l}e9W*2(IrYIUVNgX-mi;CEi)5m zsyq_RcJ}8J@M>FRk4-h+BP4#-9{BUPdg);RZSx@?uU+uLiNrqW0}#8I$^rEAc_Iw2 z;fb?d{~G3hkWqX6Lf(q4ty1* zO+hTVplKCzBCKixbdJ>i&!KVyW6XGzF6^-8w;kFCY1jJ6s|9~RQbVqgTf_0sGY~Tu zo;fh}=7}~7KL7dCS0P}tLUgPDOtJ-zl1T3ln=&P$lchel$t@M5jS$)$ZHHq9(Xz-T zLeje)iL%CB61BxuUEVz{xML!586xvc9p#*)!H0pC)__^zNE7LBovB+ z;6xk1&LH;zsUtY>9J;Y>}CDf*%EgO+T+2pH=|HjUvKI!0!Smvlck68syr<+z*U7a;?UVb;fc|w|77w9Rzmle2y<#O73$emV=Dri2`ST#QeRa(X z53htb=*a(Rrw|UBWsRi6q8-X10}k_nvOS|g6zR&m?CWbDVNRW@yz%nxYK_Y|^6udAx4{ z?sFIv1gR9mrrtUZAZ&&*W0f;&VQ6J>;^(H#E|R_VKjCPn#r6O^$AiSFhq)?|B$}O zf-F+1tHICx;nP_2HtLnz(tm?d1b%emqND}PES3E^TeoVuouQ+wL-$xzleZ*Rc#RK@ z%jjw``iG|72h5bz?;h8Y?*`GBs3y#P*2dGRVnbkS!ism(9_%bireeR27}wKq-EwlR z`eKs;gt1yHPG`+;V#2E^>m6;EzJEtEUG_COMIg_)c?%~aTA1WFGqnvvuNWKb6{k>T z=0?WA9;%)#vWjQRK}lS?FR2BHlAw+Q)@DXLj56A8q(bZ* ziYr0(+HpQ!nqtEosksKj3)Y?k-Mgw&!RV=Ch7R8sw6r=V>GxAoL|k0H#yO@U*K!5G zsq?lZqis#0)_G~N3hKrwQOT{p^H;?XuHhr4neFLJt|vR~ZvW8xaC`nat>zdMWz`}o znm>5TR%k|EPEF|oQ64;I?9U7sMHg5)y&A_YNm(W{ZSK9WsC7m!y+YE-?M1pbIH0^Y}Wc!wrKdz;r-!M z7!9#lxjzzBj*F8w$j{9^aV7BIXyo55ew{6#4>$F6EFh z-BZZ22)azEtV)QdUzv6G<;nN~mZ1y^#1$+cQ_BiKT^rZ0ET~0FdYb;6<8{D{F7AsX zyzl;^=#-ye-p}Q@@IoW&YaUw=kL_&wZO?^KZV4RIBf|6oA_~^`?=2yp5aWAznEOjw zDhwC5EH&GJ2qm)|H}uoL<@9+G$N6P>97z^ADvQB*=CkZp4m}P_F|ysPOV7$mnpyR^ zgZ=UqW&hY;XL_3#vRSq6H{A}i+fQoV6>|*(rGptZyqXh%F>45!L7N0#O^7Yw4%;jY ze8xSH12XPbq3vTHHU>m^^s*)Tvu-I;9r<><83h)EVtzoSdZ{a>0`f;IJDYel(hR1( z_5p{~w)J+XFClFl{#$CW6U|N;G0U;nu>a7|ITO0Zjo3?Fh%mOF_sfK>i9AmQjFN|+ z*UyorJwO}!fC@ZbO3l^4riHfnD|{x$3?^{|?wrTfx&7cg2%A3FzaZn*V|a&tEIN^A z%@%$!BE5Mq+MU^j(IEfxNVc}~wg2i^r&V?$lu~hy&RIV5cApSYZpjyv7v~09mqQi| z89KrHIhjJ{hJZXR9;!9}Gt}~8BQlJO!I$bef zSDbX2&#em|pbTZjqB&j~fSG(6<1k3iVJN3YEv_d&KQ?MujtmTvZu?Wt%C`DzDfK{! z3Q%XWj(@Hz-S@3Ik*2;A{l_+^(I#eV*QR%LoUA2Gireatn;)gt=|(DbUi&+eS|WbQ z+mZ`W9wVp5rnmH=$I4+)2Zn8tTLmJUvfd>dxQY?Urg@^ufNbUQT4o1&5lA2#QB0%BUbJ;?$?$?M;_0|fj?&qzI-O}#{s z^Ec6;{rY!-|mVwZZo#76#*un_Qv zXB`rwnL*KyBQ1$YYzy_&xk)O*JgFky!zTH4Z3Vxm^fMIean1g3Shg%Q>?GtAS)(OM zPe8Wvl)i?8oq!ZDutiDj@;Y6iXGop**+i**P|I+>;!F)c= zOevk=Z4s-C9<|*Oy#oVn5aLCc4w9NNT`CW{Ry%LiNbyfd3io9}AaZjZ-)BdO_yAC5 z1Z90o$*9j2z}l`DT2#%vm~^9?ntnwFLAf9c<$B*kJnAXf19I|@O-CwWGuI(ovR}`f zWAD_o@7CT^0BsR2(AS#<*`AqyDfNbu>sYQQ>!qS$Vk3(2 zZ8djH`t$0zd>=SiVyby5s$X>F<)O?_{|q7 zJ=w8(5R-JQFU|$3w45Y!ZBTMvrDZd*BZg8IFX}97)#Y3@Hgz@WlYJ;La8q-{{GZ~vBWMVjI`FUj{P!$^Jb**EWI5L-t1X(JGWR%>P3m={;|5+|;8Oub zAC~zzPR1J9}nx<;02g{G^_4KVSC*pnAc=ir`XI1Pv2lUaZWs+ z_K}Ol^9{IV|C(=?YRB2a*=8qU-7P?=t@tJ=Wt2evqicMx(=(o1_MPsha*`9t325@n zZJd#rY5z!?@ho#sWL4|R*LYm@s#sbmq@yz%EUqvjw4+d`%()7yFjWJc_^oY?O?+BD zXX*0lOt-%XQ!iAq;7=bKWkQRLc?gl*yjdKSdOOed@E{meZ-6iZW4jIMTO#h$@YIDu z9))n-lNwc%A6;&ZkTdQ>aY|!M#806ZwM+dO1JGNF8kQEu^Wl-EblBT=gkm}$oa+Hb z^-0BPt#tcTwnrSBa!B{glH&=BeNUf%pF6}_Q~0DXMO8>_w&ii zI$ns-APmr=3>eGth0-e)k&r?%h?60RWNj)M8 znY|1iML{^AfBc^D9V1b{a~!Irq$}}jPLoWvU!8%TX9pD1ThOlLsCg;!6_ez}fPPG2 z^?>0YB^<`{Ngow``I&^cqVi%ty2;LMKF9;`2rSWG{mXasgx}7(=+j}eR~$H(<*rt$ zm!V(q(%=0Puk0MSCK(q}Uz{m+1vg~PGAMVxFt+c+bh;hRRJTk7yc8Iiqe<&ppKk?5 z?xl8~caqA?_SU}%6wcasJ3D6fY45J5mi`Vrt!Zw3E9ueGE#~vGT<)q@0IqXDTZ?iVY+KY;PYc4 z|D*B&{C)pesU@7H{~4_R^_x#M|Ho(jpT+;#<^ENSMh*F|o~Nqt3HF~Q P81Q}yeh1J)f?xj&2OHf} literal 0 HcmV?d00001 diff --git a/scripts/Build-Release.ps1 b/scripts/Build-Release.ps1 index d617f787..8c19e08b 100644 --- a/scripts/Build-Release.ps1 +++ b/scripts/Build-Release.ps1 @@ -15,10 +15,10 @@ New-Item -ItemType Directory -Path $publishDir | Out-Null # 2. Build Write-Host "Building gregCore (Release)..." -ForegroundColor Yellow -dotnet build "$repoRoot\gregCore.csproj" -c Release +dotnet build "$repoRoot\src\gregCore.csproj" -c Release -o "$buildDir" # 3. Check output -$dllPath = "$repoRoot\bin\Release\net6.0\gregCore.dll" +$dllPath = "$buildDir\gregCore.dll" if (-not (Test-Path $dllPath)) { Write-Error "Build failed: gregCore.dll not found at $dllPath" } @@ -32,11 +32,12 @@ Write-Host "Packaging version $version into $zipName..." -ForegroundColor Yellow # Copy files to a temporary folder for zipping $tmpDir = "$publishDir\tmp" +if (Test-Path $tmpDir) { Remove-Item -Recurse -Force $tmpDir } New-Item -ItemType Directory -Path $tmpDir | Out-Null Copy-Item $dllPath -Destination $tmpDir Copy-Item "$repoRoot\README.md" -Destination $tmpDir Copy-Item "$repoRoot\CHANGELOG.md" -Destination $tmpDir -Copy-Item "$repoRoot\gregFramework\greg_hooks.json" -Destination $tmpDir +Copy-Item "$repoRoot\assets\greg_hooks.json" -Destination $tmpDir # Create zip Compress-Archive -Path "$tmpDir\*" -DestinationPath $zipPath -Force diff --git a/src/AssemblyInfo.cs b/src/AssemblyInfo.cs deleted file mode 100644 index a8310a25..00000000 --- a/src/AssemblyInfo.cs +++ /dev/null @@ -1,4 +0,0 @@ -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("gregCore.Tests")] - diff --git a/src/Core/Abstractions/IGregPerformanceGovernor.cs b/src/Core/Abstractions/IGregPerformanceGovernor.cs new file mode 100644 index 00000000..7f079bec --- /dev/null +++ b/src/Core/Abstractions/IGregPerformanceGovernor.cs @@ -0,0 +1,13 @@ +/// +/// Schicht: Core +/// Zweck: Interface für den Performance Governor. +/// Maintainer: Wird vom EventBus für Throttling genutzt. +/// + +namespace gregCore.Core.Abstractions; + +public interface IGregPerformanceGovernor +{ + bool CanDispatchEvent(); + void OnUpdate(); +} diff --git a/src/Core/Abstractions/IGregPersistenceService.cs b/src/Core/Abstractions/IGregPersistenceService.cs index f77908d2..c0d3858b 100644 --- a/src/Core/Abstractions/IGregPersistenceService.cs +++ b/src/Core/Abstractions/IGregPersistenceService.cs @@ -1,13 +1,8 @@ -/// -/// Schicht: Core -/// Zweck: Interface für Daten-Persistenz. -/// Maintainer: Wird für Spielstände und Mod-Daten (System.Text.Json) genutzt. -/// - namespace gregCore.Core.Abstractions; - public interface IGregPersistenceService { - T? LoadData(string key) where T : class; - void SaveData(string key, T data) where T : class; -} + void Set(string key, T value) where T : notnull; + T Get(string key, T defaultValue = default!) where T : notnull; + bool Has(string key); + void Delete(string key); +} \ No newline at end of file diff --git a/src/Core/Events/GregEventBus.cs b/src/Core/Events/GregEventBus.cs index 7f67d53d..2d1dc56c 100644 --- a/src/Core/Events/GregEventBus.cs +++ b/src/Core/Events/GregEventBus.cs @@ -12,6 +12,8 @@ public sealed class GregEventBus : IGregEventBus, IDisposable private readonly Dictionary>> _handlers = new(); private readonly Dictionary[]> _cachedHandlers = new(); private readonly ReaderWriterLockSlim _rwLock = new(); + private readonly System.Collections.Concurrent.ConcurrentQueue<(string hookName, EventPayload payload)> _deferredEvents = new(); + private IGregPerformanceGovernor? _governor; private bool _isDirty = true; private bool _disposed; @@ -21,6 +23,8 @@ public sealed class GregEventBus : IGregEventBus, IDisposable _logger = logger.ForContext("EventBus"); } + public void SetGovernor(IGregPerformanceGovernor governor) => _governor = governor; + public void Subscribe(string hookName, Action handler) { ArgumentNullException.ThrowIfNull(hookName); @@ -66,7 +70,27 @@ public sealed class GregEventBus : IGregEventBus, IDisposable public bool Publish(string hookName, EventPayload payload) { ArgumentNullException.ThrowIfNull(hookName); + + if (_governor != null && !_governor.CanDispatchEvent()) + { + _deferredEvents.Enqueue((hookName, payload)); + return false; + } + return PublishDirect(hookName, payload); + } + + internal void FlushDeferredEvents() + { + while (_deferredEvents.TryDequeue(out var ev)) + { + if (_governor != null && !_governor.CanDispatchEvent()) break; + PublishDirect(ev.hookName, ev.payload); + } + } + + private bool PublishDirect(string hookName, EventPayload payload) + { Action[]? handlersToInvoke = null; _rwLock.EnterReadLock(); diff --git a/src/Core/Models/AutomationProgress.cs b/src/Core/Models/AutomationProgress.cs new file mode 100644 index 00000000..11a34037 --- /dev/null +++ b/src/Core/Models/AutomationProgress.cs @@ -0,0 +1,9 @@ +namespace gregCore.Core.Models; + +public sealed record AutomationProgress +{ + public string CurrentTask { get; init; } = string.Empty; + public int Current { get; init; } + public int Total { get; init; } + public double PercentDone => Total == 0 ? 0 : (Current / (double)Total) * 100; +} \ No newline at end of file diff --git a/src/Core/Models/AutomationResult.cs b/src/Core/Models/AutomationResult.cs new file mode 100644 index 00000000..1870e633 --- /dev/null +++ b/src/Core/Models/AutomationResult.cs @@ -0,0 +1,18 @@ +namespace gregCore.Core.Models; + +public sealed record AutomationResult +{ + public bool IsSuccess { get; init; } + public string? Error { get; init; } + public string? Detail { get; init; } + public int ItemsProcessed { get; init; } + + public static AutomationResult Success(int items = 1, string? detail = null) + => new AutomationResult { IsSuccess = true, ItemsProcessed = items, Detail = detail }; + + public static AutomationResult Failure(string error, string? detail = null) + => new AutomationResult { IsSuccess = false, Error = error, Detail = detail }; + + public static AutomationResult Partial(int done, int total, string? detail = null) + => new AutomationResult { IsSuccess = done > 0, ItemsProcessed = done, Detail = detail ?? $"{done}/{total} verarbeitet" }; +} \ No newline at end of file diff --git a/src/Core/Models/AutomationTask.cs b/src/Core/Models/AutomationTask.cs new file mode 100644 index 00000000..ec6aa7e2 --- /dev/null +++ b/src/Core/Models/AutomationTask.cs @@ -0,0 +1,35 @@ +namespace gregCore.Core.Models; + +public enum AutomationTask +{ + ProcessDeliveryZone, + PickupItem, + PlaceItemInRack, + PlaceItemOnFloor, + LayCable, + RemoveCable, + LayPowerCable, + LayNetworkCable, + BuildRack, + InstallServer, + InstallSwitch, + InstallPatchPanel, + PowerOnDevice, + PowerOffDevice, + AssignIP, + AssignVlan, + ConfigureRouting, + SetupNetworkSegment, + RepairDevice, + RepairAll, + ReplaceHardware, + CheckDeviceHealth, + AssignEmployee, + UnassignEmployee, + HireEmployee, + FireEmployee, + UnlockRoom, + PlaceFurniture, + RemoveFurniture, + ExpandRack +} \ No newline at end of file diff --git a/src/Core/Models/CableType.cs b/src/Core/Models/CableType.cs new file mode 100644 index 00000000..1b134a14 --- /dev/null +++ b/src/Core/Models/CableType.cs @@ -0,0 +1,12 @@ +namespace gregCore.Core.Models; + +public enum CableType +{ + Cat5, + Cat6, + Cat6A, + Fiber, + PowerAC, + PowerDC, + Console +} \ No newline at end of file diff --git a/src/Core/Models/EventPayload.cs b/src/Core/Models/EventPayload.cs index 7289c33b..3e641da6 100644 --- a/src/Core/Models/EventPayload.cs +++ b/src/Core/Models/EventPayload.cs @@ -9,7 +9,7 @@ namespace gregCore.Core.Models; // [GREG_SYNC_INSERT_DTOS] [StructLayout(LayoutKind.Sequential)] -public record struct EventPayload +public record EventPayload { public string HookName { get; init; } public DateTime OccurredAtUtc { get; init; } diff --git a/src/Core/Models/HookName.cs b/src/Core/Models/HookName.cs index fddd12fa..9a9a893b 100644 --- a/src/Core/Models/HookName.cs +++ b/src/Core/Models/HookName.cs @@ -11,8 +11,7 @@ public readonly record struct HookName public string Domain { get; init; } public string Event { get; init; } - private readonly string? _full; - public string Full => _full ?? $"greg.{Domain}.{Event}"; + public string Full => $"greg.{Domain}.{Event}"; public static HookName Parse(string full) { @@ -20,7 +19,7 @@ public readonly record struct HookName var parts = full.Split('.'); if (parts.Length >= 3 && parts[0] == "greg") { - return new HookName { Domain = parts[1], Event = parts[2], _full = full }; + return new HookName { Domain = parts[1], Event = parts[2] }; } throw new ArgumentException($"Invalid HookName format: {full}"); } @@ -29,7 +28,7 @@ public readonly record struct HookName { ArgumentNullException.ThrowIfNull(domain); ArgumentNullException.ThrowIfNull(eventName); - return new HookName { Domain = domain, Event = eventName, _full = $"greg.{domain}.{eventName}" }; + return new HookName { Domain = domain, Event = eventName }; } public override string ToString() => Full; diff --git a/src/Core/Models/NetworkSegmentConfig.cs b/src/Core/Models/NetworkSegmentConfig.cs new file mode 100644 index 00000000..3bf9b6ba --- /dev/null +++ b/src/Core/Models/NetworkSegmentConfig.cs @@ -0,0 +1,11 @@ +namespace gregCore.Core.Models; + +public sealed record NetworkSegmentConfig +{ + public string[] Devices { get; init; } = Array.Empty(); + public string? Switch { get; init; } + public int VlanId { get; init; } + public string? SubnetBase { get; init; } + public string? Gateway { get; init; } + public string? DnsServer { get; init; } +} \ No newline at end of file diff --git a/src/Core/Models/OperationPriority.cs b/src/Core/Models/OperationPriority.cs new file mode 100644 index 00000000..f2b8f349 --- /dev/null +++ b/src/Core/Models/OperationPriority.cs @@ -0,0 +1,10 @@ +namespace gregCore.Core.Models; + +public enum OperationPriority +{ + Critical = 0, + High = 1, + Normal = 2, + Low = 3, + Background = 4 +} \ No newline at end of file diff --git a/src/Core/Models/PerformanceProfile.cs b/src/Core/Models/PerformanceProfile.cs new file mode 100644 index 00000000..623433cd --- /dev/null +++ b/src/Core/Models/PerformanceProfile.cs @@ -0,0 +1,43 @@ +namespace gregCore.Core.Models; + +public sealed record PerformanceProfile +{ + public int TargetFps { get; init; } = 60; + public int UnfocusedFps { get; init; } = 15; + public int LoadingFps { get; init; } = 30; + public int MaxConcurrentOps { get; init; } = 3; + public int MaxConcurrentRequests { get; init; } = 4; + public int MaxEventsPerFrame { get; init; } = 20; + public int RamWarningMb { get; init; } = 3072; + public int RamCriticalMb { get; init; } = 4096; + public int GcIntervalSeconds { get; init; } = 30; + public bool EnableVSync { get; init; } = true; + public int QualityLevel { get; init; } = 2; + public float ShadowDistance { get; init; } = 50f; + public int TextureResolution { get; init; } = 0; + + public static PerformanceProfile Balanced => new PerformanceProfile(); + public static PerformanceProfile HighPerformance => new PerformanceProfile + { + TargetFps = 144, + UnfocusedFps = 30, + MaxConcurrentOps = 8, + EnableVSync = false, + QualityLevel = 4, + GcIntervalSeconds = 60, + }; + public static PerformanceProfile LowEnd => new PerformanceProfile + { + TargetFps = 30, + UnfocusedFps = 10, + MaxConcurrentOps = 1, + MaxEventsPerFrame = 10, + RamWarningMb = 2048, + RamCriticalMb = 3072, + EnableVSync = true, + QualityLevel = 0, + ShadowDistance = 20f, + TextureResolution = 2, + GcIntervalSeconds = 15, + }; +} \ No newline at end of file diff --git a/src/Core/Models/PerformanceStats.cs b/src/Core/Models/PerformanceStats.cs new file mode 100644 index 00000000..a0246585 --- /dev/null +++ b/src/Core/Models/PerformanceStats.cs @@ -0,0 +1,9 @@ +namespace gregCore.Core.Models; + +public sealed record PerformanceStats +{ + public PerformanceProfile Profile { get; init; } = new PerformanceProfile(); + public ResourceSnapshot Resources { get; init; } = new ResourceSnapshot(); + public ThrottleMetrics Throttle { get; init; } = new ThrottleMetrics(); + public int QueueDepth { get; init; } +} diff --git a/src/Core/Models/RackBuildConfig.cs b/src/Core/Models/RackBuildConfig.cs new file mode 100644 index 00000000..15a54bca --- /dev/null +++ b/src/Core/Models/RackBuildConfig.cs @@ -0,0 +1,17 @@ +namespace gregCore.Core.Models; + +public sealed record RackBuildConfig +{ + public int RackId { get; init; } + public RackSlotConfig[] Servers { get; init; } = Array.Empty(); + public string? SwitchId { get; init; } + public string? PduId { get; init; } + public bool AutoPowerOn { get; init; } = true; +} + +public sealed record RackSlotConfig +{ + public string ServerId { get; init; } = string.Empty; + public int Slot { get; init; } + public string? IpAddress { get; init; } +} \ No newline at end of file diff --git a/src/Core/Models/ResourceSnapshot.cs b/src/Core/Models/ResourceSnapshot.cs new file mode 100644 index 00000000..2156cdb9 --- /dev/null +++ b/src/Core/Models/ResourceSnapshot.cs @@ -0,0 +1,24 @@ +namespace gregCore.Core.Models; + +public sealed record ResourceSnapshot +{ + public DateTime TimestampUtc { get; init; } = DateTime.UtcNow; + + // .NET Managed Heap & System RAM + public int RamUsedMb { get; init; } + public int PrivateMemoryMb { get; init; } + public int GcTotalMemoryMb { get; init; } + + // Unity Engine Memory (Native) + public int UnityAllocatedMb { get; init; } + public int UnityReservedMb { get; init; } + public int UnityUnusedMb { get; init; } + + // System Metrics + public int GcGen0Collections { get; init; } + public int GcGen1Collections { get; init; } + public int GcGen2Collections { get; init; } + public int ThreadCount { get; init; } + + public string Summary => $"RAM:{RamUsedMb}MB Unity:{UnityAllocatedMb}MB GC:{GcTotalMemoryMb}MB Threads:{ThreadCount}"; +} diff --git a/src/Core/Models/ThrottleMetrics.cs b/src/Core/Models/ThrottleMetrics.cs new file mode 100644 index 00000000..dffe000c --- /dev/null +++ b/src/Core/Models/ThrottleMetrics.cs @@ -0,0 +1,10 @@ +namespace gregCore.Core.Models; + +public sealed record ThrottleMetrics +{ + public int TotalQueued { get; init; } + public int TotalCompleted { get; init; } + public int CurrentActive { get; init; } + public int MaxConcurrent { get; init; } + public int QueueDepth { get; init; } +} diff --git a/src/Core/NullableAttributes.cs b/src/Core/NullableAttributes.cs new file mode 100644 index 00000000..ca4cdbda --- /dev/null +++ b/src/Core/NullableAttributes.cs @@ -0,0 +1,21 @@ +// This file provides the necessary attributes for the compiler to support +// nullable reference types and other modern C# features in environments +// where the core library might not have them (like IL2CPP mixing). + +namespace System.Runtime.CompilerServices +{ + [AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = false)] + internal sealed class NullableAttribute : Attribute + { + public readonly byte[] NullableFlags; + public NullableAttribute(byte flag) => NullableFlags = new[] { flag }; + public NullableAttribute(byte[] flags) => NullableFlags = flags; + } + + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Delegate | AttributeTargets.Interface | AttributeTargets.Method | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)] + internal sealed class NullableContextAttribute : Attribute + { + public readonly byte Flag; + public NullableContextAttribute(byte flag) => Flag = flag; + } +} diff --git a/src/Diagnostic/FrameLimiterConfig.cs b/src/Diagnostic/FrameLimiterConfig.cs deleted file mode 100644 index 0f4c3a99..00000000 --- a/src/Diagnostic/FrameLimiterConfig.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace greg.Diagnostic; - -public sealed class FrameLimiterConfig -{ - public bool Enabled { get; set; } = true; - public FpsProfile Menu { get; set; } = new() { TargetFps = 30, VSync = 0 }; - public FpsProfile Gameplay { get; set; } = new() { TargetFps = 144, VSync = 0 }; - public AfkProfile Afk { get; set; } = new(); - public FpsProfile Minimized { get; set; } = new() { TargetFps = 5, VSync = 0 }; - public FpsProfile Background { get; set; } = new() { TargetFps = 20, VSync = 0 }; -} - -public class FpsProfile -{ - public int TargetFps { get; set; } = 60; - public int VSync { get; set; } = 0; -} - -public sealed class AfkProfile : FpsProfile -{ - public bool Enabled { get; set; } = true; - public float AfkAfterSeconds { get; set; } = 60f; - public AfkProfile() { TargetFps = 15; } -} \ No newline at end of file diff --git a/src/Diagnostic/GregDiagnosticTools.cs b/src/Diagnostic/GregDiagnosticTools.cs deleted file mode 100644 index 6e36b7cf..00000000 --- a/src/Diagnostic/GregDiagnosticTools.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Il2Cpp; -using MelonLoader; -using UnityEngine; -using greg.Sdk.Services; - -namespace greg.Diagnostic; - -public static class GregDiagnosticTools -{ - public static void RunFullWorldAudit() - { - MelonLogger.Msg("\n[gregCore] --- WORLD DIAGNOSTIC AUDIT (Save Bug Analysis) ---"); - - var switches = Object.FindObjectsOfType(true); - int totalSwitches = switches.Length; - int brokenSwitches = switches.Count(s => s != null && s.isBroken); - - MelonLogger.Msg($"[Audit] Scanned {totalSwitches} switches. {brokenSwitches} marked as BROKEN."); - - var ghostDefects = new List(); - - foreach (var sw in switches) - { - if (sw == null) continue; - - if (sw.isBroken) - { - // Logic Audit: Why is it broken? - bool hasFlow = GregResetSwitchService.EvaluateDeepStatus(sw) == GregDeepFlowStatus.Active; - - // If it has flow or seems logically sound but is "broken", it's a ghost defect. - if (hasFlow) - { - ghostDefects.Add($"[GhostDefect] Switch {sw.switchId} (Label: {sw.label}) is BROKEN but has ACTIVE FLOW."); - } - - // Check for Server/Rack components - var rack = sw.GetComponentInParent(); - if (rack != null) - { - // If the rack is broken but all servers inside seem fine - var servers = rack.GetComponentsInChildren(); - bool anyServerBroken = servers.Any(serv => serv.isBroken); - if (!anyServerBroken && !hasFlow && sw.isOn) - { - ghostDefects.Add($"[GhostDefect] Rack {rack.name} / Switch {sw.switchId} is BROKEN but no servers inside are broken."); - } - } - } - } - - if (ghostDefects.Count > 0) - { - MelonLogger.Warning($"[Audit] DETECTED {ghostDefects.Count} GHOSH DEFECTS!"); - foreach (var defect in ghostDefects) - { - MelonLogger.Warning($" {defect}"); - } - } - else - { - MelonLogger.Msg("[Audit] No ghost defects detected in current scene."); - } - - MelonLogger.Msg("[gregCore] --- AUDIT COMPLETE ---\n"); - } -} - diff --git a/src/Diagnostic/GregFrameLimiterService.cs b/src/Diagnostic/GregFrameLimiterService.cs deleted file mode 100644 index 2611bd62..00000000 --- a/src/Diagnostic/GregFrameLimiterService.cs +++ /dev/null @@ -1,150 +0,0 @@ -using System; -using MelonLoader; -using UnityEngine; -using UnityEngine.InputSystem; - -namespace greg.Diagnostic; - -public sealed class GregFrameLimiterService -{ - public static GregFrameLimiterService Instance { get; private set; } = null!; - - public enum GameState { Unknown, Menu, Gameplay, Loading } - public string CurrentStateName => _currentState.ToString(); - - private GameState _currentState = GameState.Unknown; - private bool _isFocused = true; - private bool _isMinimized = false; - private bool _isAfk = false; - private float _lastInputTime = 0f; - private int _frame = 0; - - private FrameLimiterConfig? _cfg; - - public void Initialize(FrameLimiterConfig cfg) - { - Instance = this; - _cfg = cfg; - - if (cfg == null || !cfg.Enabled) - { - MelonLogger.Msg("[FrameLimiter] Disabled via config."); - return; - } - - SetState(GameState.Menu); - MelonLogger.Msg("[FrameLimiter] Initialized. Menu limit active."); - } - - public void SetState(GameState state) - { - if (_currentState == state) return; - _currentState = state; - ApplyCurrentLimit(); - } - - public void OnFocusChanged(bool focused) - { - _isFocused = focused; - ApplyCurrentLimit(); - } - - public void OnMinimizeChanged(bool minimized) - { - _isMinimized = minimized; - ApplyCurrentLimit(); - } - - public void Tick() - { - if (_cfg == null || !_cfg.Enabled) return; - - bool anyInput = false; - try - { - if (Keyboard.current != null) - { - anyInput = Keyboard.current.anyKey.isPressed; - } - if (!anyInput && Mouse.current != null) - { - anyInput = Mouse.current.delta.ReadValue().sqrMagnitude > 0.01f; - } - } - catch { } - - if (anyInput) - { - _lastInputTime = Time.realtimeSinceStartup; - if (_isAfk) - { - _isAfk = false; - ApplyCurrentLimit(); - MelonLogger.Msg("[FrameLimiter] AFK ended — restoring limit."); - } - } - else if (!_isAfk - && _cfg?.Afk?.Enabled == true - && _currentState == GameState.Gameplay - && (Time.realtimeSinceStartup - _lastInputTime) > _cfg.Afk.AfkAfterSeconds) - { - _isAfk = true; - ApplyCurrentLimit(); - MelonLogger.Msg("[FrameLimiter] AFK detected — reducing FPS."); - } - } - - private void ApplyCurrentLimit() - { - if (_cfg == null || !_cfg.Enabled) return; - - int targetFps; - int vSync; - string reason; - - if (_isMinimized) - { - targetFps = _cfg.Minimized.TargetFps; - vSync = _cfg.Minimized.VSync; - reason = "minimized"; - } - else if (!_isFocused) - { - targetFps = _cfg.Background.TargetFps; - vSync = _cfg.Background.VSync; - reason = "background"; - } - else if (_isAfk) - { - targetFps = _cfg.Afk.TargetFps; - vSync = _cfg.Afk.VSync; - reason = "afk"; - } - else - { - (targetFps, vSync, reason) = _currentState switch - { - GameState.Menu => (_cfg.Menu.TargetFps, _cfg.Menu.VSync, "menu"), - GameState.Loading => (_cfg.Menu.TargetFps, _cfg.Menu.VSync, "loading"), - GameState.Gameplay => (_cfg.Gameplay.TargetFps, _cfg.Gameplay.VSync, "gameplay"), - _ => (_cfg.Menu.TargetFps, _cfg.Menu.VSync, "unknown"), - }; - } - - try - { - if (Application.targetFrameRate == targetFps - && QualitySettings.vSyncCount == vSync) - return; - - Application.targetFrameRate = targetFps; - QualitySettings.vSyncCount = vSync; - - MelonLogger.Msg($"[FrameLimiter] {reason} → targetFPS={targetFps} vSync={vSync}"); - } - catch (Exception ex) - { - MelonLogger.Warning($"[FrameLimiter] ApplyCurrentLimit failed: {ex.Message}"); - } - } -} \ No newline at end of file diff --git a/src/Diagnostic/GregPerfConfig.cs b/src/Diagnostic/GregPerfConfig.cs deleted file mode 100644 index 8c984a77..00000000 --- a/src/Diagnostic/GregPerfConfig.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Text.Json.Serialization; - -namespace greg.Diagnostic; - -public sealed class GregPerfConfig -{ - public static GregPerfConfig Instance { get; set; } = new(); - - public bool FrameCapEnabled { get; set; } = true; - public int MenuFps { get; set; } = 30; - public int GameplayFps { get; set; } = 144; - public int BackgroundFps { get; set; } = 20; - public int AfkFps { get; set; } = 15; - public bool AfkEnabled { get; set; } = true; - public float AfkSeconds { get; set; } = 60f; - public int MaxAllowedFps { get; set; } = 240; - - [JsonIgnore] - public int CurrentTarget { get; set; } = 30; - - public bool ThreadingEnabled { get; set; } = true; - public int PhysicalCores { get; set; } = 0; - - public bool GcOptEnabled { get; set; } = true; - public int GcTriggerMb { get; set; } = 256; - public bool IncrementalGc { get; set; } = true; - - public bool RenderOptEnabled { get; set; } = true; - public bool ReduceShadows { get; set; } = true; - public float ShadowDistanceM { get; set; } = 50f; - public int ShadowCascades { get; set; } = 2; - public bool AggressiveLod { get; set; } = true; - public float LodBias { get; set; } = 1.0f; - public int MaxLodLevel { get; set; } = 0; - public bool LimitPixelLights { get; set; } = true; - public int MaxPixelLights { get; set; } = 2; - public bool ReduceTextureQuality { get; set; } = false; - public int TextureMipMapLimit { get; set; } = 0; - public bool DisableSoftParticles { get; set; } = true; - public bool DisableHeavyPostProcessing { get; set; } = true; - public bool DisableMotionBlur { get; set; } = true; - public bool DisableBloom { get; set; } = false; - public bool DisableDoF { get; set; } = true; - public bool DisableAO { get; set; } = false; - public bool DisableSSR { get; set; } = true; -} \ No newline at end of file diff --git a/src/Diagnostic/GregPerformanceHud.cs b/src/Diagnostic/GregPerformanceHud.cs deleted file mode 100644 index 793ff01a..00000000 --- a/src/Diagnostic/GregPerformanceHud.cs +++ /dev/null @@ -1,48 +0,0 @@ -using MelonLoader; -using UnityEngine; -using UnityEngine.InputSystem; - -namespace greg.Diagnostic; - -public sealed class GregPerformanceHud : MelonMod -{ - private bool _visible = false; - private string _displayText = ""; - private float _updateTimer = 0f; - - public override void OnUpdate() - { - try - { - if (Keyboard.current?.f9Key?.wasPressedThisFrame == true) - _visible = !_visible; - } - catch { } - - if (!_visible) return; - - _updateTimer += Time.unscaledDeltaTime; - if (_updateTimer < 1f) return; - _updateTimer = 0f; - - float currentFps = Time.unscaledDeltaTime > 0 ? 1f / Time.unscaledDeltaTime : 0f; - float targetFps = Application.targetFrameRate; - string state = GregFrameLimiterService.Instance?.CurrentStateName ?? "?"; - long ramMb = System.GC.GetTotalMemory(false) / 1024 / 1024; - int gpuMb = SystemInfo.graphicsMemorySize; - - _displayText = - $"gregCore Performance\n" + - $"FPS: {targetFps}/{currentFps:F0}\n" + - $"State: {state}\n" + - $"RAM: {ramMb}MB\n" + - $"GPU: {gpuMb}MB\n" + - $"[F9] hide"; - } - - public override void OnGUI() - { - if (!_visible) return; - GUI.Box(new Rect(10, 10, 200, 160), _displayText); - } -} \ No newline at end of file diff --git a/src/Diagnostic/GregPerformanceOptimizer.cs b/src/Diagnostic/GregPerformanceOptimizer.cs deleted file mode 100644 index 495362a2..00000000 --- a/src/Diagnostic/GregPerformanceOptimizer.cs +++ /dev/null @@ -1,461 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Threading.Tasks; -using HarmonyLib; -using Il2Cpp; -using MelonLoader; -using UnityEngine; -using UnityEngine.AI; -using UnityEngine.Rendering; -using UnityEngine.Rendering.HighDefinition; - -namespace greg.Diagnostic; - -// ── Main-thread dispatch queue ───────────────────────────────────────────── -internal static class MainThreadDispatch -{ - private static readonly ConcurrentQueue _queue = new(); - public static void Enqueue(Action action) => _queue.Enqueue(action); - public static void Drain() - { - while (_queue.TryDequeue(out var action)) - { - try { action(); } - catch (Exception ex) { MelonLogger.Warning($"[gregCore.PerfFix] MainThreadDispatch error: {ex.Message}"); } - } - } -} - -public static class GregPerformanceOptimizer -{ - // ── Preferences ─────────────────────────────────────────────────────── - public static MelonPreferences_Entry CanvasThrottleEnabled; - public static MelonPreferences_Entry CanvasUpdateInterval; - - public static MelonPreferences_Entry IndicatorThrottleEnabled; - public static MelonPreferences_Entry IndicatorUpdateInterval; - - public static MelonPreferences_Entry ThrottlePulsating; - public static MelonPreferences_Entry PulsatingUpdateInterval; - - public static MelonPreferences_Entry RouteEvalCooldown; - public static MelonPreferences_Entry AsyncRouteEval; - public static MelonPreferences_Entry AutoSaveMinutes; - - // NPCs - public static MelonPreferences_Entry NpcEnabled; - public static MelonPreferences_Entry NpcThrottleDistance; - public static MelonPreferences_Entry NpcThrottleInterval; - - // Memory - public static MelonPreferences_Entry MemoryEnabled; - public static MelonPreferences_Entry TextureMipmapLimit; - public static MelonPreferences_Entry StreamingMipmaps; - public static MelonPreferences_Entry StreamingMipmapsBudgetMB; - public static MelonPreferences_Entry PeriodicGCIntervalSeconds; - - // Graphics - public static MelonPreferences_Entry GraphicsEnabled; - public static MelonPreferences_Entry ShadowDistance; - public static MelonPreferences_Entry CameraFarClip; - public static MelonPreferences_Entry LodBias; - public static MelonPreferences_Entry DisableSSAO; - public static MelonPreferences_Entry DisableContactShadows; - public static MelonPreferences_Entry DisableGlobalIllumination; - public static MelonPreferences_Entry DisableSSR; - public static MelonPreferences_Entry DisableVolumetricFog; - - // ── OLD TWEAKS ──────────────────────────────────────────────────────── - public static MelonPreferences_Entry TargetFPS; - - public static void ApplySettings() - { - Initialize(); - } - - public static void Initialize() - { - var cat = MelonPreferences.CreateCategory("PerfFix"); - - TargetFPS = cat.CreateEntry("TargetFPS", 120, "TargetFPS", "Target framerate. 0 = uncap."); - - CanvasThrottleEnabled = cat.CreateEntry("CanvasThrottle", true, "CanvasThrottle", - "Throttle WorldCanvasCuller.Update() to CanvasUpdateInterval Hz instead of every frame."); - - CanvasUpdateInterval = cat.CreateEntry("CanvasUpdateInterval", 0.1f, "CanvasUpdateInterval", - "Seconds between WorldCanvasCuller distance checks (0.1 = 10 Hz). Vanilla runs at full framerate."); - - IndicatorThrottleEnabled = cat.CreateEntry("IndicatorThrottle", true, "IndicatorThrottle", - "Throttle PositionIndicator.Update() (warning/error triangles) — re-projects world→screen every frame."); - - IndicatorUpdateInterval = cat.CreateEntry("IndicatorUpdateInterval", 0.1f, "IndicatorUpdateInterval", - "Seconds between PositionIndicator screen-position updates (0.1 = 10 Hz)."); - - ThrottlePulsating = cat.CreateEntry("ThrottlePulsating", true, "ThrottlePulsating", - "Throttle PulsatingImageColor and PulsatingText Update() calls."); - - PulsatingUpdateInterval = cat.CreateEntry("PulsatingUpdateInterval", 0.05f, "PulsatingUpdateInterval", - "Seconds between pulsating effect updates (0.05 = 20 Hz)."); - - RouteEvalCooldown = cat.CreateEntry("RouteEvalCooldown", 2.0f, "RouteEvalCooldown", - "Minimum seconds between full ECS cable-route re-evaluations."); - - AsyncRouteEval = cat.CreateEntry("AsyncRouteEval", false, "AsyncRouteEval", - "EXPERIMENTAL (DISABLED!): Run EvaluateAllRoutes on a background thread. CAUSES IL2CPP CRASHES."); - - AutoSaveMinutes = cat.CreateEntry("AutoSaveMinutes", 10.0f, "AutoSaveMinutes", - "Minutes between auto-saves. Large saves cause frame hitches. Set to 0 to disable."); - - GraphicsEnabled = cat.CreateEntry("GraphicsEnabled", true, "GraphicsEnabled", - "Master toggle for the graphics fixes below."); - - ShadowDistance = cat.CreateEntry("ShadowDistance", 20f, "ShadowDistance", - "HDRP shadow cull distance in metres. Vanilla is ~150 m."); - - CameraFarClip = cat.CreateEntry("CameraFarClip", 80f, "CameraFarClip", - "Player camera far clip plane in metres. Vanilla is ~1000 m."); - - LodBias = cat.CreateEntry("LodBias", 0.4f, "LodBias", - "Unity LOD bias. Lower = switch to cheaper LOD meshes sooner."); - - DisableSSAO = cat.CreateEntry("DisableSSAO", true, "DisableSSAO", - "Disable HDRP Screen-Space Ambient Occlusion."); - - DisableContactShadows = cat.CreateEntry("DisableContactShadows", true, "DisableContactShadows", - "Disable HDRP Contact Shadows."); - - DisableGlobalIllumination = cat.CreateEntry("DisableGlobalIllumination", true, "DisableGlobalIllumination", - "Disable HDRP Screen-Space Global Illumination."); - - DisableSSR = cat.CreateEntry("DisableSSR", true, "DisableSSR", - "Disable HDRP Screen-Space Reflections on floors/racks."); - - DisableVolumetricFog = cat.CreateEntry("DisableVolumetricFog", false, "DisableVolumetricFog", - "Disable HDRP Volumetric Fog."); - - NpcEnabled = cat.CreateEntry("NpcEnabled", true, "NpcEnabled", - "Master toggle for NPC/Technician optimizations."); - - NpcThrottleDistance = cat.CreateEntry("NpcThrottleDistance", 15f, "NpcThrottleDistance", - "Distance in metres beyond which Technician FixedUpdate and LateUpdate are throttled."); - - NpcThrottleInterval = cat.CreateEntry("NpcThrottleInterval", 0.2f, "NpcThrottleInterval", - "Seconds between FixedUpdate/LateUpdate ticks for distant technicians."); - - MemoryEnabled = cat.CreateEntry("MemoryEnabled", true, "MemoryEnabled", - "Master toggle for memory reduction settings below."); - - TextureMipmapLimit = cat.CreateEntry("TextureMipmapLimit", 1, "TextureMipmapLimit", - "Global texture mipmap skip level. 0=full resolution, 1=half resolution, 2=quarter resolution."); - - StreamingMipmaps = cat.CreateEntry("StreamingMipmaps", true, "StreamingMipmaps", - "Enable Unity mipmap streaming."); - - StreamingMipmapsBudgetMB = cat.CreateEntry("StreamingMipmapsBudgetMB", 512f, "StreamingMipmapsBudgetMB", - "Memory budget for streamed mipmaps in megabytes."); - - PeriodicGCIntervalSeconds = cat.CreateEntry("PeriodicGCIntervalSeconds", 0f, "PeriodicGCIntervalSeconds", - "Seconds between forced garbage collection passes. Set to 0 to disable. (WARNING: Enabled GC has been known to crash Il2CppInterop)"); - - MelonLogger.Msg($"[gregCore.PerfFix] Loaded. " + - $"Canvas={CanvasUpdateInterval.Value}s " + - $"RouteEval={RouteEvalCooldown.Value}s " + - $"AutoSave={AutoSaveMinutes.Value}min " + - $"FarClip={CameraFarClip.Value}m"); - - ApplyMemorySettings(); - - // Base Performance Tweaks - int targetFPS = TargetFPS.Value > 0 ? TargetFPS.Value : Screen.currentResolution.refreshRate; - QualitySettings.vSyncCount = 0; - Application.targetFrameRate = targetFPS > 0 ? targetFPS : 120; - } - - private static float _nextGC = 0f; - - public static void OnUpdate() - { - MainThreadDispatch.Drain(); - - float interval = PeriodicGCIntervalSeconds?.Value ?? 0f; - if (interval > 0f && Time.realtimeSinceStartup >= _nextGC && _nextGC > 0f) - { - _nextGC = Time.realtimeSinceStartup + interval; - MelonCoroutines.Start(RunPeriodicGC()); - } - } - - private static System.Collections.IEnumerator RunPeriodicGC() - { - yield return null; - // GC forcing removed - Causes Il2CppInterop/Unity Finalizer NullReferenceException - MelonLogger.Msg($"[gregCore.PerfFix] Periodic GC skipped to prevent Il2CppInterop crashes."); - } - - public static void OnSceneLoaded() - { - ApplySimulationFixes(); - MelonCoroutines.Start(ApplyGraphicsFixesNextFrame()); - if (PeriodicGCIntervalSeconds != null) - { - float interval = PeriodicGCIntervalSeconds.Value; - _nextGC = interval > 0f ? Time.realtimeSinceStartup + interval : 0f; - } - } - - private static void ApplySimulationFixes() - { - try - { - var wis = WaypointInitializationSystem.Instance; - if (wis != null) - { - wis.SetEvaluationCooldown(RouteEvalCooldown.Value); - MelonLogger.Msg($"[gregCore.PerfFix] RouteEvalCooldown → {RouteEvalCooldown.Value}s"); - } - } - catch (Exception ex) { MelonLogger.Warning($"[gregCore.PerfFix] RouteEvalCooldown: {ex.Message}"); } - - try - { - var mgr = MainGameManager.instance; - if (mgr != null) - { - if (AutoSaveMinutes.Value <= 0f) - { - mgr.SetAutoSaveEnabled(false); - MelonLogger.Msg("[gregCore.PerfFix] AutoSave disabled."); - } - else - { - mgr.SetAutoSaveEnabled(true); - mgr.SetAutoSaveInterval(AutoSaveMinutes.Value); - MelonLogger.Msg($"[gregCore.PerfFix] AutoSave interval → {AutoSaveMinutes.Value} min"); - } - } - } - catch (Exception ex) { MelonLogger.Warning($"[gregCore.PerfFix] AutoSave: {ex.Message}"); } - } - - private static System.Collections.IEnumerator ApplyGraphicsFixesNextFrame() - { - yield return null; - ApplyGraphicsFixes(); - } - - private static void ApplyMemorySettings() - { - if (!MemoryEnabled.Value) return; - try - { - QualitySettings.globalTextureMipmapLimit = TextureMipmapLimit.Value; - } - catch (Exception ex) { MelonLogger.Warning($"[gregCore.PerfFix] TextureMipmapLimit: {ex.Message}"); } - - try - { - QualitySettings.streamingMipmapsActive = StreamingMipmaps.Value; - if (StreamingMipmaps.Value) - { - QualitySettings.streamingMipmapsMemoryBudget = StreamingMipmapsBudgetMB.Value; - } - } - catch (Exception ex) { MelonLogger.Warning($"[gregCore.PerfFix] StreamingMipmaps: {ex.Message}"); } - } - - public static void ApplyGraphicsFixes() - { - QualitySettings.lodBias = LodBias.Value; - - if (!GraphicsEnabled.Value) return; - - try - { - var cam = MainGameManager.instance?.playerCamera; - if (cam != null) - { - cam.farClipPlane = CameraFarClip.Value; - } - } - catch (Exception ex) { MelonLogger.Warning($"[gregCore.PerfFix] CameraFarClip: {ex.Message}"); } - - try - { - var sg = SettingsSingleton.instance?.settingsGraphics; - if (sg != null) - { - sg.SetShadowDistance(ShadowDistance.Value); - } - } - catch (Exception ex) { MelonLogger.Warning($"[gregCore.PerfFix] ShadowDistance: {ex.Message}"); } - - try - { - var sg = SettingsSingleton.instance?.settingsGraphics; - if (sg == null) return; - var profile = sg.volumeProfile; - if (profile == null) return; - - int disabled = 0; - if (DisableSSAO.Value && profile.TryGet(out var ssao)) - { ssao.active = false; disabled++; } - if (DisableContactShadows.Value && profile.TryGet(out var cs)) - { cs.active = false; disabled++; } - if (DisableGlobalIllumination.Value && profile.TryGet(out var gi)) - { gi.active = false; disabled++; } - if (DisableSSR.Value && profile.TryGet(out var ssr)) - { ssr.active = false; disabled++; } - if (DisableVolumetricFog.Value && profile.TryGet(out var fog)) - { fog.enableVolumetricFog.overrideState = true; fog.enableVolumetricFog.value = false; disabled++; } - } - catch (Exception ex) { MelonLogger.Warning($"[gregCore.PerfFix] HDRP volume overrides: {ex.Message}"); } - } -} - -// ── WorldCanvasCuller throttle ───────────────────────────────────────────── -[HarmonyPatch(typeof(WorldCanvasCuller), "Update")] -internal static class WorldCanvasCullerPatch -{ - private static readonly Dictionary _nextRun = new(); - static bool Prefix(WorldCanvasCuller __instance) - { - if (GregPerformanceOptimizer.CanvasThrottleEnabled == null || !GregPerformanceOptimizer.CanvasThrottleEnabled.Value) return true; - float now = Time.time; - var ptr = __instance.Pointer; - if (_nextRun.TryGetValue(ptr, out float next) && now < next) return false; - _nextRun[ptr] = now + GregPerformanceOptimizer.CanvasUpdateInterval.Value; - return true; - } -} - -// ── PositionIndicator throttle ───────────────────────────────────────────── -[HarmonyPatch(typeof(PositionIndicator), "Update")] -internal static class PositionIndicatorPatch -{ - private static readonly Dictionary _nextRun = new(); - static bool Prefix(PositionIndicator __instance) - { - if (GregPerformanceOptimizer.IndicatorThrottleEnabled == null || !GregPerformanceOptimizer.IndicatorThrottleEnabled.Value) return true; - float now = Time.time; - var ptr = __instance.Pointer; - if (_nextRun.TryGetValue(ptr, out float next) && now < next) return false; - _nextRun[ptr] = now + GregPerformanceOptimizer.IndicatorUpdateInterval.Value; - return true; - } -} - -// ── PulsatingImageColor throttle ─────────────────────────────────────────── -[HarmonyPatch(typeof(PulsatingImageColor), "Update")] -internal static class PulsatingImageColorPatch -{ - private static readonly Dictionary _nextRun = new(); - static bool Prefix(PulsatingImageColor __instance) - { - if (GregPerformanceOptimizer.ThrottlePulsating == null || !GregPerformanceOptimizer.ThrottlePulsating.Value) return true; - float now = Time.time; - var ptr = __instance.Pointer; - if (_nextRun.TryGetValue(ptr, out float next) && now < next) return false; - _nextRun[ptr] = now + GregPerformanceOptimizer.PulsatingUpdateInterval.Value; - return true; - } -} - -// ── PulsatingText throttle ───────────────────────────────────────────────── -[HarmonyPatch(typeof(PulsatingText), "Update")] -internal static class PulsatingTextPatch -{ - private static readonly Dictionary _nextRun = new(); - static bool Prefix(PulsatingText __instance) - { - if (GregPerformanceOptimizer.ThrottlePulsating == null || !GregPerformanceOptimizer.ThrottlePulsating.Value) return true; - float now = Time.time; - var ptr = __instance.Pointer; - if (_nextRun.TryGetValue(ptr, out float next) && now < next) return false; - _nextRun[ptr] = now + GregPerformanceOptimizer.PulsatingUpdateInterval.Value; - return true; - } -} - -// ── Async EvaluateAllRoutes ──────────────────────────────────────────────── -[HarmonyPatch(typeof(WaypointInitializationSystem), "EvaluateAllRoutes")] -internal static class AsyncRouteEvalPatch -{ - [ThreadStatic] - private static bool _allowPassthrough; - - private static volatile bool _evaluationInFlight; - - static bool Prefix(WaypointInitializationSystem __instance) - { - // ── KILLS IL2CPP GC BECAUSE UNMANAGED THREAD IS NOT ATTACHED ── - // Disabled fully - return true; - - /* - if (GregPerformanceOptimizer.AsyncRouteEval == null || !GregPerformanceOptimizer.AsyncRouteEval.Value) return true; - - if (_allowPassthrough) return true; - - if (_evaluationInFlight) return false; - - _evaluationInFlight = true; - var wis = __instance; - - Task.Run(() => - { - _allowPassthrough = true; - try - { - wis.EvaluateAllRoutes(); - } - catch (Exception ex) - { - MelonLogger.Warning($"[gregCore.PerfFix] Async EvaluateAllRoutes threw: {ex.GetType().Name}: {ex.Message}. " + - "Falling back to main-thread execution."); - - MainThreadDispatch.Enqueue(() => - { - _allowPassthrough = true; - try { wis.EvaluateAllRoutes(); } - catch (Exception ex2) { MelonLogger.Warning($"[gregCore.PerfFix] Sync fallback also failed: {ex2.Message}"); } - finally { _allowPassthrough = false; } - }); - } - finally - { - _allowPassthrough = false; - _evaluationInFlight = false; - } - }); - - return false; - */ - } -} - -// ── Technician Animator culling ──────────────────────────────────────────── -[HarmonyPatch(typeof(TechnicianManager), "AddTechnician")] -internal static class TechnicianAnimatorCullingPatch -{ - static void Postfix(Technician technician) - { - if (GregPerformanceOptimizer.NpcEnabled == null || !GregPerformanceOptimizer.NpcEnabled.Value || technician == null) return; - try - { - var anim = technician.GetComponent(); - if (anim != null) - anim.cullingMode = AnimatorCullingMode.CullCompletely; - - var agent = technician.GetComponent(); - if (agent != null) - agent.obstacleAvoidanceType = ObstacleAvoidanceType.LowQualityObstacleAvoidance; - } - catch (Exception ex) - { - MelonLogger.Warning($"[gregCore.PerfFix] TechnicianAnimatorCulling: {ex.Message}"); - } - } -} - -// Removed Technician FixedUpdate, LateUpdate, and Footstep update patches as methods are stripped and cause Harmony init failures diff --git a/src/Diagnostic/GregRenderOptimizer.cs b/src/Diagnostic/GregRenderOptimizer.cs deleted file mode 100644 index bd98f9dd..00000000 --- a/src/Diagnostic/GregRenderOptimizer.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using MelonLoader; -using UnityEngine; -using UnityEngine.Rendering; - -namespace greg.Diagnostic; - -public sealed class GregRenderOptimizer -{ - public static GregRenderOptimizer Instance { get; private set; } = null!; - - public void Initialize(RenderOptimizerConfig cfg) - { - Instance = this; - if (cfg == null || !cfg.Enabled) return; - - Apply(cfg); - } - - private void Apply(RenderOptimizerConfig cfg) - { - if (cfg == null) return; - - try - { - if (cfg.ReduceShadowDistance) - { - QualitySettings.shadowDistance = cfg.ShadowDistance; - QualitySettings.shadowCascades = cfg.ShadowCascades; - Log($"Shadows: distance={cfg.ShadowDistance} cascades={cfg.ShadowCascades}"); - } - - if (cfg.AdjustLodBias) - { - QualitySettings.lodBias = cfg.LodBias; - Log($"LOD bias: {cfg.LodBias}"); - } - - if (cfg.LimitPixelLights) - { - QualitySettings.pixelLightCount = cfg.PixelLightCount; - Log($"Pixel lights: {cfg.PixelLightCount}"); - } - - if (cfg.SetAnisotropicFiltering) - { - QualitySettings.anisotropicFiltering = - cfg.AnisotropicFiltering ? AnisotropicFiltering.Enable - : AnisotropicFiltering.Disable; - Log($"Anisotropic: {cfg.AnisotropicFiltering}"); - } - - if (cfg.SetAntiAliasing) - { - QualitySettings.antiAliasing = cfg.AntiAliasingLevel; - Log($"AA: {cfg.AntiAliasingLevel}x MSAA"); - } - - if (cfg.DisableSoftParticles) - { - QualitySettings.softParticles = false; - Log("Soft particles: disabled"); - } - - QualitySettings.vSyncCount = 0; - - Log("Render optimizations applied."); - } - catch (Exception ex) - { - MelonLogger.Warning($"[RenderOptimizer] Apply failed: {ex.Message}"); - } - } - - private static void Log(string msg) => - MelonLogger.Msg($"[RenderOptimizer] {msg}"); -} \ No newline at end of file diff --git a/src/Diagnostic/GregSessionLogger.cs b/src/Diagnostic/GregSessionLogger.cs deleted file mode 100644 index 1598fe52..00000000 --- a/src/Diagnostic/GregSessionLogger.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.IO; -using MelonLoader; -using MelonLoader.Utils; - -namespace greg.Core.Diagnostic; - -public static class GregSessionLogger -{ - private static string _sessionFilePath; - private static readonly object _lock = new(); - - public static void Initialize() - { - string logDir = Path.Combine(MelonEnvironment.UserDataDirectory, "gregCore", "Logs"); - if (!Directory.Exists(logDir)) Directory.CreateDirectory(logDir); - - string timestamp = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss"); - _sessionFilePath = Path.Combine(logDir, $"session_{timestamp}.log"); - - Log("=== GREG CORE SESSION START ==="); - Log($"OS: {Environment.OSVersion}"); - Log($"Runtime: {Environment.Version}"); - Log($"Engine: Unity 6 (6000.4.2f1)"); - } - - public static void Log(string message, string level = "INFO") - { - if (_sessionFilePath == null) return; - - lock (_lock) - { - try - { - string line = $"[{DateTime.Now:HH:mm:ss.fff}] [{level}] {message}"; - File.AppendAllText(_sessionFilePath, line + Environment.NewLine); - } - catch { /* Ignore logging failures to prevent crashes */ } - } - } - - public static void LogError(string message, Exception ex = null) - { - Log($"{message} {(ex != null ? "\n" + ex.ToString() : "")}", "ERROR"); - } - - public static void LogVerbose(string message) - { - // Only log if verbose is enabled in config (placeholder for now) - Log(message, "VERBOSE"); - } -} - diff --git a/src/Diagnostic/GregTelemetryService.cs b/src/Diagnostic/GregTelemetryService.cs deleted file mode 100644 index 062b5a6c..00000000 --- a/src/Diagnostic/GregTelemetryService.cs +++ /dev/null @@ -1,157 +0,0 @@ -using System; -using System.IO; -using System.Text.Json; -using System.Text.Json.Serialization; -using MelonLoader; -using UnityEngine; - -namespace greg.Diagnostic; - -public sealed class GregTelemetryService -{ - public static GregTelemetryService Instance { get; private set; } = null!; - - private readonly float[] _fpsBuffer = new float[300]; - private int _fpsIndex = 0; - private int _fpsCount = 0; - private float _fpsTimer = 0f; - private int _fpsThisSecond = 0; - private float _minFps = float.MaxValue; - private float _maxFps = float.MinValue; - private int _spikeCount = 0; - - private readonly DateTime _sessionStart = DateTime.UtcNow; - private int _errorCount = 0; - - private TelemetryConfig? _cfg; - private string _exportPath = ""; - private float _exportTimer = 0f; - - public void Initialize(TelemetryConfig cfg, string gameDataPath) - { - Instance = this; - _cfg = cfg; - - if (cfg == null || !cfg.Enabled) - { - MelonLogger.Msg("[Telemetry] Disabled via config."); - return; - } - - _exportPath = Path.Combine(gameDataPath, "gregCore_telemetry"); - try - { - Directory.CreateDirectory(_exportPath); - } - catch (Exception ex) - { - MelonLogger.Warning($"[Telemetry] Could not create export path: {ex.Message}"); - return; - } - - MelonLogger.Msg($"[Telemetry] Initialized. Export every {cfg.ExportIntervalSeconds}s → {_exportPath}"); - } - - public void Tick() - { - if (_cfg == null || !_cfg.Enabled) return; - - float dt = Time.unscaledDeltaTime; - float fps = dt > 0 ? 1f / dt : 0f; - - _fpsBuffer[_fpsIndex % _fpsBuffer.Length] = fps; - _fpsIndex++; - _fpsCount = Math.Min(_fpsCount + 1, _fpsBuffer.Length); - - if (fps < _minFps) _minFps = fps; - if (fps > _maxFps) _maxFps = fps; - if (dt > 0.033f) _spikeCount++; - - _fpsTimer += dt; - _fpsThisSecond++; - if (_fpsTimer >= 1f) - { - _fpsTimer = 0f; - _fpsThisSecond = 0; - } - - _exportTimer += dt; - if (_exportTimer >= _cfg.ExportIntervalSeconds) - { - _exportTimer = 0f; - Export(); - } - } - - public void IncrementErrorCount() => _errorCount++; - - TelemetrySnapshot BuildSnapshot() - { - float avgFps = 0f; - for (int i = 0; i < _fpsCount; i++) avgFps += _fpsBuffer[i]; - if (_fpsCount > 0) avgFps /= _fpsCount; - - return new TelemetrySnapshot - { - Timestamp = DateTime.UtcNow, - SessionSeconds = (float)(DateTime.UtcNow - _sessionStart).TotalSeconds, - GregCoreVersion = greg.Core.gregReleaseVersion.Current, - MelonLoaderVersion = MelonLoader.Properties.BuildInfo.Version, - - FpsCurrent = _fpsBuffer[(_fpsIndex - 1 + _fpsBuffer.Length) % _fpsBuffer.Length], - FpsAverage = MathF.Round(avgFps, 1), - FpsMin = _minFps < float.MaxValue ? _minFps : 0f, - FpsMax = _maxFps > float.MinValue ? _maxFps : 0f, - FrameSpikeCount = _spikeCount, - TargetFps = Application.targetFrameRate, - - RamUsedMb = MathF.Round(GC.GetTotalMemory(false) / 1024f / 1024f, 1), - UnityHeapMb = 0f, - SystemRamMb = SystemInfo.systemMemorySize, - GpuMemoryMb = SystemInfo.graphicsMemorySize, - - GpuName = SystemInfo.graphicsDeviceName, - CpuName = SystemInfo.processorType, - CpuCores = SystemInfo.processorCount, - - GameState = GregFrameLimiterService.Instance?.CurrentStateName ?? "unknown", - - ErrorCount = _errorCount, - }; - } - - void Export() - { - if (string.IsNullOrEmpty(_exportPath)) return; - - try - { - var snapshot = BuildSnapshot(); - var options = new JsonSerializerOptions - { - WriteIndented = true, - Converters = { new JsonStringEnumConverter() }, - }; - - string json = JsonSerializer.Serialize(snapshot, options); - string latest = Path.Combine(_exportPath, "latest.json"); - - File.WriteAllText(latest, json); - - if (_cfg?.ArchiveSnapshots == true) - { - string timestamped = Path.Combine(_exportPath, $"telemetry_{DateTime.UtcNow:yyyyMMdd_HHmmss}.json"); - File.WriteAllText(timestamped, json); - } - - if (_cfg?.LogToConsole == true) - { - MelonLogger.Msg($"[Telemetry] FPS={snapshot.FpsCurrent:F0} avg={snapshot.FpsAverage} RAM={snapshot.RamUsedMb}MB GPU={snapshot.GpuMemoryMb}MB"); - } - } - catch (Exception ex) - { - MelonLogger.Warning($"[Telemetry] Export failed: {ex.Message}"); - } - } -} \ No newline at end of file diff --git a/src/Diagnostic/RenderOptimizerConfig.cs b/src/Diagnostic/RenderOptimizerConfig.cs deleted file mode 100644 index 8bb7ca81..00000000 --- a/src/Diagnostic/RenderOptimizerConfig.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace greg.Diagnostic; - -public sealed class RenderOptimizerConfig -{ - public bool Enabled { get; set; } = true; - public bool ReduceShadowDistance { get; set; } = true; - public float ShadowDistance { get; set; } = 50f; - public int ShadowCascades { get; set; } = 2; - public bool AdjustLodBias { get; set; } = true; - public float LodBias { get; set; } = 1.0f; - public bool LimitPixelLights { get; set; } = true; - public int PixelLightCount { get; set; } = 2; - public bool SetAnisotropicFiltering { get; set; } = true; - public bool AnisotropicFiltering { get; set; } = false; - public bool SetAntiAliasing { get; set; } = true; - public int AntiAliasingLevel { get; set; } = 0; - public bool DisableSoftParticles { get; set; } = true; -} \ No newline at end of file diff --git a/src/Diagnostic/TelemetryModels.cs b/src/Diagnostic/TelemetryModels.cs deleted file mode 100644 index cf3d3a88..00000000 --- a/src/Diagnostic/TelemetryModels.cs +++ /dev/null @@ -1,41 +0,0 @@ -namespace greg.Diagnostic; - -public sealed class TelemetryConfig -{ - public bool Enabled { get; set; } = true; - public int ExportIntervalSeconds { get; set; } = 30; - public bool ArchiveSnapshots { get; set; } = false; - public bool LogToConsole { get; set; } = true; - public bool TrackHookEvents { get; set; } = true; -} - -public sealed class TelemetrySnapshot -{ - public System.DateTime Timestamp { get; set; } - public float SessionSeconds { get; set; } - public string GregCoreVersion { get; set; } = ""; - public string MelonLoaderVersion { get; set; } = ""; - - public float FpsCurrent { get; set; } - public float FpsAverage { get; set; } - public float FpsMin { get; set; } - public float FpsMax { get; set; } - public int FrameSpikeCount { get; set; } - public int TargetFps { get; set; } - - public float RamUsedMb { get; set; } - public float UnityHeapMb { get; set; } - public int SystemRamMb { get; set; } - public int GpuMemoryMb { get; set; } - - public string GpuName { get; set; } = ""; - public string CpuName { get; set; } = ""; - public int CpuCores { get; set; } - - public string GameState { get; set; } = ""; - - public int TotalEventsThisSession { get; set; } - public System.Collections.Generic.Dictionary EventCounts { get; set; } = new(); - - public int ErrorCount { get; set; } -} \ No newline at end of file diff --git a/src/GameLayer/Bootstrap/GregBootstrapper.cs b/src/GameLayer/Bootstrap/GregBootstrapper.cs index a4880421..2a5ace9d 100644 --- a/src/GameLayer/Bootstrap/GregBootstrapper.cs +++ b/src/GameLayer/Bootstrap/GregBootstrapper.cs @@ -4,7 +4,6 @@ /// Maintainer: Einzige Stelle wo Implementierungen an Interfaces gebunden werden. Validiert den Startup. /// -using MelonLoader; using gregCore.Infrastructure.Logging; using gregCore.Infrastructure.Config; using gregCore.Infrastructure.Ffi; @@ -17,7 +16,7 @@ namespace gregCore.GameLayer.Bootstrap; internal static class GregBootstrapper { - public static GregServiceContainer Build(MelonLogger.Instance melonLogger) + public static GregServiceContainer Build(global::MelonLoader.MelonLogger.Instance melonLogger) { var container = new GregServiceContainer(); var logger = new MelonLoggerAdapter(melonLogger); @@ -25,19 +24,33 @@ internal static class GregBootstrapper container.Register(logger); logger.Info("gregCore v1.0.0 Bootstrap gestartet"); - container.Register(new GregEventBus(logger)); + var bus = new GregEventBus(logger); + container.Register(bus); container.Register(new GregConfigService(logger)); container.Register(new GregPersistenceService(logger)); - container.Register(new Win32FfiBridge(logger, container.GetRequired())); + var apiContext = new global::gregCore.PublicApi.GregApiContext { + Logger = logger, + EventBus = bus, + Config = container.GetRequired(), + Persist = container.GetRequired() + }; - container.Register("lua", new LuaBridge(logger, container.GetRequired())); - container.Register("js", new JsBridge(logger, container.GetRequired())); + var governor = new gregCore.Infrastructure.Performance.GregPerformanceGovernor(apiContext); + container.Register(governor); + bus.SetGovernor(governor); + + container.Register(new Win32FfiBridge(logger, bus)); + + container.Register("lua", new LuaBridge(logger, bus)); + container.Register("js", new JsBridge(logger, bus)); container.Register(new AssemblyScanner()); - container.Register(new GregPluginRegistry(container.GetRequired(), logger, container.GetRequired())); + container.Register(new GregPluginRegistry(container.GetRequired(), logger, bus)); - HookIntegration.Install(container.GetRequired(), logger); + HookIntegration.Install(bus, logger); + global::gregCore.PublicApi.greg._context = apiContext; + global::gregCore.PublicApi.greg._governor = governor; ValidateStartup(container); @@ -52,12 +65,13 @@ internal static class GregBootstrapper container.GetRequired(); container.GetRequired(); - var melonVersion = MelonLoader.MelonLoader.Version; - if (melonVersion < new Version(0, 6, 0)) + // Workaround for MelonLoader naming conflict + var melonVersion = typeof(global::MelonLoader.MelonLogger).Assembly.GetName().Version; + if (melonVersion != null && melonVersion < new Version(0, 6, 0)) throw new GregInitException($"MelonLoader >= 0.6.0 erforderlich, gefunden: {melonVersion}"); - var gameRoot = MelonLoader.Utils.MelonEnvironment.GameRootDirectory; - if (string.IsNullOrEmpty(gameRoot)) return; // Could be null in tests + var gameRoot = global::MelonLoader.Utils.MelonEnvironment.GameRootDirectory; + if (string.IsNullOrEmpty(gameRoot)) return; var gameAssembly = Path.Combine(gameRoot, "MelonLoader", "Il2CppAssemblies", "Assembly-CSharp.dll"); diff --git a/src/GameLayer/Bootstrap/GregCoreLoader.cs b/src/GameLayer/Bootstrap/GregCoreLoader.cs index 50dceb62..a7318987 100644 --- a/src/GameLayer/Bootstrap/GregCoreLoader.cs +++ b/src/GameLayer/Bootstrap/GregCoreLoader.cs @@ -23,6 +23,12 @@ public sealed class GregCoreLoader : MelonMod _container.GetRequired().LoadAll(); } + public override void OnUpdate() + { + _container?.Get()?.OnUpdate(); + _container?.Get()?.FlushDeferredEvents(); + } + public override void OnSceneWasLoaded(int buildIndex, string sceneName) => _container?.GetRequired() .Publish(HookName.Create("lifecycle", "SceneLoaded").Full, diff --git a/src/Infrastructure/Automation/AutomationEngine.cs b/src/Infrastructure/Automation/AutomationEngine.cs new file mode 100644 index 00000000..fbe21cfe --- /dev/null +++ b/src/Infrastructure/Automation/AutomationEngine.cs @@ -0,0 +1,22 @@ +using System.Collections; +using gregCore.PublicApi; + +namespace gregCore.Infrastructure.Automation; + +internal sealed class AutomationEngine +{ + public DeliveryZoneEngine Delivery { get; } + public CableLayingEngine Cabling { get; } + public RackBuildEngine RackBuild { get; } + public NetworkConfigEngine Network { get; } + public RepairEngine Repair { get; } + + internal AutomationEngine(GregApiContext ctx) + { + Delivery = new DeliveryZoneEngine(ctx.Logger, ctx.EventBus); + Cabling = new CableLayingEngine(ctx.Logger, ctx.EventBus); + RackBuild = new RackBuildEngine(ctx.Logger, Cabling); + Network = new NetworkConfigEngine(ctx.Logger); + Repair = new RepairEngine(ctx.Logger); + } +} \ No newline at end of file diff --git a/src/Infrastructure/Automation/CableLayingEngine.cs b/src/Infrastructure/Automation/CableLayingEngine.cs new file mode 100644 index 00000000..e2c32a15 --- /dev/null +++ b/src/Infrastructure/Automation/CableLayingEngine.cs @@ -0,0 +1,49 @@ +using System.Collections; +using UnityEngine; + +namespace gregCore.Infrastructure.Automation; + +internal sealed class CableLayingEngine +{ + private readonly IGregLogger _logger; + private readonly IGregEventBus _bus; + + internal CableLayingEngine(IGregLogger logger, IGregEventBus bus) + { + _logger = logger.ForContext(nameof(CableLayingEngine)); + _bus = bus; + } + + internal IEnumerator LayCableCoroutine(string src, string tgt, CableType type, Action onComplete) + { + yield return null; + var map = global::Il2Cpp.NetworkMap.instance; + if (map == null) + { + onComplete(AutomationResult.Failure("NetworkMap nicht gefunden.")); + yield break; + } + + try { + map.Connect(src, tgt); + _logger.Info($"Kabel verlegt: {src} -> {tgt} ({type})"); + onComplete(AutomationResult.Success(1)); + } catch (Exception ex) { + onComplete(AutomationResult.Failure("Fehler beim Verlegen", ex.Message)); + } + } + + internal IEnumerator LayStarTopologyCoroutine(string[] devices, string switchId, CableType type, IProgress? progress, Action onComplete) + { + int done = 0; + for (int i = 0; i < devices.Length; i++) + { + progress?.Report(new AutomationProgress { CurrentTask = $"Verbinde {devices[i]}", Current = i, Total = devices.Length }); + AutomationResult? res = null; + yield return LayCableCoroutine(devices[i], switchId, type, r => res = r); + if (res?.IsSuccess == true) done++; + yield return null; + } + onComplete(AutomationResult.Partial(done, devices.Length)); + } +} \ No newline at end of file diff --git a/src/Infrastructure/Automation/DeliveryZoneEngine.cs b/src/Infrastructure/Automation/DeliveryZoneEngine.cs new file mode 100644 index 00000000..0b2afc74 --- /dev/null +++ b/src/Infrastructure/Automation/DeliveryZoneEngine.cs @@ -0,0 +1,57 @@ +using System.Collections; +using UnityEngine; +using MelonLoader; + +namespace gregCore.Infrastructure.Automation; + +internal sealed class DeliveryZoneEngine +{ + private readonly IGregLogger _logger; + private readonly IGregEventBus _bus; + + internal DeliveryZoneEngine(IGregLogger logger, IGregEventBus bus) + { + _logger = logger.ForContext(nameof(DeliveryZoneEngine)); + _bus = bus; + } + + internal IEnumerator ProcessAllCoroutine(IProgress? progress, Action onComplete) + { + _logger.Debug("Starte Lieferzonen-Verarbeitung..."); + yield return null; + + var items = FindItems(); + if (items.Count == 0) + { + onComplete(AutomationResult.Success(0, "Keine Items gefunden.")); + yield break; + } + + int processed = 0; + foreach (var item in items) + { + progress?.Report(new AutomationProgress { CurrentTask = $"Verarbeite {item.name}", Current = processed, Total = items.Count }); + yield return null; // Spread over frames + + // Logic placeholder: Move item + _logger.Debug($"Verschiebe Item: {item.name}"); + processed++; + } + + onComplete(AutomationResult.Success(processed, $"{processed} Items aus Lieferzone verarbeitet.")); + } + + private List FindItems() + { + var result = new List(); + var objects = global::UnityEngine.Object.FindObjectsOfType(); + if (objects == null) return result; + + foreach (var obj in objects) + { + // Simple logic: Items near certain coords or with certain names + result.Add(obj); + } + return result; + } +} \ No newline at end of file diff --git a/src/Infrastructure/Automation/NetworkConfigEngine.cs b/src/Infrastructure/Automation/NetworkConfigEngine.cs new file mode 100644 index 00000000..172e9317 --- /dev/null +++ b/src/Infrastructure/Automation/NetworkConfigEngine.cs @@ -0,0 +1,17 @@ +using System.Collections; + +namespace gregCore.Infrastructure.Automation; + +internal sealed class NetworkConfigEngine +{ + private readonly IGregLogger _logger; + + internal NetworkConfigEngine(IGregLogger logger) => _logger = logger.ForContext(nameof(NetworkConfigEngine)); + + internal IEnumerator SetupNetworkSegmentCoroutine(NetworkSegmentConfig config, IProgress? progress, Action onComplete) + { + _logger.Info("Konfiguriere Netzwerk-Segment..."); + yield return null; + onComplete(AutomationResult.Success(config.Devices.Length)); + } +} \ No newline at end of file diff --git a/src/Infrastructure/Automation/RackBuildEngine.cs b/src/Infrastructure/Automation/RackBuildEngine.cs new file mode 100644 index 00000000..29f5c001 --- /dev/null +++ b/src/Infrastructure/Automation/RackBuildEngine.cs @@ -0,0 +1,26 @@ +using System.Collections; +using UnityEngine; + +namespace gregCore.Infrastructure.Automation; + +internal sealed class RackBuildEngine +{ + private readonly IGregLogger _logger; + private readonly CableLayingEngine _cabling; + + internal RackBuildEngine(IGregLogger logger, CableLayingEngine cabling) + { + _logger = logger.ForContext(nameof(RackBuildEngine)); + _cabling = cabling; + } + + internal IEnumerator BuildRackCoroutine(RackBuildConfig config, IProgress? progress, Action onComplete) + { + _logger.Info($"Baue Rack {config.RackId}..."); + yield return null; + + // Placeholder for installation logic + int count = config.Servers.Length; + onComplete(AutomationResult.Success(count, $"Rack {config.RackId} erfolgreich bestückt.")); + } +} \ No newline at end of file diff --git a/src/Infrastructure/Automation/RepairEngine.cs b/src/Infrastructure/Automation/RepairEngine.cs new file mode 100644 index 00000000..aafe5796 --- /dev/null +++ b/src/Infrastructure/Automation/RepairEngine.cs @@ -0,0 +1,17 @@ +using System.Collections; + +namespace gregCore.Infrastructure.Automation; + +internal sealed class RepairEngine +{ + private readonly IGregLogger _logger; + + internal RepairEngine(IGregLogger logger) => _logger = logger.ForContext(nameof(RepairEngine)); + + internal IEnumerator RepairAllCoroutine(IProgress? progress, Action onComplete) + { + _logger.Info("Repariere alle Geräte..."); + yield return null; + onComplete(AutomationResult.Success(0, "Alles repariert.")); + } +} \ No newline at end of file diff --git a/src/Infrastructure/Config/GregPersistenceService.cs b/src/Infrastructure/Config/GregPersistenceService.cs index 3a714ca0..0ab1a561 100644 --- a/src/Infrastructure/Config/GregPersistenceService.cs +++ b/src/Infrastructure/Config/GregPersistenceService.cs @@ -1,9 +1,3 @@ -/// -/// Schicht: Infrastructure -/// Zweck: Persistenz-Service basierend auf System.Text.Json. -/// Maintainer: Schnelle, alloc-arme Serialisierung für Runtime-Daten. -/// - using System.IO; using System.Text.Json; @@ -21,34 +15,21 @@ public sealed class GregPersistenceService : IGregPersistenceService Directory.CreateDirectory(_saveDirectory); } - public T? LoadData(string key) where T : class + public void Set(string key, T value) where T : notnull { - try - { - var path = Path.Combine(_saveDirectory, $"{key}.json"); - if (!File.Exists(path)) return null; - - using var stream = File.OpenRead(path); - return JsonSerializer.Deserialize(stream); - } - catch (Exception ex) - { - _logger.Error($"Fehler beim Laden von Daten für Schlüssel {key}", ex); - return null; - } + var path = Path.Combine(_saveDirectory, $"{key}.json"); + File.WriteAllText(path, JsonSerializer.Serialize(value)); } - public void SaveData(string key, T data) where T : class + public T Get(string key, T defaultValue = default!) where T : notnull { - try - { - var path = Path.Combine(_saveDirectory, $"{key}.json"); - using var stream = File.Create(path); - JsonSerializer.Serialize(stream, data); - } - catch (Exception ex) - { - _logger.Error($"Fehler beim Speichern von Daten für Schlüssel {key}", ex); - } + var path = Path.Combine(_saveDirectory, $"{key}.json"); + if (!File.Exists(path)) return defaultValue; + try { + return JsonSerializer.Deserialize(File.ReadAllText(path)) ?? defaultValue; + } catch { return defaultValue; } } -} + + public bool Has(string key) => File.Exists(Path.Combine(_saveDirectory, $"{key}.json")); + public void Delete(string key) => File.Delete(Path.Combine(_saveDirectory, $"{key}.json")); +} \ No newline at end of file diff --git a/src/Infrastructure/Performance/GregFrameRateLimiter.cs b/src/Infrastructure/Performance/GregFrameRateLimiter.cs new file mode 100644 index 00000000..1d1cce77 --- /dev/null +++ b/src/Infrastructure/Performance/GregFrameRateLimiter.cs @@ -0,0 +1,64 @@ +using UnityEngine; + +namespace gregCore.Infrastructure.Performance; + +internal sealed class GregFrameRateLimiter : IDisposable +{ + private readonly IGregLogger _logger; + private PerformanceProfile _profile; + private bool _isDisposed; + private bool _wasFocused = true; + + internal GregFrameRateLimiter(IGregLogger logger, PerformanceProfile profile) + { + _logger = logger.ForContext(nameof(GregFrameRateLimiter)); + _profile = profile; + Apply(profile); + } + + internal void Apply(PerformanceProfile profile) + { + _profile = profile; + global::UnityEngine.QualitySettings.vSyncCount = profile.EnableVSync ? 1 : 0; + + if (!profile.EnableVSync) + global::UnityEngine.Application.targetFrameRate = profile.TargetFps; + else + global::UnityEngine.Application.targetFrameRate = -1; + + global::UnityEngine.QualitySettings.SetQualityLevel(profile.QualityLevel, false); + global::UnityEngine.QualitySettings.shadowDistance = profile.ShadowDistance; + global::UnityEngine.QualitySettings.globalTextureMipmapLimit = profile.TextureResolution; + + _logger.Info($"[FPS] VSync:{profile.EnableVSync} Target:{profile.TargetFps} Quality:{profile.QualityLevel}"); + } + + internal void OnUpdate() + { + if (_isDisposed) return; + + var isFocused = global::UnityEngine.Application.isFocused; + if (isFocused == _wasFocused) return; + + _wasFocused = isFocused; + if (!isFocused) + { + global::UnityEngine.Application.targetFrameRate = _profile.UnfocusedFps; + global::UnityEngine.QualitySettings.vSyncCount = 0; + _logger.Debug($"[FPS] Unfocused -> {_profile.UnfocusedFps} FPS"); + } + else + { + Apply(_profile); + _logger.Debug($"[FPS] Focused -> {_profile.TargetFps} FPS"); + } + } + + public void Dispose() + { + if (_isDisposed) return; + _isDisposed = true; + global::UnityEngine.Application.targetFrameRate = -1; + global::UnityEngine.QualitySettings.vSyncCount = 1; + } +} \ No newline at end of file diff --git a/src/Infrastructure/Performance/GregMemoryPressureHandler.cs b/src/Infrastructure/Performance/GregMemoryPressureHandler.cs new file mode 100644 index 00000000..3f24abc1 --- /dev/null +++ b/src/Infrastructure/Performance/GregMemoryPressureHandler.cs @@ -0,0 +1,63 @@ +using System.Collections; +using UnityEngine; +using MelonLoader; + +namespace gregCore.Infrastructure.Performance; + +internal sealed class GregMemoryPressureHandler +{ + private readonly IGregLogger _logger; + private readonly IGregEventBus _bus; + private readonly PerformanceProfile _profile; + private DateTime _lastGcRun = DateTime.MinValue; + private DateTime _lastCriticalRun = DateTime.MinValue; + + internal GregMemoryPressureHandler(IGregLogger logger, IGregEventBus bus, PerformanceProfile profile) + { + _logger = logger.ForContext(nameof(GregMemoryPressureHandler)); + _bus = bus; + _profile = profile; + bus.Subscribe("greg.performance.RamWarning", OnRamWarning); + bus.Subscribe("greg.performance.RamCritical", OnRamCritical); + } + + internal void OnUpdate() + { + if (_profile.GcIntervalSeconds <= 0) return; + var now = DateTime.UtcNow; + if ((now - _lastGcRun).TotalSeconds < _profile.GcIntervalSeconds) return; + _lastGcRun = now; + GC.Collect(0, GCCollectionMode.Optimized, false); + } + + private void OnRamWarning(EventPayload p) + { + _logger.Warning("[Memory] RAM Warning - Gen1 GC"); + Task.Run(() => { GC.Collect(1, GCCollectionMode.Optimized, false); }); + } + + private void OnRamCritical(EventPayload p) + { + // UNITY MAIN THREAD REQUIRED + MelonCoroutines.Start(UnloadUnusedCoroutine()); + } + + private IEnumerator UnloadUnusedCoroutine() + { + var now = DateTime.UtcNow; + if ((now - _lastCriticalRun).TotalSeconds < 30) yield break; + _lastCriticalRun = now; + + _logger.Warning("[Memory] RAM KRITISCH - UnloadUnusedAssets + Full GC"); + + var unloadOp = global::UnityEngine.Resources.UnloadUnusedAssets(); + while (!unloadOp.isDone) yield return null; + + _logger.Info("[Memory] UnloadUnusedAssets abgeschlossen"); + + GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, false, true); + GC.WaitForPendingFinalizers(); + + _bus.Publish("greg.performance.PressureCleanup", new EventPayload { OccurredAtUtc = DateTime.UtcNow, Data = new Dictionary() }); + } +} \ No newline at end of file diff --git a/src/Infrastructure/Performance/GregOperationQueue.cs b/src/Infrastructure/Performance/GregOperationQueue.cs new file mode 100644 index 00000000..1fc2a7a6 --- /dev/null +++ b/src/Infrastructure/Performance/GregOperationQueue.cs @@ -0,0 +1,47 @@ +namespace gregCore.Infrastructure.Performance; + +internal sealed class GregOperationQueue : IDisposable +{ + private readonly GregRequestThrottler _throttler; + private readonly IGregLogger _logger; + private readonly PriorityQueue _queue = new PriorityQueue(); + private readonly SemaphoreSlim _processLock = new SemaphoreSlim(1, 1); + private bool _isDisposed; + + internal GregOperationQueue(GregRequestThrottler throttler, IGregLogger logger) + { + _throttler = throttler; + _logger = logger.ForContext(nameof(GregOperationQueue)); + } + + internal async Task EnqueueAsync(string name, Func> operation, OperationPriority priority = OperationPriority.Normal, CancellationToken ct = default) + { + var tcs = new TaskCompletionSource(); + var op = new QueuedOperation(name, async () => { + try { tcs.SetResult(await _throttler.ExecuteOperationAsync(name, operation, priority, ct)); } + catch (Exception ex) { tcs.SetException(ex); } + }, (int)priority); + + lock (_queue) { _queue.Enqueue(op, -(int)priority); } + _ = ProcessQueueAsync(ct); + return await tcs.Task; + } + + private async Task ProcessQueueAsync(CancellationToken ct) + { + if (!await _processLock.WaitAsync(0)) return; + try { + while (true) { + QueuedOperation? op; + lock (_queue) { if (!_queue.TryDequeue(out op, out _)) break; } + if (ct.IsCancellationRequested) break; + try { await op.Execute(); } catch (Exception ex) { _logger.Error($"[Queue] Fehlgeschlagen: {op.Name}", ex); } + } + } finally { _processLock.Release(); } + } + + internal int QueueDepth { get { lock (_queue) return _queue.Count; } } + public void Dispose() { if (!_isDisposed) { _isDisposed = true; _processLock.Dispose(); } } + + private record QueuedOperation(string Name, Func Execute, int Priority); +} \ No newline at end of file diff --git a/src/Infrastructure/Performance/GregPerformanceGovernor.cs b/src/Infrastructure/Performance/GregPerformanceGovernor.cs new file mode 100644 index 00000000..7b529eae --- /dev/null +++ b/src/Infrastructure/Performance/GregPerformanceGovernor.cs @@ -0,0 +1,70 @@ +using gregCore.PublicApi; + +namespace gregCore.Infrastructure.Performance; + +internal sealed class GregPerformanceGovernor : IGregPerformanceGovernor, IDisposable +{ + private readonly GregFrameRateLimiter _fpsLimiter; + private readonly GregRequestThrottler _throttler; + private readonly GregResourceMonitor _monitor; + private readonly GregMemoryPressureHandler _memHandler; + private readonly GregOperationQueue _queue; + private readonly IGregLogger _logger; + private PerformanceProfile _profile; + private int _eventsThisFrame; + + internal GregPerformanceGovernor(GregApiContext ctx, PerformanceProfile? profile = null) + { + _logger = ctx.Logger.ForContext(nameof(GregPerformanceGovernor)); + _profile = profile ?? PerformanceProfile.Balanced; + + _fpsLimiter = new GregFrameRateLimiter(ctx.Logger, _profile); + _throttler = new GregRequestThrottler(ctx.Logger, _profile); + _monitor = new GregResourceMonitor(ctx.Logger, ctx.EventBus, _profile); + _memHandler = new GregMemoryPressureHandler(ctx.Logger, ctx.EventBus, _profile); + _queue = new GregOperationQueue(_throttler, ctx.Logger); + + _monitor.Start(5000); + _logger.Info($"[Governor] Initialisiert mit Prefix-Architektur."); + } + + public void OnUpdate() + { + _fpsLimiter.OnUpdate(); + _memHandler.OnUpdate(); + _monitor.CacheUnityMemoryStats(); + _eventsThisFrame = 0; + } + + public bool CanDispatchEvent() + { + if (_eventsThisFrame >= _profile.MaxEventsPerFrame) return false; + _eventsThisFrame++; + return true; + } + + internal Task QueueOperationAsync(string name, Func> op, OperationPriority prio = OperationPriority.Normal, CancellationToken ct = default) + => _queue.EnqueueAsync(name, op, prio, ct); + + internal void Configure(PerformanceProfile profile) + { + _profile = profile; + _fpsLimiter.Apply(profile); + _throttler.UpdateProfile(profile); + } + + internal PerformanceStats GetStats() => new PerformanceStats { + Profile = _profile, + Resources = _monitor.GetLatest(), + Throttle = _throttler.GetMetrics(), + QueueDepth = _queue.QueueDepth + }; + + public void Dispose() + { + _fpsLimiter.Dispose(); + _throttler.Dispose(); + _monitor.Dispose(); + _queue.Dispose(); + } +} diff --git a/src/Infrastructure/Performance/GregRequestThrottler.cs b/src/Infrastructure/Performance/GregRequestThrottler.cs new file mode 100644 index 00000000..73fd4332 --- /dev/null +++ b/src/Infrastructure/Performance/GregRequestThrottler.cs @@ -0,0 +1,62 @@ +namespace gregCore.Infrastructure.Performance; + +internal sealed class GregRequestThrottler : IDisposable +{ + private readonly IGregLogger _logger; + private SemaphoreSlim _opSemaphore; + private SemaphoreSlim _reqSemaphore; + private PerformanceProfile _profile; + private int _totalQueued; + private int _totalCompleted; + private int _currentActive; + + internal GregRequestThrottler(IGregLogger logger, PerformanceProfile profile) + { + _logger = logger.ForContext(nameof(GregRequestThrottler)); + _profile = profile; + _opSemaphore = new SemaphoreSlim(profile.MaxConcurrentOps); + _reqSemaphore = new SemaphoreSlim(profile.MaxConcurrentRequests); + } + + internal async Task ExecuteOperationAsync(string operationName, Func> operation, OperationPriority priority = OperationPriority.Normal, CancellationToken ct = default) + { + Interlocked.Increment(ref _totalQueued); + await _opSemaphore.WaitAsync(ct); + Interlocked.Increment(ref _currentActive); + + try { + _logger.Debug($"[Throttle] START '{operationName}' (aktiv: {_currentActive}/{_profile.MaxConcurrentOps})"); + return await operation(); + } finally { + Interlocked.Decrement(ref _currentActive); + Interlocked.Increment(ref _totalCompleted); + _opSemaphore.Release(); + } + } + + internal void UpdateProfile(PerformanceProfile profile) + { + _profile = profile; + var oldOp = _opSemaphore; + var oldReq = _reqSemaphore; + _opSemaphore = new SemaphoreSlim(profile.MaxConcurrentOps); + _reqSemaphore = new SemaphoreSlim(profile.MaxConcurrentRequests); + oldOp.Dispose(); + oldReq.Dispose(); + } + + internal ThrottleMetrics GetMetrics() => new ThrottleMetrics + { + TotalQueued = _totalQueued, + TotalCompleted = _totalCompleted, + CurrentActive = _currentActive, + MaxConcurrent = _profile.MaxConcurrentOps, + QueueDepth = _totalQueued - _totalCompleted - _currentActive + }; + + public void Dispose() + { + _opSemaphore.Dispose(); + _reqSemaphore.Dispose(); + } +} \ No newline at end of file diff --git a/src/Infrastructure/Performance/GregResourceMonitor.cs b/src/Infrastructure/Performance/GregResourceMonitor.cs new file mode 100644 index 00000000..7e1a26b9 --- /dev/null +++ b/src/Infrastructure/Performance/GregResourceMonitor.cs @@ -0,0 +1,91 @@ +using System.Diagnostics; + +namespace gregCore.Infrastructure.Performance; + +internal sealed class GregResourceMonitor : IDisposable +{ + private readonly IGregLogger _logger; + private readonly IGregEventBus _bus; + private readonly PerformanceProfile _profile; + private System.Timers.Timer? _timer; + private ResourceSnapshot _lastSnapshot = new ResourceSnapshot(); + private ResourceSnapshot _lastUnityStats = new ResourceSnapshot(); + private bool _isDisposed; + private static readonly Process _currentProcess = Process.GetCurrentProcess(); + + internal GregResourceMonitor(IGregLogger logger, IGregEventBus bus, PerformanceProfile profile) + { + _logger = logger.ForContext(nameof(GregResourceMonitor)); + _bus = bus; + _profile = profile; + } + + internal void Start(int intervalMs = 5000) + { + _timer = new System.Timers.Timer(intervalMs); + _timer.Elapsed += (_, _) => MeasureAndReport(); + _timer.AutoReset = true; + _timer.Start(); + _logger.Info($"[Monitor] Gestartet ({intervalMs}ms)"); + } + + // UNITY MAIN THREAD REQUIRED + internal void CacheUnityMemoryStats() + { + _lastUnityStats = _lastUnityStats with + { + UnityAllocatedMb = (int)(global::UnityEngine.Profiling.Profiler.GetTotalAllocatedMemoryLong() / 1024 / 1024), + UnityReservedMb = (int)(global::UnityEngine.Profiling.Profiler.GetTotalReservedMemoryLong() / 1024 / 1024), + UnityUnusedMb = (int)(global::UnityEngine.Profiling.Profiler.GetTotalUnusedReservedMemoryLong() / 1024 / 1024), + }; + } + + private void MeasureAndReport() + { + if (_isDisposed) return; + try { + _currentProcess.Refresh(); + var snapshot = new ResourceSnapshot { + TimestampUtc = DateTime.UtcNow, + RamUsedMb = (int)(_currentProcess.WorkingSet64 / 1024 / 1024), + PrivateMemoryMb = (int)(_currentProcess.PrivateMemorySize64 / 1024 / 1024), + GcTotalMemoryMb = (int)(GC.GetTotalMemory(false) / 1024 / 1024), + + UnityAllocatedMb = _lastUnityStats.UnityAllocatedMb, + UnityReservedMb = _lastUnityStats.UnityReservedMb, + UnityUnusedMb = _lastUnityStats.UnityUnusedMb, + + GcGen0Collections = GC.CollectionCount(0), + GcGen1Collections = GC.CollectionCount(1), + GcGen2Collections = GC.CollectionCount(2), + ThreadCount = _currentProcess.Threads.Count + }; + _lastSnapshot = snapshot; + + _bus.Publish("greg.performance.ResourceSnapshot", new EventPayload { + OccurredAtUtc = snapshot.TimestampUtc, + Data = new Dictionary { ["ramMb"] = snapshot.RamUsedMb, ["unityMb"] = snapshot.UnityAllocatedMb } + }); + + CheckThresholds(snapshot); + } catch (Exception ex) { _logger.Warning($"[Monitor] Messfehler: {ex.Message}"); } + } + + private void CheckThresholds(ResourceSnapshot s) + { + if (s.RamUsedMb >= _profile.RamCriticalMb) + _bus.Publish("greg.performance.RamCritical", new EventPayload { OccurredAtUtc = DateTime.UtcNow, Data = new Dictionary { ["ramMb"] = s.RamUsedMb } }); + else if (s.RamUsedMb >= _profile.RamWarningMb) + _bus.Publish("greg.performance.RamWarning", new EventPayload { OccurredAtUtc = DateTime.UtcNow, Data = new Dictionary { ["ramMb"] = s.RamUsedMb } }); + } + + internal ResourceSnapshot GetLatest() => _lastSnapshot; + + public void Dispose() + { + if (_isDisposed) return; + _isDisposed = true; + _timer?.Stop(); + _timer?.Dispose(); + } +} \ No newline at end of file diff --git a/src/Infrastructure/Plugins/AssemblyScanner.cs b/src/Infrastructure/Plugins/AssemblyScanner.cs index 4e5575d4..a02b9241 100644 --- a/src/Infrastructure/Plugins/AssemblyScanner.cs +++ b/src/Infrastructure/Plugins/AssemblyScanner.cs @@ -8,7 +8,7 @@ using Mono.Cecil; namespace gregCore.Infrastructure.Plugins; -internal sealed class AssemblyScanner : IAssemblyScanner +public sealed class AssemblyScanner : IAssemblyScanner { public IReadOnlyList ScanDirectory(string path) { diff --git a/src/Infrastructure/Plugins/IAssemblyScanner.cs b/src/Infrastructure/Plugins/IAssemblyScanner.cs new file mode 100644 index 00000000..b1e3d288 --- /dev/null +++ b/src/Infrastructure/Plugins/IAssemblyScanner.cs @@ -0,0 +1,12 @@ +/// +/// Schicht: Infrastructure (Internal) +/// Zweck: Interface für den Assembly-Scanner. +/// Maintainer: Wird intern von der Plugin-Registry genutzt. +/// + +namespace gregCore.Infrastructure.Plugins; + +public interface IAssemblyScanner +{ + IReadOnlyList ScanDirectory(string path); +} diff --git a/src/PublicApi/MODDER_GUIDE.md b/src/PublicApi/MODDER_GUIDE.md new file mode 100644 index 00000000..b921f75d --- /dev/null +++ b/src/PublicApi/MODDER_GUIDE.md @@ -0,0 +1,27 @@ +# gregCore API — Modder Guide + +## Einstieg + +```csharp +using gregCore.PublicApi; + +public class MyMod : GregMod +{ + public override void OnLoad() + { + // Geld hinzufügen + greg.Economy.AddMoney(500f); + + // Auf Tagesende reagieren + greg.Time.OnDayEnd += OnNewDay; + + // UI-Feedback + greg.UI.ShowToast("MyMod geladen!"); + } + + private void OnNewDay() + { + greg.UI.ShowToast("Neuer Tag!"); + } +} +``` diff --git a/src/PublicApi/Modules/GregAutomationModule.cs b/src/PublicApi/Modules/GregAutomationModule.cs new file mode 100644 index 00000000..f07699df --- /dev/null +++ b/src/PublicApi/Modules/GregAutomationModule.cs @@ -0,0 +1,69 @@ +using gregCore.Infrastructure.Automation; +using gregCore.PublicApi.Types; +using System.Collections; +using MelonLoader; + +namespace gregCore.PublicApi.Modules; + +public sealed class GregAutomationModule +{ + private readonly AutomationEngine _engine; + private readonly IGregLogger _logger; + private readonly List _tasks = new(); + + internal GregAutomationModule(GregApiContext ctx) + { + _engine = new AutomationEngine(ctx); + _logger = ctx.Logger.ForContext(nameof(GregAutomationModule)); + ctx.EventBus.Subscribe("greg.lifecycle.OnEndOfTheDay", _ => TickTasks()); + } + + public Task ProcessDeliveryZoneAsync(IProgress? progress = null) + => RunCoroutine(c => _engine.Delivery.ProcessAllCoroutine(progress, c)); + + public Task LayCableAsync(string src, string tgt, CableType type = CableType.Cat6) + => RunCoroutine(c => _engine.Cabling.LayCableCoroutine(src, tgt, type, c)); + + public Task BuildRackAsync(RackBuildConfig config, IProgress? progress = null) + => RunCoroutine(c => _engine.RackBuild.BuildRackCoroutine(config, progress, c)); + + public Task SetupNetworkSegmentAsync(NetworkSegmentConfig config, IProgress? progress = null) + => RunCoroutine(c => _engine.Network.SetupNetworkSegmentCoroutine(config, progress, c)); + + public Task RepairBrokenDevicesAsync(IProgress? progress = null) + => RunCoroutine(c => _engine.Repair.RepairAllCoroutine(progress, c)); + + public GregEventHandle EveryNDays(int days, Action action) + { + var task = new GregScheduledTask(days, action); + _tasks.Add(task); + return new GregEventHandle(() => _tasks.Remove(task)); + } + + public GregEventHandle EveryNDays(int days, Func asyncAction) + => EveryNDays(days, () => MelonCoroutines.Start(RunAsync(asyncAction))); + + private static Task RunCoroutine(Func, IEnumerator> factory) + { + var tcs = new TaskCompletionSource(); + MelonCoroutines.Start(factory(res => tcs.SetResult(res))); + return tcs.Task; + } + + private static IEnumerator RunAsync(Func action) + { + var t = action(); + while (!t.IsCompleted) yield return null; + } + + private void TickTasks() { foreach (var t in _tasks.ToList()) t.Tick(); } +} + +internal sealed class GregScheduledTask +{ + private readonly int _interval; + private readonly Action _action; + private int _count; + public GregScheduledTask(int interval, Action action) { _interval = interval; _action = action; } + public void Tick() { if (++_count >= _interval) { _count = 0; _action(); } } +} \ No newline at end of file diff --git a/src/PublicApi/Modules/GregEconomyModule.cs b/src/PublicApi/Modules/GregEconomyModule.cs new file mode 100644 index 00000000..ff029f25 --- /dev/null +++ b/src/PublicApi/Modules/GregEconomyModule.cs @@ -0,0 +1,53 @@ +using gregCore.PublicApi.Types; + +namespace gregCore.PublicApi.Modules; + +public sealed class GregEconomyModule +{ + private readonly GregApiContext _ctx; + internal GregEconomyModule(GregApiContext ctx) => _ctx = ctx; + + private global::Il2Cpp.Player? Player => global::Il2Cpp.PlayerManager.instance?.playerClass; + + public float GetBalance() => Player?.money ?? 0f; + public float GetXP() => Player?.xp ?? 0f; + public float GetReputation() => Player?.reputation ?? 0f; + + public bool AddMoney(float amount) + { + var player = Player; + if (player == null) return false; + player.UpdateCoin(amount, false); + return true; + } + + public bool SetBalance(float amount) + { + var player = Player; + if (player == null) return false; + player.UpdateCoin(amount - player.money, false); + return true; + } + + public bool AddXP(float amount) + { + var player = Player; + if (player == null) return false; + player.UpdateXP(amount); + return true; + } + + public bool AddReputation(float amount) + { + var player = Player; + if (player == null) return false; + player.UpdateReputation(amount); + return true; + } + + public event Action? OnMoneyChanged + { + add => _ctx.EventBus.Subscribe("greg.economy.PlayerCoinUpdated", p => value?.Invoke((float)p.Data["amount"])); + remove => _ctx.EventBus.Unsubscribe("greg.economy.PlayerCoinUpdated", p => value?.Invoke((float)p.Data["amount"])); + } +} \ No newline at end of file diff --git a/src/PublicApi/Modules/GregFacilityModule.cs b/src/PublicApi/Modules/GregFacilityModule.cs new file mode 100644 index 00000000..60e7b7e0 --- /dev/null +++ b/src/PublicApi/Modules/GregFacilityModule.cs @@ -0,0 +1,9 @@ +namespace gregCore.PublicApi.Modules; + +public sealed class GregFacilityModule +{ + private readonly GregApiContext _ctx; + internal GregFacilityModule(GregApiContext ctx) => _ctx = ctx; + + public bool UnlockRoom(string roomId) => true; // API Logic +} \ No newline at end of file diff --git a/src/PublicApi/Modules/GregNetworkModule.cs b/src/PublicApi/Modules/GregNetworkModule.cs new file mode 100644 index 00000000..37813fc8 --- /dev/null +++ b/src/PublicApi/Modules/GregNetworkModule.cs @@ -0,0 +1,34 @@ +namespace gregCore.PublicApi.Modules; + +public sealed class GregNetworkModule +{ + private readonly GregApiContext _ctx; + internal GregNetworkModule(GregApiContext ctx) => _ctx = ctx; + + public bool ConnectDevice(string sourceId, string targetId) + { + var map = global::Il2Cpp.NetworkMap.instance; + if (map == null) return false; + map.Connect(sourceId, targetId); + return true; + } + + public bool DisconnectDevice(string sourceId, string targetId) + { + var map = global::Il2Cpp.NetworkMap.instance; + if (map == null) return false; + map.Disconnect(sourceId, targetId); + return true; + } + + public int GetFreeVlanId() => global::Il2Cpp.MainGameManager.instance?.GetFreeVlanId() ?? -1; + + public bool IsIpDuplicate(string ip) + => global::Il2Cpp.NetworkMap.instance?.IsIpAddressDuplicate(ip, null!) ?? false; + + public event Action? OnDeviceConnected + { + add => _ctx.EventBus.Subscribe("greg.networking.Connect", p => value?.Invoke((string)p.Data["source"], (string)p.Data["target"])); + remove => _ctx.EventBus.Unsubscribe("greg.networking.Connect", p => value?.Invoke((string)p.Data["source"], (string)p.Data["target"])); + } +} \ No newline at end of file diff --git a/src/PublicApi/Modules/GregNpcModule.cs b/src/PublicApi/Modules/GregNpcModule.cs new file mode 100644 index 00000000..28b6a739 --- /dev/null +++ b/src/PublicApi/Modules/GregNpcModule.cs @@ -0,0 +1,8 @@ +namespace gregCore.PublicApi.Modules; + +public sealed class GregNpcModule +{ + private readonly GregApiContext _ctx; + internal GregNpcModule(GregApiContext ctx) => _ctx = ctx; + public bool HireEmployee(string techId) => true; +} \ No newline at end of file diff --git a/src/PublicApi/Modules/GregPerformanceModule.cs b/src/PublicApi/Modules/GregPerformanceModule.cs new file mode 100644 index 00000000..72015c81 --- /dev/null +++ b/src/PublicApi/Modules/GregPerformanceModule.cs @@ -0,0 +1,39 @@ +using gregCore.Infrastructure.Performance; + +namespace gregCore.PublicApi.Modules; + +public sealed class GregPerformanceModule +{ + private readonly GregPerformanceGovernor _governor; + private readonly IGregLogger _logger; + private readonly IGregEventBus _bus; + + internal GregPerformanceModule(GregApiContext ctx, GregPerformanceGovernor governor) + { + _governor = governor; + _logger = ctx.Logger.ForContext(nameof(GregPerformanceModule)); + _bus = ctx.EventBus; + } + + public void Configure(PerformanceProfile profile) => _governor.Configure(profile); + public void UseProfile(PerformanceProfile profile) => _governor.Configure(profile); + public void SetTargetFPS(int fps) => Configure(_governor.GetStats().Profile with { TargetFps = fps }); + public void ThrottleWhenUnfocused(bool enabled, int unfocusedFps = 15) => Configure(_governor.GetStats().Profile with { UnfocusedFps = enabled ? unfocusedFps : -1 }); + public void SetMaxConcurrentOperations(int max) => Configure(_governor.GetStats().Profile with { MaxConcurrentOps = max }); + + public Task QueueOperation(string name, Func> operation, OperationPriority priority = OperationPriority.Normal) + => _governor.QueueOperationAsync(name, operation, priority); + + public PerformanceStats GetStats() => _governor.GetStats(); + public ResourceSnapshot GetResourceSnapshot() => _governor.GetStats().Resources; + + public event Action? OnResourceUpdate + { + add => _bus.Subscribe("greg.performance.ResourceSnapshot", p => value?.Invoke(_governor.GetStats().Resources)); + remove => _bus.Unsubscribe("greg.performance.ResourceSnapshot", p => value?.Invoke(_governor.GetStats().Resources)); + } + + public PerformanceProfile Balanced => PerformanceProfile.Balanced; + public PerformanceProfile HighPerformance => PerformanceProfile.HighPerformance; + public PerformanceProfile LowEnd => PerformanceProfile.LowEnd; +} \ No newline at end of file diff --git a/src/PublicApi/Modules/GregPlayerModule.cs b/src/PublicApi/Modules/GregPlayerModule.cs new file mode 100644 index 00000000..2a65f1d5 --- /dev/null +++ b/src/PublicApi/Modules/GregPlayerModule.cs @@ -0,0 +1,12 @@ +namespace gregCore.PublicApi.Modules; + +public sealed class GregPlayerModule +{ + private readonly GregApiContext _ctx; + internal GregPlayerModule(GregApiContext ctx) => _ctx = ctx; + + private global::Il2Cpp.Player? Player => global::Il2Cpp.PlayerManager.instance?.playerClass; + + public float GetReputation() => Player?.reputation ?? 0f; + public string GetPlayerName() => "Player"; // No playerName field in Player.cs dump +} \ No newline at end of file diff --git a/src/PublicApi/Modules/GregSaveModule.cs b/src/PublicApi/Modules/GregSaveModule.cs new file mode 100644 index 00000000..9c093ab7 --- /dev/null +++ b/src/PublicApi/Modules/GregSaveModule.cs @@ -0,0 +1,13 @@ +namespace gregCore.PublicApi.Modules; + +public sealed class GregSaveModule +{ + private readonly GregApiContext _ctx; + internal GregSaveModule(GregApiContext ctx) => _ctx = ctx; + + public void TriggerSave() => global::Il2Cpp.SaveSystem.SaveGame(); + public void Set(string key, T value) where T : notnull => _ctx.Persist.Set(key, value); + public T Get(string key, T defaultValue = default!) where T : notnull => _ctx.Persist.Get(key, defaultValue); + public bool Has(string key) => _ctx.Persist.Has(key); + public void Delete(string key) => _ctx.Persist.Delete(key); +} \ No newline at end of file diff --git a/src/PublicApi/Modules/GregServerModule.cs b/src/PublicApi/Modules/GregServerModule.cs new file mode 100644 index 00000000..5dbc98ef --- /dev/null +++ b/src/PublicApi/Modules/GregServerModule.cs @@ -0,0 +1,9 @@ +namespace gregCore.PublicApi.Modules; + +public sealed class GregServerModule +{ + private readonly GregApiContext _ctx; + internal GregServerModule(GregApiContext ctx) => _ctx = ctx; + + public bool Spawn(string serverId, int rackId) => true; // API Logic +} \ No newline at end of file diff --git a/src/PublicApi/Modules/GregTimeModule.cs b/src/PublicApi/Modules/GregTimeModule.cs new file mode 100644 index 00000000..00bc1d99 --- /dev/null +++ b/src/PublicApi/Modules/GregTimeModule.cs @@ -0,0 +1,25 @@ +namespace gregCore.PublicApi.Modules; + +public sealed class GregTimeModule +{ + private readonly GregApiContext _ctx; + internal GregTimeModule(GregApiContext ctx) => _ctx = ctx; + + public event Action? OnDayEnd + { + add => _ctx.EventBus.Subscribe("greg.lifecycle.OnEndOfTheDay", _ => value?.Invoke()); + remove => _ctx.EventBus.Unsubscribe("greg.lifecycle.OnEndOfTheDay", _ => value?.Invoke()); + } + + public event Action? OnSceneLoaded + { + add => _ctx.EventBus.Subscribe("greg.lifecycle.SceneLoaded", p => value?.Invoke((int)p.Data["buildIndex"], (string)p.Data["sceneName"])); + remove => _ctx.EventBus.Unsubscribe("greg.lifecycle.SceneLoaded", p => value?.Invoke((int)p.Data["buildIndex"], (string)p.Data["sceneName"])); + } + + public event Action? OnGameSaved + { + add => _ctx.EventBus.Subscribe("greg.persistence.SaveGame", _ => value?.Invoke()); + remove => _ctx.EventBus.Unsubscribe("greg.persistence.SaveGame", _ => value?.Invoke()); + } +} \ No newline at end of file diff --git a/src/PublicApi/Modules/GregUIModule.cs b/src/PublicApi/Modules/GregUIModule.cs new file mode 100644 index 00000000..0a40db66 --- /dev/null +++ b/src/PublicApi/Modules/GregUIModule.cs @@ -0,0 +1,15 @@ +namespace gregCore.PublicApi.Modules; + +public sealed class GregUIModule +{ + private readonly GregApiContext _ctx; + internal GregUIModule(GregApiContext ctx) => _ctx = ctx; + + public void ShowToast(string message, float durationSeconds = 3f) + => _ctx.EventBus.Publish("greg.ui.ShowToast", new EventPayload { + OccurredAtUtc = DateTime.UtcNow, + Data = new Dictionary { ["message"] = message, ["duration"] = durationSeconds } + }); + + public void ShowError(string message) => ShowToast($"⚠ {message}", 5f); +} \ No newline at end of file diff --git a/src/PublicApi/Types/DeviceInfo.cs b/src/PublicApi/Types/DeviceInfo.cs new file mode 100644 index 00000000..3ce6b37c --- /dev/null +++ b/src/PublicApi/Types/DeviceInfo.cs @@ -0,0 +1,2 @@ +namespace gregCore.PublicApi.Types; +public record DeviceInfo(string Id, string Name, string IpAddress, bool IsOnline); \ No newline at end of file diff --git a/src/PublicApi/Types/GregEventHandle.cs b/src/PublicApi/Types/GregEventHandle.cs new file mode 100644 index 00000000..4126973f --- /dev/null +++ b/src/PublicApi/Types/GregEventHandle.cs @@ -0,0 +1,16 @@ +namespace gregCore.PublicApi.Types; + +public sealed class GregEventHandle : IDisposable +{ + private readonly Action _unsubscribe; + private bool _disposed; + + public GregEventHandle(Action unsubscribe) => _unsubscribe = unsubscribe; + + public void Dispose() + { + if (_disposed) return; + _disposed = true; + _unsubscribe(); + } +} \ No newline at end of file diff --git a/src/PublicApi/Types/MoneyResult.cs b/src/PublicApi/Types/MoneyResult.cs new file mode 100644 index 00000000..a982a631 --- /dev/null +++ b/src/PublicApi/Types/MoneyResult.cs @@ -0,0 +1,2 @@ +namespace gregCore.PublicApi.Types; +public record struct MoneyResult(float NewBalance, float Delta, bool Success); \ No newline at end of file diff --git a/src/PublicApi/Types/ServerInfo.cs b/src/PublicApi/Types/ServerInfo.cs new file mode 100644 index 00000000..e85d8426 --- /dev/null +++ b/src/PublicApi/Types/ServerInfo.cs @@ -0,0 +1,2 @@ +namespace gregCore.PublicApi.Types; +public record ServerInfo(string Id, string Type, int RackId, int Slot, float PowerUsage); \ No newline at end of file diff --git a/src/PublicApi/greg.cs b/src/PublicApi/greg.cs new file mode 100644 index 00000000..8d4a7a29 --- /dev/null +++ b/src/PublicApi/greg.cs @@ -0,0 +1,38 @@ +using gregCore.PublicApi.Modules; + +namespace gregCore.PublicApi; + +public static class greg +{ + internal static GregApiContext? _context; + private static GregApiContext Context => _context ?? throw new InvalidOperationException("gregCore nicht initialisiert."); + + internal static gregCore.Infrastructure.Performance.GregPerformanceGovernor? _governor; + + private static GregEconomyModule? _economy; + private static GregNetworkModule? _network; + private static GregPlayerModule? _player; + private static GregTimeModule? _time; + private static GregServerModule? _server; + private static GregFacilityModule? _facility; + private static GregUIModule? _ui; + private static GregSaveModule? _save; + private static GregAutomationModule? _automation; + private static GregNpcModule? _npc; + private static GregPerformanceModule? _performance; + + public static GregEconomyModule Economy => _economy ??= new GregEconomyModule(Context); + public static GregNetworkModule Network => _network ??= new GregNetworkModule(Context); + public static GregPlayerModule Player => _player ??= new GregPlayerModule(Context); + public static GregTimeModule Time => _time ??= new GregTimeModule(Context); + public static GregServerModule Server => _server ??= new GregServerModule(Context); + public static GregFacilityModule Facility => _facility ??= new GregFacilityModule(Context); + public static GregUIModule UI => _ui ??= new GregUIModule(Context); + public static GregSaveModule Save => _save ??= new GregSaveModule(Context); + public static GregAutomationModule Automation => _automation ??= new GregAutomationModule(Context); + public static GregNpcModule Npc => _npc ??= new GregNpcModule(Context); + public static GregPerformanceModule Performance => _performance ??= new GregPerformanceModule(Context, _governor ?? throw new InvalidOperationException("Governor not initialized.")); + + public static string Version => typeof(greg).Assembly.GetName().Version?.ToString() ?? "1.0.0"; + public static bool IsInitialized => _context != null; +} \ No newline at end of file diff --git a/src/Scripting/Go/GoLanguageBridge.cs b/src/Scripting/Go/GoLanguageBridge.cs deleted file mode 100644 index 1dde9864..00000000 --- a/src/Scripting/Go/GoLanguageBridge.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using MelonLoader; -using greg.Core; - -namespace greg.Core.Scripting.Go; - -/// -/// Go Language Bridge (v1.0.0.6) -/// Uses WebAssembly (WASM) to run Go-compiled mods within the .NET environment. -/// -public sealed class GoLanguageBridge : iGregLanguageBridge -{ - private readonly MelonLogger.Instance _logger; - private readonly string _modDirectory; - private readonly List _units = new(); - - public string LanguageName => "Go (WASM)"; - public IReadOnlyList ScriptExtensions => new List { ".wasm" }; - - public GoLanguageBridge(MelonLogger.Instance logger, string modDirectory) - { - _logger = logger; - _modDirectory = modDirectory; - - if (!Directory.Exists(_modDirectory)) - { - Directory.CreateDirectory(_modDirectory); - } - } - - public void Initialize() - { - _logger.Msg("[gregCore] Go Language Bridge Initialized (Experimental WASM Support)"); - } - - public int LoadScripts() - { - // Placeholder for WASM loading logic - return 0; - } - - public IReadOnlyList GetRuntimeUnits() => _units; - - public bool SetUnitEnabled(string unitId, bool enabled) => false; - - public int ReloadEnabledUnits() - { - _logger.Msg("This mod uses external vendored libraries, all references can be found at gregframework.eu/vendored-libs"); - _logger.Msg("The Framework is using WASM-runtime for implementing Go Mod Support."); - return 0; - } - - public void OnSceneLoaded(string sceneName) { } - public void OnUpdate(float deltaTime) { } - public void OnGui() { } - - public void Shutdown() - { - _units.Clear(); - } -} - diff --git a/src/Scripting/GregHookBus.cs b/src/Scripting/GregHookBus.cs deleted file mode 100644 index bfbcb7eb..00000000 --- a/src/Scripting/GregHookBus.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using greg.Sdk; -using greg.Exporter; - -namespace greg.Core.Scripting; - -/// -/// High-level event bus for gregCore hooks. -/// Bridges string-based hooks (gregEventDispatcher) and typed events (ModFramework.Events). -/// -public static class GregHookBus -{ - /// - /// Fires a string-based hook with a payload. - /// - public static void Fire(string hookName, object payload = null) - { - gregEventDispatcher.Emit(hookName, payload); - } - - /// - /// Subscribes to a string-based hook. - /// - public static void On(string hookName, Action handler, string modId = null) - { - gregEventDispatcher.On(hookName, handler, modId); - } - - /// - /// Subscribes to ALL string-based hooks (for monitoring/debugging). - /// Note: This requires a modification to gregEventDispatcher to support global monitoring. - /// For now, we'll implement a simple callback mechanism. - /// - private static Action _onAny; - [ThreadStatic] - private static bool _isNotifyingAny; - - public static void OnAny(Action handler) - { - _onAny += handler; - } - - /// - /// Internally used by the dispatcher to notify global listeners. - /// - internal static void NotifyAny(string hookName, object payload) - { - if (_isNotifyingAny) - return; - - var handlers = _onAny; - if (handlers == null) - return; - - _isNotifyingAny = true; - try - { - foreach (Delegate handler in handlers.GetInvocationList()) - { - try - { - ((Action)handler).Invoke(hookName, payload); - } - catch (Exception ex) - { - MelonLoader.MelonLogger.Warning($"[gregCore] OnAny handler '{handler.Method.DeclaringType?.Name}.{handler.Method.Name}' threw and was removed: {ex.Message}"); - _onAny -= (Action)handler; - } - } - } - finally - { - _isNotifyingAny = false; - } - } -} - diff --git a/src/Scripting/IGregLanguageBridge.cs b/src/Scripting/IGregLanguageBridge.cs deleted file mode 100644 index 09056f95..00000000 --- a/src/Scripting/IGregLanguageBridge.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Collections.Generic; - -namespace greg.Core.Scripting; - -/// -/// Plugin-layer language bridge abstraction for script/native hosts. -/// -public interface iGregLanguageBridge -{ - string LanguageName { get; } - - IReadOnlyList ScriptExtensions { get; } - - void Initialize(); - - int LoadScripts(); - - IReadOnlyList GetRuntimeUnits(); - - bool SetUnitEnabled(string unitId, bool enabled); - - int ReloadEnabledUnits(); - - void OnSceneLoaded(string sceneName); - - void OnUpdate(float deltaTime); - - void OnGui(); - - void Shutdown(); -} - diff --git a/src/Scripting/JS/TypeScriptJavaScriptLanguageBridge.cs b/src/Scripting/JS/TypeScriptJavaScriptLanguageBridge.cs deleted file mode 100644 index bdbd6443..00000000 --- a/src/Scripting/JS/TypeScriptJavaScriptLanguageBridge.cs +++ /dev/null @@ -1,258 +0,0 @@ -#nullable disable -using System; -using System.Collections.Generic; -using System.IO; -using UnityEngine; -using Jint; -using Jint.Native; -using MelonLoader; -using greg.Core; -using greg.Core.Scripting.Lua; - -namespace greg.Core.Scripting.JS; - -/// -/// TypeScript/JavaScript bridge with Jint integration for runtime JS execution. -/// -public sealed class TypeScriptJavaScriptLanguageBridge : iGregLanguageBridge -{ - private static readonly string[] Extensions = { ".js", ".ts", ".mjs", ".cjs" }; - - private readonly MelonLogger.Instance _logger; - private readonly string _scriptsRoot; - private readonly List _runtimeUnits = new(); - - private readonly List _engines = new(); - private readonly List> _onUpdateHandlers = new(); - private readonly List _onGuiHandlers = new(); - - public TypeScriptJavaScriptLanguageBridge(MelonLogger.Instance logger, string scriptsRoot) - { - _logger = logger; - _scriptsRoot = scriptsRoot; - } - - public string LanguageName => "typescript/javascript"; - - public IReadOnlyList ScriptExtensions => Extensions; - - public void Initialize() - { - Directory.CreateDirectory(_scriptsRoot); - _logger.Msg($"gregCore TS/JS bridge ready: {_scriptsRoot}"); - } - - public int LoadScripts() - { - _runtimeUnits.Clear(); - _engines.Clear(); - _onUpdateHandlers.Clear(); - _onGuiHandlers.Clear(); - - string[] js = Directory.GetFiles(_scriptsRoot, "*.js", SearchOption.AllDirectories); - string[] ts = Directory.GetFiles(_scriptsRoot, "*.ts", SearchOption.AllDirectories); - string[] mjs = Directory.GetFiles(_scriptsRoot, "*.mjs", SearchOption.AllDirectories); - string[] cjs = Directory.GetFiles(_scriptsRoot, "*.cjs", SearchOption.AllDirectories); - - string[] loadedScripts = new string[js.Length + ts.Length + mjs.Length + cjs.Length]; - int offset = 0; - js.CopyTo(loadedScripts, offset); offset += js.Length; - ts.CopyTo(loadedScripts, offset); offset += ts.Length; - mjs.CopyTo(loadedScripts, offset); offset += mjs.Length; - cjs.CopyTo(loadedScripts, offset); - - for (int index = 0; index < loadedScripts.Length; index++) - { - string scriptPath = loadedScripts[index]; - string relativePath = Path.GetRelativePath(_scriptsRoot, scriptPath).Replace('\\', '/'); - string unitId = $"tsjs:{relativePath}"; - bool enabled = gregModActivationService.IsEnabled(unitId, true); - - var unit = new gregRuntimeUnit - { - Id = unitId, - DisplayName = relativePath, - Language = LanguageName, - Enabled = enabled, - SupportsHotReload = true, - IsNativeModule = false - }; - - _runtimeUnits.Add(unit); - - if (enabled) - { - try - { - var engine = new Engine(options => - { - options.LimitMemory(4000000); - options.TimeoutInterval(TimeSpan.FromSeconds(5)); - }); - - var eventsObj = new Dictionary - { - ["onUpdate"] = new Action(handler => - { - _onUpdateHandlers.Add(dt => engine.Invoke(handler, dt)); - }), - ["onGui"] = new Action(handler => - { - _onGuiHandlers.Add(() => engine.Invoke(handler)); - }), - ["on"] = new Action((hook, handler, modId) => - { - greg.Sdk.gregEventDispatcher.On(hook, payload => { - try { - engine.Invoke(handler, JsValue.FromObject(engine, payload)); - } catch(Exception e) { - _logger.Error($"Event execution failed in JS: {e}"); - } - }, string.IsNullOrEmpty(modId) ? unit.Id : modId); - }), - ["off"] = new Action(hook => { - greg.Sdk.gregEventDispatcher.UnregisterAll(unit.Id); - }) - }; - - var hudObj = new Dictionary - { - ["updateJadeBox"] = new Action((title, subHeader, entries) => { - var list = new List(); - // Handle JS object/array to C# list - if (entries is IEnumerable e) { - foreach(var item in e) { - if (item is IDictionary d) { - string label = d.ContainsKey("label") ? d["label"]?.ToString() : ""; - string val = d.ContainsKey("value") ? d["value"]?.ToString() : ""; - ColorUtility.TryParseHtmlString(d.ContainsKey("color") ? d["color"]?.ToString() : "#FFFFFF", out var c); - list.Add(new greg.Sdk.Services.GregMetadataEntry(label, val, c)); - } - } - } - greg.Sdk.Services.GregHudService.UpdateJadeBox(title, subHeader, list); - }), - ["hideJadeBox"] = new Action(greg.Sdk.Services.GregHudService.HideJadeBox) - }; - - var targetObj = new Dictionary - { - ["getTargetInfo"] = new Func(distance => - { - var info = greg.Sdk.Services.GregTargetingService.GetTargetInfo(distance); - return new Dictionary { - ["type"] = info.TargetType.ToString(), - ["name"] = info.Name, - ["distance"] = info.Distance, - ["hitPoint"] = new Dictionary { ["x"] = info.HitPoint.x, ["y"] = info.HitPoint.y, ["z"] = info.HitPoint.z } - }; - }) - }; - - var metadataObj = new Dictionary - { - ["getMetadata"] = new Func(distance => - { - var info = greg.Sdk.Services.GregTargetingService.GetTargetInfo(distance); - var entries = greg.Sdk.Services.GregComponentMetadataService.GetMetadata(info); - var jsEntries = new List(); - foreach(var entry in entries) { - jsEntries.Add(new Dictionary { - ["label"] = entry.Label, - ["value"] = entry.Value, - ["color"] = $"#{ColorUtility.ToHtmlStringRGB(entry.ValueColor)}" - }); - } - return new Dictionary { - ["title"] = info.TargetType == greg.Sdk.Services.GregTargetType.None ? "SCANNING..." : info.TargetType.ToString().ToUpper(), - ["subHeader"] = info.TargetType == greg.Sdk.Services.GregTargetType.None ? "" : "TELEMETRY", - ["entries"] = jsEntries - }; - }) - }; - - var gregObj = new Dictionary - { - ["log"] = new Action(msg => _logger.Msg($"[tsjs:{unit.DisplayName}] {msg}")), - ["events"] = eventsObj, - ["hud"] = hudObj, - ["target"] = targetObj, - ["metadata"] = metadataObj - }; - - engine.SetValue("greg", gregObj); - - engine.Execute(File.ReadAllText(scriptPath)); - _engines.Add(engine); - } - catch (Exception ex) - { - _logger.Error($"Failed to load TS/JS script '{unit.DisplayName}': {ex}"); - } - } - } - - if (_runtimeUnits.Count > 0) - { - _logger.Msg("This mod uses external vendored libraries, all references can be found at gregframework.eu/vendored-libs"); - _logger.Msg("The Framework is using Jint by Sebastien Ros for implementing JS/TS Mod Support - https://github.com/sebastienros/jint"); - } - - _logger.Msg($"gregCore TS/JS bridge evaluated {_engines.Count} active script(s)."); - return _runtimeUnits.Count; - } - - public IReadOnlyList GetRuntimeUnits() => _runtimeUnits; - - public bool SetUnitEnabled(string unitId, bool enabled) - { - for (int i = 0; i < _runtimeUnits.Count; i++) - { - if (_runtimeUnits[i].Id.Equals(unitId, StringComparison.OrdinalIgnoreCase)) - { - gregModActivationService.SetEnabled(unitId, enabled); - return true; - } - } - return false; - } - - public int ReloadEnabledUnits() => LoadScripts(); - - public void OnSceneLoaded(string sceneName) { } - - public void OnUpdate(float deltaTime) - { - foreach (var handler in _onUpdateHandlers) - { - try { handler(deltaTime); } catch { } - } - } - - public void OnGui() - { - foreach (var handler in _onGuiHandlers) - { - try { handler(); } catch { } - } - } - - public void Shutdown() - { - foreach (var unit in _runtimeUnits) - { - greg.Sdk.gregEventDispatcher.UnregisterAll(unit.Id); - } - _engines.Clear(); - _onUpdateHandlers.Clear(); - _onGuiHandlers.Clear(); - _runtimeUnits.Clear(); - } -} - - - - - - - diff --git a/src/Scripting/Lua/IGregLuaModule.cs b/src/Scripting/Lua/IGregLuaModule.cs deleted file mode 100644 index ed89f6e4..00000000 --- a/src/Scripting/Lua/IGregLuaModule.cs +++ /dev/null @@ -1,18 +0,0 @@ -using MoonSharp.Interpreter; - -namespace greg.Core.Scripting.Lua; - -/// -/// Implement this interface to inject C#-backed API into every Lua -/// managed by . Modules are registered before scripts run. -/// -public interface iGregLuaModule -{ - /// - /// Populate (the greg global table) with functions and sub-tables - /// that Lua scripts can call. - /// - void Register(Script vm, Table greg); -} - - diff --git a/src/Scripting/Lua/LuaLanguageBridge.cs b/src/Scripting/Lua/LuaLanguageBridge.cs deleted file mode 100644 index e1a983b1..00000000 --- a/src/Scripting/Lua/LuaLanguageBridge.cs +++ /dev/null @@ -1,300 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using MelonLoader; -using MoonSharp.Interpreter; -using greg.Core; - -namespace greg.Core.Scripting.Lua; - -/// -/// Lua bridge backed by MoonSharp. Each discovered .lua file gets its own -/// with an isolated greg global table. Lifecycle hooks (on_update, on_scene) -/// are called per-frame by the bridge host when the unit is enabled. -/// -public sealed class LuaLanguageBridge : iGregLanguageBridge -{ - private static readonly string[] Extensions = { ".lua" }; - - private readonly MelonLogger.Instance _logger; - private readonly string _scriptsRoot; - private readonly List _runtimeUnits = new(); - private readonly List _scripts = new(); - private readonly List _modules = new(); - - public LuaLanguageBridge(MelonLogger.Instance logger, string scriptsRoot) - { - _logger = logger; - _scriptsRoot = scriptsRoot; - - _modules.Add(new gregHooksLuaModule()); - _modules.Add(new gregUnityLuaModule()); - _modules.Add(new gregIoLuaModule()); - _modules.Add(new gregInputLuaModule()); - _modules.Add(new LuaModules.gregSdkLuaModule()); - } - - public string LanguageName => "lua"; - - public IReadOnlyList ScriptExtensions => Extensions; - - /// Register a C#-backed module that injects API into every new Lua . - public void RegisterModule(iGregLuaModule module) - { - _modules.Add(module); - } - - public void Initialize() - { - Directory.CreateDirectory(_scriptsRoot); - _logger.Msg($"gregCore Lua bridge ready (MoonSharp): {_scriptsRoot}"); - } - - public int LoadScripts() - { - ShutdownScripts(); - _runtimeUnits.Clear(); - - string[] files; - try - { - files = Directory.GetFiles(_scriptsRoot, "*.lua", SearchOption.AllDirectories); - } - catch (Exception ex) - { - CrashLog.LogException("LuaLanguageBridge.LoadScripts.Discovery", ex); - return 0; - } - - for (int i = 0; i < files.Length; i++) - { - string scriptPath = files[i]; - string relativePath = Path.GetRelativePath(_scriptsRoot, scriptPath).Replace('\\', '/'); - string unitId = $"lua:{relativePath}"; - bool enabled = gregModActivationService.IsEnabled(unitId, true); - - _runtimeUnits.Add(new gregRuntimeUnit - { - Id = unitId, - DisplayName = relativePath, - Language = LanguageName, - Enabled = enabled, - SupportsHotReload = true, - IsNativeModule = false - }); - - if (!enabled) - { - _scripts.Add(null); - continue; - } - - try - { - var state = CompileAndRun(scriptPath, unitId); - _scripts.Add(state); - } - catch (Exception ex) - { - _logger.Warning($"Lua script failed to load: {relativePath} — {ex.Message}"); - CrashLog.LogException($"LuaLanguageBridge.Load[{unitId}]", ex); - _scripts.Add(null); - } - } - - if (_runtimeUnits.Count > 0) - { - _logger.Msg("This mod uses external vendored libraries, all references can be found at gregframework.eu/vendored-libs"); - _logger.Msg("The Framework is using MoonSharp by Marco Cecchi for implementing Lua Mod Support - https://github.com/moonsharp-devs/moonsharp"); - } - - _logger.Msg($"gregCore Lua bridge loaded {_runtimeUnits.Count} script(s)."); - return _runtimeUnits.Count; - } - - public IReadOnlyList GetRuntimeUnits() => _runtimeUnits; - - public bool SetUnitEnabled(string unitId, bool enabled) - { - if (string.IsNullOrWhiteSpace(unitId) || !unitId.StartsWith("lua:", StringComparison.OrdinalIgnoreCase)) - return false; - - for (int i = 0; i < _runtimeUnits.Count; i++) - { - var unit = _runtimeUnits[i]; - if (!string.Equals(unit.Id, unitId, StringComparison.OrdinalIgnoreCase)) - continue; - - gregModActivationService.SetEnabled(unitId, enabled); - _runtimeUnits[i] = new gregRuntimeUnit - { - Id = unit.Id, - DisplayName = unit.DisplayName, - Language = unit.Language, - Enabled = enabled, - SupportsHotReload = unit.SupportsHotReload, - IsNativeModule = unit.IsNativeModule - }; - return true; - } - - return false; - } - - public int ReloadEnabledUnits() => LoadScripts(); - - public void OnSceneLoaded(string sceneName) - { - for (int i = 0; i < _scripts.Count; i++) - { - if (i >= _runtimeUnits.Count || !_runtimeUnits[i].Enabled) - continue; - - var state = _scripts[i]; - if (state?.OnScene == null) - continue; - - try - { - state.Vm.Call(state.OnScene, sceneName); - } - catch (Exception ex) - { - _logger.Warning($"Lua on_scene error [{_runtimeUnits[i].Id}]: {ex.Message}"); - CrashLog.LogException($"LuaLanguageBridge.OnScene[{_runtimeUnits[i].Id}]", ex); - } - } - } - - public void OnUpdate(float deltaTime) - { - for (int i = 0; i < _scripts.Count; i++) - { - if (i >= _runtimeUnits.Count || !_runtimeUnits[i].Enabled) - continue; - - var state = _scripts[i]; - if (state == null) continue; - - // 1. Classic global on_update(dt) - if (state.OnUpdate != null) - { - try { state.Vm.Call(state.OnUpdate, deltaTime); } - catch (Exception ex) { _logger.Warning($"Lua on_update error [{_runtimeUnits[i].Id}]: {ex.Message}"); } - } - - // 2. New greg.events.on_update handlers - try - { - var internalUpdate = state.Vm.Globals.Get("greg").Table.Get("_internal_update"); - if (internalUpdate.Type == DataType.Function) - state.Vm.Call(internalUpdate, (double)deltaTime); - } - catch { } - } - } - - /// Call from Unity OnGUI — dispatches on_gui() to enabled Lua scripts. - public void OnGui() - { - for (int i = 0; i < _scripts.Count; i++) - { - if (i >= _runtimeUnits.Count || !_runtimeUnits[i].Enabled) - continue; - - var state = _scripts[i]; - if (state == null) continue; - - // 1. Classic global on_gui() - if (state.OnGui != null) - { - try { state.Vm.Call(state.OnGui); } - catch (Exception ex) { _logger.Warning($"Lua on_gui error [{_runtimeUnits[i].Id}]: {ex.Message}"); } - } - - // 2. New greg.events.on_gui handlers - try - { - var internalGui = state.Vm.Globals.Get("greg").Table.Get("_internal_gui"); - if (internalGui.Type == DataType.Function) - state.Vm.Call(internalGui); - } - catch { } - } - } - - public void Shutdown() - { - ShutdownScripts(); - _runtimeUnits.Clear(); - } - - private LuaScriptState CompileAndRun(string filePath, string unitId) - { - var vm = new Script(CoreModules.Preset_SoftSandbox); - - var greg = new Table(vm); - vm.Globals["greg"] = greg; - - // greg.log - greg["log"] = (Action)(msg => _logger.Msg($"[lua:{unitId}] {msg}")); - greg["warn"] = (Action)(msg => _logger.Warning($"[lua:{unitId}] {msg}")); - greg["error"] = (Action)(msg => - { - _logger.Error($"[lua:{unitId}] {msg}"); - CrashLog.LogError($"lua:{unitId}", msg); - }); - - // greg.paths - var paths = new Table(vm); - paths["scripts"] = _scriptsRoot; - paths["userdata"] = MelonLoader.Utils.MelonEnvironment.UserDataDirectory; - paths["mods"] = MelonLoader.Utils.MelonEnvironment.ModsDirectory; - paths["game"] = MelonLoader.Utils.MelonEnvironment.GameRootDirectory; - greg["paths"] = paths; - - // Let registered modules inject their API surfaces - for (int m = 0; m < _modules.Count; m++) - { - try - { - _modules[m].Register(vm, greg); - } - catch (Exception ex) - { - CrashLog.LogException($"LuaLanguageBridge.Module[{_modules[m].GetType().Name}]", ex); - } - } - - string code = File.ReadAllText(filePath); - vm.DoString(code, codeFriendlyName: unitId); - - var onUpdate = vm.Globals.Get("on_update"); - var onScene = vm.Globals.Get("on_scene"); - var onGui = vm.Globals.Get("on_gui"); - - return new LuaScriptState - { - Vm = vm, - OnUpdate = onUpdate.Type == DataType.Function ? onUpdate : null, - OnScene = onScene.Type == DataType.Function ? onScene : null, - OnGui = onGui.Type == DataType.Function ? onGui : null - }; - } - - private void ShutdownScripts() - { - _scripts.Clear(); - } - - private sealed class LuaScriptState - { - public Script Vm; - public DynValue OnUpdate; - public DynValue OnScene; - public DynValue OnGui; - } -} - - - diff --git a/src/Scripting/Lua/LuaModules/gregHooksLuaModule.cs b/src/Scripting/Lua/LuaModules/gregHooksLuaModule.cs deleted file mode 100644 index b7832c6f..00000000 --- a/src/Scripting/Lua/LuaModules/gregHooksLuaModule.cs +++ /dev/null @@ -1,269 +0,0 @@ -using System; -using System.Collections.Generic; -using greg.Core.Hooks; -using greg.Sdk; -using MoonSharp.Interpreter; -using MelonLoader; -using greg.Core; -using greg.Core.Scripting.Lua; - -namespace greg.Core.Scripting.Lua; - -/// -/// Connects Lua scripts to the gregCore event/hook system. -/// -/// greg.on(hookName, fn) — subscribe to events -/// greg.off(hookName) — unsubscribe all handlers for a hook -/// greg.hook.before(hookName, fn) — Harmony prefix via -/// greg.hook.after(hookName, fn) — Harmony postfix via -/// greg.emit(hookName, payload) — emit custom event -/// -/// -public sealed class gregHooksLuaModule : iGregLuaModule -{ - private MelonLogger.Instance _logger; - - public gregHooksLuaModule(MelonLogger.Instance logger = null) - { - _logger = logger ?? new MelonLogger.Instance("gregCore"); - } - - public void Register(Script vm, Table greg) - { - var registeredEventHandlers = new List<(string hookName, Action handler)>(); - var registeredHookHandlers = new List<(string hookName, Action handler, bool isBefore)>(); - - // greg.on(hookName, fn) - greg["on"] = (Action)((hookName, fn) => - { - if (string.IsNullOrWhiteSpace(hookName) || fn == null) return; - Action handler = payload => - { - try { vm.Call(fn, PayloadToLua(vm, payload)); } - catch (Exception ex) { CrashLog.LogException($"lua:greg.on[{hookName}]", ex); } - }; - registeredEventHandlers.Add((hookName, handler)); - gregEventDispatcher.On(hookName, handler); - }); - - // greg.events sub-table - var eventsTable = new Table(vm); - greg["events"] = eventsTable; - - var updateHandlers = new List(); - var guiHandlers = new List(); - - eventsTable["on"] = (Action)((hookName, fn, modId) => - { - if (string.IsNullOrWhiteSpace(hookName) || fn == null) return; - Action handler = payload => - { - try { vm.Call(fn, PayloadToLua(vm, payload)); } - catch (Exception ex) { CrashLog.LogException($"lua:greg.events.on[{hookName}]", ex); } - }; - registeredEventHandlers.Add((hookName, handler)); - gregEventDispatcher.On(hookName, handler, modId); - }); - - eventsTable["on_update"] = (Action)(fn => { if (fn != null) updateHandlers.Add(fn); }); - eventsTable["on_gui"] = (Action)(fn => { if (fn != null) guiHandlers.Add(fn); }); - eventsTable["off"] = greg["off"]; - - // Expose handlers to the bridge for execution - greg["_internal_update"] = (Action)(dt => { - for (int i = 0; i < updateHandlers.Count; i++) { - try { vm.Call(updateHandlers[i], dt); } catch { } - } - }); - greg["_internal_gui"] = (Action)(() => { - for (int i = 0; i < guiHandlers.Count; i++) { - try { vm.Call(guiHandlers[i]); } catch { } - } - }); - - // greg.registry sub-table - var registryTable = new Table(vm); - greg["registry"] = registryTable; - registryTable["registerMod"] = (Action)(meta => - { - // Stub for mod registration - string id = meta["id"]?.ToString() ?? "unknown"; - _logger.Msg($"[lua] Registered mod: {id}"); - }); - - // greg.off(hookName) — removes all Lua handlers for this hook - greg["off"] = (Action)(hookName => - { - if (string.IsNullOrWhiteSpace(hookName)) return; - for (int i = registeredEventHandlers.Count - 1; i >= 0; i--) - { - var (name, handler) = registeredEventHandlers[i]; - if (string.Equals(name, hookName, StringComparison.Ordinal)) - { - gregEventDispatcher.Off(hookName, handler); - registeredEventHandlers.RemoveAt(i); - } - } - }); - - // greg.payload sub-table - var payloadTable = new Table(vm); - greg["payload"] = payloadTable; - payloadTable["get"] = (Func)((p, field, fallback) => - { - return gregPayload.Get(p, field, fallback); - }); - - // greg.framework sub-table - var frameworkTable = new MoonSharp.Interpreter.Table(vm); - greg["framework"] = frameworkTable; - frameworkTable["publish_tick"] = (Action)(tick => - { - float dt = tick["deltaTime"] is double d ? (float)d : 0f; - int frame = tick["frame"] is double f ? (int)f : 0; - global::greg.Exporter.ModFramework.Events.Publish(new global::greg.Exporter.ModTickEvent(dt, frame)); - }); - - // greg.emit(hookName, payload) - greg["emit"] = (Action)((hookName, payload) => - { - if (string.IsNullOrWhiteSpace(hookName)) return; - object nativePayload = payload?.Type == DataType.Table ? LuaTableToDict(payload.Table) : payload?.ToObject(); - gregEventDispatcher.Emit(hookName, nativePayload); - }); - - // greg.hook sub-table - var hook = new Table(vm); - greg["hook"] = hook; - - // greg.hook.before(hookName, fn) - hook["before"] = (Action)((hookName, fn) => - { - if (string.IsNullOrWhiteSpace(hookName) || fn == null) return; - Action handler = ctx => - { - try - { - var ctxTable = HookContextToLua(vm, ctx); - vm.Call(fn, ctxTable); - } - catch (Exception ex) { CrashLog.LogException($"lua:greg.hook.before[{hookName}]", ex); } - }; - registeredHookHandlers.Add((hookName, handler, true)); - HookBinder.OnBefore(hookName, handler); - }); - - // greg.hook.after(hookName, fn) - hook["after"] = (Action)((hookName, fn) => - { - if (string.IsNullOrWhiteSpace(hookName) || fn == null) return; - Action handler = ctx => - { - try - { - var ctxTable = HookContextToLua(vm, ctx); - vm.Call(fn, ctxTable); - } - catch (Exception ex) { CrashLog.LogException($"lua:greg.hook.after[{hookName}]", ex); } - }; - registeredHookHandlers.Add((hookName, handler, false)); - HookBinder.OnAfter(hookName, handler); - }); - - // greg.hook.off(hookName) - hook["off"] = (Action)(hookName => - { - if (string.IsNullOrWhiteSpace(hookName)) return; - HookBinder.Unregister(hookName); - registeredHookHandlers.RemoveAll(x => string.Equals(x.hookName, hookName, StringComparison.Ordinal)); - }); - } - - private static DynValue PayloadToLua(Script vm, object payload) - { - if (payload == null) - return DynValue.Nil; - - if (payload is string s) - return DynValue.NewString(s); - - if (payload is int i) - return DynValue.NewNumber(i); - - if (payload is double d) - return DynValue.NewNumber(d); - - if (payload is float f) - return DynValue.NewNumber(f); - - if (payload is bool b) - return DynValue.NewBoolean(b); - - // Anonymous types / rich payloads: flatten public properties to Lua table - var t = new Table(vm); - try - { - foreach (var prop in payload.GetType().GetProperties()) - { - try - { - var val = prop.GetValue(payload); - if (val == null) continue; - if (val is string sv) t[prop.Name] = sv; - else if (val is int iv) t[prop.Name] = iv; - else if (val is double dv) t[prop.Name] = dv; - else if (val is float fv) t[prop.Name] = (double)fv; - else if (val is bool bv) t[prop.Name] = bv; - else if (val is uint uv) t[prop.Name] = (double)uv; - else t[prop.Name] = val.ToString(); - } - catch { } - } - } - catch { } - - return DynValue.NewTable(t); - } - - private static Table HookContextToLua(Script vm, HookContext ctx) - { - var t = new Table(vm); - t["hook_name"] = ctx.HookName; - t["method_name"] = ctx.Method?.Name ?? ""; - t["type_name"] = ctx.Method?.DeclaringType?.FullName ?? ""; - t["has_instance"] = ctx.Instance != null; - t["arg_count"] = ctx.Arguments?.Length ?? 0; - if (ctx.Instance != null) - { - // Store a handle for the instance so Lua can use greg.unity on it - t["instance_handle"] = gregLuaObjectHandleRegistry.Register(ctx.Instance); - } - if (ctx.Arguments != null) - { - for (int i = 0; i < ctx.Arguments.Length; i++) - { - if (ctx.Arguments[i] != null) - t[$"arg{i}_handle"] = gregLuaObjectHandleRegistry.Register(ctx.Arguments[i]); - } - } - return t; - } - - private static Dictionary LuaTableToDict(Table table) - { - var dict = new Dictionary(); - foreach (var pair in table.Pairs) - { - string key = pair.Key.Type == DataType.String ? pair.Key.String : pair.Key.ToString(); - dict[key] = pair.Value.ToObject(); - } - return dict; - } -} - - - - - - - diff --git a/src/Scripting/Lua/LuaModules/gregInputLuaModule.cs b/src/Scripting/Lua/LuaModules/gregInputLuaModule.cs deleted file mode 100644 index 2963892d..00000000 --- a/src/Scripting/Lua/LuaModules/gregInputLuaModule.cs +++ /dev/null @@ -1,156 +0,0 @@ -using System; -using MoonSharp.Interpreter; - -namespace greg.Core.Scripting.Lua; - -/// -/// Keyboard/mouse input API for Lua: greg.input.*. -/// Uses Unity InputSystem (Keyboard.current, Mouse.current) via dynamic to avoid CI ref issues. -/// -public sealed class gregInputLuaModule : iGregLuaModule -{ - public void Register(Script vm, Table greg) - { - var input = new Table(vm); - greg["input"] = input; - - // greg.input.key_pressed(keyName) -> bool (was pressed THIS frame) - input["key_pressed"] = (Func)(keyName => - { - try - { - dynamic kb = GetKeyboard(); - if (kb == null) return false; - var key = FindKey(kb, keyName); - return key?.wasPressedThisFrame ?? false; - } - catch { return false; } - }); - - // greg.input.key_down(keyName) -> bool (is currently held) - input["key_down"] = (Func)(keyName => - { - try - { - dynamic kb = GetKeyboard(); - if (kb == null) return false; - var key = FindKey(kb, keyName); - return key?.isPressed ?? false; - } - catch { return false; } - }); - - // greg.input.ctrl() -> bool - input["ctrl"] = (Func)(() => - { - try - { - dynamic kb = GetKeyboard(); - if (kb == null) return false; - return kb.leftCtrlKey.isPressed || kb.rightCtrlKey.isPressed; - } - catch { return false; } - }); - - // greg.input.shift() -> bool - input["shift"] = (Func)(() => - { - try - { - dynamic kb = GetKeyboard(); - if (kb == null) return false; - return kb.leftShiftKey.isPressed || kb.rightShiftKey.isPressed; - } - catch { return false; } - }); - - // greg.input.alt() -> bool - input["alt"] = (Func)(() => - { - try - { - dynamic kb = GetKeyboard(); - if (kb == null) return false; - return kb.leftAltKey.isPressed || kb.rightAltKey.isPressed; - } - catch { return false; } - }); - } - - private static object GetKeyboard() - { - try - { - var type = Type.GetType("UnityEngine.InputSystem.Keyboard, Unity.InputSystem"); - if (type == null) return null; - var prop = type.GetProperty("current", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static); - return prop?.GetValue(null); - } - catch { return null; } - } - - private static dynamic FindKey(dynamic kb, string name) - { - if (string.IsNullOrEmpty(name)) return null; - var upper = name.ToUpperInvariant(); - return upper switch - { - "F1" => kb.f1Key, - "F2" => kb.f2Key, - "F3" => kb.f3Key, - "F4" => kb.f4Key, - "F5" => kb.f5Key, - "F6" => kb.f6Key, - "F7" => kb.f7Key, - "F8" => kb.f8Key, - "F9" => kb.f9Key, - "F10" => kb.f10Key, - "F11" => kb.f11Key, - "F12" => kb.f12Key, - "ESCAPE" or "ESC" => kb.escapeKey, - "SPACE" => kb.spaceKey, - "ENTER" or "RETURN" => kb.enterKey, - "TAB" => kb.tabKey, - "BACKSPACE" => kb.backspaceKey, - "DELETE" => kb.deleteKey, - "INSERT" => kb.insertKey, - "HOME" => kb.homeKey, - "END" => kb.endKey, - "PAGEUP" => kb.pageUpKey, - "PAGEDOWN" => kb.pageDownKey, - "UP" => kb.upArrowKey, - "DOWN" => kb.downArrowKey, - "LEFT" => kb.leftArrowKey, - "RIGHT" => kb.rightArrowKey, - _ => TryFindByCharacter(kb, upper) - }; - } - - private static dynamic TryFindByCharacter(dynamic kb, string upper) - { - try - { - if (upper.Length == 1) - { - char c = upper[0]; - if (c >= 'A' && c <= 'Z') - { - return c switch - { - 'A' => kb.aKey, 'B' => kb.bKey, 'C' => kb.cKey, 'D' => kb.dKey, - 'E' => kb.eKey, 'F' => kb.fKey, 'G' => kb.gKey, 'H' => kb.hKey, - 'I' => kb.iKey, 'J' => kb.jKey, 'K' => kb.kKey, 'L' => kb.lKey, - 'M' => kb.mKey, 'N' => kb.nKey, 'O' => kb.oKey, 'P' => kb.pKey, - 'Q' => kb.qKey, 'R' => kb.rKey, 'S' => kb.sKey, 'T' => kb.tKey, - 'U' => kb.uKey, 'V' => kb.vKey, 'W' => kb.wKey, 'X' => kb.xKey, - 'Y' => kb.yKey, 'Z' => kb.zKey, - _ => null - }; - } - } - } - catch { } - return null; - } -} - diff --git a/src/Scripting/Lua/LuaModules/gregIoLuaModule.cs b/src/Scripting/Lua/LuaModules/gregIoLuaModule.cs deleted file mode 100644 index ab8511ed..00000000 --- a/src/Scripting/Lua/LuaModules/gregIoLuaModule.cs +++ /dev/null @@ -1,120 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using MoonSharp.Interpreter; - -namespace greg.Core.Scripting.Lua; - -/// -/// File system API for Lua: greg.io.*. -/// Read/write files, check existence, list directories. -/// Paths are sandboxed — only game dir and userdata are accessible. -/// -public sealed class gregIoLuaModule : iGregLuaModule -{ - public void Register(Script vm, Table greg) - { - var io = new Table(vm); - greg["io"] = io; - - io["read_file"] = (Func)(path => - { - try { return File.Exists(path) ? File.ReadAllText(path) : null; } - catch { return null; } - }); - - io["read_lines"] = (Func)(path => - { - var t = new Table(vm); - try - { - if (!File.Exists(path)) return t; - var lines = File.ReadAllLines(path); - for (int i = 0; i < lines.Length; i++) - t.Append(DynValue.NewString(lines[i])); - } - catch { } - return t; - }); - - // Read first N lines from a file (useful for log scanning) - io["read_head"] = (Func)((path, maxLines) => - { - var t = new Table(vm); - try - { - if (!File.Exists(path)) return t; - using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - using var reader = new StreamReader(stream); - int count = 0; - while (!reader.EndOfStream && count < maxLines) - { - var line = reader.ReadLine(); - if (line != null) t.Append(DynValue.NewString(line)); - count++; - } - } - catch { } - return t; - }); - - io["write_file"] = (Action)((path, content) => - { - try - { - var dir = Path.GetDirectoryName(path); - if (!string.IsNullOrWhiteSpace(dir)) Directory.CreateDirectory(dir); - File.WriteAllText(path, content ?? ""); - } - catch { } - }); - - io["file_exists"] = (Func)(path => - { - try { return File.Exists(path); } - catch { return false; } - }); - - io["dir_exists"] = (Func)(path => - { - try { return Directory.Exists(path); } - catch { return false; } - }); - - io["list_files"] = (Func)((path, pattern, recursive) => - { - var t = new Table(vm); - try - { - if (!Directory.Exists(path)) return t; - var opt = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; - var files = Directory.GetFiles(path, pattern ?? "*.*", opt); - for (int i = 0; i < Math.Min(files.Length, 256); i++) - t.Append(DynValue.NewString(files[i])); - } - catch { } - return t; - }); - - io["path_combine"] = (Func)((a, b) => - { - try { return Path.Combine(a ?? "", b ?? ""); } - catch { return ""; } - }); - - io["path_filename"] = (Func)(path => - { - try { return Path.GetFileName(path); } - catch { return ""; } - }); - - io["path_ext"] = (Func)(path => - { - try { return Path.GetExtension(path)?.ToLowerInvariant() ?? ""; } - catch { return ""; } - }); - } -} - - - diff --git a/src/Scripting/Lua/LuaModules/gregLuaObjectHandleRegistry.cs b/src/Scripting/Lua/LuaModules/gregLuaObjectHandleRegistry.cs deleted file mode 100644 index 2b6d946f..00000000 --- a/src/Scripting/Lua/LuaModules/gregLuaObjectHandleRegistry.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; - -namespace greg.Core.Scripting.Lua; - -/// -/// Thread-safe registry mapping integer handles to live .NET/Il2Cpp objects. -/// Lua scripts use these handles to reference game objects without holding direct managed references. -/// Handles are periodically pruned when objects become null (destroyed Unity objects). -/// -internal static class gregLuaObjectHandleRegistry -{ - private static readonly Dictionary> Handles = new(); - private static int _nextHandle; - - public static int Register(object obj) - { - if (obj == null) return 0; - int handle = Interlocked.Increment(ref _nextHandle); - lock (Handles) { Handles[handle] = new WeakReference(obj); } - return handle; - } - - public static object Resolve(int handle) - { - if (handle <= 0) return null; - lock (Handles) - { - if (!Handles.TryGetValue(handle, out var wr)) return null; - if (wr.TryGetTarget(out var obj)) return obj; - Handles.Remove(handle); - return null; - } - } - - public static T Resolve(int handle) where T : class - { - return Resolve(handle) as T; - } - - public static void Release(int handle) - { - if (handle <= 0) return; - lock (Handles) { Handles.Remove(handle); } - } - - public static void Prune() - { - lock (Handles) - { - var dead = new List(); - foreach (var kv in Handles) - { - if (!kv.Value.TryGetTarget(out _)) - dead.Add(kv.Key); - } - for (int i = 0; i < dead.Count; i++) - Handles.Remove(dead[i]); - } - } - - public static void Clear() - { - lock (Handles) { Handles.Clear(); } - } -} - - - diff --git a/src/Scripting/Lua/LuaModules/gregSdkLuaModule.cs b/src/Scripting/Lua/LuaModules/gregSdkLuaModule.cs deleted file mode 100644 index 211fa498..00000000 --- a/src/Scripting/Lua/LuaModules/gregSdkLuaModule.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using MoonSharp.Interpreter; -using UnityEngine; -using greg.Sdk.Services; - -namespace greg.Core.Scripting.Lua.LuaModules; - -public sealed class gregSdkLuaModule : iGregLuaModule -{ - public void Register(Script vm, Table greg) - { - var sdk = new Table(vm); - greg["sdk"] = sdk; - - // --- Targeting Service --- - var targeting = new Table(vm); - sdk["targeting"] = targeting; - - targeting["get_target_info"] = (Func)(maxDist => { - var info = GregTargetingService.GetTargetInfo(maxDist); - var t = new Table(vm); - t["type"] = info.TargetType.ToString(); - t["name"] = info.Name; - t["distance"] = info.Distance; - t["hit_point"] = new Table(vm) { ["x"] = info.HitPoint.x, ["y"] = info.HitPoint.y, ["z"] = info.HitPoint.z }; - return t; - }); - - // --- Metadata Service --- - var metadata = new Table(vm); - sdk["metadata"] = metadata; - - metadata["get_metadata"] = (Func)(maxDist => { - var info = GregTargetingService.GetTargetInfo(maxDist); - var entries = GregComponentMetadataService.GetMetadata(info); - - var result = new Table(vm); - result["title"] = info.TargetType == GregTargetType.None ? "SCANNING..." : info.TargetType.ToString().ToUpper(); - result["sub_header"] = info.TargetType == GregTargetType.None ? "" : "TELEMETRY"; - - var list = new Table(vm); - for (int i = 0; i < entries.Count; i++) - { - var entry = entries[i]; - var et = new Table(vm); - et["label"] = entry.Label; - et["value"] = entry.Value; - et["color"] = $"#{ColorUtility.ToHtmlStringRGB(entry.ValueColor)}"; - list[i + 1] = et; - } - result["entries"] = list; - return result; - }); - - // --- HUD Service --- - var hud = new Table(vm); - sdk["hud"] = hud; - - hud["update_jade_box"] = (Action)((title, subHeader, entriesTable) => { - var entries = new List(); - foreach (var pair in entriesTable.Pairs) - { - var et = pair.Value.Table; - if (et == null) continue; - - string label = et.Get("label").String ?? ""; - string value = et.Get("value").String ?? ""; - string hexColor = et.Get("color").String ?? "#FFFFFF"; - - ColorUtility.TryParseHtmlString(hexColor, out var color); - entries.Add(new GregMetadataEntry(label, value, color)); - } - GregHudService.UpdateJadeBox(title, subHeader, entries); - }); - - hud["hide_jade_box"] = (Action)(() => GregHudService.HideJadeBox()); - - // --- UI Hijack Service (v12) --- - var ui = new Table(vm); - sdk["ui"] = ui; - - ui["hijack_canvas"] = (Action)((name, active) => { - var canvases = GameObject.FindObjectsOfType(true); - foreach (var c in canvases) - { - if (c.name == name) - { - c.gameObject.SetActive(active); - break; - } - } - }); - - ui["create_modern_canvas"] = (Func)((name, sorting) => { - var canvas = GregUiService.CreateCanvas(name, sorting); - return UserData.Create(canvas); - }); - } -} diff --git a/src/Scripting/Lua/LuaModules/gregUnityLuaModule.cs b/src/Scripting/Lua/LuaModules/gregUnityLuaModule.cs deleted file mode 100644 index 92990cb7..00000000 --- a/src/Scripting/Lua/LuaModules/gregUnityLuaModule.cs +++ /dev/null @@ -1,858 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Reflection; -using Il2Cpp; -using MoonSharp.Interpreter; -using UnityEngine; -using greg.Core; - -namespace greg.Core.Scripting.Lua; - -/// -/// Generic Unity/Il2Cpp API surface for Lua via integer handles. -/// All heavy Unity work stays in C#; Lua drives policy via handle IDs. -/// -public sealed class gregUnityLuaModule : iGregLuaModule -{ - public void Register(Script vm, Table greg) - { - var unity = new Table(vm); - greg["unity"] = unity; - RegisterFinders(vm, unity); - RegisterComponents(vm, unity); - RegisterProperties(vm, unity); - RegisterTransform(vm, unity); - RegisterInstantiate(vm, unity); - RegisterMaterial(vm, unity); - RegisterTmpro(vm, unity); - RegisterTextMesh(vm, unity); - RegisterPhysics(vm, unity); - RegisterGameObject(vm, unity); - - var color = new Table(vm); - greg["color"] = color; - RegisterColor(vm, color); - - var config = new Table(vm); - greg["config"] = config; - RegisterConfig(vm, config); - - var gui = new Table(vm); - greg["gui"] = gui; - RegisterGui(vm, gui); - - var hud = new Table(vm); - greg["hud"] = hud; - RegisterHud(vm, hud); - - var target = new Table(vm); - greg["target"] = target; - RegisterTarget(vm, target); - } - - private static void RegisterHud(Script vm, Table hud) - { - hud["begin_panel"] = (Action)((id, rect) => - { - float x = rect["x"] is double dx ? (float)dx : 0f; - float y = rect["y"] is double dy ? (float)dy : 0f; - float w = rect["w"] is double dw ? (float)dw : 200f; - float h = rect["h"] is double dh ? (float)dh : 100f; - gregGameHooks.GuiBeginPanel(id, x, y, w, h); - }); - - hud["label"] = (Action)(text => gregGameHooks.GuiLabel(text)); - hud["end_panel"] = (Action)(() => gregGameHooks.GuiEndPanel()); - } - - private static void RegisterTarget(Script vm, Table target) - { - target["raycast_forward"] = (Func)(distance => - { - var hit = gregGameHooks.RaycastForward((float)distance); - if (hit == null) return null; - - var t = new Table(vm); - t["name"] = hit.Value.Name; - t["distance"] = (double)hit.Value.Distance; - var pt = new Table(vm); - pt["x"] = (double)hit.Value.Point.x; - pt["y"] = (double)hit.Value.Point.y; - pt["z"] = (double)hit.Value.Point.z; - t["point"] = pt; - - if (hit.Value.Entity != null) - { - t["entity_handle"] = gregLuaObjectHandleRegistry.Register(hit.Value.Entity); - } - - return t; - }); - } - - private static void RegisterFinders(Script vm, Table unity) - { - // greg.unity.find(typeName) -> table of handles - unity["find"] = (Func)(typeName => - { - var result = new Table(vm); - try - { - var type = ResolveIl2CppType(typeName); - if (type == null) return result; - var objects = UnityEngine.Object.FindObjectsOfType(Il2CppInterop.Runtime.Il2CppType.From(type)); - if (objects == null) return result; - for (int i = 0; i < objects.Count; i++) - { - var obj = objects[i]; - if (obj != null) - result.Append(DynValue.NewNumber(gregLuaObjectHandleRegistry.Register(obj))); - } - } - catch (Exception ex) { CrashLog.LogException("lua:greg.unity.find", ex); } - return result; - }); - - // greg.unity.find_child(handle, childName) -> handle or 0 - unity["find_child"] = (Func)((handle, childName) => - { - try - { - var obj = gregLuaObjectHandleRegistry.Resolve(handle); - Transform t = null; - if (obj is GameObject go) t = go.transform; - else if (obj is Component c) t = c.transform; - if (t == null) return 0; - var child = t.Find(childName); - return child != null ? gregLuaObjectHandleRegistry.Register(child.gameObject) : 0; - } - catch { return 0; } - }); - - // greg.unity.handle_alive(handle) -> bool - unity["handle_alive"] = (Func)(handle => - { - var obj = gregLuaObjectHandleRegistry.Resolve(handle); - if (obj == null) return false; - if (obj is UnityEngine.Object uo) return uo != null; - return true; - }); - - // greg.unity.release(handle) - unity["release"] = (Action)(handle => gregLuaObjectHandleRegistry.Release(handle)); - } - - private static void RegisterComponents(Script vm, Table unity) - { - // greg.unity.get_component(handle, typeName) -> handle or 0 - unity["get_component"] = (Func)((handle, typeName) => - { - try - { - var obj = gregLuaObjectHandleRegistry.Resolve(handle); - GameObject go = null; - if (obj is GameObject g) go = g; - else if (obj is Component c) go = c.gameObject; - if (go == null) return 0; - - var type = ResolveIl2CppType(typeName); - if (type == null) return 0; - var comp = go.GetComponent(Il2CppInterop.Runtime.Il2CppType.From(type)); - return comp != null ? gregLuaObjectHandleRegistry.Register(comp) : 0; - } - catch { return 0; } - }); - - // greg.unity.add_component(handle, typeName) -> handle or 0 - unity["add_component"] = (Func)((handle, typeName) => - { - try - { - var obj = gregLuaObjectHandleRegistry.Resolve(handle); - GameObject go = null; - if (obj is GameObject g) go = g; - else if (obj is Component c) go = c.gameObject; - if (go == null) return 0; - - var type = ResolveIl2CppType(typeName); - if (type == null) return 0; - var comp = go.AddComponent(Il2CppInterop.Runtime.Il2CppType.From(type)); - return comp != null ? gregLuaObjectHandleRegistry.Register(comp) : 0; - } - catch { return 0; } - }); - } - - private static void RegisterProperties(Script vm, Table unity) - { - // greg.unity.get_string(handle, memberName) -> string or nil - unity["get_string"] = (Func)((handle, name) => - { - try - { - var obj = gregLuaObjectHandleRegistry.Resolve(handle); - if (obj == null) return null; - var val = GetMemberValue(obj, name); - return val?.ToString(); - } - catch { return null; } - }); - - // greg.unity.get_number(handle, memberName) -> number or 0 - unity["get_number"] = (Func)((handle, name) => - { - try - { - var obj = gregLuaObjectHandleRegistry.Resolve(handle); - if (obj == null) return 0; - var val = GetMemberValue(obj, name); - if (val is float f) return f; - if (val is double d) return d; - if (val is int i) return i; - if (val is uint u) return u; - return 0; - } - catch { return 0; } - }); - - // greg.unity.get_bool(handle, memberName) -> bool - unity["get_bool"] = (Func)((handle, name) => - { - try - { - var obj = gregLuaObjectHandleRegistry.Resolve(handle); - if (obj == null) return false; - var val = GetMemberValue(obj, name); - return val is true; - } - catch { return false; } - }); - - // greg.unity.set_string(handle, memberName, value) - unity["set_string"] = (Action)((handle, name, value) => - { - try - { - var obj = gregLuaObjectHandleRegistry.Resolve(handle); - if (obj != null) SetMemberValue(obj, name, value); - } - catch { } - }); - - // greg.unity.set_number(handle, memberName, value) - unity["set_number"] = (Action)((handle, name, value) => - { - try - { - var obj = gregLuaObjectHandleRegistry.Resolve(handle); - if (obj == null) return; - var member = FindMember(obj.GetType(), name); - if (member is FieldInfo fi) - { - if (fi.FieldType == typeof(float)) fi.SetValue(obj, (float)value); - else if (fi.FieldType == typeof(int)) fi.SetValue(obj, (int)value); - else fi.SetValue(obj, value); - } - else if (member is PropertyInfo pi && pi.CanWrite) - { - if (pi.PropertyType == typeof(float)) pi.SetValue(obj, (float)value); - else if (pi.PropertyType == typeof(int)) pi.SetValue(obj, (int)value); - else pi.SetValue(obj, value); - } - } - catch { } - }); - - // greg.unity.set_bool(handle, memberName, value) - unity["set_bool"] = (Action)((handle, name, value) => - { - try - { - var obj = gregLuaObjectHandleRegistry.Resolve(handle); - if (obj != null) SetMemberValue(obj, name, value); - } - catch { } - }); - - // greg.unity.get_handle(handle, memberName) -> handle to the referenced object - unity["get_handle"] = (Func)((handle, name) => - { - try - { - var obj = gregLuaObjectHandleRegistry.Resolve(handle); - if (obj == null) return 0; - var val = GetMemberValue(obj, name); - return val != null ? gregLuaObjectHandleRegistry.Register(val) : 0; - } - catch { return 0; } - }); - } - - private static void RegisterTransform(Script vm, Table unity) - { - // greg.unity.position(handle) -> x, y, z (as table {x, y, z}) - unity["position"] = (Func)(handle => - { - var t = new Table(vm); - try - { - var obj = gregLuaObjectHandleRegistry.Resolve(handle); - Transform tr = null; - if (obj is GameObject go) tr = go.transform; - else if (obj is Component c) tr = c.transform; - else if (obj is Transform tt) tr = tt; - if (tr != null) - { - t["x"] = (double)tr.position.x; - t["y"] = (double)tr.position.y; - t["z"] = (double)tr.position.z; - } - } - catch { } - return t; - }); - - // greg.unity.set_position(handle, x, y, z) - unity["set_position"] = (Action)((handle, x, y, z) => - { - try - { - var obj = gregLuaObjectHandleRegistry.Resolve(handle); - Transform tr = null; - if (obj is GameObject go) tr = go.transform; - else if (obj is Component c) tr = c.transform; - else if (obj is Transform tt) tr = tt; - if (tr != null) tr.position = new Vector3((float)x, (float)y, (float)z); - } - catch { } - }); - - // greg.unity.set_local_scale(handle, x, y, z) - unity["set_local_scale"] = (Action)((handle, x, y, z) => - { - try - { - var obj = gregLuaObjectHandleRegistry.Resolve(handle); - Transform tr = null; - if (obj is GameObject go) tr = go.transform; - else if (obj is Component c) tr = c.transform; - else if (obj is Transform tt) tr = tt; - if (tr != null) tr.localScale = new Vector3((float)x, (float)y, (float)z); - } - catch { } - }); - - // greg.unity.set_rotation(handle, x, y, z, w) - unity["set_rotation"] = (Action)((handle, x, y, z, w) => - { - try - { - var obj = gregLuaObjectHandleRegistry.Resolve(handle); - Transform tr = null; - if (obj is GameObject go) tr = go.transform; - else if (obj is Component c) tr = c.transform; - else if (obj is Transform tt) tr = tt; - if (tr != null) tr.rotation = new Quaternion((float)x, (float)y, (float)z, (float)w); - } - catch { } - }); - - // greg.unity.set_parent(handle, parentHandle) - unity["set_parent"] = (Action)((handle, parentHandle) => - { - try - { - var obj = gregLuaObjectHandleRegistry.Resolve(handle); - var parentObj = gregLuaObjectHandleRegistry.Resolve(parentHandle); - Transform child = null, parent = null; - if (obj is GameObject go) child = go.transform; - else if (obj is Component c) child = c.transform; - if (parentObj is GameObject pgo) parent = pgo.transform; - else if (parentObj is Component pc) parent = pc.transform; - else if (parentObj is Transform pt) parent = pt; - if (child != null) child.SetParent(parent, true); - } - catch { } - }); - } - - private static void RegisterInstantiate(Script vm, Table unity) - { - // greg.unity.instantiate(handle, parentHandle?) -> new handle - unity["instantiate"] = (Func)((handle, parentHandle) => - { - try - { - var obj = gregLuaObjectHandleRegistry.Resolve(handle); - if (obj is not UnityEngine.Object uo) return 0; - var parent = gregLuaObjectHandleRegistry.Resolve(parentHandle); - Transform parentT = null; - if (parent is GameObject pgo) parentT = pgo.transform; - else if (parent is Component pc) parentT = pc.transform; - else if (parent is Transform pt) parentT = pt; - var clone = parentT != null - ? UnityEngine.Object.Instantiate(uo, parentT) - : UnityEngine.Object.Instantiate(uo); - return clone != null ? gregLuaObjectHandleRegistry.Register(clone) : 0; - } - catch { return 0; } - }); - - // greg.unity.destroy(handle) - unity["destroy"] = (Action)(handle => - { - try - { - var obj = gregLuaObjectHandleRegistry.Resolve(handle); - if (obj is UnityEngine.Object uo) UnityEngine.Object.Destroy(uo); - gregLuaObjectHandleRegistry.Release(handle); - } - catch { } - }); - } - - private static void RegisterMaterial(Script vm, Table unity) - { - // greg.unity.material_hex(handle, propertyName) -> "#RRGGBB" or nil - unity["material_hex"] = (Func)((handle, prop) => - { - try - { - var obj = gregLuaObjectHandleRegistry.Resolve(handle); - Material mat = null; - if (obj is Material m) mat = m; - else if (obj is Renderer r) mat = r.sharedMaterial; - else if (obj is Component c) - { - var rend = c.GetComponent(); - if (rend != null) mat = rend.sharedMaterial; - } - if (mat == null || !mat.HasProperty(prop)) return null; - var col = mat.GetColor(prop); - return ColorToHex(col); - } - catch { return null; } - }); - } - - private static void RegisterTmpro(Script vm, Table unity) - { - // greg.unity.tmpro_set(handle, text, fontSize, fontMin, fontMax, autoSize, wordWrap, alignment, colorHex) - unity["tmpro_set"] = (Action)( - (handle, text, fontSize, fontMin, fontMax, autoSize, wordWrap, alignment, colorHex) => - { - try - { - var obj = gregLuaObjectHandleRegistry.Resolve(handle); - TextMeshProUGUI tmp = null; - if (obj is TextMeshProUGUI t) tmp = t; - else if (obj is GameObject go) tmp = go.GetComponent(); - else if (obj is Component c) tmp = c.GetComponent(); - if (tmp == null) return; - - if (text != null) tmp.text = text; - if (fontSize > 0) tmp.fontSize = (float)fontSize; - tmp.fontSizeMin = (float)fontMin; - tmp.fontSizeMax = (float)fontMax; - tmp.enableAutoSizing = autoSize; - tmp.enableWordWrapping = wordWrap; - tmp.alignment = (TextAlignmentOptions)alignment; - if (!string.IsNullOrEmpty(colorHex) && ColorUtility.TryParseHtmlString(colorHex, out var col)) - tmp.color = col; - } - catch { } - }); - - // greg.unity.tmpro_get_text(handle) -> string - unity["tmpro_get_text"] = (Func)(handle => - { - try - { - var obj = gregLuaObjectHandleRegistry.Resolve(handle); - TextMeshProUGUI tmp = null; - if (obj is TextMeshProUGUI t) tmp = t; - else if (obj is GameObject go) tmp = go.GetComponent(); - else if (obj is Component c) tmp = c.GetComponent(); - return tmp?.text; - } - catch { return null; } - }); - - // greg.unity.tmpro_anchored_pos(handle) -> {x, y} - unity["tmpro_anchored_pos"] = (Func)(handle => - { - var t = new Table(vm); - try - { - var obj = gregLuaObjectHandleRegistry.Resolve(handle); - TextMeshProUGUI tmp = null; - if (obj is TextMeshProUGUI tt) tmp = tt; - else if (obj is GameObject go) tmp = go.GetComponent(); - else if (obj is Component c) tmp = c.GetComponent(); - if (tmp?.rectTransform != null) - { - t["x"] = (double)tmp.rectTransform.anchoredPosition.x; - t["y"] = (double)tmp.rectTransform.anchoredPosition.y; - } - } - catch { } - return t; - }); - - // greg.unity.tmpro_set_anchored_pos(handle, x, y) - unity["tmpro_set_anchored_pos"] = (Action)((handle, x, y) => - { - try - { - var obj = gregLuaObjectHandleRegistry.Resolve(handle); - TextMeshProUGUI tmp = null; - if (obj is TextMeshProUGUI t) tmp = t; - else if (obj is GameObject go) tmp = go.GetComponent(); - else if (obj is Component c) tmp = c.GetComponent(); - if (tmp?.rectTransform != null) - tmp.rectTransform.anchoredPosition = new Vector2((float)x, (float)y); - } - catch { } - }); - } - - private static void RegisterTextMesh(Script vm, Table unity) - { - // greg.unity.textmesh_set(handle, text, fontSize, charSize, colorHex, anchor, alignment) - unity["textmesh_set"] = (Action)( - (handle, text, fontSize, charSize, colorHex, anchor, alignment) => - { - try - { - var obj = gregLuaObjectHandleRegistry.Resolve(handle); - TextMesh tm = null; - if (obj is TextMesh t) tm = t; - else if (obj is GameObject go) tm = go.GetComponent(); - else if (obj is Component c) tm = c.GetComponent(); - if (tm == null) return; - - if (text != null) tm.text = text; - if (fontSize > 0) tm.fontSize = fontSize; - if (charSize > 0) tm.characterSize = (float)charSize; - tm.anchor = (TextAnchor)anchor; - tm.alignment = (TextAlignment)alignment; - if (!string.IsNullOrEmpty(colorHex) && ColorUtility.TryParseHtmlString(colorHex, out var col)) - tm.color = col; - } - catch { } - }); - } - - private static void RegisterPhysics(Script vm, Table unity) - { - // greg.unity.raycast(ox, oy, oz, dx, dy, dz, maxDist) -> { hit, handle, point = {x,y,z} } - unity["raycast"] = (Func)( - (ox, oy, oz, dx, dy, dz, maxDist) => - { - var t = new Table(vm); - t["hit"] = false; - try - { - var origin = new Vector3((float)ox, (float)oy, (float)oz); - var direction = new Vector3((float)dx, (float)dy, (float)dz); - if (Physics.Raycast(new Ray(origin, direction), out var hitInfo, (float)maxDist)) - { - t["hit"] = true; - t["handle"] = gregLuaObjectHandleRegistry.Register(hitInfo.collider.gameObject); - var pt = new Table(vm); - pt["x"] = (double)hitInfo.point.x; - pt["y"] = (double)hitInfo.point.y; - pt["z"] = (double)hitInfo.point.z; - t["point"] = pt; - } - } - catch { } - return t; - }); - - // greg.unity.camera_ray() -> { ox, oy, oz, dx, dy, dz } from gregMain camera - unity["camera_ray"] = (Func
)(() => - { - var t = new Table(vm); - try - { - var cam = Camera.main; - if (cam != null) - { - var pos = cam.transform.position; - var fwd = cam.transform.forward; - t["ox"] = (double)pos.x; t["oy"] = (double)pos.y; t["oz"] = (double)pos.z; - t["dx"] = (double)fwd.x; t["dy"] = (double)fwd.y; t["dz"] = (double)fwd.z; - } - } - catch { } - return t; - }); - } - - private static void RegisterGameObject(Script vm, Table unity) - { - // greg.unity.create_gameobject(name, parentHandle) -> handle - unity["create_gameobject"] = (Func)((name, parentHandle) => - { - try - { - var go = new GameObject(name); - var parent = gregLuaObjectHandleRegistry.Resolve(parentHandle); - Transform pt = null; - if (parent is GameObject pgo) pt = pgo.transform; - else if (parent is Component pc) pt = pc.transform; - if (pt != null) go.transform.SetParent(pt, true); - return gregLuaObjectHandleRegistry.Register(go); - } - catch { return 0; } - }); - - // greg.unity.set_name(handle, name) - unity["set_name"] = (Action)((handle, name) => - { - try - { - var obj = gregLuaObjectHandleRegistry.Resolve(handle); - if (obj is GameObject go) go.name = name; - else if (obj is Component c) c.gameObject.name = name; - } - catch { } - }); - - // greg.unity.get_name(handle) -> string - unity["get_name"] = (Func)(handle => - { - try - { - var obj = gregLuaObjectHandleRegistry.Resolve(handle); - if (obj is GameObject go) return go.name; - if (obj is Component c) return c.gameObject?.name; - return null; - } - catch { return null; } - }); - - // greg.unity.get_parent_component(handle, typeName) -> handle - unity["get_parent_component"] = (Func)((handle, typeName) => - { - try - { - var obj = gregLuaObjectHandleRegistry.Resolve(handle); - Component src = null; - if (obj is GameObject go) src = go.transform; - else if (obj is Component c) src = c; - if (src == null) return 0; - - var type = ResolveIl2CppType(typeName); - if (type == null) return 0; - var comp = src.GetComponentInParent(Il2CppInterop.Runtime.Il2CppType.From(type)); - return comp != null ? gregLuaObjectHandleRegistry.Register(comp) : 0; - } - catch { return 0; } - }); - - // greg.unity.get_children_components(handle, typeName) -> table of handles - unity["get_children_components"] = (Func)((handle, typeName) => - { - var result = new Table(vm); - try - { - var obj = gregLuaObjectHandleRegistry.Resolve(handle); - Component src = null; - if (obj is GameObject go) src = go.transform; - else if (obj is Component c) src = c; - if (src == null) return result; - - var type = ResolveIl2CppType(typeName); - if (type == null) return result; - var comps = src.GetComponentsInChildren(Il2CppInterop.Runtime.Il2CppType.From(type), true); - if (comps == null) return result; - for (int i = 0; i < comps.Count; i++) - { - if (comps[i] != null) - result.Append(DynValue.NewNumber(gregLuaObjectHandleRegistry.Register(comps[i]))); - } - } - catch { } - return result; - }); - } - - private static void RegisterColor(Script vm, Table color) - { - // greg.color.to_hex(r, g, b) -> "#RRGGBB" - color["to_hex"] = (Func)((r, g, b) => - ColorToHex(new Color((float)r, (float)g, (float)b))); - - // greg.color.normalize_hex(raw) -> "#RRGGBB" or nil - color["normalize_hex"] = (Func)(raw => - { - if (string.IsNullOrWhiteSpace(raw)) return null; - var s = raw.Trim(); - if (!s.StartsWith("#")) s = "#" + s; - if (ColorUtility.TryParseHtmlString(s, out var c)) - return ColorToHex(c); - return null; - }); - - // greg.color.parse(hex) -> {r, g, b} - color["parse"] = (Func)(hex => - { - var t = new Table(vm); - if (!string.IsNullOrEmpty(hex)) - { - var s = hex.Trim(); - if (!s.StartsWith("#")) s = "#" + s; - if (ColorUtility.TryParseHtmlString(s, out var c)) - { - t["r"] = (double)c.r; t["g"] = (double)c.g; t["b"] = (double)c.b; - } - } - return t; - }); - } - - private static void RegisterConfig(Script vm, Table config) - { - // greg.config.load(path) -> table of {key = value} - config["load"] = (Func)(path => - { - var t = new Table(vm); - try - { - if (!File.Exists(path)) return t; - foreach (var line in File.ReadAllLines(path)) - { - if (string.IsNullOrWhiteSpace(line)) continue; - var trimmed = line.Trim(); - if (trimmed.StartsWith("#")) continue; - var eq = trimmed.IndexOf('='); - if (eq <= 0 || eq >= trimmed.Length - 1) continue; - var key = trimmed.Substring(0, eq).Trim(); - var val = trimmed.Substring(eq + 1).Trim(); - if (float.TryParse(val, NumberStyles.Float, CultureInfo.InvariantCulture, out var num)) - t[key] = (double)num; - else - t[key] = val; - } - } - catch { } - return t; - }); - - // greg.config.save(path, table) - config["save"] = (Action)((path, data) => - { - try - { - var dir = Path.GetDirectoryName(path); - if (!string.IsNullOrWhiteSpace(dir)) Directory.CreateDirectory(dir); - var lines = new List(); - foreach (var pair in data.Pairs) - { - var key = pair.Key.CastToString(); - var val = pair.Value.Type == DataType.Number - ? pair.Value.Number.ToString(CultureInfo.InvariantCulture) - : pair.Value.CastToString(); - lines.Add($"{key}={val}"); - } - File.WriteAllLines(path, lines); - } - catch { } - }); - - // greg.config.userdata_path() -> string - config["userdata_path"] = (Func)(() => - MelonLoader.Utils.MelonEnvironment.UserDataDirectory); - } - - private static void RegisterGui(Script vm, Table gui) - { - // greg.gui functions are only valid inside on_gui callbacks. - // The LuaLanguageBridge calls on_gui Lua functions inside Unity's OnGUI. - - gui["box"] = (Action)((x, y, w, h, title) => - GUI.Box(new Rect((float)x, (float)y, (float)w, (float)h), title ?? "")); - - gui["label"] = (Action)((x, y, w, h, text) => - GUI.Label(new Rect((float)x, (float)y, (float)w, (float)h), text ?? "")); - - gui["button"] = (Func)((x, y, w, h, text) => - GUI.Button(new Rect((float)x, (float)y, (float)w, (float)h), text ?? "")); - - gui["toggle"] = (Func)((x, y, w, h, value, text) => - GUI.Toggle(new Rect((float)x, (float)y, (float)w, (float)h), value, text ?? "")); - - gui["screen_width"] = (Func)(() => Screen.width); - gui["screen_height"] = (Func)(() => Screen.height); - } - - // --- helpers --- - - private static string ColorToHex(Color c) - { - int r = Mathf.Clamp(Mathf.RoundToInt(c.r * 255f), 0, 255); - int g = Mathf.Clamp(Mathf.RoundToInt(c.g * 255f), 0, 255); - int b = Mathf.Clamp(Mathf.RoundToInt(c.b * 255f), 0, 255); - return $"#{r:X2}{g:X2}{b:X2}"; - } - - private static Type ResolveIl2CppType(string typeName) - { - if (string.IsNullOrWhiteSpace(typeName)) return null; - - // Try Il2Cpp namespace first - var t = Type.GetType($"Il2Cpp.{typeName}, Assembly-CSharp"); - if (t != null) return t; - - // Try Il2CppTMPro - t = Type.GetType($"Il2CppTMPro.{typeName}, Il2CppTMPro"); - if (t != null) return t; - - // Try UnityEngine - t = Type.GetType($"UnityEngine.{typeName}, UnityEngine.CoreModule"); - if (t != null) return t; - - // Try fully qualified - foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) - { - t = asm.GetType(typeName); - if (t != null) return t; - } - return null; - } - - private static object GetMemberValue(object obj, string name) - { - if (obj == null || string.IsNullOrEmpty(name)) return null; - var member = FindMember(obj.GetType(), name); - if (member is FieldInfo fi) return fi.GetValue(obj); - if (member is PropertyInfo pi && pi.GetIndexParameters().Length == 0) return pi.GetValue(obj); - return null; - } - - private static void SetMemberValue(object obj, string name, object value) - { - if (obj == null || string.IsNullOrEmpty(name)) return; - var member = FindMember(obj.GetType(), name); - if (member is FieldInfo fi) fi.SetValue(obj, value); - else if (member is PropertyInfo pi && pi.CanWrite) pi.SetValue(obj, value); - } - - private static MemberInfo FindMember(Type type, string name) - { - var fi = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); - if (fi != null) return fi; - var pi = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); - return pi; - } -} - - - - - - diff --git a/src/Scripting/Rust/RustLanguageBridgeAdapter.cs b/src/Scripting/Rust/RustLanguageBridgeAdapter.cs deleted file mode 100644 index 0b5ad2a1..00000000 --- a/src/Scripting/Rust/RustLanguageBridgeAdapter.cs +++ /dev/null @@ -1,126 +0,0 @@ -using System; -using System.Collections.Generic; -using MelonLoader; -using greg.Core; - -namespace greg.Core.Scripting.Rust; - -/// -/// Adapter that represents native Rust/C mod support inside the shared language host. -/// Delegates all lifecycle calls to . -/// -public sealed class RustLanguageBridgeAdapter : iGregLanguageBridge -{ - private static readonly string[] Extensions = { ".dll", ".greg", ".gregr", ".gregl", ".gregp" }; - - private readonly MelonLogger.Instance _logger; - private readonly string _modsPath; - private readonly gregFfiBridge _ffiBridge; - - public RustLanguageBridgeAdapter(MelonLogger.Instance logger, string modsPath, gregFfiBridge gregFfiBridge) - { - _logger = logger; - _modsPath = modsPath; - _ffiBridge = gregFfiBridge; - } - - public string LanguageName => "rust/native"; - - public IReadOnlyList ScriptExtensions => Extensions; - - public void Initialize() - { - _logger.Msg($"[gregCore] Rust/native bridge initialized, mods path: {_modsPath}"); - CrashLog.Log("RustLanguageBridgeAdapter: Initialize"); - } - - public int LoadScripts() - { - try - { - _ffiBridge.LoadAllMods(); - var units = _ffiBridge.GetLoadedRuntimeUnits(); - _logger.Msg($"[gregCore] Rust/native bridge loaded {units.Count} mod(s)."); - return units.Count; - } - catch (Exception ex) - { - _logger.Error($"[gregCore] Rust/native bridge LoadAllMods failed: {ex.Message}"); - CrashLog.LogException("RustLanguageBridgeAdapter.LoadScripts", ex); - return 0; - } - } - - public IReadOnlyList GetRuntimeUnits() - { - return _ffiBridge.GetLoadedRuntimeUnits(); - } - - public bool SetUnitEnabled(string unitId, bool enabled) - { - if (string.IsNullOrWhiteSpace(unitId) || !unitId.StartsWith("native:", StringComparison.OrdinalIgnoreCase)) - return false; - - _ffiBridge.SetModEnabled(unitId, enabled); - return true; - } - - public int ReloadEnabledUnits() - { - return _ffiBridge.ReloadAllMods(); - } - - public void OnSceneLoaded(string sceneName) - { - try - { - _ffiBridge.OnSceneLoaded(sceneName); - } - catch (Exception ex) - { - _logger.Error($"[gregCore] Rust/native bridge OnSceneLoaded failed: {ex.Message}"); - CrashLog.LogException("RustLanguageBridgeAdapter.OnSceneLoaded", ex); - } - } - - public void OnUpdate(float deltaTime) - { - try - { - _ffiBridge.OnUpdate(deltaTime); - } - catch (Exception ex) - { - CrashLog.LogException("RustLanguageBridgeAdapter.OnUpdate", ex); - } - } - - public void OnGui() - { - try - { - _ffiBridge.OnGui(); - } - catch (Exception ex) - { - CrashLog.LogException("RustLanguageBridgeAdapter.OnGui", ex); - } - } - - public void Shutdown() - { - try - { - _logger.Msg("[gregCore] Shutting down Rust/native bridge..."); - _ffiBridge.Shutdown(); - } - catch (Exception ex) - { - _logger.Error($"[gregCore] Rust/native bridge Shutdown failed: {ex.Message}"); - CrashLog.LogException("RustLanguageBridgeAdapter.Shutdown", ex); - } - } -} - - - diff --git a/src/Scripting/gregLanguageBridgeHost.cs b/src/Scripting/gregLanguageBridgeHost.cs deleted file mode 100644 index 74ebe9bd..00000000 --- a/src/Scripting/gregLanguageBridgeHost.cs +++ /dev/null @@ -1,250 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using MelonLoader; -using MelonLoader.Utils; -using greg.Core; - -namespace greg.Core.Scripting; - -/// -/// gregCoreLoader SDK runtime host that orchestrates language bridges and keeps failures isolated. -/// -public sealed class gregLanguageBridgeHost -{ - private readonly MelonLogger.Instance _logger; - private readonly List _bridges = new(); - - public Lua.LuaLanguageBridge LuaBridge { get; } - - public gregLanguageBridgeHost(MelonLogger.Instance logger, string rustModsPath, greg.Core.gregFfiBridge gregFfiBridge) - { - _logger = logger; - - string scriptRoot = Path.Combine(MelonEnvironment.ModsDirectory, "ScriptMods"); - string luaRoot = Path.Combine(scriptRoot, "lua"); - string jsTsRoot = Path.Combine(scriptRoot, "js"); - string goRoot = Path.Combine(scriptRoot, "go"); - - LuaBridge = TryCreateBridge( - bridgeDisplayName: "Lua", - factory: () => new Lua.LuaLanguageBridge(_logger, luaRoot)) as Lua.LuaLanguageBridge; - - if (LuaBridge != null) - { - _bridges.Add(LuaBridge); - } - - TryAddBridge( - bridgeDisplayName: "TS/JS", - factory: () => new JS.TypeScriptJavaScriptLanguageBridge(_logger, jsTsRoot)); - - TryAddBridge( - bridgeDisplayName: "Rust/Native", - factory: () => new Rust.RustLanguageBridgeAdapter(_logger, rustModsPath, gregFfiBridge)); - - TryAddBridge( - bridgeDisplayName: "Go (WASM)", - factory: () => new Go.GoLanguageBridge(_logger, goRoot)); - } - - private void TryAddBridge(string bridgeDisplayName, Func factory) - { - iGregLanguageBridge bridge = TryCreateBridge(bridgeDisplayName, factory); - if (bridge != null) - { - _bridges.Add(bridge); - } - } - - private iGregLanguageBridge TryCreateBridge(string bridgeDisplayName, Func factory) - { - try - { - return factory(); - } - catch (Exception exception) when ( - exception is FileNotFoundException || - exception is FileLoadException || - exception is DllNotFoundException || - exception is BadImageFormatException) - { - _logger.Warning($"gregCore bridge '{bridgeDisplayName}' disabled due to missing/invalid dependency: {exception.Message}"); - CrashLog.LogException($"gregLanguageBridgeHost.Create.{bridgeDisplayName}", exception); - return null; - } - catch (Exception exception) - { - _logger.Warning($"gregCore bridge '{bridgeDisplayName}' disabled due to initialization error: {exception.Message}"); - CrashLog.LogException($"gregLanguageBridgeHost.Create.{bridgeDisplayName}", exception); - return null; - } - } - - public void Initialize() - { - for (int index = 0; index < _bridges.Count; index++) - { - iGregLanguageBridge bridge = _bridges[index]; - try - { - bridge.Initialize(); - } - catch (Exception exception) - { - CrashLog.LogException($"gregLanguageBridgeHost.Initialize.{bridge.LanguageName}", exception); - } - } - } - - public int LoadAll() - { - int total = 0; - for (int index = 0; index < _bridges.Count; index++) - { - iGregLanguageBridge bridge = _bridges[index]; - try - { - total += bridge.LoadScripts(); - } - catch (Exception exception) - { - CrashLog.LogException($"gregLanguageBridgeHost.LoadAll.{bridge.LanguageName}", exception); - } - } - - _logger.Msg($"gregCore language host initialized bridges={_bridges.Count}, loadedUnits={total}."); - return total; - } - - public void OnSceneLoaded(string sceneName) - { - for (int index = 0; index < _bridges.Count; index++) - { - iGregLanguageBridge bridge = _bridges[index]; - try - { - bridge.OnSceneLoaded(sceneName); - } - catch (Exception exception) - { - CrashLog.LogException($"gregLanguageBridgeHost.OnSceneLoaded.{bridge.LanguageName}", exception); - } - } - } - - public void OnUpdate(float deltaTime) - { - for (int index = 0; index < _bridges.Count; index++) - { - iGregLanguageBridge bridge = _bridges[index]; - try - { - bridge.OnUpdate(deltaTime); - } - catch (Exception exception) - { - CrashLog.LogException($"gregLanguageBridgeHost.OnUpdate.{bridge.LanguageName}", exception); - } - } - } - - public void OnGui() - { - for (int index = 0; index < _bridges.Count; index++) - { - iGregLanguageBridge bridge = _bridges[index]; - try - { - bridge.OnGui(); - } - catch (Exception exception) - { - CrashLog.LogException($"gregLanguageBridgeHost.OnGui.{bridge.LanguageName}", exception); - } - } - } - - public IReadOnlyList GetRuntimeUnits() - { - var units = new List(); - for (int index = 0; index < _bridges.Count; index++) - { - iGregLanguageBridge bridge = _bridges[index]; - try - { - var bridgeUnits = bridge.GetRuntimeUnits(); - for (int unitIndex = 0; unitIndex < bridgeUnits.Count; unitIndex++) - { - units.Add(bridgeUnits[unitIndex]); - } - } - catch (Exception exception) - { - CrashLog.LogException($"gregLanguageBridgeHost.GetRuntimeUnits.{bridge.LanguageName}", exception); - } - } - - return units; - } - - public bool SetUnitEnabled(string unitId, bool enabled) - { - for (int index = 0; index < _bridges.Count; index++) - { - iGregLanguageBridge bridge = _bridges[index]; - try - { - if (bridge.SetUnitEnabled(unitId, enabled)) - { - return true; - } - } - catch (Exception exception) - { - CrashLog.LogException($"gregLanguageBridgeHost.SetUnitEnabled.{bridge.LanguageName}", exception); - } - } - - return false; - } - - public int ReloadHotloadableUnits() - { - int total = 0; - for (int index = 0; index < _bridges.Count; index++) - { - iGregLanguageBridge bridge = _bridges[index]; - try - { - total += bridge.ReloadEnabledUnits(); - } - catch (Exception exception) - { - CrashLog.LogException($"gregLanguageBridgeHost.ReloadEnabledUnits.{bridge.LanguageName}", exception); - } - } - - return total; - } - - public void Shutdown() - { - for (int index = 0; index < _bridges.Count; index++) - { - iGregLanguageBridge bridge = _bridges[index]; - try - { - bridge.Shutdown(); - } - catch (Exception exception) - { - CrashLog.LogException($"gregLanguageBridgeHost.Shutdown.{bridge.LanguageName}", exception); - } - } - } -} - - - - diff --git a/src/Scripting/gregRuntimeUnit.cs b/src/Scripting/gregRuntimeUnit.cs deleted file mode 100644 index 1c044cc6..00000000 --- a/src/Scripting/gregRuntimeUnit.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace greg.Core.Scripting; - -public sealed class gregRuntimeUnit -{ - public string Id; - public string DisplayName; - public string Language; - public bool Enabled; - public bool SupportsHotReload; - public bool IsNativeModule; -} - diff --git a/src/Tests/Core/DependencyResolverTests.cs b/src/Tests/Core/DependencyResolverTests.cs deleted file mode 100644 index 7c7ce303..00000000 --- a/src/Tests/Core/DependencyResolverTests.cs +++ /dev/null @@ -1,32 +0,0 @@ -/// -/// Schicht: Tests -/// Zweck: Tests für den GregDependencyResolver. -/// Maintainer: Testet lineare, zyklische und fehlende Abhängigkeiten. -/// - -using Xunit; -using FluentAssertions; -using gregCore.Infrastructure.Plugins; -using gregCore.Core.Models; -using gregCore.Core.Exceptions; - -namespace gregCore.Tests.Core; - -public class DependencyResolverTests -{ - [Fact] - public void Resolve_WithLinearDependencies_ShouldReturnCorrectOrder() - { - var resolver = new GregDependencyResolver(); - var plugins = new List - { - new() { Manifest = new ModManifest { Id = "C", Dependencies = new[] { "B" } } }, - new() { Manifest = new ModManifest { Id = "A", Dependencies = Array.Empty() } }, - new() { Manifest = new ModManifest { Id = "B", Dependencies = new[] { "A" } } } - }; - - var result = resolver.Resolve(plugins); - - result.Should().NotBeEmpty(); - } -} diff --git a/src/Tests/Events/GregEventBusTests.cs b/src/Tests/Events/GregEventBusTests.cs deleted file mode 100644 index 1e2e9c60..00000000 --- a/src/Tests/Events/GregEventBusTests.cs +++ /dev/null @@ -1,53 +0,0 @@ -/// -/// Schicht: Tests -/// Zweck: Tests für den GregEventBus. -/// Maintainer: Stellt Thread-Safety und Funktionalität sicher. -/// - -using Xunit; -using FluentAssertions; -using gregCore.Core.Events; -using gregCore.Core.Models; -using gregCore.Tests.Mocks; - -namespace gregCore.Tests.Events; - -public class GregEventBusTests -{ - [Fact] - public void SubscribeAndPublish_ShouldInvokeHandler() - { - var bus = new GregEventBus(new MockLogger()); - var invoked = false; - - bus.Subscribe("test.hook", p => invoked = true); - bus.Publish("test.hook", new EventPayload()); - - invoked.Should().BeTrue(); - } - - [Fact] - public void Unsubscribe_ShouldNotInvokeHandler() - { - var bus = new GregEventBus(new MockLogger()); - var invoked = false; - Action handler = p => invoked = true; - - bus.Subscribe("test.hook", handler); - bus.Unsubscribe("test.hook", handler); - bus.Publish("test.hook", new EventPayload()); - - invoked.Should().BeFalse(); - } - - [Fact] - public void CancelableEvent_ShouldReturnFalseWhenCancelled() - { - var bus = new GregEventBus(new MockLogger()); - - bus.Subscribe("test.hook", p => p.IsCancelled = true); - var result = bus.Publish("test.hook", new EventPayload { IsCancelable = true }); - - result.Should().BeFalse(); - } -} diff --git a/src/Tests/Mocks/MockLogger.cs b/src/Tests/Mocks/MockLogger.cs deleted file mode 100644 index 2ae9f62b..00000000 --- a/src/Tests/Mocks/MockLogger.cs +++ /dev/null @@ -1,23 +0,0 @@ -/// -/// Schicht: Tests -/// Zweck: Mock-Logger für Unit-Tests. -/// Maintainer: Nur in Tests verwenden. -/// - -namespace gregCore.Tests.Mocks; - -public class MockLogger : IGregLogger -{ - public enum LogLevel { Debug, Info, Warning, Error } - public List<(LogLevel Level, string Message)> Logs { get; } = new(); - - public void Debug(string message) => Logs.Add((LogLevel.Debug, message)); - public void Info(string message) => Logs.Add((LogLevel.Info, message)); - public void Warning(string message) => Logs.Add((LogLevel.Warning, message)); - public void Error(string message, Exception? ex = null) => Logs.Add((LogLevel.Error, $"{message} {ex?.Message}")); - - public IGregLogger ForContext(string context) => this; - - public bool AssertLogged(LogLevel level, string partialMessage) => - Logs.Any(l => l.Level == level && l.Message.Contains(partialMessage)); -} diff --git a/src/Tests/gregCore.Tests.csproj b/src/Tests/gregCore.Tests.csproj deleted file mode 100644 index d7851541..00000000 --- a/src/Tests/gregCore.Tests.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - net8.0 - enable - enable - false - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - diff --git a/src/UI/Components/GregBadge.cs b/src/UI/Components/GregBadge.cs deleted file mode 100644 index c94d8dbc..00000000 --- a/src/UI/Components/GregBadge.cs +++ /dev/null @@ -1,38 +0,0 @@ -using UnityEngine; -using UnityEngine.UI; - -namespace greg.Core.UI.Components; - -public class GregBadge : IGregUIComponent -{ - public string Text { get; set; } = "Badge"; - public Color? Color { get; set; } - - public GameObject Build(Transform parent) - { - var go = new GameObject($"Badge_{Text}"); - go.transform.SetParent(parent, false); - - var rect = go.AddComponent(); - rect.sizeDelta = new Vector2(60, 20); - - var img = go.AddComponent(); - img.color = Color ?? GregUITheme.PrimaryContainer; - - var textGO = new GameObject("Text"); - textGO.transform.SetParent(go.transform, false); - var text = textGO.AddComponent(); - text.text = Text; - text.fontSize = 10; - text.color = GregUITheme.OnPrimary; - text.alignment = TextAlignmentOptions.Center; - - var textRect = textGO.GetComponent(); - textRect.anchorMin = Vector2.zero; - textRect.anchorMax = Vector2.one; - textRect.sizeDelta = Vector2.zero; - - return go; - } -} - diff --git a/src/UI/Components/GregButton.cs b/src/UI/Components/GregButton.cs deleted file mode 100644 index 32de8332..00000000 --- a/src/UI/Components/GregButton.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using UnityEngine; -using UnityEngine.UI; - -namespace greg.Core.UI.Components; - -public enum GregButtonStyle { Primary, Secondary, Tertiary, Danger } - -public class GregButton : IGregUIComponent -{ - public string Label { get; set; } = "Button"; - public Action OnClick { get; set; } - public GregButtonStyle Style { get; set; } = GregButtonStyle.Primary; - - public GameObject Build(Transform parent) - { - var go = new GameObject($"Button_{Label}"); - go.transform.SetParent(parent, false); - - var rect = go.AddComponent(); - rect.sizeDelta = new Vector2(0, 32); - - var img = go.AddComponent(); - img.color = Style switch - { - GregButtonStyle.Primary => GregUITheme.Primary, - GregButtonStyle.Secondary => GregUITheme.Secondary, - GregButtonStyle.Tertiary => GregUITheme.Tertiary, - GregButtonStyle.Danger => GregUITheme.Error, - _ => GregUITheme.Primary - }; - - var btn = go.AddComponent