├── .config └── dotnet-tools.json ├── .editorconfig ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug.yml │ └── config.yml ├── renovate.json └── workflows │ ├── codeql.yml │ ├── format-json.yml │ ├── formatter.yml │ ├── main.yml │ └── update-resource.yml ├── .gitignore ├── .gitmodules ├── README.md ├── analysers ├── CommandAnalyser.cs ├── CommandCodeFix.cs ├── ModifierAnalyser.cs ├── PatchAnalyser.cs ├── analysers.csproj ├── nuget.config └── packages.lock.json ├── biome.json ├── launch-dev.bat ├── launch.bat ├── lc-hax ├── Scripts │ ├── Attributes │ │ ├── CommandAttribute.cs │ │ ├── DebugCommandAttribute.cs │ │ ├── PrivilegedCommandAttribute.cs │ │ └── RequiredNamedArgsAttribute.cs │ ├── Commands │ │ ├── BerserkCommand.cs │ │ ├── BetaCommand.cs │ │ ├── BlockCommand.cs │ │ ├── Bomb │ │ │ ├── BombCommand.cs │ │ │ └── BombardCommand.cs │ │ ├── ClearCommand.cs │ │ ├── Debug │ │ │ ├── DebugCommand.cs │ │ │ ├── EnemiesCommand.cs │ │ │ ├── FixCameraCommand.cs │ │ │ ├── ItemsCommand.cs │ │ │ ├── LevelsCommand.cs │ │ │ ├── MuteCommand.cs │ │ │ ├── ScrapsCommand.cs │ │ │ └── UnlockablesCommand.cs │ │ ├── DestroyCommand.cs │ │ ├── Door │ │ │ ├── LockCommand.cs │ │ │ └── UnlockCommand.cs │ │ ├── EndCommand.cs │ │ ├── Entrance │ │ │ ├── EnterCommand.cs │ │ │ └── ExitCommand.cs │ │ ├── ExperienceCommand.cs │ │ ├── ExplodeCommand.cs │ │ ├── FakeDeathCommand.cs │ │ ├── FatalityCommand.cs │ │ ├── GarageCommand.cs │ │ ├── GodCommand.cs │ │ ├── GrabCommand.cs │ │ ├── Hate │ │ │ ├── FallCommand.cs │ │ │ ├── HateCommand.cs │ │ │ ├── LagCommand.cs │ │ │ └── MobCommand.cs │ │ ├── HealCommand.cs │ │ ├── HearCommand.cs │ │ ├── ICommand.cs │ │ ├── InvisibleCommand.cs │ │ ├── KillClickCommand.cs │ │ ├── KillCommand.cs │ │ ├── LightCommand.cs │ │ ├── LobbyCommand.cs │ │ ├── LocationCommand.cs │ │ ├── MaskCommand.cs │ │ ├── NoClipCommand.cs │ │ ├── NoiseCommand.cs │ │ ├── PlayersCommand.cs │ │ ├── PoisonCommand.cs │ │ ├── PrefixCommand.cs │ │ ├── Privileged │ │ │ ├── CreditCommand.cs │ │ │ ├── EjectCommand.cs │ │ │ ├── GodsCommand.cs │ │ │ ├── LandCommand.cs │ │ │ ├── PrivilegedCommand.cs │ │ │ ├── QuotaCommand.cs │ │ │ ├── ReviveCommand.cs │ │ │ ├── SpawnCommand.cs │ │ │ └── TimescaleCommand.cs │ │ ├── RapidCommand.cs │ │ ├── SayCommand.cs │ │ ├── SellCommand.cs │ │ ├── Ship │ │ │ ├── CloseCommand.cs │ │ │ └── OpenCommand.cs │ │ ├── ShovelCommand.cs │ │ ├── SpinCommand.cs │ │ ├── StartCommand.cs │ │ ├── StunCommand.cs │ │ ├── StunOnClickCommand.cs │ │ ├── TeleportCommand.cs │ │ ├── TranslateCommand.cs │ │ ├── UnlimitedJumpCommand.cs │ │ ├── Unlockable │ │ │ ├── BuildCommand.cs │ │ │ ├── BuyCommand.cs │ │ │ ├── HomeCommand.cs │ │ │ ├── HornCommand.cs │ │ │ ├── RandomCommand.cs │ │ │ ├── SignalCommand.cs │ │ │ └── SuitCommand.cs │ │ ├── UprightCommand.cs │ │ ├── VisitCommand.cs │ │ └── VoidCommand.cs │ ├── Components │ │ ├── AsyncBehaviour.cs │ │ ├── CharacterMovement.cs │ │ ├── KeyboardMovement.cs │ │ ├── MousePan.cs │ │ ├── TransientBehaviour.cs │ │ ├── WaitForBehaviour.cs │ │ └── WaitForGameEndBehaviour.cs │ ├── Core │ │ ├── GameListener.cs │ │ ├── HaxObjects.cs │ │ ├── InputListener.cs │ │ └── ScreenListener.cs │ ├── Enums │ │ └── Unlockable.cs │ ├── Extensions │ │ ├── Copy.cs │ │ ├── First.cs │ │ ├── ForEach.cs │ │ ├── FuzzyMatch.cs │ │ ├── GetValue.cs │ │ ├── Range.cs │ │ ├── StartResilientCoroutine.cs │ │ ├── ToTitleCase.cs │ │ ├── TryParse.cs │ │ ├── Unfake.cs │ │ └── WhereIsNotNull.cs │ ├── Features │ │ └── Translator.cs │ ├── Helpers │ │ ├── Camera.cs │ │ ├── CreateComponent.cs │ │ ├── Enemies.cs │ │ ├── FindObjects.cs │ │ ├── GUI.cs │ │ ├── Grabbables.cs │ │ ├── Landmine.cs │ │ ├── Managers.cs │ │ ├── Map.cs │ │ ├── Notification.cs │ │ ├── PlaceObject.cs │ │ ├── Players.cs │ │ ├── Screen.cs │ │ ├── ShortDelay.cs │ │ ├── SphereCast.cs │ │ ├── Teleporter.cs │ │ ├── Try.cs │ │ ├── Unlockable.cs │ │ └── WaitUntil.cs │ ├── Loader.cs │ ├── Mixins │ │ ├── IEnemyPrompter.cs │ │ ├── IJetpack.cs │ │ ├── ISecureDoor.cs │ │ ├── IShipDoor.cs │ │ ├── IStun.cs │ │ └── ITeleporter.cs │ ├── Modules │ │ ├── AntiKickMod.cs │ │ ├── ChatMod.cs │ │ ├── ClearVisionMod.cs │ │ ├── CrosshairMod.cs │ │ ├── DisconnectMod.cs │ │ ├── ESPMod.cs │ │ ├── FollowMod.cs │ │ ├── InstantInteractMod.cs │ │ ├── KillClickMod.cs │ │ ├── MinimalGUIMod.cs │ │ ├── PhantomMod.cs │ │ ├── Possession │ │ │ ├── Controllers │ │ │ │ ├── BaboonHawkController.cs │ │ │ │ ├── BrackenController.cs │ │ │ │ ├── CircuitBeesController.cs │ │ │ │ ├── CoilHeadController.cs │ │ │ │ ├── CrawlerController.cs │ │ │ │ ├── EarthLeviathanController.cs │ │ │ │ ├── EyelessDogController.cs │ │ │ │ ├── ForestGiantController.cs │ │ │ │ ├── HoardingBugController.cs │ │ │ │ ├── HygrodereController.cs │ │ │ │ ├── IEnemyController.cs │ │ │ │ ├── JesterEnemyController.cs │ │ │ │ ├── LassoManController.cs │ │ │ │ ├── MaskedController.cs │ │ │ │ ├── NutcrackerController.cs │ │ │ │ ├── SandSpiderController.cs │ │ │ │ ├── SnareFleaController.cs │ │ │ │ ├── SporeLizardController.cs │ │ │ │ └── TestEnemyController.cs │ │ │ ├── Possession.cs │ │ │ └── PossessionMod.cs │ │ ├── SaneMod.cs │ │ ├── StaminaMod.cs │ │ ├── StunClickMod.cs │ │ ├── TriggerMod.cs │ │ └── WeightMod.cs │ ├── Patches │ │ ├── AntiFlashPatch.cs │ │ ├── AntiKickPatch.cs │ │ ├── CommandPatch.cs │ │ ├── DeathNotificationPatch.cs │ │ ├── Dependencies │ │ │ ├── EnemyDependencyPatch.cs │ │ │ ├── GrabbableDependencyPatch.cs │ │ │ ├── LevelDependencyPatch.cs │ │ │ ├── LobbyDependencyPatch.cs │ │ │ └── MenuDependencyPatch.cs │ │ ├── DepthOfFieldPatch.cs │ │ ├── EnemyPatch.cs │ │ ├── FakeDeathPatch.cs │ │ ├── Fixes │ │ │ ├── HoarderBugAIFixPatch.cs │ │ │ └── WaitForShipFixPatch.cs │ │ ├── GiftBoxPatch.cs │ │ ├── GodModePatch.cs │ │ ├── GrabInLobbyPatch.cs │ │ ├── HUDPatch.cs │ │ ├── HearPatch.cs │ │ ├── InfiniteAmmoPatch.cs │ │ ├── InfiniteBatteryPatch.cs │ │ ├── InfiniteDepositPatch.cs │ │ ├── InfiniteGrabPatch.cs │ │ ├── InfiniteItemUsagePatch.cs │ │ ├── InfiniteScanRangePatch.cs │ │ ├── InvisiblePatch.cs │ │ ├── JebPatch.cs │ │ ├── LobbyPatch.cs │ │ ├── LookDownPatch.cs │ │ ├── NoCooldownPatch.cs │ │ ├── OneHandedItemPatch.cs │ │ ├── PossessionPatch.cs │ │ ├── RadarPatch.cs │ │ ├── ShotgunPatch.cs │ │ ├── ShovelPatch.cs │ │ ├── TeleportWithItemsPatch.cs │ │ ├── UnconstrainedBuildPatch.cs │ │ ├── UnlimitedJumpPatch.cs │ │ ├── UntargetablePatch.cs │ │ └── ZapGunPatch.cs │ ├── Static │ │ ├── Chat.cs │ │ ├── CodeAnalysis.cs │ │ ├── CompilerServices.cs │ │ ├── Logger.cs │ │ ├── Setting.cs │ │ └── State.cs │ ├── Structures │ │ ├── ConnectedLobby.cs │ │ ├── ObjectPlacement.cs │ │ ├── RendererPair.cs │ │ ├── Result.cs │ │ ├── Size.cs │ │ └── StringArray.cs │ └── Utils │ │ ├── MultiObjectPool.cs │ │ ├── Reflector.cs │ │ └── SingleObjectPool..cs ├── lc-hax.csproj ├── nuget.config └── packages.lock.json ├── omnisharp.json ├── package.json ├── requirements.bat └── scripts └── UpdateEmbeddedResource.csx /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "dotnet-script": { 6 | "version": "1.6.0", 7 | "commands": ["dotnet-script"], 8 | "rollForward": false 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [winstxnhdw] 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug report 3 | title: "[Bug]: " 4 | labels: ["bug", "triage"] 5 | assignees: winstxnhdw 6 | body: 7 | - type: textarea 8 | id: what-happened 9 | attributes: 10 | label: What happened? 11 | description: Also tell us, what did you expect to happen? 12 | validations: 13 | required: true 14 | 15 | - type: input 16 | id: commit-hash 17 | attributes: 18 | label: Current Commit Hash 19 | description: Enter the current commit hash. You can find this by running `git rev-parse HEAD` in your terminal. 20 | placeholder: a1b2c3d4 21 | validations: 22 | required: true 23 | 24 | - type: checkboxes 25 | id: injector-check 26 | attributes: 27 | label: Injector 28 | description: Please specify the injector you are using. 29 | options: 30 | - label: SharpMonoInjectorCore 31 | - label: Others 32 | 33 | - type: input 34 | id: other-injector 35 | attributes: 36 | label: If you selected "Others" above, please specify the injector you are using. 37 | placeholder: BepinEx, etc 38 | validations: 39 | required: false 40 | 41 | - type: textarea 42 | id: logs 43 | attributes: 44 | label: Log output 45 | description: Log files (if any) can be found in `steamapps\common\Lethal Company` 46 | 47 | - type: checkboxes 48 | id: duplicate-check 49 | attributes: 50 | label: Acknowledgement 51 | options: 52 | - label: I did not have any other mods other than `lc-hax` installed when the issue occurred 53 | required: true 54 | - label: I have confirmed that my anti-virus is not blocking any of the relevant programs 55 | required: true 56 | - label: I have done my due diligence to check for similar issues 57 | required: true 58 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Question 4 | about: Ask a question in GitHub Discussions. 5 | url: https://github.com/winstxnhdw/lc-hax/discussions 6 | - name: Feature Request 7 | about: To suggest an idea or ask about a feature, please start with a question saying what you would like to achieve. There might be a way to do it already. 8 | url: https://github.com/winstxnhdw/lc-hax/discussions 9 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["github>winstxnhdw/renovate"], 3 | "packageRules": [ 4 | { 5 | "matchPackageNames": ["LethalCompany.GameLibs.Steam"], 6 | "ignoreUnstable": false 7 | } 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: CodeQL 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | schedule: 9 | - cron: '24 23 * * 0' 10 | 11 | env: 12 | NUGET_PACKAGES: ${{ github.workspace }}\.nuget\packages 13 | 14 | permissions: 15 | security-events: write 16 | actions: read 17 | contents: read 18 | 19 | jobs: 20 | analyze: 21 | runs-on: windows-latest 22 | 23 | steps: 24 | - name: Checkout repository 25 | uses: actions/checkout@v5.0.0 26 | with: 27 | show-progress: false 28 | 29 | - name: Initialise CodeQL 30 | uses: github/codeql-action/init@v4.31.2 31 | with: 32 | languages: csharp 33 | 34 | - name: Setup .NET 35 | uses: actions/setup-dotnet@v5.0.0 36 | with: 37 | dotnet-version: 9.x 38 | cache: true 39 | cache-dependency-path: lc-hax\packages.lock.json 40 | 41 | - name: Build 42 | run: dotnet build lc-hax -restoreProperty:RestoreLockedMode=true 43 | 44 | - name: Perform CodeQL Analysis 45 | uses: github/codeql-action/analyze@v4.31.2 46 | with: 47 | category: /language:csharp 48 | -------------------------------------------------------------------------------- /.github/workflows/format-json.yml: -------------------------------------------------------------------------------- 1 | name: Format JSON 2 | 3 | on: 4 | pull_request: 5 | push: 6 | paths-ignore: ['**/packages.lock.json'] 7 | paths: 8 | - .github/workflows/format-json.yml 9 | - '**/*.json' 10 | 11 | concurrency: Format-JSON-${{ github.ref }} 12 | permissions: 13 | contents: write 14 | 15 | jobs: 16 | format-json: 17 | uses: winstxnhdw/actions/.github/workflows/format-biome.yml@main 18 | -------------------------------------------------------------------------------- /.github/workflows/formatter.yml: -------------------------------------------------------------------------------- 1 | name: Format 2 | 3 | on: 4 | pull_request: 5 | push: 6 | paths: 7 | - .github/workflows/formatter.yml 8 | - .editorconfig 9 | - lc-hax/Scripts/**/*.cs 10 | 11 | concurrency: ${{ github.workflow }}-${{ github.ref }} 12 | permissions: 13 | contents: write 14 | 15 | jobs: 16 | format: 17 | runs-on: windows-latest 18 | 19 | steps: 20 | - name: Checkout repository 21 | uses: actions/checkout@v5.0.0 22 | with: 23 | show-progress: false 24 | repository: ${{ github.event.pull_request.head.repo.full_name }} 25 | ref: ${{ github.head_ref }} 26 | 27 | - name: Setup .NET 28 | uses: actions/setup-dotnet@v5.0.0 29 | with: 30 | dotnet-version: 9.x 31 | 32 | - name: Format scripts 33 | run: | 34 | dotnet format lc-hax 35 | dotnet format analysers 36 | 37 | - name: Set Git config 38 | run: | 39 | git config user.email github-actions[bot]@users.noreply.github.com 40 | git config user.name github-actions[bot] 41 | 42 | - name: Commit changes 43 | run: | 44 | git commit -am "style: format scripts" || true 45 | git pull --autostash --rebase origin ${{ github.head_ref || github.ref_name }} 46 | git push 47 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | pull_request: 5 | push: 6 | paths: 7 | - .github/workflows/main.yml 8 | - .editorconfig 9 | - lc-hax/** 10 | 11 | env: 12 | NUGET_PACKAGES: ${{ github.workspace }}\.nuget\packages 13 | 14 | jobs: 15 | build: 16 | runs-on: windows-latest 17 | 18 | steps: 19 | - name: Checkout repository 20 | uses: actions/checkout@v5.0.0 21 | with: 22 | show-progress: false 23 | 24 | - name: Setup .NET 25 | uses: actions/setup-dotnet@v5.0.0 26 | with: 27 | dotnet-version: 9.x 28 | cache: true 29 | cache-dependency-path: lc-hax\packages.lock.json 30 | 31 | - name: Build 32 | run: dotnet build lc-hax -restoreProperty:RestoreLockedMode=true /warnaserror 33 | -------------------------------------------------------------------------------- /.github/workflows/update-resource.yml: -------------------------------------------------------------------------------- 1 | name: Update Resource(s) 2 | 3 | on: 4 | pull_request: 5 | push: 6 | paths: 7 | - .github/workflows/update-resource.yml 8 | - lc-hax/*.csproj 9 | 10 | concurrency: Format-Resource-${{ github.ref }} 11 | permissions: 12 | contents: write 13 | 14 | jobs: 15 | update-resource: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Checkout repository 20 | uses: actions/checkout@v5.0.0 21 | with: 22 | show-progress: false 23 | repository: ${{ github.event.pull_request.head.repo.full_name }} 24 | ref: ${{ github.head_ref }} 25 | token: ${{ secrets.PAT }} 26 | 27 | - name: Cache .NET tools 28 | uses: actions/cache@v4.3.0 29 | with: 30 | path: ~/.nuget/packages 31 | key: dotnet-tools-${{ runner.os }}-${{ hashFiles('.config/dotnet-tools.json') }} 32 | restore-keys: dotnet-tools-${{ runner.os }}- 33 | 34 | - name: Setup .NET 35 | uses: actions/setup-dotnet@v5.0.0 36 | with: 37 | dotnet-version: 9.x 38 | 39 | - name: Restore tools 40 | run: dotnet tool restore 41 | 42 | - name: Update Resource 43 | run: dotnet script scripts/UpdateEmbeddedResource.csx 44 | env: 45 | CSPROJ_PATH: lc-hax/lc-hax.csproj 46 | 47 | - name: Set Git config 48 | run: | 49 | git config user.email github-actions[bot]@users.noreply.github.com 50 | git config user.name github-actions[bot] 51 | 52 | - name: Commit changes 53 | run: | 54 | git commit -am "build: update resource path(s)" || true 55 | git pull --autostash --rebase origin ${{ github.head_ref || github.ref_name }} 56 | git push 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | obj/ 3 | .vs/ 4 | .vscode/ 5 | .mono/ 6 | .cr/ 7 | *.sln 8 | node_modules/ 9 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "submodules/SharpMonoInjectorCore"] 2 | path = submodules/SharpMonoInjectorCore 3 | url = https://github.com/winstxnhdw/SharpMonoInjectorCore.git 4 | shallow = true 5 | -------------------------------------------------------------------------------- /analysers/CommandAnalyser.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.Linq; 3 | using Microsoft.CodeAnalysis; 4 | using Microsoft.CodeAnalysis.CSharp; 5 | using Microsoft.CodeAnalysis.CSharp.Syntax; 6 | using Microsoft.CodeAnalysis.Diagnostics; 7 | 8 | [DiagnosticAnalyzer(LanguageNames.CSharp)] 9 | sealed class CommandAnalyzer : DiagnosticAnalyzer { 10 | internal const string DiagnosticID = "HAX001"; 11 | 12 | static DiagnosticDescriptor Rule { get; } = new( 13 | DiagnosticID, 14 | "Implement ICommand Interface", 15 | "Command '{0}' must implement ICommand", 16 | "Usage", 17 | DiagnosticSeverity.Warning, 18 | isEnabledByDefault: true 19 | ); 20 | 21 | public override ImmutableArray SupportedDiagnostics => [CommandAnalyzer.Rule]; 22 | 23 | public override void Initialize(AnalysisContext context) { 24 | context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); 25 | context.EnableConcurrentExecution(); 26 | context.RegisterSyntaxNodeAction(AnalyseSyntaxNode, SyntaxKind.ClassDeclaration); 27 | } 28 | 29 | static void AnalyseSyntaxNode(SyntaxNodeAnalysisContext context) { 30 | if (context.Node is not ClassDeclarationSyntax classDeclaration) return; 31 | if (context.SemanticModel.GetDeclaredSymbol(classDeclaration, context.CancellationToken) is not INamedTypeSymbol namedTypeSymbol) return; 32 | if (!namedTypeSymbol.Name.EndsWith("Command")) return; 33 | if (ImplementsICommand(namedTypeSymbol)) return; 34 | 35 | context.ReportDiagnostic( 36 | Diagnostic.Create(CommandAnalyzer.Rule, classDeclaration.Identifier.GetLocation(), namedTypeSymbol.Name) 37 | ); 38 | } 39 | 40 | static bool ImplementsICommand(INamedTypeSymbol classSymbol) => classSymbol.AllInterfaces.Any(i => i.Name is "ICommand"); 41 | } 42 | -------------------------------------------------------------------------------- /analysers/CommandCodeFix.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading; 3 | using System.Composition; 4 | using System.Threading.Tasks; 5 | using System.Collections.Immutable; 6 | using Microsoft.CodeAnalysis; 7 | using Microsoft.CodeAnalysis.Text; 8 | using Microsoft.CodeAnalysis.CSharp; 9 | using Microsoft.CodeAnalysis.CodeFixes; 10 | using Microsoft.CodeAnalysis.CodeActions; 11 | using Microsoft.CodeAnalysis.CSharp.Syntax; 12 | 13 | [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(CommandCodeFix)), Shared] 14 | public class CommandCodeFix : CodeFixProvider { 15 | public override ImmutableArray FixableDiagnosticIds => [CommandAnalyzer.DiagnosticID]; 16 | 17 | public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; 18 | 19 | public override async Task RegisterCodeFixesAsync(CodeFixContext context) { 20 | Diagnostic diagnostic = context.Diagnostics.First(); 21 | TextSpan diagnosticSpan = diagnostic.Location.SourceSpan; 22 | 23 | CodeAction codeAction = CodeAction.Create( 24 | "Implement ICommand interface", 25 | cancellationToken => this.ImplementICommandAsync(context.Document, diagnosticSpan, cancellationToken), 26 | nameof(CommandCodeFix) 27 | ); 28 | 29 | context.RegisterCodeFix(codeAction, diagnostic); 30 | 31 | await Task.CompletedTask; 32 | } 33 | 34 | async Task ImplementICommandAsync(Document document, TextSpan diagnosticSpan, CancellationToken cancellationToken) { 35 | SyntaxNode? root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); 36 | 37 | if (root?.FindToken(diagnosticSpan.Start).Parent?.AncestorsAndSelf().OfType().First() is not ClassDeclarationSyntax classDeclaration) { 38 | return document; 39 | } 40 | 41 | BaseListSyntax baseList = classDeclaration?.BaseList ?? SyntaxFactory.BaseList(); 42 | BaseListSyntax newBaseList = baseList.AddTypes(SyntaxFactory.SimpleBaseType(SyntaxFactory.ParseTypeName("ICommand"))); 43 | 44 | return classDeclaration?.WithBaseList(newBaseList) is not ClassDeclarationSyntax newClassDeclaration 45 | ? document 46 | : document.WithSyntaxRoot(root.ReplaceNode(classDeclaration, newClassDeclaration)); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /analysers/ModifierAnalyser.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using Microsoft.CodeAnalysis; 3 | using Microsoft.CodeAnalysis.CSharp; 4 | using Microsoft.CodeAnalysis.CSharp.Syntax; 5 | using Microsoft.CodeAnalysis.Diagnostics; 6 | 7 | [DiagnosticAnalyzer(LanguageNames.CSharp)] 8 | public class ModifierAnalyser : DiagnosticAnalyzer { 9 | public const string DiagnosticID = "HAX003"; 10 | 11 | static DiagnosticDescriptor Rule { get; } = new( 12 | DiagnosticID, 13 | "Private modifier is explicit", 14 | "The `private` modifier is redundant and can be removed.", 15 | "Style", 16 | DiagnosticSeverity.Warning, 17 | isEnabledByDefault: true 18 | ); 19 | 20 | public override ImmutableArray SupportedDiagnostics => [Rule]; 21 | 22 | public override void Initialize(AnalysisContext context) { 23 | context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); 24 | context.EnableConcurrentExecution(); 25 | context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.MethodDeclaration, SyntaxKind.FieldDeclaration, SyntaxKind.PropertyDeclaration); 26 | } 27 | 28 | static void AnalyzeNode(SyntaxNodeAnalysisContext context) { 29 | if (context.Node as MemberDeclarationSyntax is not { Modifiers: SyntaxTokenList modifiers }) return; 30 | 31 | foreach (SyntaxToken modifier in modifiers) { 32 | if (!modifier.IsKind(SyntaxKind.PrivateKeyword)) continue; 33 | 34 | Diagnostic diagnostic = Diagnostic.Create(Rule, modifier.GetLocation()); 35 | context.ReportDiagnostic(diagnostic); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /analysers/PatchAnalyser.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.Linq; 3 | using Microsoft.CodeAnalysis; 4 | using Microsoft.CodeAnalysis.CSharp; 5 | using Microsoft.CodeAnalysis.CSharp.Syntax; 6 | using Microsoft.CodeAnalysis.Diagnostics; 7 | 8 | [DiagnosticAnalyzer(LanguageNames.CSharp)] 9 | sealed class PatchAnalyser : DiagnosticAnalyzer { 10 | internal const string DiagnosticID = "HAX002"; 11 | 12 | static DiagnosticDescriptor Rule { get; } = new( 13 | DiagnosticID, 14 | "Patch instance requires double underscore prefix", 15 | "Instance parameter of Patch method '{0}' must be named '__instance'", 16 | "Usage", 17 | DiagnosticSeverity.Error, 18 | isEnabledByDefault: true 19 | ); 20 | 21 | public override ImmutableArray SupportedDiagnostics => [PatchAnalyser.Rule]; 22 | 23 | public override void Initialize(AnalysisContext context) { 24 | context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); 25 | context.EnableConcurrentExecution(); 26 | context.RegisterSyntaxNodeAction(AnalyseSyntaxNode, SyntaxKind.MethodDeclaration); 27 | } 28 | 29 | static void AnalyseSyntaxNode(SyntaxNodeAnalysisContext context) { 30 | if (context.Node is not MethodDeclarationSyntax methodDeclaration) return; 31 | if (context.SemanticModel.GetDeclaredSymbol(methodDeclaration, context.CancellationToken) is not IMethodSymbol methodSymbol) return; 32 | if (!methodSymbol.ContainingType.Name.EndsWith("Patch")) return; 33 | if (!methodSymbol.Parameters.Any(parameter => parameter.Name is "instance")) return; 34 | 35 | context.ReportDiagnostic( 36 | Diagnostic.Create(PatchAnalyser.Rule, methodDeclaration.Identifier.GetLocation(), methodSymbol.Name) 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /analysers/analysers.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | enable 5 | preview 6 | netstandard2.0 7 | true 8 | true 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /analysers/nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/2.0.4/schema.json", 3 | "vcs": { 4 | "enabled": true, 5 | "clientKind": "git", 6 | "useIgnoreFile": true 7 | }, 8 | "formatter": { 9 | "indentStyle": "space", 10 | "lineWidth": 120 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /launch-dev.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | set project_name=lc-hax 4 | 5 | :begin 6 | dotnet build %project_name% -c Release -restoreProperty:RestoreLockedMode=true 7 | start /wait /b ./submodules/SharpMonoInjectorCore/dist/SharpMonoInjector.exe inject -p "Lethal Company" -a bin/%project_name%.dll -n Hax -c Loader -m Load 8 | 9 | pause 10 | goto begin 11 | -------------------------------------------------------------------------------- /launch.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | git pull --rebase --autostash 4 | call launch-dev.bat 5 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Attributes/CommandAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | [AttributeUsage(AttributeTargets.Class)] 4 | sealed class CommandAttribute(string syntax) : Attribute { 5 | internal string Syntax { get; } = syntax; 6 | } 7 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Attributes/DebugCommandAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | [AttributeUsage(AttributeTargets.Class)] 4 | sealed class DebugCommandAttribute(string syntax) : Attribute { 5 | internal string Syntax { get; } = syntax; 6 | } 7 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Attributes/PrivilegedCommandAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | [AttributeUsage(AttributeTargets.Class)] 4 | sealed class PrivilegedCommandAttribute(string syntax) : Attribute { 5 | internal string Syntax { get; } = syntax; 6 | } 7 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Attributes/RequiredNamedArgsAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | [AttributeUsage(AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Struct)] 4 | sealed class RequireNamedArgsAttribute : Attribute { } 5 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/BerserkCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | [Command("berserk")] 5 | sealed class BerserkCommand : ICommand { 6 | public async Task Execute(Arguments args, CancellationToken cancellationToken) => 7 | Helper.FindObjects() 8 | .ForEach(turret => turret.EnterBerserkModeServerRpc(-1)); 9 | } 10 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/BetaCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | [Command("beta")] 5 | sealed class BetaCommand : ICommand { 6 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 7 | bool playedDuringBeta = ES3.Load("playedDuringBeta", "LCGeneralSaveData", true); 8 | ES3.Save("playedDuringBeta", !playedDuringBeta, "LCGeneralSaveData"); 9 | Chat.Print($"Beta badge: {(!playedDuringBeta ? "obtained" : "removed")}"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/BlockCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | [Command("block")] 5 | sealed class BlockCommand : ICommand { 6 | static string BlockEnemy() { 7 | Setting.EnableUntargetable = !Setting.EnableUntargetable; 8 | 9 | return $"{(Setting.EnableUntargetable 10 | ? "Enemies will no longer target you!" 11 | : "Enemies can now target you!")}"; 12 | } 13 | 14 | static string BlockRadar() { 15 | Setting.EnableBlockRadar = !Setting.EnableBlockRadar; 16 | 17 | return $"{(Setting.EnableBlockRadar 18 | ? "Blocking radar targets!" 19 | : "No longer blocking radar targets!")}"; 20 | } 21 | 22 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 23 | if (args.Length is 0) { 24 | Chat.Print("Usage: block "); 25 | return; 26 | } 27 | 28 | string result = args[0] switch { 29 | "enemy" => BlockCommand.BlockEnemy(), 30 | "radar" => BlockCommand.BlockRadar(), 31 | _ => "Invalid property!" 32 | }; 33 | 34 | Chat.Print(result); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/Bomb/BombCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using GameNetcodeStuff; 4 | 5 | [Command("bomb")] 6 | sealed class BombCommand : ICommand, IJetpack { 7 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 8 | if (Helper.LocalPlayer is not PlayerControllerB localPlayer) return; 9 | if (args.Length is 0) { 10 | Chat.Print("Usage: bomb "); 11 | return; 12 | } 13 | 14 | if (Helper.GetActivePlayer(args[0]) is not PlayerControllerB targetPlayer) { 15 | Chat.Print("Target player is not alive or found!"); 16 | return; 17 | } 18 | 19 | if (this.GetAvailableJetpack() is not JetpackItem jetpack) { 20 | Chat.Print("A free jetpack is required to use this command!"); 21 | return; 22 | } 23 | 24 | if (!localPlayer.GrabObject(jetpack)) { 25 | Chat.Print("You must have an empty inventory slot!"); 26 | return; 27 | } 28 | 29 | GrabbableObject[] itemSlots = localPlayer.ItemSlots; 30 | await Helper.WaitUntil(() => itemSlots[localPlayer.currentItemSlot] == jetpack, cancellationToken); 31 | localPlayer.DiscardHeldObject(placeObject: true, parentObjectTo: targetPlayer.NetworkObject); 32 | await Task.Delay(500, cancellationToken); 33 | jetpack.ExplodeJetpackServerRpc(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/Bomb/BombardCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using GameNetcodeStuff; 5 | using UnityEngine; 6 | using ZLinq; 7 | 8 | [Command("bombard")] 9 | sealed class BombardCommand : ICommand, IJetpack { 10 | /// 11 | /// Grab and discard jetpacks to a random location of the same elevation near the target player. 12 | /// If the target player is far away, it may take a while for the jetpacks to reach the player. 13 | /// The jetpacks will only explode if they within 5 units of the target player. 14 | /// 15 | static IEnumerator BombardAsync(PlayerControllerB player, Transform targetTransform, JetpackItem[] jetpacks) { 16 | float currentWeight = player.carryWeight; 17 | 18 | foreach (JetpackItem jetpack in jetpacks) { 19 | if (!player.GrabObject(jetpack)) continue; 20 | yield return new WaitUntil(() => player.ItemSlots[player.currentItemSlot] == jetpack); 21 | 22 | const float bombardRadius = 10.0f; 23 | Vector2 randomDirection = Random.insideUnitCircle * bombardRadius; 24 | Vector3 randomDirectionXZ = new(randomDirection.x, 0.0f, randomDirection.y); 25 | player.DiscardHeldObject(placeObject: true, placePosition: targetTransform.position + randomDirectionXZ); 26 | 27 | Helper.CreateComponent() 28 | .SetPredicate(() => Vector3.Distance(jetpack.transform.position, targetTransform.position) < 5.0f) 29 | .Init(() => Helper.ShortDelay(jetpack.ExplodeJetpackServerRpc)); 30 | } 31 | 32 | player.carryWeight = currentWeight; 33 | } 34 | 35 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 36 | if (Helper.LocalPlayer is not PlayerControllerB localPlayer) return; 37 | if (args.Length is 0) { 38 | Chat.Print("Usage: bombard "); 39 | return; 40 | } 41 | 42 | if (localPlayer.ItemSlots.WhereIsNotNull().AsValueEnumerable().Count() >= 4) { 43 | Chat.Print("You must have an empty inventory slot!"); 44 | return; 45 | } 46 | 47 | if (Helper.GetActivePlayer(args[0]) is not PlayerControllerB targetPlayer) { 48 | Chat.Print("Target player is not alive or found!"); 49 | return; 50 | } 51 | 52 | JetpackItem[] jetpacks = this.GetAvailableJetpacks(); 53 | 54 | if (jetpacks.Length is 0) { 55 | Chat.Print("A usable jetpack is required to use this command!"); 56 | return; 57 | } 58 | 59 | Helper.CreateComponent() 60 | .Init(() => BombardCommand.BombardAsync(localPlayer, targetPlayer.transform, jetpacks)); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/ClearCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | [Command("clear")] 5 | sealed class ClearCommand : ICommand { 6 | public async Task Execute(Arguments args, CancellationToken cancellationToken) => Chat.Clear(); 7 | } 8 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/Debug/DebugCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | sealed class DebugCommand(ICommand command) : ICommand { 5 | ICommand Command { get; } = command; 6 | 7 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 8 | Chat.Print("This debug command is for testing purposes and is not meant for use!"); 9 | await this.Command.Execute(args, cancellationToken); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/Debug/EnemiesCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using UnityEngine; 4 | 5 | [DebugCommand("enemies")] 6 | sealed class EnemiesCommand : ICommand { 7 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 8 | Resources.FindObjectsOfTypeAll().ForEach(enemy => 9 | Logger.Write(enemy.enemyName) 10 | ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/Debug/FixCameraCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using GameNetcodeStuff; 4 | 5 | [DebugCommand("fixcamera")] 6 | sealed class FixCameraCommand : ICommand { 7 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 8 | if (Helper.LocalPlayer is not PlayerControllerB localPlayer) return; 9 | 10 | Helper.Terminal?.terminalTrigger.Interact(localPlayer.transform); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/Debug/ItemsCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | [DebugCommand("items")] 5 | sealed class ItemsCommand : ICommand { 6 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 7 | Helper.Terminal?.buyableItemsList.ForEach((i, item) => 8 | Logger.Write($"{item.name} = {i}") 9 | ); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/Debug/LevelsCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | [DebugCommand("levels")] 5 | sealed class LevelsCommand : ICommand { 6 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 7 | Helper.StartOfRound?.levels.ForEach((i, level) => 8 | Logger.Write($"{level.name} = {i}") 9 | ); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/Debug/MuteCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using GameNetcodeStuff; 5 | using UnityEngine; 6 | 7 | [DebugCommand("mute")] 8 | sealed class MuteCommand : ICommand { 9 | static Dictionary MutedPlayers { get; } = []; 10 | 11 | static bool MutedPlayerHasMessaged(string playerUsername) => 12 | Helper.HUDManager?.ChatMessageHistory.First(message => 13 | message.StartsWith($"{playerUsername}: '" 14 | )) is not null; 15 | 16 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 17 | if (args.Length is 0) { 18 | Chat.Print("Usage: mute "); 19 | return; 20 | } 21 | 22 | if (Helper.GetPlayer(args[0]) is not PlayerControllerB player) { 23 | Chat.Print("Player is not alive or found!"); 24 | return; 25 | } 26 | 27 | if (MuteCommand.MutedPlayers.TryGetValue(player.playerUsername, out TransientBehaviour existingMuterObject)) { 28 | Object.Destroy(existingMuterObject); 29 | _ = MuteCommand.MutedPlayers.Remove(player.playerUsername); 30 | Chat.Print($"Unmuted {player.playerUsername}!"); 31 | return; 32 | } 33 | 34 | TransientBehaviour playerMuterObject = Helper.CreateComponent().Init(_ => { 35 | if (!MuteCommand.MutedPlayerHasMessaged(player.playerUsername)) return; 36 | Chat.Clear(); 37 | }, 10000.0f); 38 | 39 | MuteCommand.MutedPlayers.Add(player.playerUsername, playerMuterObject); 40 | Chat.Print($"Muted {player.playerUsername}!"); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/Debug/ScrapsCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | [DebugCommand("scraps")] 5 | sealed class ScrapsCommand : ICommand { 6 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 7 | Helper.RoundManager?.currentLevel.spawnableScrap.ForEach((i, spawnableScrap) => 8 | Logger.Write($"{spawnableScrap.spawnableItem.name} = {i}") 9 | ); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/Debug/UnlockablesCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | [DebugCommand("unlockables")] 5 | sealed class UnlockablesCommand : ICommand { 6 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 7 | Helper.StartOfRound?.unlockablesList.unlockables.ForEach((i, unlockable) => 8 | Logger.Write($"{unlockable.unlockableName} = {i}") 9 | ); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/DestroyCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using GameNetcodeStuff; 5 | using UnityEngine; 6 | using ZLinq; 7 | 8 | [Command("destroy")] 9 | sealed class DestroyCommand : ICommand { 10 | static IEnumerator DestroyAllItemsAsync(PlayerControllerB player) { 11 | float currentWeight = player.carryWeight; 12 | 13 | foreach (GrabbableObject grabbable in Helper.Grabbables) { 14 | if (!player.GrabObject(grabbable)) continue; 15 | yield return new WaitUntil(() => player.ItemSlots[player.currentItemSlot] == grabbable); 16 | player.DespawnHeldObject(); 17 | } 18 | 19 | player.carryWeight = currentWeight; 20 | } 21 | 22 | static Result DestroyHeldItem(PlayerControllerB player) { 23 | if (player.currentlyHeldObjectServer is null) { 24 | return new Result { Message = "You are not holding anything!" }; 25 | } 26 | 27 | player.DespawnHeldObject(); 28 | return new Result { Success = true }; 29 | } 30 | 31 | static Result DestroyAllItems(PlayerControllerB player) { 32 | Helper.CreateComponent() 33 | .Init(() => DestroyCommand.DestroyAllItemsAsync(player)); 34 | 35 | return new Result { Success = true }; 36 | } 37 | 38 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 39 | if (Helper.LocalPlayer is not PlayerControllerB player) return; 40 | 41 | if (player.ItemSlots.WhereIsNotNull().AsValueEnumerable().Count() >= 4) { 42 | Chat.Print("You must have an empty inventory slot!"); 43 | return; 44 | } 45 | 46 | Result result = args[0] switch { 47 | null => DestroyCommand.DestroyHeldItem(player), 48 | "--all" => DestroyCommand.DestroyAllItems(player), 49 | _ => new Result { Message = "Invalid arguments!" } 50 | }; 51 | 52 | if (!result.Success) { 53 | Chat.Print(result.Message); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/Door/LockCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | [Command("lock")] 5 | sealed class LockCommand : ICommand, ISecureDoor { 6 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 7 | this.SetSecureDoorState(false); 8 | Chat.Print("All gates locked!"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/Door/UnlockCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | [Command("unlock")] 5 | sealed class UnlockCommand : ICommand, ISecureDoor { 6 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 7 | this.SetSecureDoorState(true); 8 | Helper.FindObjects() 9 | .ForEach(door => door.UnlockDoorSyncWithServer()); 10 | 11 | Chat.Print("All doors unlocked!"); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/EndCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using GameNetcodeStuff; 4 | 5 | [Command("end")] 6 | sealed class EndCommand : ICommand { 7 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 8 | if (args.Length is 0) { 9 | Helper.StartOfRound?.EndGameServerRpc(-1); 10 | } 11 | 12 | else if (Helper.GetPlayer(args[0]) is PlayerControllerB player) { 13 | player.playersManager.EndGameServerRpc(player.PlayerIndex()); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/Entrance/EnterCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | [Command("enter")] 5 | sealed class EnterCommand : ICommand { 6 | public async Task Execute(Arguments args, CancellationToken cancellationToken) => Helper.LocalPlayer?.EntranceTeleport(false); 7 | } 8 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/Entrance/ExitCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | [Command("exit")] 5 | sealed class ExitCommand : ICommand { 6 | public async Task Execute(Arguments args, CancellationToken cancellationToken) => Helper.LocalPlayer?.EntranceTeleport(true); 7 | } 8 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/ExperienceCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | enum Rank { 5 | INTERN, 6 | PART_TIME, 7 | EMPLOYEE, 8 | LEADER, 9 | BOSS 10 | } 11 | 12 | [Command("xp")] 13 | sealed class ExperienceCommand : ICommand { 14 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 15 | if (Helper.HUDManager is not HUDManager hudManager) return; 16 | if (args.Length is 0) { 17 | Chat.Print("Usage: xp "); 18 | return; 19 | } 20 | 21 | if (!int.TryParse(args[0], out int amount)) { 22 | Chat.Print($"Invalid {nameof(amount)}!"); 23 | return; 24 | } 25 | 26 | Rank rank = (hudManager.localPlayerXP += amount) switch { 27 | < 50 => Rank.INTERN, 28 | < 100 => Rank.PART_TIME, 29 | < 200 => Rank.EMPLOYEE, 30 | < 500 => Rank.LEADER, 31 | _ => Rank.BOSS 32 | }; 33 | 34 | hudManager.localPlayerLevel = unchecked((int)rank); 35 | 36 | ES3.Save("PlayerXPNum", hudManager.localPlayerXP, "LCGeneralSaveData"); 37 | ES3.Save("PlayerLevel", hudManager.localPlayerLevel, "LCGeneralSaveData"); 38 | 39 | hudManager.SyncPlayerLevelServerRpc( 40 | hudManager.localPlayer.PlayerIndex(), 41 | hudManager.localPlayerLevel, 42 | ES3.Load("playedDuringBeta", "LCGeneralSaveData", true) 43 | ); 44 | 45 | Chat.Print($"You are a {rank} with {hudManager.localPlayerXP} XP!"); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/ExplodeCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | [Command("explode")] 5 | sealed class ExplodeCommand : ICommand { 6 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 7 | if (args.Length is 0) { 8 | Helper.FindObjects() 9 | .ForEach(jetpack => jetpack.ExplodeJetpackServerRpc()); 10 | } 11 | 12 | else if (args[0] is "mine") { 13 | Helper.FindObjects() 14 | .ForEach(landmine => landmine.TriggerMine()); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/FakeDeathCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using GameNetcodeStuff; 4 | using UnityEngine; 5 | 6 | [Command("fakedeath")] 7 | sealed class FakeDeathCommand : ICommand { 8 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 9 | if (Helper.LocalPlayer is not PlayerControllerB player) return; 10 | 11 | Setting.EnableFakeDeath = true; 12 | 13 | player.KillPlayerServerRpc( 14 | playerId: player.PlayerIndex(), 15 | spawnBody: false, 16 | bodyVelocity: Vector3.zero, 17 | causeOfDeath: unchecked((int)CauseOfDeath.Unknown), 18 | deathAnimation: 0, 19 | positionOffset: Vector3.zero 20 | ); 21 | 22 | await Helper.WaitUntil(() => player.playersManager.shipIsLeaving, cancellationToken); 23 | player.KillPlayer(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/GarageCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | [Command("garage")] 5 | sealed class GarageCommand : ICommand { 6 | static InteractTrigger? GarageTrigger => HaxObjects.Instance?.InteractTriggers?.WhereIsNotNull().First( 7 | interactTrigger => interactTrigger.name is "Cube" && interactTrigger.transform.parent.name is "Cutscenes" 8 | ); 9 | 10 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 11 | if (Helper.RoundManager is not RoundManager roundManager) return; 12 | if (roundManager.currentLevel.levelID is not 0) { 13 | Chat.Print("You must be in Experimentation to use this command!"); 14 | return; 15 | } 16 | 17 | if (GarageCommand.GarageTrigger is not InteractTrigger garageTrigger) { 18 | Chat.Print("Garage trigger is not found!"); 19 | return; 20 | } 21 | 22 | garageTrigger.randomChancePercentage = 100; 23 | garageTrigger.Interact(Helper.LocalPlayer?.transform); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/GodCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | [Command("god")] 5 | sealed class GodCommand : ICommand { 6 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 7 | Setting.EnableGodMode = !Setting.EnableGodMode; 8 | Chat.Print($"God mode: {(Setting.EnableGodMode ? "Enabled" : "Disabled")}"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/Hate/FallCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using GameNetcodeStuff; 5 | using UnityEngine; 6 | 7 | [Command("fall")] 8 | sealed class FallCommand : ICommand { 9 | static IEnumerator WaitForEnemyOwnershipChange(PlayerControllerB player, EnemyAI enemy) { 10 | WaitForEndOfFrame waitForEndOfFrame = new(); 11 | 12 | while (enemy.currentOwnershipOnThisClient != player.PlayerIndex()) { 13 | if (Helper.StartOfRound is { inShipPhase: true }) yield break; 14 | 15 | enemy.ChangeEnemyOwnerServerRpc(player.actualClientId); 16 | yield return waitForEndOfFrame; 17 | } 18 | } 19 | 20 | static IEnumerator MoveTargetPlayerFloor(PlayerControllerB localPlayer, PlayerControllerB targetPlayer, EnemyAI enemy) { 21 | yield return FallCommand.WaitForEnemyOwnershipChange(localPlayer, enemy); 22 | 23 | enemy.UpdateEnemyPositionServerRpc( 24 | enemy.serverPosition == Vector3.positiveInfinity 25 | ? targetPlayer.transform.position 26 | : Vector3.positiveInfinity 27 | ); 28 | 29 | yield return FallCommand.WaitForEnemyOwnershipChange(targetPlayer, enemy); 30 | } 31 | 32 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 33 | if (Helper.LocalPlayer is not PlayerControllerB localPlayer) return; 34 | if (args.Length is 0) { 35 | Chat.Print("Usage: fall "); 36 | return; 37 | } 38 | 39 | if (Helper.Enemies.First() is not EnemyAI enemy) { 40 | Chat.Print("An enemy must have spawned to use this command!"); 41 | return; 42 | } 43 | 44 | if (Helper.GetPlayer(args[0]) is not PlayerControllerB targetPlayer) { 45 | Chat.Print("Target player is not found!"); 46 | return; 47 | } 48 | 49 | Helper.CreateComponent() 50 | .Init(() => FallCommand.MoveTargetPlayerFloor(localPlayer, targetPlayer, enemy)); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/Hate/HateCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using GameNetcodeStuff; 5 | 6 | [Command("hate")] 7 | sealed class HateCommand : IEnemyPrompter, ICommand { 8 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 9 | if (args.Length is 0) { 10 | Chat.Print("Usage: hate "); 11 | return; 12 | } 13 | 14 | if (Helper.GetActivePlayer(args[0]) is not PlayerControllerB targetPlayer) { 15 | Chat.Print("Target player is not alive or found!"); 16 | return; 17 | } 18 | 19 | List promptedEnemies = this.PromptEnemiesToTarget(targetPlayer: targetPlayer); 20 | 21 | if (promptedEnemies.Count is 0) { 22 | Chat.Print("No enemies found!"); 23 | return; 24 | } 25 | 26 | promptedEnemies.ForEach(enemy => Chat.Print($"{enemy} prompted!")); 27 | Chat.Print($"Enemies prompted: {promptedEnemies.Count}"); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/Hate/LagCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using GameNetcodeStuff; 5 | using UnityEngine; 6 | 7 | [Command("lag")] 8 | sealed class LagCommand : ICommand { 9 | static IEnumerator WaitForEnemyOwnershipChange(PlayerControllerB player, EnemyAI enemy) { 10 | WaitForEndOfFrame waitForEndOfFrame = new(); 11 | 12 | while (enemy.currentOwnershipOnThisClient != player.PlayerIndex()) { 13 | if (Helper.StartOfRound is { inShipPhase: true }) yield break; 14 | 15 | enemy.ChangeEnemyOwnerServerRpc(player.actualClientId); 16 | yield return waitForEndOfFrame; 17 | } 18 | } 19 | 20 | static IEnumerator PassBrackenComputeToTargetPlayer( 21 | PlayerControllerB localPlayer, 22 | PlayerControllerB targetPlayer, 23 | FlowermanAI bracken 24 | ) { 25 | yield return LagCommand.WaitForEnemyOwnershipChange(localPlayer, bracken); 26 | 27 | bracken.SetMovingTowardsTargetPlayer(targetPlayer); 28 | bracken.SetBehaviourState(BehaviourState.AGGRAVATED); 29 | bracken.EnterAngerModeServerRpc(float.MaxValue); 30 | bracken.UpdateEnemyPositionServerRpc(targetPlayer.transform.position); 31 | 32 | yield return LagCommand.WaitForEnemyOwnershipChange(targetPlayer, bracken); 33 | } 34 | 35 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 36 | if (Helper.LocalPlayer is not PlayerControllerB localPlayer) return; 37 | if (args.Length is 0) { 38 | Chat.Print("Usage: lag "); 39 | return; 40 | } 41 | 42 | if (Helper.GetEnemy() is not FlowermanAI bracken) { 43 | Chat.Print("A Bracken must have spawned to use this command!"); 44 | return; 45 | } 46 | 47 | if (Helper.GetPlayer(args[0]) is not PlayerControllerB targetPlayer) { 48 | Chat.Print("Target player is not found!"); 49 | return; 50 | } 51 | 52 | if (targetPlayer.isInsideFactory) { 53 | Chat.Print("Target player must be outside of the factory!"); 54 | return; 55 | } 56 | 57 | Helper.CreateComponent() 58 | .Init(() => LagCommand.PassBrackenComputeToTargetPlayer(localPlayer, targetPlayer, bracken)); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/Hate/MobCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using GameNetcodeStuff; 5 | 6 | [Command("mob")] 7 | sealed class MobCommand : IEnemyPrompter, ICommand { 8 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 9 | if (args.Length is 0) { 10 | Chat.Print("Usage: mob "); 11 | return; 12 | } 13 | 14 | if (Helper.GetActivePlayer(args[0]) is not PlayerControllerB targetPlayer) { 15 | Chat.Print("Target player is not alive or found!"); 16 | return; 17 | } 18 | 19 | List mobs = this.PromptEnemiesToTarget(targetPlayer: targetPlayer, willTeleportEnemies: true); 20 | 21 | if (mobs.Count is 0) { 22 | Chat.Print("No mobs found!"); 23 | return; 24 | } 25 | 26 | mobs.ForEach(enemy => Chat.Print($"{enemy} is in the mob!")); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/HearCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | [Command("hear")] 5 | sealed class HearCommand : ICommand { 6 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 7 | Setting.EnableEavesdrop = !Setting.EnableEavesdrop; 8 | Chat.Print($"Hear: {(Setting.EnableEavesdrop ? "on" : "off")}"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/ICommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | interface ICommand { 5 | Task Execute(Arguments args, CancellationToken cancellationToken); 6 | } 7 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/InvisibleCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using UnityEngine; 4 | 5 | [Command("invis")] 6 | sealed class InvisibleCommand : ICommand { 7 | static void ImmediatelyUpdatePlayerPosition() => 8 | Helper.LocalPlayer?.UpdatePlayerPositionServerRpc(Vector3.zero, true, true, false, true); 9 | 10 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 11 | Setting.EnableInvisible = !Setting.EnableInvisible; 12 | Chat.Print($"Invisible: {(Setting.EnableInvisible ? "enabled" : "disabled")}"); 13 | 14 | if (Setting.EnableInvisible) { 15 | InvisibleCommand.ImmediatelyUpdatePlayerPosition(); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/KillClickCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | [Command("killclick")] 5 | sealed class KillClickCommand : ICommand { 6 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 7 | Setting.EnableKillOnLeftClick = !Setting.EnableKillOnLeftClick; 8 | Chat.Print($"Killclick: {(Setting.EnableKillOnLeftClick ? "Enabled" : "Disabled")}"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/KillCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using GameNetcodeStuff; 4 | 5 | [Command("kill")] 6 | sealed class KillCommand : ICommand { 7 | bool EnableGodMode { get; set; } 8 | 9 | static Result KillSelf() { 10 | Helper.LocalPlayer?.KillPlayer(); 11 | return new Result { Success = true }; 12 | } 13 | 14 | static Result KillTargetPlayer(string[] args) { 15 | if (Helper.GetActivePlayer(args[0]) is not PlayerControllerB targetPlayer) { 16 | return new Result { Message = "Target player is not alive or found!" }; 17 | } 18 | 19 | targetPlayer.KillPlayer(); 20 | return new Result { Success = true }; 21 | } 22 | 23 | static Result KillAllPlayers() { 24 | Helper.Players?.ForEach(player => player.KillPlayer()); 25 | return new Result { Success = true }; 26 | } 27 | 28 | static Result KillAllEnemies() { 29 | Helper.Enemies.ForEach(Helper.Kill); 30 | return new Result { Success = true }; 31 | } 32 | 33 | static void HandleResult(Result result) { 34 | if (result.Success) return; 35 | Chat.Print(result.Message); 36 | } 37 | 38 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 39 | if (args.Length is 0) { 40 | KillCommand.HandleResult(KillSelf()); 41 | return; 42 | } 43 | 44 | this.EnableGodMode = Setting.EnableGodMode; 45 | Setting.EnableGodMode = false; 46 | 47 | Result result = args[0] switch { 48 | "--all" => KillCommand.KillAllPlayers(), 49 | "--enemy" => KillCommand.KillAllEnemies(), 50 | _ => KillCommand.KillTargetPlayer(args) 51 | }; 52 | 53 | Helper.ShortDelay(() => Setting.EnableGodMode = this.EnableGodMode); 54 | KillCommand.HandleResult(result); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/LightCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | [Command("light")] 5 | sealed class LightCommand : ICommand { 6 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 7 | if (Helper.FindObject() is not ShipLights shipLights) return; 8 | shipLights.SetShipLightsServerRpc(!shipLights.areLightsOn); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/LobbyCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using UnityEngine; 4 | 5 | [Command("lobby")] 6 | sealed class LobbyCommand : ICommand { 7 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 8 | if (State.ConnectedLobby is not ConnectedLobby connectedLobby) return; 9 | 10 | GUIUtility.systemCopyBuffer = connectedLobby.Lobby.Owner.Id.ToString(); 11 | Chat.Print($"The host's Steam ID {GUIUtility.systemCopyBuffer} has been copied!"); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/LocationCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using UnityEngine; 4 | 5 | [Command("xyz")] 6 | sealed class LocationCommand : ICommand { 7 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 8 | if (Helper.CurrentCamera is not Camera camera) return; 9 | 10 | Vector3 currentPostion = camera.transform.position; 11 | Chat.Print($"{currentPostion.x:0} {currentPostion.y:0} {currentPostion.z:0}"); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/MaskCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using GameNetcodeStuff; 4 | 5 | [Command("mask")] 6 | sealed class MaskCommand : ICommand { 7 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 8 | if (Helper.LocalPlayer is not PlayerControllerB localPlayer) return; 9 | if (Helper.Grabbables.First(grabbable => grabbable is HauntedMaskItem) is not HauntedMaskItem hauntedMaskItem) { 10 | Chat.Print("No mask found!"); 11 | return; 12 | } 13 | 14 | if (!localPlayer.GrabObject(hauntedMaskItem)) { 15 | Chat.Print("You must have an empty inventory slot!"); 16 | return; 17 | } 18 | 19 | PlayerControllerB? targetPlayer = args.Length is 0 20 | ? localPlayer 21 | : Helper.GetActivePlayer(args[0]); 22 | 23 | if (targetPlayer is null) { 24 | Chat.Print("Target player is not alive or found!"); 25 | return; 26 | } 27 | 28 | if (!args[1].TryParse(defaultValue: 1, result: out ulong amount)) { 29 | Chat.Print($"Mask {nameof(amount)} must be a positive number!"); 30 | return; 31 | } 32 | 33 | GrabbableObject[] itemSlots = localPlayer.ItemSlots; 34 | await Helper.WaitUntil(() => localPlayer.ItemSlots[localPlayer.currentItemSlot] is HauntedMaskItem, cancellationToken); 35 | 36 | for (ulong i = 0; i < amount; i++) { 37 | hauntedMaskItem.CreateMimicServerRpc(targetPlayer.isInsideFactory, targetPlayer.transform.position); 38 | await Task.Delay(100, cancellationToken); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/NoClipCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using GameNetcodeStuff; 4 | using UnityEngine; 5 | 6 | [Command("noclip")] 7 | sealed class NoClipCommand : ICommand { 8 | static bool EnabledGodMode { get; set; } 9 | 10 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 11 | if (Helper.LocalPlayer is not PlayerControllerB localPlayer) return; 12 | if (localPlayer.gameObject.TryGetComponent(out KeyboardMovement keyboard)) { 13 | Setting.EnableGodMode = EnabledGodMode; 14 | GameObject.Destroy(keyboard); 15 | Chat.Print("NoClip has been disabled!"); 16 | } 17 | 18 | else { 19 | NoClipCommand.EnabledGodMode = Setting.EnableGodMode; 20 | _ = localPlayer.gameObject.AddComponent(); 21 | Setting.EnableGodMode = true; 22 | Chat.Print("NoClip has been enabled!"); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/NoiseCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using GameNetcodeStuff; 4 | using UnityEngine; 5 | 6 | [Command("noise")] 7 | sealed class NoiseCommand : ICommand { 8 | static async Task PlayNoiseContinuously(PlayerControllerB player, float duration, CancellationToken cancellationToken) { 9 | float startTime = Time.time; 10 | 11 | while (Time.time - startTime < duration) { 12 | if (cancellationToken.IsCancellationRequested) break; 13 | if (player.playersManager.inShipPhase) break; 14 | 15 | Helper.RoundManager?.PlayAudibleNoise(player.transform.position, float.MaxValue, float.MaxValue, 10, false); 16 | await Task.Yield(); 17 | } 18 | } 19 | 20 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 21 | if (args.Length is 0) { 22 | Chat.Print("Usage: noise "); 23 | return; 24 | } 25 | 26 | if (Helper.GetActivePlayer(args[0]) is not PlayerControllerB player) { 27 | Chat.Print("Target player is not alive or found!"); 28 | return; 29 | } 30 | 31 | if (!args[1].TryParse(defaultValue: 30, result: out ulong duration)) { 32 | Chat.Print($"Noise {nameof(duration)} must be a positive number!"); 33 | return; 34 | } 35 | 36 | await NoiseCommand.PlayNoiseContinuously(player, duration, cancellationToken); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/PlayersCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using ZLinq; 4 | 5 | [Command("players")] 6 | sealed class PlayersCommand : ICommand { 7 | public async Task Execute(Arguments args, CancellationToken cancellationToken) => Chat.Print( 8 | $"\n{Helper.Players.AsValueEnumerable().Select(player => $"{player.playerClientId}: {player.playerUsername}").JoinToString("\n")}" 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/PoisonCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using GameNetcodeStuff; 6 | using UnityEngine; 7 | 8 | [Command("poison")] 9 | sealed class PoisonCommand : ICommand { 10 | static async Task PoisonPlayer(PlayerControllerB player, int damage, ulong delay, ulong duration, CancellationToken cancellationToken) { 11 | float startTime = Time.time; 12 | 13 | while (Time.time - startTime < duration) { 14 | if (player.playersManager.inShipPhase) break; 15 | 16 | player.DamagePlayerRpc(damage); 17 | await Task.Delay(TimeSpan.FromSeconds(delay), cancellationToken); 18 | } 19 | } 20 | 21 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 22 | if (args.Length < 3) { 23 | Chat.Print("Usages:", 24 | "poison ", 25 | "poison --all " 26 | ); 27 | 28 | return; 29 | } 30 | 31 | if (!int.TryParse(args[1], out int damage)) { 32 | Chat.Print($"Invalid {nameof(damage)}!"); 33 | return; 34 | } 35 | 36 | if (!ulong.TryParse(args[2], out ulong duration)) { 37 | Chat.Print($"Poison {nameof(duration)} must be a positive number!"); 38 | return; 39 | } 40 | 41 | if (!args[3].TryParse(defaultValue: 1, result: out ulong delay)) { 42 | Chat.Print($"Poison {nameof(delay)} must be a positive number!"); 43 | return; 44 | } 45 | 46 | if (args[0] is "--all") { 47 | await Task.WhenAll( 48 | Helper.ActivePlayers.Select(player => PoisonCommand.PoisonPlayer(player, damage, delay, duration, cancellationToken)) 49 | ); 50 | } 51 | 52 | else if (Helper.GetActivePlayer(args[0]) is PlayerControllerB player) { 53 | await PoisonCommand.PoisonPlayer(player, damage, delay, duration, cancellationToken); 54 | } 55 | 56 | else { 57 | Chat.Print("Target player is not alive or found!"); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/PrefixCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | [Command("prefix")] 5 | sealed class PrefixCommand : ICommand { 6 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 7 | if (args.Length is 0) { 8 | Chat.Print("Usage: prefix "); 9 | return; 10 | } 11 | 12 | if (!char.TryParse(args[0], out char prefix)) { 13 | Chat.Print("The prefix must be a single character!"); 14 | return; 15 | } 16 | 17 | State.CommandPrefix = prefix; 18 | Chat.Print($"The command prefix has been set to '{prefix}'"); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/Privileged/CreditCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | [PrivilegedCommand("credit")] 5 | sealed class CreditCommand : ICommand { 6 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 7 | if (Helper.Terminal is not Terminal terminal) return; 8 | if (args.Length is 0) { 9 | Chat.Print("Usage: credit "); 10 | return; 11 | } 12 | 13 | if (!int.TryParse(args[0], out int amount)) { 14 | Chat.Print($"Invalid {nameof(amount)}!"); 15 | return; 16 | } 17 | 18 | terminal.groupCredits += amount; 19 | terminal.SyncGroupCreditsServerRpc(terminal.groupCredits, terminal.numberOfItemsInDropship); 20 | Chat.Print($"You now have {terminal.groupCredits} credits!"); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/Privileged/EjectCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | [PrivilegedCommand("eject")] 5 | sealed class EjectCommand : ICommand { 6 | public async Task Execute(Arguments args, CancellationToken cancellationToken) => Helper.StartOfRound?.ManuallyEjectPlayersServerRpc(); 7 | } 8 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/Privileged/GodsCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | [PrivilegedCommand("gods")] 5 | sealed class GodsCommand : ICommand { 6 | public async Task Execute(Arguments args, CancellationToken cancellationToken) => Helper.StartOfRound?.Debug_ToggleAllowDeathServerRpc(); 7 | } 8 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/Privileged/LandCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using UnityEngine; 4 | 5 | [PrivilegedCommand("land")] 6 | sealed class LandCommand : ICommand { 7 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 8 | if (Helper.StartOfRound is not StartOfRound startOfRound) return; 9 | if (startOfRound.travellingToNewLevel) { 10 | Chat.Print("You cannot land while travelling to a new level!"); 11 | return; 12 | } 13 | 14 | float originalTimeScale = Time.timeScale; 15 | Time.timeScale = 5.0f; 16 | 17 | try { 18 | startOfRound.StartGameServerRpc(); 19 | await Helper.WaitUntil(() => startOfRound.shipHasLanded, cancellationToken); 20 | } 21 | 22 | finally { 23 | Time.timeScale = originalTimeScale; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/Privileged/PrivilegedCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | sealed class PrivilegedCommand(ICommand command) : ICommand { 5 | ICommand Command { get; } = command; 6 | 7 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 8 | if (Helper.LocalPlayer is not { IsHost: true }) { 9 | Chat.Print("You must be the host to use this command!"); 10 | return; 11 | } 12 | 13 | await this.Command.Execute(args, cancellationToken); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/Privileged/QuotaCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | [PrivilegedCommand("quota")] 5 | sealed class QuotaCommand : ICommand { 6 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 7 | if (Helper.TimeOfDay is not TimeOfDay timeOfDay) return; 8 | if (args.Length is 0) { 9 | Chat.Print("Usage: quota "); 10 | return; 11 | } 12 | 13 | if (!ushort.TryParse(args[0], out ushort amount)) { 14 | Chat.Print($"Quota {nameof(amount)} must be a positive number!"); 15 | return; 16 | } 17 | 18 | if (!args[1].TryParse(defaultValue: 0, result: out ushort fulfilled)) { 19 | Chat.Print($"The {nameof(fulfilled)} amount must be a positive number!"); 20 | return; 21 | } 22 | 23 | timeOfDay.profitQuota = amount; 24 | timeOfDay.quotaFulfilled = fulfilled; 25 | timeOfDay.UpdateProfitQuotaCurrentTime(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/Privileged/ReviveCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | [PrivilegedCommand("revive")] 5 | sealed class ReviveCommand : ICommand { 6 | public async Task Execute(Arguments args, CancellationToken cancellationToken) => Helper.StartOfRound?.Debug_ReviveAllPlayersServerRpc(); 7 | } 8 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/Privileged/TimescaleCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using UnityEngine; 4 | 5 | [PrivilegedCommand("timescale")] 6 | sealed class TimescaleCommand : ICommand { 7 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 8 | if (args.Length is 0) { 9 | Chat.Print("Usage: timescale "); 10 | return; 11 | } 12 | 13 | if (!float.TryParse(args[0], out float timescale)) { 14 | Chat.Print($"Invalid {nameof(timescale)}!"); 15 | return; 16 | } 17 | 18 | Time.timeScale = timescale; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/RapidCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | [Command("rapid")] 5 | sealed class RapidCommand : ICommand { 6 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 7 | Setting.EnableNoCooldown = !Setting.EnableNoCooldown; 8 | Chat.Print($"Rapid fire: {(Setting.EnableNoCooldown ? "Enabled" : "Disabled")}"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/SayCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using GameNetcodeStuff; 4 | 5 | [Command("say")] 6 | sealed class SayCommand : ICommand { 7 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 8 | if (args.Length < 2) { 9 | Chat.Print("Usage: say "); 10 | } 11 | 12 | if (Helper.GetPlayer(args[0]) is not PlayerControllerB player) { 13 | Chat.Print("Target player is not found!"); 14 | return; 15 | } 16 | 17 | string message = string.Join(" ", args[1..]); 18 | 19 | if (message.Length > 50) { 20 | Chat.Print($"You have exceeded the max message length by {message.Length - 50} characters!"); 21 | return; 22 | } 23 | 24 | Helper.HUDManager?.AddTextToChatOnServer(message, player.PlayerIndex()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/Ship/CloseCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | [Command("close")] 5 | sealed class CloseCommand : ICommand, IShipDoor { 6 | public async Task Execute(Arguments args, CancellationToken cancellationToken) => this.SetShipDoorState(true); 7 | } 8 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/Ship/OpenCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | [Command("open")] 5 | sealed class OpenCommand : ICommand, IShipDoor { 6 | public async Task Execute(Arguments args, CancellationToken cancellationToken) => this.SetShipDoorState(false); 7 | } 8 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/ShovelCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | [Command("shovel")] 5 | sealed class ShovelCommand : ICommand { 6 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 7 | if (args.Length is 0) { 8 | Chat.Print("Usage: shovel "); 9 | return; 10 | } 11 | 12 | if (!ushort.TryParse(args[0], out ushort shovelHitForce)) { 13 | Chat.Print("Shovel force must be a positive number!"); 14 | return; 15 | } 16 | 17 | State.ShovelHitForce = shovelHitForce; 18 | Chat.Print($"Shovel hit force is now set to {shovelHitForce}!"); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/SpinCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using UnityEngine; 5 | 6 | [Command("spin")] 7 | sealed class SpinCommand : ICommand { 8 | static Action PlaceObjectAtRotation(PlaceableShipObject shipObject) => (timeElapsed) => 9 | Helper.PlaceObjectAtPosition( 10 | shipObject, 11 | shipObject.transform.position, 12 | new Vector3(0.0f, timeElapsed * 810.0f, 0.0f) 13 | ); 14 | 15 | static Action SpinObject(ulong duration) => (shipObject) => 16 | Helper.CreateComponent() 17 | .Init(SpinCommand.PlaceObjectAtRotation(shipObject), duration); 18 | 19 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 20 | if (args.Length is 0) { 21 | Chat.Print("Usage: spin "); 22 | } 23 | 24 | if (!ulong.TryParse(args[0], out ulong duration)) { 25 | Chat.Print($"Spin {nameof(duration)} must be a positive number!"); 26 | } 27 | 28 | Helper.FindObjects() 29 | .ForEach(SpinCommand.SpinObject(duration)); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/StartCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | [Command("start")] 5 | sealed class StartGameCommand : ICommand { 6 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 7 | if (Helper.StartOfRound is not StartOfRound startOfRound) return; 8 | if (startOfRound.travellingToNewLevel) { 9 | Chat.Print("You cannot start the game while travelling to a new level!"); 10 | return; 11 | } 12 | 13 | startOfRound.StartGameServerRpc(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/StunCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using UnityEngine; 4 | 5 | [Command("stun")] 6 | sealed class StunCommand : ICommand, IStun { 7 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 8 | if (Helper.CurrentCamera is not Camera camera) return; 9 | if (args.Length is 0) { 10 | Chat.Print("Usage: stun "); 11 | return; 12 | } 13 | 14 | if (!ulong.TryParse(args[0], out ulong duration)) { 15 | Chat.Print($"Stun {nameof(duration)} must be a positive number!"); 16 | return; 17 | } 18 | 19 | this.Stun(camera.transform.position, float.MaxValue, duration); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/StunOnClickCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | [Command("stunclick")] 5 | sealed class StunOnClickCommand : ICommand { 6 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 7 | Setting.EnableStunOnLeftClick = !Setting.EnableStunOnLeftClick; 8 | Chat.Print($"Stunclick: {(Setting.EnableStunOnLeftClick ? "Enabled" : "Disabled")}"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/TranslateCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using GameNetcodeStuff; 4 | 5 | [Command("translate")] 6 | sealed class TranslateCommand : ICommand { 7 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 8 | if (Helper.LocalPlayer is not PlayerControllerB player) return; 9 | if (args.Length < 2) { 10 | Chat.Print("Usages: translate "); 11 | return; 12 | } 13 | 14 | string? language = args[0]; 15 | 16 | if (string.IsNullOrWhiteSpace(language)) { 17 | Chat.Print($"Invalid {nameof(language)}!"); 18 | return; 19 | } 20 | 21 | using Translator translator = new(language, cancellationToken); 22 | string? translatedText = await translator.Translate(string.Join(' ', args[1..])); 23 | 24 | if (string.IsNullOrWhiteSpace(translatedText)) { 25 | Chat.Print("Failed to translate the text!"); 26 | return; 27 | } 28 | 29 | Helper.HUDManager?.AddPlayerChatMessageServerRpc(translatedText, player.PlayerIndex()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/UnlimitedJumpCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | [Command("jump")] 5 | sealed class UnlimitedJumpCommand : ICommand { 6 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 7 | Setting.EnableUnlimitedJump = !Setting.EnableUnlimitedJump; 8 | Helper.SendNotification(title: "Unlimited Jump", body: Setting.EnableUnlimitedJump ? " enabled" : "disabled"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/Unlockable/BuildCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using UnityEngine; 5 | using ZLinq; 6 | 7 | [Command("build")] 8 | sealed class BuildCommand : ICommand { 9 | static Dictionary? Unlockables { get; set; } 10 | 11 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 12 | if (Helper.StartOfRound is not StartOfRound startOfRound) return; 13 | if (Helper.CurrentCamera is not Camera camera) return; 14 | if (args[0] is not string unlockableName) { 15 | Chat.Print("Usage: build "); 16 | return; 17 | } 18 | 19 | BuildCommand.Unlockables ??= 20 | startOfRound 21 | .unlockablesList 22 | .unlockables 23 | .AsValueEnumerable() 24 | .Select((unlockable, i) => (unlockable, i)) 25 | .ToDictionary( 26 | pair => pair.unlockable.unlockableName.ToLower(), 27 | pair => pair.i 28 | ); 29 | 30 | if (!unlockableName.FuzzyMatch(BuildCommand.Unlockables.Keys, out string key)) { 31 | Chat.Print("Failed to find unlockable!"); 32 | return; 33 | } 34 | 35 | Unlockable unlockable = (Unlockable)BuildCommand.Unlockables[key]; 36 | Helper.BuyUnlockable(unlockable); 37 | Helper.ReturnUnlockable(unlockable); 38 | 39 | Chat.Print($"Attempting to build a {string.Join(' ', unlockable.ToString().Split('_')).ToTitleCase()}!"); 40 | 41 | if (Helper.GetUnlockable(unlockable) is not PlaceableShipObject shipObject) { 42 | Chat.Print("Unlockable is not found or placeable!"); 43 | return; 44 | } 45 | 46 | Vector3 newPosition = camera.transform.position + (camera.transform.forward * 3.0f); 47 | Vector3 newRotation = camera.transform.eulerAngles; 48 | newRotation.x = -90.0f; 49 | 50 | Helper.PlaceObjectAtPosition( 51 | shipObject, 52 | newPosition, 53 | newRotation 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/Unlockable/HomeCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using GameNetcodeStuff; 5 | 6 | [Command("home")] 7 | sealed class HomeCommand : ITeleporter, ICommand { 8 | static ShipTeleporter? Teleporter => Helper.ShipTeleporters.First( 9 | teleporter => teleporter is not null && !teleporter.isInverseTeleporter 10 | ); 11 | 12 | static Action TeleportPlayerToBaseLater(PlayerControllerB targetPlayer) => () => { 13 | HaxObjects.Instance?.ShipTeleporters?.Renew(); 14 | 15 | if (HomeCommand.Teleporter is not ShipTeleporter teleporter) { 16 | Chat.Print("Ship Teleporter is not found!"); 17 | return; 18 | } 19 | 20 | Helper.SwitchRadarTarget(targetPlayer); 21 | Helper.CreateComponent() 22 | .SetPredicate(() => Helper.IsRadarTarget(targetPlayer.playerClientId)) 23 | .Init(teleporter.PressTeleportButtonServerRpc); 24 | }; 25 | 26 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 27 | if (Helper.StartOfRound is not StartOfRound startOfRound) return; 28 | if (args.Length is 0) { 29 | startOfRound.ForcePlayerIntoShip(); 30 | startOfRound.localPlayerController.isInsideFactory = false; 31 | return; 32 | } 33 | 34 | if (Helper.GetPlayer(args[0]) is not PlayerControllerB targetPlayer) { 35 | Chat.Print("Target player is not found!"); 36 | return; 37 | } 38 | 39 | this.PrepareToTeleport(HomeCommand.TeleportPlayerToBaseLater(targetPlayer)); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/Unlockable/HornCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | [Command("horn")] 6 | sealed class HornCommand : ICommand { 7 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 8 | if (args.Length is 0) { 9 | Chat.Print("Usage: horn "); 10 | return; 11 | } 12 | 13 | if (!ulong.TryParse(args[0], out ulong duration)) { 14 | Chat.Print($"Horn {nameof(duration)} must be a positive number!"); 15 | return; 16 | } 17 | 18 | Helper.BuyUnlockable(Unlockable.LOUD_HORN); 19 | Helper.ReturnUnlockable(Unlockable.LOUD_HORN); 20 | 21 | if (await Helper.FindObject(cancellationToken) is not ShipAlarmCord shipAlarmCord) { 22 | return; 23 | } 24 | 25 | try { 26 | shipAlarmCord.PullCordServerRpc(-1); 27 | await Task.Delay(TimeSpan.FromSeconds(duration), cancellationToken); 28 | } 29 | 30 | finally { 31 | shipAlarmCord.StopPullingCordServerRpc(-1); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/Unlockable/SignalCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | [Command("signal")] 5 | sealed class SignalCommand : ICommand { 6 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 7 | if (args.Length is 0) { 8 | Chat.Print("Usage: signal "); 9 | return; 10 | } 11 | 12 | string message = string.Join(" ", args); 13 | 14 | if (message.Length > 12) { 15 | Chat.Print($"You've exceeded the maximum message length by {message.Length - 12} character(s)!"); 16 | return; 17 | } 18 | 19 | Helper.BuyUnlockable(Unlockable.SIGNAL_TRANSMITTER); 20 | Helper.ReturnUnlockable(Unlockable.SIGNAL_TRANSMITTER); 21 | Helper.HUDManager?.UseSignalTranslatorServerRpc(message); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/Unlockable/SuitCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using ZLinq; 6 | 7 | [Command("suit")] 8 | sealed class SuitCommand : ICommand { 9 | internal static Dictionary SuitUnlockables => 10 | Enum.GetValues(typeof(Unlockable)) 11 | .AsValueEnumerable() 12 | .Cast() 13 | .Where(u => u.ToString().EndsWith("_SUIT")) 14 | .ToDictionary(suit => suit.ToString().Replace("_SUIT", "").ToLower(), suit => suit); 15 | 16 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 17 | if (args[0] is not string suit) { 18 | Chat.Print("Usage: suit "); 19 | return; 20 | } 21 | 22 | if (!suit.FuzzyMatch(SuitCommand.SuitUnlockables.Keys, out string key)) { 23 | Chat.Print("Suit is not found!"); 24 | return; 25 | } 26 | 27 | Unlockable selectedSuit = SuitCommand.SuitUnlockables[key]; 28 | Helper.BuyUnlockable(selectedSuit); 29 | 30 | Helper 31 | .FindObjects() 32 | .First(suit => selectedSuit.Is(suit.suitID))? 33 | .SwitchSuitToThis(Helper.LocalPlayer); 34 | 35 | string suitTitle = 36 | selectedSuit 37 | .ToString() 38 | .Split('_') 39 | .AsValueEnumerable() 40 | .Select(s => s.ToLower()) 41 | .JoinToString(" ") 42 | .ToTitleCase(); 43 | 44 | Chat.Print($"Wearing {suitTitle}!"); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/UprightCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using UnityEngine; 4 | 5 | [Command("upright")] 6 | sealed class UprightCommand : ICommand { 7 | void SetObjectUpright(PlaceableShipObject shipObject) { 8 | Vector3 uprightRotation = shipObject.transform.eulerAngles; 9 | 10 | if (uprightRotation.x == -90.0f) { 11 | return; 12 | } 13 | 14 | uprightRotation.x = -90.0f; 15 | Helper.PlaceObjectAtPosition(shipObject, shipObject.transform.position, uprightRotation); 16 | } 17 | 18 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 19 | Helper.FindObjects() 20 | .ForEach(this.SetObjectUpright); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/VisitCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using ZLinq; 5 | 6 | [Command("visit")] 7 | sealed class VisitCommand : ICommand { 8 | static Dictionary? Levels { get; set; } 9 | 10 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 11 | if (Helper.Terminal is not Terminal terminal) return; 12 | if (Helper.StartOfRound is not StartOfRound startOfRound) return; 13 | if (args[0] is not string moon) { 14 | Chat.Print("Usage: visit "); 15 | return; 16 | } 17 | 18 | if (!startOfRound.inShipPhase) { 19 | Chat.Print("You cannot use this command outside of the ship phase!"); 20 | return; 21 | } 22 | 23 | if (startOfRound.travellingToNewLevel) { 24 | Chat.Print("You cannot use this command while travelling to a new level!"); 25 | return; 26 | } 27 | 28 | VisitCommand.Levels ??= startOfRound.levels.AsValueEnumerable().ToDictionary( 29 | level => level.name[..(level.name.Length - "Level".Length)].ToLower(), 30 | level => level.levelID 31 | ); 32 | 33 | if (!moon.FuzzyMatch(VisitCommand.Levels.Keys, out string key)) { 34 | Chat.Print("Failed to find moon!"); 35 | return; 36 | } 37 | 38 | startOfRound.ChangeLevelServerRpc(VisitCommand.Levels[key], terminal.groupCredits); 39 | Chat.Print($"Visiting {key.ToTitleCase()}!"); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Commands/VoidCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using GameNetcodeStuff; 4 | 5 | [Command("void")] 6 | sealed class VoidCommand : ITeleporter, ICommand { 7 | public async Task Execute(Arguments args, CancellationToken cancellationToken) { 8 | if (args.Length is 0) { 9 | Chat.Print("Usage: /void "); 10 | return; 11 | } 12 | 13 | if (Helper.GetActivePlayer(args[0]) is not PlayerControllerB player) { 14 | Chat.Print("Target player is not found!"); 15 | return; 16 | } 17 | 18 | this.PrepareToTeleport(this.TeleportPlayerToPositionLater( 19 | player, 20 | player.playersManager.notSpawnedPosition.position 21 | )); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Components/AsyncBehaviour.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using UnityEngine; 4 | 5 | sealed class AsyncBehaviour : MonoBehaviour { 6 | Func? Func { get; set; } 7 | 8 | internal void Init(Func func) { 9 | this.Func = func; 10 | _ = this.StartCoroutine(this.AsyncCoroutine()); 11 | } 12 | 13 | IEnumerator AsyncCoroutine() { 14 | yield return this.Func?.Invoke(); 15 | Destroy(this.gameObject); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Components/KeyboardMovement.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEngine.InputSystem; 3 | 4 | sealed class KeyboardMovement : MonoBehaviour { 5 | const float BaseSpeed = 20; 6 | float SprintMultiplier { get; set; } = 1; 7 | 8 | internal Vector3 LastPosition { get; set; } 9 | internal bool IsPaused { get; set; } 10 | 11 | void OnEnable() => this.LastPosition = this.transform.position; 12 | 13 | void LateUpdate() { 14 | if (this.IsPaused) return; 15 | 16 | Vector3 direction = new( 17 | Keyboard.current.dKey.ReadValue() - Keyboard.current.aKey.ReadValue(), 18 | Keyboard.current.spaceKey.ReadValue() - Keyboard.current.ctrlKey.ReadValue(), 19 | Keyboard.current.wKey.ReadValue() - Keyboard.current.sKey.ReadValue() 20 | ); 21 | 22 | this.UpdateSprintMultiplier(Keyboard.current); 23 | this.Move(direction); 24 | } 25 | 26 | void UpdateSprintMultiplier(Keyboard keyboard) => 27 | this.SprintMultiplier = keyboard.shiftKey.IsPressed() 28 | ? Mathf.Min(this.SprintMultiplier + (5.0f * Time.deltaTime), 5.0f) 29 | : 1.0f; 30 | 31 | void Move(Vector3 direction) { 32 | Vector3 translatedDirection = 33 | (this.transform.right * direction.x) + 34 | (this.transform.up * direction.y) + 35 | (this.transform.forward * direction.z); 36 | 37 | this.LastPosition += translatedDirection * Time.deltaTime * KeyboardMovement.BaseSpeed * this.SprintMultiplier; 38 | this.transform.position = this.LastPosition; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Components/MousePan.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEngine.InputSystem; 3 | 4 | sealed class MousePan : MonoBehaviour { 5 | float Sensitivity { get; set; } = 0.2f; 6 | float Yaw { get; set; } 7 | float Pitch { get; set; } 8 | 9 | void OnEnable() { 10 | InputListener.OnLeftBracketPress += this.DecreaseMouseSensitivity; 11 | InputListener.OnRightBracketPress += this.IncreaseMouseSensitivity; 12 | } 13 | 14 | void OnDisable() { 15 | InputListener.OnLeftBracketPress -= this.DecreaseMouseSensitivity; 16 | InputListener.OnRightBracketPress -= this.IncreaseMouseSensitivity; 17 | } 18 | 19 | void IncreaseMouseSensitivity() => this.Sensitivity += 0.1f; 20 | 21 | void DecreaseMouseSensitivity() => this.Sensitivity = Mathf.Max(this.Sensitivity - 0.1f, 0.1f); 22 | 23 | void Update() { 24 | this.Yaw += Mouse.current.delta.x.ReadValue() * this.Sensitivity; 25 | this.Yaw = (this.Yaw + 360) % 360; 26 | 27 | this.Pitch -= Mouse.current.delta.y.ReadValue() * this.Sensitivity * (Setting.InvertYAxis ? -1 : 1); 28 | this.Pitch = Mathf.Clamp(this.Pitch, -90, 90); 29 | 30 | this.transform.localEulerAngles = new Vector3(this.Pitch, this.Yaw, 0.0f); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Components/TransientBehaviour.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using UnityEngine; 4 | 5 | sealed class TransientBehaviour : MonoBehaviour { 6 | Action? Action { get; set; } 7 | Action? DisposeAction { get; set; } 8 | float ExpireTime { get; set; } 9 | 10 | internal TransientBehaviour Init(Action action, float expireTime, float delay = 0.0f) { 11 | this.Action = action; 12 | this.ExpireTime = expireTime; 13 | 14 | _ = delay > 0.0f 15 | ? this.StartCoroutine(this.TransientCoroutine(delay)) 16 | : this.StartCoroutine(this.TransientCoroutine()); 17 | 18 | return this; 19 | } 20 | 21 | internal void Dispose(Action disposeAction) => this.DisposeAction = disposeAction; 22 | 23 | internal void Unless(Func predicate) => this.StartCoroutine(this.UnlessCoroutine(predicate)); 24 | 25 | IEnumerator UnlessCoroutine(Func predicate) { 26 | yield return new WaitUntil(predicate); 27 | this.Finalise(); 28 | } 29 | 30 | IEnumerator TransientCoroutine(float delay) { 31 | WaitForSeconds waitForDelay = new(delay); 32 | float timeElapsed = 0.0f; 33 | 34 | while (this.ExpireTime > 0.0f) { 35 | this.ExpireTime -= delay; 36 | this.Action?.Invoke(timeElapsed += delay); 37 | 38 | yield return waitForDelay; 39 | } 40 | 41 | this.Finalise(); 42 | } 43 | 44 | IEnumerator TransientCoroutine() { 45 | WaitForEndOfFrame waitForEndOfFrame = new(); 46 | float timeElapsed = 0.0f; 47 | 48 | while (this.ExpireTime > 0.0f) { 49 | float deltaTime = Time.deltaTime; 50 | this.ExpireTime -= deltaTime; 51 | this.Action?.Invoke(timeElapsed += deltaTime); 52 | 53 | yield return waitForEndOfFrame; 54 | } 55 | 56 | this.Finalise(); 57 | } 58 | 59 | void Finalise() { 60 | this.DisposeAction?.Invoke(); 61 | Destroy(this.gameObject); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Components/WaitForBehaviour.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using UnityEngine; 4 | 5 | sealed class WaitForBehaviour : MonoBehaviour { 6 | Action? Action { get; set; } 7 | Func? TimerPredicate { get; set; } 8 | Func? Predicate { get; set; } 9 | 10 | internal void Init(Action action) { 11 | this.Action = action; 12 | _ = this.Predicate is null 13 | ? this.StartCoroutine(this.WaitForTimerPredicateCoroutine()) 14 | : this.StartCoroutine(this.WaitForPredicateCoroutine()); 15 | } 16 | 17 | internal WaitForBehaviour SetPredicate(Func predicate) { 18 | this.TimerPredicate = predicate; 19 | return this; 20 | } 21 | 22 | internal WaitForBehaviour SetPredicate(Func predicate) { 23 | this.Predicate = predicate; 24 | return this; 25 | } 26 | 27 | IEnumerator WaitForPredicateCoroutine() { 28 | yield return new WaitUntil(this.Predicate); 29 | this.Finalise(); 30 | } 31 | 32 | IEnumerator WaitForTimerPredicateCoroutine() { 33 | WaitForEndOfFrame waitForEndOfFrame = new(); 34 | float timer = 0.0f; 35 | 36 | while (true) { 37 | if (this.TimerPredicate?.Invoke(timer) is not false) break; 38 | timer += Time.deltaTime; 39 | yield return waitForEndOfFrame; 40 | } 41 | 42 | this.Finalise(); 43 | } 44 | 45 | void Finalise() { 46 | this.Action?.Invoke(); 47 | Destroy(this.gameObject); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Components/WaitForGameEndBehaviour.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | sealed class WaitForGameEndBehaviour : MonoBehaviour { 5 | Action? ActionBefore { get; set; } 6 | Action? ActionAfter { get; set; } 7 | bool HasGameEnded { get; set; } 8 | 9 | void OnEnable() => GameListener.OnGameEnd += this.OnGameEnd; 10 | 11 | void OnDisable() => GameListener.OnGameEnd -= this.OnGameEnd; 12 | 13 | void OnGameEnd() => this.HasGameEnded = true; 14 | 15 | internal void AddActionBefore(Action action) => this.ActionBefore = action; 16 | 17 | internal void AddActionAfter(Action action) => this.ActionAfter = action; 18 | 19 | void Update() { 20 | if (this.HasGameEnded) { 21 | this.Finalise(); 22 | return; 23 | } 24 | 25 | this.ActionBefore?.Invoke(); 26 | } 27 | 28 | void Finalise() { 29 | this.ActionAfter?.Invoke(); 30 | Destroy(this.gameObject); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Core/GameListener.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | sealed class GameListener : MonoBehaviour { 5 | internal static event Action? OnGameStart; 6 | internal static event Action? OnGameEnd; 7 | internal static event Action? OnShipDescent; 8 | internal static event Action? OnShipLeave; 9 | internal static event Action? OnLevelGenerated; 10 | 11 | bool InGame { get; set; } 12 | bool HasShipBegunDescent { get; set; } 13 | bool HasShipLeft { get; set; } 14 | 15 | void OnEnable() => LevelDependencyPatch.OnFinishLevelGeneration += this.OnFinishLevelGeneration; 16 | 17 | void OnDisable() => LevelDependencyPatch.OnFinishLevelGeneration -= this.OnFinishLevelGeneration; 18 | 19 | void LateUpdate() { 20 | this.InGameListener(); 21 | this.ShipListener(); 22 | } 23 | 24 | void OnFinishLevelGeneration() => GameListener.OnLevelGenerated?.Invoke(); 25 | 26 | void ShipListener() { 27 | if (Helper.StartOfRound is not StartOfRound startOfRound) return; 28 | if (!startOfRound.inShipPhase && !this.HasShipBegunDescent) { 29 | this.HasShipBegunDescent = true; 30 | this.HasShipLeft = false; 31 | GameListener.OnShipDescent?.Invoke(); 32 | } 33 | 34 | else if (startOfRound.shipIsLeaving && !this.HasShipLeft) { 35 | this.HasShipLeft = true; 36 | this.HasShipBegunDescent = false; 37 | GameListener.OnShipLeave?.Invoke(); 38 | } 39 | } 40 | 41 | void InGameListener() { 42 | bool inGame = Helper.LocalPlayer is not null; 43 | 44 | if (this.InGame == inGame) { 45 | return; 46 | } 47 | 48 | this.InGame = inGame; 49 | 50 | if (this.InGame) { 51 | GameListener.OnGameStart?.Invoke(); 52 | } 53 | 54 | else { 55 | GameListener.OnGameEnd?.Invoke(); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Core/HaxObjects.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEngine.Rendering.HighDefinition; 3 | 4 | sealed class HaxObjects : MonoBehaviour { 5 | internal static HaxObjects? Instance { get; private set; } 6 | 7 | internal SingleObjectPool? DepositItemsDesk { get; private set; } 8 | internal MultiObjectPool? ShipTeleporters { get; private set; } 9 | internal MultiObjectPool? LocalVolumetricFogs { get; private set; } 10 | internal MultiObjectPool? SteamValves { get; private set; } 11 | internal MultiObjectPool? InteractTriggers { get; private set; } 12 | 13 | void Awake() { 14 | this.DepositItemsDesk = new(this, 3.0f); 15 | this.ShipTeleporters = new(this); 16 | this.LocalVolumetricFogs = new(this); 17 | this.InteractTriggers = new(this); 18 | this.SteamValves = new(this, 5.0f); 19 | HaxObjects.Instance = this; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Core/ScreenListener.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | sealed class ScreenListener : MonoBehaviour { 5 | internal static event Action? OnScreenSizeChange; 6 | 7 | int LastScreenWidth { get; set; } = Screen.width; 8 | int LastScreenHeight { get; set; } = Screen.height; 9 | 10 | void Update() => this.ScreenSizeListener(); 11 | 12 | void ScreenSizeListener() { 13 | if (Screen.width == this.LastScreenWidth && Screen.height == this.LastScreenHeight) return; 14 | 15 | this.LastScreenWidth = Screen.width; 16 | this.LastScreenHeight = Screen.height; 17 | ScreenListener.OnScreenSizeChange?.Invoke(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Enums/Unlockable.cs: -------------------------------------------------------------------------------- 1 | enum Unlockable { 2 | ORANGE_SUIT = 0, 3 | GREEN_SUIT = 1, 4 | HAZARD_SUIT = 2, 5 | PAJAMA_SUIT = 3, 6 | COZY_LIGHTS = 4, 7 | TELEPORTER = 5, 8 | TELEVISION = 6, 9 | CUPBOARD = 7, 10 | FILE_CABINET = 8, 11 | TOILET = 9, 12 | SHOWER = 10, 13 | LIGHT_SWITCH = 11, 14 | RECORD_PLAYER = 12, 15 | TABLE = 13, 16 | ROMANTIC_TABLE = 14, 17 | BUNKBEDS = 15, 18 | TERMINAL = 16, 19 | SIGNAL_TRANSMITTER = 17, 20 | LOUD_HORN = 18, 21 | INVERSE_TELEPORTER = 19, 22 | JACK_O_LANTERN = 20, 23 | WELCOME_MAT = 21, 24 | GOLDFISH = 22, 25 | PLUSHIE_PAJAMA_MAN = 23, 26 | PURPLE_SUIT = 24, 27 | BEE_SUIT = 25, 28 | BUNNY_SUIT = 26, 29 | DISCO_BALL = 27, 30 | } 31 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Extensions/Copy.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | static partial class Extensions { 4 | internal static Vector3 Copy(this Vector3 vector) { 5 | return new Vector3( 6 | vector.x, 7 | vector.y, 8 | vector.z 9 | ); 10 | } 11 | 12 | internal static Quaternion Copy(this Quaternion quaternion) { 13 | return new Quaternion( 14 | quaternion.x, 15 | quaternion.y, 16 | quaternion.z, 17 | quaternion.w 18 | ); 19 | } 20 | 21 | internal static Transform Copy(this Transform transform) { 22 | GameObject gameObject = new(); 23 | gameObject.transform.position = transform.position.Copy(); 24 | gameObject.transform.eulerAngles = transform.eulerAngles.Copy(); 25 | gameObject.transform.rotation = transform.rotation.Copy(); 26 | gameObject.transform.localScale = transform.localScale.Copy(); 27 | 28 | return gameObject.transform; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Extensions/First.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using ZLinq; 4 | 5 | static partial class Extensions { 6 | internal static T? First(this IEnumerable array, Func predicate) => array.AsValueEnumerable().FirstOrDefault(predicate); 7 | 8 | internal static T? First(this IEnumerable array) => array.AsValueEnumerable().FirstOrDefault(); 9 | } 10 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Extensions/ForEach.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using ZLinq; 4 | using UnityObject = UnityEngine.Object; 5 | 6 | static partial class Extensions { 7 | internal static void ForEach(this IEnumerable array, Action action) { 8 | foreach (T item in array) { 9 | action(item); 10 | } 11 | } 12 | 13 | internal static void ForEach(this IEnumerable array, Action action) { 14 | int i = 0; 15 | 16 | foreach (T item in array) { 17 | action(i++, item); 18 | } 19 | } 20 | 21 | internal static void ForEach(this ValueEnumerable, T> array, Action action) { 22 | foreach (T item in array) { 23 | action(item); 24 | } 25 | } 26 | 27 | internal static void ForEach(this ValueEnumerable, T>, T> array, Action action) { 28 | foreach (T item in array) { 29 | action(item); 30 | } 31 | } 32 | 33 | internal static void ForEach(this ValueEnumerable, T> array, Action action) { 34 | int i = 0; 35 | 36 | foreach (T item in array) { 37 | action(i++, item); 38 | } 39 | } 40 | 41 | internal static void ForEach(this ValueEnumerable, T>, T> array, Action action) { 42 | int i = 0; 43 | 44 | foreach (T item in array) { 45 | action(i++, item); 46 | } 47 | } 48 | 49 | internal static void ForEach(this T[] array, Action action) { 50 | for (int i = 0; i < array.Length; i++) { 51 | action(i, array[i]); 52 | } 53 | } 54 | 55 | internal static void ForEach(this List array, Action action) { 56 | for (int i = 0; i < array.Count; i++) { 57 | action(i, array[i]); 58 | } 59 | } 60 | 61 | internal static void ForEach(this MultiObjectPool multiObjectPool, Action action) where T : UnityObject => 62 | multiObjectPool.Objects.ForEach(action); 63 | 64 | internal static void ForEach(this MultiObjectPool multiObjectPool, Action action) where T : UnityObject => 65 | multiObjectPool.Objects.ForEach(action); 66 | } 67 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Extensions/GetValue.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | static partial class Extensions { 4 | internal static TValue? GetValue(this IReadOnlyDictionary dictionary, TKey? key) where TValue : class => 5 | key is null ? null : dictionary.GetValueOrDefault(key); 6 | } 7 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Extensions/Range.cs: -------------------------------------------------------------------------------- 1 | using ZLinq; 2 | 3 | static partial class Extensions { 4 | internal static ValueEnumerable Range(this int end) => ValueEnumerable.Range(0, end); 5 | } 6 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Extensions/StartResilientCoroutine.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using UnityEngine; 4 | 5 | enum CoroutineState { 6 | RUNNING, 7 | EXHAUSTED, 8 | ERROR 9 | } 10 | 11 | sealed class InvalidCoroutineState(CoroutineState state) : Exception($"Invalid CoroutineState: {state}") { } 12 | 13 | static partial class Extensions { 14 | static CoroutineState ExecuteCoroutineStep(IEnumerator coroutine) { 15 | try { 16 | return coroutine.MoveNext() ? CoroutineState.RUNNING : CoroutineState.EXHAUSTED; 17 | } 18 | 19 | catch { 20 | return CoroutineState.ERROR; 21 | } 22 | } 23 | 24 | static IEnumerator ResilientCoroutine(Func coroutineFactory, object[] args) { 25 | IEnumerator coroutine = coroutineFactory(args); 26 | WaitForSeconds waitForOneSecond = new(1.0f); 27 | 28 | while (true) { 29 | CoroutineState state = Extensions.ExecuteCoroutineStep(coroutine); 30 | 31 | switch (state) { 32 | case CoroutineState.RUNNING: 33 | yield return coroutine.Current; 34 | break; 35 | 36 | case CoroutineState.ERROR: 37 | coroutine = coroutineFactory(args); 38 | yield return waitForOneSecond; 39 | break; 40 | 41 | case CoroutineState.EXHAUSTED: 42 | yield break; 43 | 44 | default: 45 | throw new InvalidCoroutineState(state); 46 | } 47 | } 48 | } 49 | 50 | internal static Coroutine StartResilientCoroutine(this MonoBehaviour self, Func coroutineFactory, params object[] args) => 51 | self.StartCoroutine(Extensions.ResilientCoroutine(coroutineFactory, args)); 52 | 53 | internal static Coroutine StartResilientCoroutine(this MonoBehaviour self, Func coroutineFactory) => 54 | self.StartResilientCoroutine(coroutineFactory, []); 55 | } 56 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Extensions/ToTitleCase.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | 3 | static partial class Extensions { 4 | static TextInfo TextInfo { get; } = new CultureInfo("en-SG", true).TextInfo; 5 | 6 | internal static string ToTitleCase(this string str) => Extensions.TextInfo.ToTitleCase(str); 7 | } 8 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Extensions/TryParse.cs: -------------------------------------------------------------------------------- 1 | static partial class Extensions { 2 | [RequireNamedArgs] 3 | internal static bool TryParse(this string? value, ushort defaultValue, out ushort result) { 4 | if (string.IsNullOrWhiteSpace(value)) { 5 | result = defaultValue; 6 | return true; 7 | } 8 | 9 | return ushort.TryParse(value, out result); 10 | } 11 | 12 | [RequireNamedArgs] 13 | internal static bool TryParse(this string? value, ulong defaultValue, out ulong result) { 14 | if (string.IsNullOrWhiteSpace(value)) { 15 | result = defaultValue; 16 | return true; 17 | } 18 | 19 | return ulong.TryParse(value, out result); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Extensions/Unfake.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using UnityEngine; 3 | 4 | static partial class Extensions { 5 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 6 | internal static T? Unfake(this T? obj) where T : Object => obj is null || obj.Equals(null) ? null : obj; 7 | } 8 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Extensions/WhereIsNotNull.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityObject = UnityEngine.Object; 3 | 4 | static partial class Extensions { 5 | internal static IEnumerable WhereIsNotNull(this IEnumerable array) where T : class { 6 | foreach (T? element in array) { 7 | if (element == null) continue; 8 | yield return element; 9 | } 10 | } 11 | 12 | internal static IEnumerable WhereIsNotNull(this MultiObjectPool multiObjectPool) where T : UnityObject => 13 | multiObjectPool.Objects.WhereIsNotNull(); 14 | } 15 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Helpers/Camera.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | static partial class Helper { 4 | internal static Camera? CurrentCamera => 5 | Helper.LocalPlayer?.gameplayCamera is Camera { enabled: true } gameplayCamera 6 | ? gameplayCamera 7 | : Helper.StartOfRound?.spectateCamera; 8 | 9 | internal static Vector3 WorldToEyesPoint(this Camera camera, Vector3 worldPosition) { 10 | Vector3 screen = camera.WorldToViewportPoint(worldPosition); 11 | screen.x *= Screen.width; 12 | screen.y *= Screen.height; 13 | screen.y = Screen.height - screen.y; 14 | 15 | return screen; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Helpers/CreateComponent.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | 4 | static partial class Helper { 5 | static Dictionary> ComponentPools { get; } = []; 6 | 7 | static T CreateComponent(string name) where T : Component => new GameObject(name).AddComponent(); 8 | 9 | /// 10 | /// Attaches a component to a created game object. 11 | /// 12 | /// the created component 13 | internal static T CreateComponent() where T : Component => new GameObject().AddComponent(); 14 | 15 | /// 16 | /// Creates a component and adds it to a queue. 17 | /// Used to restrict the total number of existing component(s) belonging to a pool. 18 | /// 19 | /// the created component 20 | internal static T CreateComponent(string poolName, int poolSize = 1) where T : Component { 21 | T gameObject = Helper.CreateComponent(poolName); 22 | 23 | if (!Helper.ComponentPools.TryGetValue(poolName, out Queue pool)) { 24 | pool = new Queue(poolSize); 25 | Helper.ComponentPools[poolName] = pool; 26 | } 27 | 28 | if (pool.Count == poolSize) { 29 | GameObject.Destroy(pool.Dequeue()); 30 | } 31 | 32 | pool.Enqueue(gameObject); 33 | 34 | return gameObject; 35 | } 36 | 37 | /// 38 | /// Similar to `CreateComponent`, but for components that are implicitly toggleable. 39 | /// If the component already exists, no component is created and null is returned. 40 | /// 41 | /// a component if it has been created, null otherwise 42 | internal static T? CreateToggleableComponent(string poolName) where T : Component { 43 | if (!Helper.ComponentPools.TryGetValue(poolName, out Queue pool)) { 44 | return Helper.CreateComponent(poolName); 45 | } 46 | 47 | GameObject.Destroy(pool.Dequeue()); 48 | return null; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Helpers/Enemies.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using GameNetcodeStuff; 4 | using Unity.Netcode; 5 | using UnityEngine; 6 | using ZLinq; 7 | 8 | static partial class Helper { 9 | internal static HashSet Enemies { get; } = Helper.StartOfRound is { inShipPhase: false } 10 | ? Helper.FindObjects().WhereIsNotNull().AsValueEnumerable().Where(enemy => enemy.IsSpawned).ToHashSet() 11 | : []; 12 | 13 | internal static T? GetEnemy() where T : EnemyAI => Helper.Enemies.First(enemy => enemy is T) as T; 14 | 15 | internal static void Kill(this EnemyAI enemy, ulong actualClientId) { 16 | enemy.ChangeEnemyOwnerServerRpc(actualClientId); 17 | 18 | if (enemy is NutcrackerEnemyAI nutcracker) { 19 | nutcracker.KillEnemy(); 20 | } 21 | 22 | else { 23 | enemy.KillEnemyServerRpc(true); 24 | } 25 | } 26 | 27 | internal static void SetOutsideStatus(this EnemyAI enemy, bool isOutside) { 28 | if (enemy.isOutside == isOutside) return; 29 | 30 | enemy.isOutside = isOutside; 31 | enemy.allAINodes = GameObject.FindGameObjectsWithTag(enemy.isOutside ? "OutsideAINode" : "AINode"); 32 | } 33 | 34 | internal static void Kill(EnemyAI enemy) { 35 | if (Helper.LocalPlayer is not PlayerControllerB localPlayer) return; 36 | enemy.Kill(localPlayer.actualClientId); 37 | } 38 | 39 | internal static bool IsBehaviourState(this EnemyAI enemy, Enum state) => 40 | enemy.currentBehaviourStateIndex == Convert.ToInt32(state); 41 | 42 | internal static void SetBehaviourState(this EnemyAI enemy, Enum state) { 43 | if (enemy.IsBehaviourState(state)) return; 44 | enemy.SwitchToBehaviourServerRpc(Convert.ToInt32(state)); 45 | } 46 | 47 | internal static GrabbableObject? FindNearbyItem(this EnemyAI enemy, float grabRange = 1.0f) { 48 | foreach (Collider collider in Physics.OverlapSphere(enemy.transform.position, grabRange)) { 49 | if (!collider.TryGetComponent(out GrabbableObject item)) continue; 50 | if (!item.TryGetComponent(out NetworkObject _)) continue; 51 | 52 | return item; 53 | } 54 | 55 | return null; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Helpers/FindObjects.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using UnityEngine; 4 | 5 | static partial class Helper { 6 | internal static T? FindObject() where T : Component => Object.FindAnyObjectByType(); 7 | 8 | internal static T[] FindObjects(FindObjectsSortMode sortMode = FindObjectsSortMode.None) where T : Component => 9 | Object.FindObjectsByType(sortMode); 10 | 11 | internal static async Task FindObject(CancellationToken cancellationToken) where T : Component { 12 | T? obj = Helper.FindObject(); 13 | 14 | while (obj is null && !cancellationToken.IsCancellationRequested) { 15 | await Task.Yield(); 16 | obj = Helper.FindObject(); 17 | } 18 | 19 | return obj; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Helpers/GUI.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | static partial class Helper { 4 | internal static void DrawLabel(Vector2 position, string label, Color colour) { 5 | GUIStyle labelStyle = new(GUI.skin.label) { 6 | fontStyle = FontStyle.Bold 7 | }; 8 | 9 | Vector2 size = labelStyle.CalcSize(new GUIContent(label)); 10 | Vector2 newPosition = position - (size * 0.5f); 11 | 12 | labelStyle.normal.textColor = Color.black; 13 | GUI.Label(new Rect(newPosition.x, newPosition.y, size.x, size.y), label, labelStyle); 14 | 15 | labelStyle.normal.textColor = colour; 16 | GUI.Label(new Rect(newPosition.x + 1, newPosition.y + 1, size.x, size.y), label, labelStyle); 17 | } 18 | 19 | internal static void DrawLabel(Vector2 position, string label) => Helper.DrawLabel(position, label, Color.white); 20 | 21 | internal static void DrawOutlineBox(Vector2 centrePosition, Size size, float lineWidth, Color colour) { 22 | float halfWidth = 0.5f * size.Width; 23 | float halfHeight = 0.5f * size.Height; 24 | 25 | float leftX = centrePosition.x - halfWidth; 26 | float rightX = centrePosition.x + halfWidth; 27 | float topY = centrePosition.y - halfHeight; 28 | float bottomY = centrePosition.y + halfHeight; 29 | 30 | Size horizontalSize = size with { Height = lineWidth }; 31 | Size verticalSize = size with { Width = lineWidth }; 32 | 33 | Vector2 topLeft = new(leftX, topY); 34 | Helper.DrawBox(topLeft, horizontalSize, colour); 35 | 36 | Vector2 rightBorderTopLeft = new(rightX - lineWidth, topY); 37 | Helper.DrawBox(rightBorderTopLeft, verticalSize, colour); 38 | 39 | Vector2 bottomBorderTopLeft = new(leftX, bottomY - lineWidth); 40 | Helper.DrawBox(bottomBorderTopLeft, horizontalSize, colour); 41 | 42 | Helper.DrawBox(topLeft, verticalSize, colour); 43 | } 44 | 45 | internal static void DrawOutlineBox(Vector2 centrePosition, Size size, float lineWidth) => 46 | Helper.DrawOutlineBox(centrePosition, size, lineWidth, Color.white); 47 | 48 | internal static void DrawBox(Vector2 position, Size size, Color colour) { 49 | Color previousCOlour = GUI.color; 50 | GUI.color = colour; 51 | 52 | Rect rect = new(position.x, position.y, size.Width, size.Height); 53 | GUI.DrawTexture(rect, Texture2D.whiteTexture, ScaleMode.StretchToFill); 54 | 55 | GUI.color = previousCOlour; 56 | } 57 | 58 | internal static void DrawBox(Vector2 position, Size size) => Helper.DrawBox(position, size, Color.white); 59 | } 60 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Helpers/Grabbables.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | using ZLinq; 4 | 5 | 6 | static partial class Helper { 7 | internal static HashSet Grabbables { get; } = Helper.LocalPlayer is not null 8 | ? Helper.FindObjects().WhereIsNotNull().AsValueEnumerable().Where(scrap => scrap.IsSpawned).ToHashSet() 9 | : []; 10 | 11 | internal static void ShootShotgun(this ShotgunItem item, Transform origin) { 12 | item.gunShootAudio.volume = 0.15f; 13 | item.shotgunRayPoint = origin; 14 | item.ShootGunAndSync(false); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Helpers/Landmine.cs: -------------------------------------------------------------------------------- 1 | static partial class Helper { 2 | internal static void TriggerMine(this Landmine landmine) => landmine.TriggerMineOnLocalClientByExiting(); 3 | } 4 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Helpers/Managers.cs: -------------------------------------------------------------------------------- 1 | static partial class Helper { 2 | internal static HUDManager? HUDManager => HUDManager.Instance.Unfake(); 3 | 4 | internal static RoundManager? RoundManager => RoundManager.Instance.Unfake(); 5 | 6 | internal static SoundManager? SoundManager => SoundManager.Instance.Unfake(); 7 | 8 | internal static GameNetworkManager? GameNetworkManager => GameNetworkManager.Instance.Unfake(); 9 | 10 | internal static StartOfRound? StartOfRound => StartOfRound.Instance.Unfake(); 11 | 12 | internal static TimeOfDay? TimeOfDay => TimeOfDay.Instance.Unfake(); 13 | 14 | internal static Terminal? Terminal => Helper.HUDManager?.terminalScript.Unfake(); 15 | } 16 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Helpers/Map.cs: -------------------------------------------------------------------------------- 1 | using GameNetcodeStuff; 2 | 3 | static partial class Helper { 4 | static ManualCameraRenderer? ManualCameraRenderer => Helper.StartOfRound?.mapScreen; 5 | 6 | internal static bool IsRadarTarget(ulong playerClientId) => Helper.ManualCameraRenderer?.targetTransformIndex == unchecked((int)playerClientId); 7 | 8 | internal static void SwitchRadarTarget(int playerClientId) => Helper.ManualCameraRenderer?.SwitchRadarTargetServerRpc(playerClientId); 9 | 10 | internal static void SwitchRadarTarget(PlayerControllerB player) => Helper.SwitchRadarTarget(player.PlayerIndex()); 11 | } 12 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Helpers/Notification.cs: -------------------------------------------------------------------------------- 1 | static partial class Helper { 2 | [RequireNamedArgs] 3 | internal static void SendNotification(string title, string body, bool isWarning = false) => 4 | Helper.HUDManager?.DisplayTip(title, body, isWarning, false); 5 | } 6 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Helpers/PlaceObject.cs: -------------------------------------------------------------------------------- 1 | using Unity.Netcode; 2 | using UnityEngine; 3 | 4 | static partial class Helper { 5 | internal static ShipBuildModeManager? ShipBuildModeManager => ShipBuildModeManager.Instance; 6 | 7 | static NetworkObject GetNetworkObject(M gameObject) where M : MonoBehaviour => 8 | gameObject.GetComponentInChildren() 9 | .parentObject 10 | .GetComponent(); 11 | 12 | internal static void PlaceObjectAtTransform( 13 | T targetObject, 14 | M gameObject, 15 | Vector3 positionOffset = new(), 16 | Vector3 rotationOffset = new() 17 | ) where T : Transform where M : MonoBehaviour { 18 | NetworkObject networkObject = Helper.GetNetworkObject(gameObject); 19 | Helper.ShipBuildModeManager?.PlaceShipObjectServerRpc( 20 | targetObject.position + positionOffset, 21 | targetObject.eulerAngles + rotationOffset, 22 | networkObject, 23 | -1 24 | ); 25 | } 26 | 27 | internal static void PlaceObjectAtTransform( 28 | ObjectPlacement placement 29 | ) where T : Transform where M : MonoBehaviour => Helper.PlaceObjectAtTransform( 30 | placement.TargetObject, 31 | placement.GameObject, 32 | placement.PositionOffset, 33 | placement.RotationOffset 34 | ); 35 | 36 | internal static void PlaceObjectAtPosition( 37 | T targetObject, 38 | M gameObject, 39 | Vector3 positionOffset = new(), 40 | Vector3 rotationOffset = new() 41 | ) where T : Transform where M : MonoBehaviour { 42 | NetworkObject networkObject = Helper.GetNetworkObject(gameObject); 43 | Helper.ShipBuildModeManager?.PlaceShipObjectServerRpc( 44 | targetObject.position + positionOffset, 45 | rotationOffset, 46 | networkObject, 47 | -1 48 | ); 49 | } 50 | 51 | internal static void PlaceObjectAtPosition( 52 | M gameObject, 53 | Vector3 position, 54 | Vector3 rotation = new() 55 | ) where M : MonoBehaviour { 56 | NetworkObject networkObject = Helper.GetNetworkObject(gameObject); 57 | Helper.ShipBuildModeManager?.PlaceShipObjectServerRpc( 58 | position, 59 | rotation, 60 | networkObject, 61 | -1 62 | ); 63 | } 64 | 65 | internal static void PlaceObjectAtPosition( 66 | ObjectPlacement placement 67 | ) where T : Transform where M : MonoBehaviour => Helper.PlaceObjectAtPosition( 68 | placement.TargetObject, 69 | placement.GameObject, 70 | placement.PositionOffset, 71 | placement.RotationOffset 72 | ); 73 | } 74 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Helpers/Screen.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | static partial class Helper { 4 | internal static Vector2 GetScreenCentre() => new Vector2(Screen.width, Screen.height) * 0.5f; 5 | } 6 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Helpers/ShortDelay.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | static partial class Helper { 4 | internal static void ShortDelay(Action action) => 5 | Helper.CreateComponent() 6 | .SetPredicate(time => time >= 0.5f) 7 | .Init(action); 8 | } 9 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Helpers/SphereCast.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | static partial class Helper { 5 | internal static int SphereCastForward(this RaycastHit[] array, Transform transform, float sphereRadius = 1.0f) { 6 | try { 7 | return Physics.SphereCastNonAlloc( 8 | transform.position + (transform.forward * (sphereRadius + 1.75f)), 9 | sphereRadius, 10 | transform.forward, 11 | array, 12 | float.MaxValue 13 | ); 14 | } 15 | 16 | catch (NullReferenceException) { 17 | return 0; 18 | } 19 | } 20 | 21 | [RequireNamedArgs] 22 | internal static RaycastHit[] SphereCastForward(this Transform transform, float sphereRadius = 1.0f) { 23 | try { 24 | return Physics.SphereCastAll( 25 | transform.position + (transform.forward * (sphereRadius + 1.75f)), 26 | sphereRadius, 27 | transform.forward, 28 | float.MaxValue 29 | ); 30 | } 31 | 32 | catch (NullReferenceException) { 33 | return []; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Helpers/Teleporter.cs: -------------------------------------------------------------------------------- 1 | static partial class Helper { 2 | internal static ShipTeleporter?[] ShipTeleporters => HaxObjects.Instance?.ShipTeleporters?.Objects ?? []; 3 | } 4 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Helpers/Try.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | static partial class Helper { 4 | internal static T? Try(Func function, Action? onError = null) where T : class { 5 | try { 6 | return function(); 7 | } 8 | 9 | catch (Exception exception) { 10 | onError?.Invoke(exception); 11 | return null; 12 | } 13 | } 14 | 15 | internal static bool Try(Func function, Action? onError = null) { 16 | try { 17 | return function(); 18 | } 19 | 20 | catch (Exception exception) { 21 | onError?.Invoke(exception); 22 | return false; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Helpers/Unlockable.cs: -------------------------------------------------------------------------------- 1 | static partial class Helper { 2 | internal static bool Is(this Unlockable unlockable, int unlockableId) => unlockableId == unchecked((int)unlockable); 3 | 4 | internal static void BuyUnlockable(Unlockable unlockable) { 5 | if (Helper.Terminal is not Terminal terminal) return; 6 | 7 | Helper.StartOfRound?.BuyShipUnlockableServerRpc( 8 | unchecked((int)unlockable), 9 | terminal.groupCredits 10 | ); 11 | } 12 | 13 | internal static void ReturnUnlockable(Unlockable unlockable) => 14 | Helper.StartOfRound?.ReturnUnlockableFromStorageServerRpc(unchecked((int)unlockable)); 15 | 16 | internal static PlaceableShipObject? GetUnlockable(Unlockable unlockable) => 17 | Helper.FindObjects() 18 | .First(placeableObject => unlockable.Is(placeableObject.unlockableID)); 19 | } 20 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Helpers/WaitUntil.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | static partial class Helper { 6 | internal static async Task WaitUntil(Func predicate, CancellationToken cancellationToken) { 7 | while (!predicate()) { 8 | if (cancellationToken.IsCancellationRequested) break; 9 | await Task.Yield(); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Mixins/IJetpack.cs: -------------------------------------------------------------------------------- 1 | using ZLinq; 2 | 3 | interface IJetpack; 4 | 5 | static class JetpackMixin { 6 | internal static JetpackItem[] GetAvailableJetpacks(this IJetpack _) => 7 | Helper.FindObjects().AsValueEnumerable().Where(jetpack => !jetpack.jetpackBroken).ToArray(); 8 | 9 | internal static JetpackItem? GetAvailableJetpack(this IJetpack _) => 10 | Helper.FindObjects().First(jetpack => !jetpack.jetpackBroken); 11 | } 12 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Mixins/ISecureDoor.cs: -------------------------------------------------------------------------------- 1 | interface ISecureDoor; 2 | 3 | static class SecureDoorMixin { 4 | static TerminalAccessibleObject[]? TerminalAccessibleObjects { get; set; } 5 | 6 | internal static void SetSecureDoorState(this ISecureDoor _, bool isUnlocked) { 7 | SecureDoorMixin.TerminalAccessibleObjects ??= Helper.FindObjects(); 8 | SecureDoorMixin.TerminalAccessibleObjects.ForEach( 9 | terminalObject => terminalObject.SetDoorOpenServerRpc(isUnlocked) 10 | ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Mixins/IShipDoor.cs: -------------------------------------------------------------------------------- 1 | interface IShipDoor; 2 | 3 | static class ShipDoorMixin { 4 | static HangarShipDoor? HangarShipDoor { get; set; } 5 | 6 | internal static void SetShipDoorState(this IShipDoor _, bool closed) { 7 | string targetAnimation = closed ? "CloseDoor" : "OpenDoor"; 8 | ShipDoorMixin.HangarShipDoor ??= Helper.FindObject(); 9 | 10 | ShipDoorMixin 11 | .HangarShipDoor? 12 | .GetComponentsInChildren() 13 | .First(trigger => trigger.GetComponent().animationString == targetAnimation)? 14 | .onInteract 15 | .Invoke(Helper.LocalPlayer); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Mixins/IStun.cs: -------------------------------------------------------------------------------- 1 | using GameNetcodeStuff; 2 | using UnityEngine; 3 | 4 | interface IStun; 5 | 6 | static class StunMixin { 7 | internal static void Stun(this IStun _, Vector3 position, float radius, float duration = 1.0f) { 8 | if (Helper.LocalPlayer is not PlayerControllerB localPlayer) return; 9 | 10 | Physics.OverlapSphere(position, radius, 524288) 11 | .ForEach(collider => { 12 | if (!collider.TryGetComponent(out EnemyAICollisionDetect enemy)) return; 13 | enemy.mainScript.ChangeEnemyOwnerServerRpc(localPlayer.actualClientId); 14 | enemy.mainScript.SetEnemyStunned(true, duration); 15 | }); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Modules/AntiKickMod.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using UnityEngine; 3 | 4 | sealed class AntiKickMod : MonoBehaviour { 5 | void OnEnable() { 6 | InputListener.OnBackslashPress += this.ToggleAntiKick; 7 | GameListener.OnGameStart += this.OnGameStart; 8 | GameListener.OnGameEnd += this.OnGameEnd; 9 | } 10 | 11 | void OnDisable() { 12 | InputListener.OnBackslashPress -= this.ToggleAntiKick; 13 | GameListener.OnGameStart -= this.OnGameStart; 14 | GameListener.OnGameEnd -= this.OnGameEnd; 15 | } 16 | 17 | static IEnumerator RejoinLobby() { 18 | if (State.ConnectedLobby is not ConnectedLobby connectedLobby) yield break; 19 | 20 | WaitForEndOfFrame waitForEndOfFrame = new(); 21 | 22 | while (Helper.FindObject() is null) { 23 | yield return waitForEndOfFrame; 24 | } 25 | 26 | while (Helper.GameNetworkManager?.currentLobby.HasValue is not false) { 27 | yield return waitForEndOfFrame; 28 | } 29 | 30 | Helper.GameNetworkManager?.JoinLobby(connectedLobby.Lobby, connectedLobby.SteamId); 31 | } 32 | 33 | void OnGameEnd() { 34 | if (State.DisconnectedVoluntarily) return; 35 | if (!Setting.EnableAntiKick) return; 36 | 37 | _ = this.StartCoroutine(AntiKickMod.RejoinLobby()); 38 | } 39 | 40 | void OnGameStart() { 41 | if (!Setting.EnableAntiKick) return; 42 | if (!Setting.EnableInvisible) return; 43 | 44 | Chat.Clear(); 45 | Helper.SendNotification( 46 | title: "Anti-kick", 47 | body: "You are invisible to other players! Do /invis to disable!", 48 | isWarning: true 49 | ); 50 | } 51 | 52 | void ToggleAntiKick() { 53 | if (Helper.LocalPlayer is not null) { 54 | Chat.Print("You cannot toggle anti-kick while in-game!"); 55 | return; 56 | } 57 | 58 | Setting.EnableAntiKick = !Setting.EnableAntiKick; 59 | Setting.EnableInvisible = Setting.EnableAntiKick; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Modules/ChatMod.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | sealed class ChatMod : MonoBehaviour { 6 | List CommandHistory { get; } = []; 7 | int HistoryIndex { get; set; } = -1; 8 | 9 | void OnEnable() { 10 | InputListener.OnUpArrowPress += this.CycleBackInHistory; 11 | InputListener.OnDownArrowPress += this.CycleForwardInHistory; 12 | Chat.OnExecuteCommandAttempt += this.OnHistoryAdded; 13 | } 14 | 15 | void OnDisable() { 16 | InputListener.OnUpArrowPress -= this.CycleBackInHistory; 17 | InputListener.OnDownArrowPress -= this.CycleForwardInHistory; 18 | Chat.OnExecuteCommandAttempt -= this.OnHistoryAdded; 19 | } 20 | 21 | void OnHistoryAdded(string command) { 22 | _ = this.CommandHistory.Remove(command); 23 | this.CommandHistory.Add(command); 24 | } 25 | 26 | void CycleBackInHistory() { 27 | if (Helper.LocalPlayer is not { isTypingChat: true }) return; 28 | if (Helper.HUDManager is not HUDManager hudManager) return; 29 | 30 | this.HistoryIndex = Math.Clamp(this.HistoryIndex + 1, 0, this.CommandHistory.Count - 1); 31 | int commandHistoryIndex = this.CommandHistory.Count - this.HistoryIndex - 1; 32 | hudManager.chatTextField.text = this.CommandHistory[commandHistoryIndex]; 33 | hudManager.chatTextField.caretPosition = hudManager.chatTextField.text.Length; 34 | } 35 | 36 | void CycleForwardInHistory() { 37 | if (this.HistoryIndex < 0) return; 38 | if (Helper.LocalPlayer is not { isTypingChat: true }) return; 39 | if (Helper.HUDManager is not HUDManager hudManager) return; 40 | 41 | this.HistoryIndex = Math.Clamp(this.HistoryIndex - 1, 0, this.CommandHistory.Count - 1); 42 | int commandHistoryIndex = this.CommandHistory.Count - this.HistoryIndex - 1; 43 | hudManager.chatTextField.text = this.CommandHistory[commandHistoryIndex]; 44 | hudManager.chatTextField.caretPosition = hudManager.chatTextField.text.Length; 45 | } 46 | 47 | void Update() => this.HistoryIndex = Helper.LocalPlayer is { isTypingChat: true } ? this.HistoryIndex : -1; 48 | } 49 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Modules/CrosshairMod.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | sealed class CrosshairMod : MonoBehaviour { 4 | const float GapSize = 7.0f; 5 | const float Thickness = 3.0f; 6 | const float Length = 10.0f; 7 | 8 | Vector2 TopCrosshairPosition { get; set; } 9 | Vector2 BottomCrosshairPosition { get; set; } 10 | Vector2 LeftCrosshairPosition { get; set; } 11 | Vector2 RightCrosshairPosition { get; set; } 12 | 13 | bool InGame { get; set; } 14 | 15 | void OnEnable() { 16 | ScreenListener.OnScreenSizeChange += this.InitialiseCrosshairPositions; 17 | GameListener.OnGameStart += this.ToggleInGame; 18 | GameListener.OnGameEnd += this.ToggleNotInGame; 19 | } 20 | 21 | void OnDisable() { 22 | ScreenListener.OnScreenSizeChange -= this.InitialiseCrosshairPositions; 23 | GameListener.OnGameStart -= this.ToggleInGame; 24 | GameListener.OnGameEnd -= this.ToggleNotInGame; 25 | } 26 | 27 | void Start() => this.InitialiseCrosshairPositions(); 28 | 29 | void OnGUI() { 30 | if (!this.InGame) return; 31 | this.RenderCrosshair(); 32 | } 33 | 34 | void ToggleInGame() => this.InGame = true; 35 | 36 | void ToggleNotInGame() => this.InGame = false; 37 | 38 | void InitialiseCrosshairPositions() { 39 | Vector2 screenCentre = Helper.GetScreenCentre(); 40 | float halfWidth = 0.5f * CrosshairMod.Thickness; 41 | float lengthToCentre = CrosshairMod.GapSize + CrosshairMod.Length; 42 | float topLeftX = screenCentre.x - halfWidth; 43 | float topLeftY = screenCentre.y - halfWidth; 44 | 45 | this.TopCrosshairPosition = new Vector2(topLeftX, screenCentre.y - lengthToCentre); 46 | this.BottomCrosshairPosition = new Vector2(topLeftX, screenCentre.y + CrosshairMod.GapSize); 47 | this.RightCrosshairPosition = new Vector2(screenCentre.x + CrosshairMod.GapSize, topLeftY); 48 | this.LeftCrosshairPosition = new Vector2(screenCentre.x - lengthToCentre, topLeftY); 49 | } 50 | 51 | void RenderCrosshair() { 52 | Size verticalSize = new() { 53 | Width = CrosshairMod.Thickness, 54 | Height = CrosshairMod.Length 55 | }; 56 | 57 | Size horizontalSize = new() { 58 | Width = CrosshairMod.Length, 59 | Height = CrosshairMod.Thickness 60 | }; 61 | 62 | Helper.DrawBox(this.TopCrosshairPosition, verticalSize); 63 | Helper.DrawBox(this.BottomCrosshairPosition, verticalSize); 64 | Helper.DrawBox(this.RightCrosshairPosition, horizontalSize); 65 | Helper.DrawBox(this.LeftCrosshairPosition, horizontalSize); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Modules/DisconnectMod.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | using Steamworks; 3 | using UnityEngine; 4 | 5 | sealed class DisconnectMod : MonoBehaviour { 6 | bool IsShiftHeld { get; set; } 7 | 8 | void OnEnable() { 9 | InputListener.OnShiftButtonHold += this.HoldShift; 10 | InputListener.OnF4Press += this.TryToDisconnect; 11 | InputListener.OnF5Press += this.TryToConnect; 12 | } 13 | 14 | void OnDisable() { 15 | InputListener.OnShiftButtonHold -= this.HoldShift; 16 | InputListener.OnF4Press -= this.TryToDisconnect; 17 | InputListener.OnF5Press -= this.TryToConnect; 18 | } 19 | 20 | void HoldShift(bool isHeld) => this.IsShiftHeld = isHeld; 21 | 22 | static SteamId? GetSteamIdFromClipboard() { 23 | string clipboardText = GUIUtility.systemCopyBuffer; 24 | 25 | return !string.IsNullOrWhiteSpace(clipboardText) && Regex.IsMatch(clipboardText, @"^\d{17}$") 26 | ? new SteamId { Value = ulong.Parse(clipboardText) } 27 | : null; 28 | } 29 | 30 | void TryToDisconnect() { 31 | if (!this.IsShiftHeld) return; 32 | if (Helper.LocalPlayer is null) return; 33 | 34 | Helper.GameNetworkManager?.Disconnect(); 35 | State.DisconnectedVoluntarily = true; 36 | } 37 | 38 | void TryToConnect() { 39 | if (!this.IsShiftHeld) return; 40 | if (DisconnectMod.GetSteamIdFromClipboard() is SteamId playerSteamId) { 41 | Helper.GameNetworkManager?.StartClient(playerSteamId); 42 | } 43 | 44 | else if (State.ConnectedLobby is ConnectedLobby connectedLobby) { 45 | Helper.GameNetworkManager?.JoinLobby(connectedLobby.Lobby, connectedLobby.SteamId); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Modules/InstantInteractMod.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using UnityEngine; 3 | 4 | sealed class InstantInteractMod : MonoBehaviour { 5 | IEnumerator SetTimeToHold(object[] args) { 6 | WaitForSeconds waitForFiveSeconds = new(5.0f); 7 | 8 | while (true) { 9 | HaxObjects.Instance?.InteractTriggers?.WhereIsNotNull().ForEach(interactTrigger => { 10 | interactTrigger.interactable = true; 11 | interactTrigger.timeToHold = interactTrigger.name is "EntranceTeleportB(Clone)" ? 0.3f : 0.0f; 12 | }); 13 | 14 | yield return waitForFiveSeconds; 15 | } 16 | } 17 | 18 | void Start() => this.StartResilientCoroutine(this.SetTimeToHold); 19 | } 20 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Modules/KillClickMod.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using GameNetcodeStuff; 3 | 4 | sealed class KillClickMod : MonoBehaviour { 5 | RaycastHit[] RaycastHits { get; set; } = new RaycastHit[100]; 6 | 7 | void OnEnable() => InputListener.OnLeftButtonPress += this.Kill; 8 | 9 | void OnDisable() => InputListener.OnLeftButtonPress -= this.Kill; 10 | 11 | void Kill() { 12 | if (!Setting.EnableKillOnLeftClick) return; 13 | if (Helper.CurrentCamera is not Camera camera) return; 14 | if (Helper.LocalPlayer is not PlayerControllerB localPlayer) return; 15 | 16 | for (int i = 0; i < this.RaycastHits.SphereCastForward(camera.transform); i++) { 17 | if (!this.RaycastHits[i].collider.TryGetComponent(out EnemyAICollisionDetect enemy)) continue; 18 | 19 | enemy.mainScript.ChangeOwnershipOfEnemy(localPlayer.actualClientId); 20 | enemy.mainScript.Kill(localPlayer.actualClientId); 21 | break; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Modules/MinimalGUIMod.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | sealed class MinimalGUIMod : MonoBehaviour { 4 | bool InGame { get; set; } 5 | 6 | void OnEnable() { 7 | GameListener.OnGameStart += this.ToggleInGame; 8 | GameListener.OnGameEnd += this.ToggleNotInGame; 9 | } 10 | 11 | void OnDisable() { 12 | GameListener.OnGameStart -= this.ToggleInGame; 13 | GameListener.OnGameEnd -= this.ToggleNotInGame; 14 | } 15 | 16 | void OnGUI() { 17 | if (this.InGame) return; 18 | 19 | string labelText = $"Anti-Kick: {(Setting.EnableAntiKick ? "On" : "Off")}"; 20 | GUIStyle labelStyle = GUI.skin.label; 21 | Vector2 labelSize = labelStyle.CalcSize(new GUIContent(labelText)); 22 | float xPosition = Screen.width - labelSize.x - 10; 23 | float yPosition = 0; 24 | Rect labelRect = new(xPosition, yPosition, labelSize.x, labelSize.y); 25 | GUI.Label(labelRect, labelText); 26 | } 27 | 28 | void ToggleInGame() => this.InGame = true; 29 | 30 | void ToggleNotInGame() => this.InGame = false; 31 | } 32 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Modules/Possession/Controllers/BaboonHawkController.cs: -------------------------------------------------------------------------------- 1 | using Unity.Netcode; 2 | using Vector3 = UnityEngine.Vector3; 3 | 4 | enum BaboonState { 5 | SCOUTING = 0, 6 | RETURNING = 1, 7 | AGGRESSIVE = 2, 8 | } 9 | 10 | sealed class BaboonHawkController : IEnemyController { 11 | Vector3 CustomCamp { get; } = new Vector3(1000.0f, 0.0f, 0.0f); 12 | Vector3 OriginalCamp { get; set; } = Vector3.zero; 13 | 14 | public void OnDeath(BaboonBirdAI enemy) { 15 | if (enemy.heldScrap is not null) { 16 | enemy.DropHeldItemAndSync(); 17 | } 18 | } 19 | 20 | public void OnPossess(BaboonBirdAI _) { 21 | if (BaboonBirdAI.baboonCampPosition != this.CustomCamp) return; 22 | 23 | this.OriginalCamp = BaboonBirdAI.baboonCampPosition; 24 | BaboonBirdAI.baboonCampPosition = this.CustomCamp; 25 | } 26 | 27 | public void OnUnpossess(BaboonBirdAI _) { 28 | if (BaboonBirdAI.baboonCampPosition == this.OriginalCamp) return; 29 | BaboonBirdAI.baboonCampPosition = this.OriginalCamp; 30 | } 31 | 32 | static void GrabItemAndSync(BaboonBirdAI enemy, GrabbableObject item) { 33 | if (!item.TryGetComponent(out NetworkObject networkObject)) return; 34 | enemy.GrabItemAndSync(networkObject); 35 | } 36 | 37 | public void UsePrimarySkill(BaboonBirdAI enemy) { 38 | if (enemy.heldScrap is null && enemy.FindNearbyItem() is GrabbableObject grabbable) { 39 | BaboonHawkController.GrabItemAndSync(enemy, grabbable); 40 | return; 41 | } 42 | 43 | if (enemy.heldScrap is ShotgunItem shotgun) { 44 | shotgun.ShootShotgun(enemy.transform); 45 | return; 46 | } 47 | } 48 | 49 | public void UseSecondarySkill(BaboonBirdAI enemy) { 50 | if (enemy.heldScrap is null) return; 51 | enemy.DropHeldItemAndSync(); 52 | } 53 | 54 | public string GetPrimarySkillName(BaboonBirdAI enemy) => enemy.heldScrap is not null ? "" : "Grab Item"; 55 | 56 | public string GetSecondarySkillName(BaboonBirdAI enemy) => enemy.heldScrap is null ? "" : "Drop item"; 57 | 58 | public float InteractRange(BaboonBirdAI _) => 1.5f; 59 | } 60 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Modules/Possession/Controllers/BrackenController.cs: -------------------------------------------------------------------------------- 1 | enum BrackenState { 2 | SCOUTING, 3 | STAND, 4 | ANGER 5 | } 6 | 7 | sealed class BrackenController : IEnemyController { 8 | public void UsePrimarySkill(FlowermanAI enemy) { 9 | if (!enemy.carryingPlayerBody) { 10 | enemy.SetBehaviourState(BrackenState.ANGER); 11 | } 12 | 13 | enemy.DropPlayerBodyServerRpc(); 14 | } 15 | 16 | public void UseSecondarySkill(FlowermanAI enemy) => enemy.SetBehaviourState(BrackenState.STAND); 17 | 18 | public void ReleaseSecondarySkill(FlowermanAI enemy) => enemy.SetBehaviourState(BrackenState.SCOUTING); 19 | 20 | public bool IsAbleToMove(FlowermanAI enemy) => !enemy.inSpecialAnimation; 21 | 22 | public string GetPrimarySkillName(FlowermanAI enemy) => enemy.carryingPlayerBody ? "Drop body" : ""; 23 | 24 | public string GetSecondarySkillName(FlowermanAI _) => "Stand"; 25 | 26 | public float InteractRange(FlowermanAI _) => 1.5f; 27 | 28 | public bool SyncAnimationSpeedEnabled(FlowermanAI _) => false; 29 | } 30 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Modules/Possession/Controllers/CircuitBeesController.cs: -------------------------------------------------------------------------------- 1 | enum BeesState { 2 | IDLE, 3 | DEFENSIVE, 4 | ATTACK 5 | } 6 | 7 | sealed class CircuitBeesController : IEnemyController { 8 | public bool CanUseEntranceDoors(RedLocustBees _) => true; 9 | 10 | public float InteractRange(RedLocustBees _) => 2.5f; 11 | 12 | public void UsePrimarySkill(RedLocustBees enemy) { 13 | enemy.SetBehaviourState(BeesState.ATTACK); 14 | enemy.EnterAttackZapModeServerRpc(-1); 15 | } 16 | 17 | public void UseSecondarySkill(RedLocustBees enemy) => enemy.SetBehaviourState(BeesState.IDLE); 18 | } 19 | 20 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Modules/Possession/Controllers/CoilHeadController.cs: -------------------------------------------------------------------------------- 1 | sealed class CoilHeadController : IEnemyController { 2 | static bool GetStoppingMovement(SpringManAI enemy) => enemy.stoppingMovement; 3 | 4 | public void OnSecondarySkillHold(SpringManAI enemy) => enemy.SetAnimationGoServerRpc(); 5 | 6 | public void ReleaseSecondarySkill(SpringManAI enemy) => enemy.SetAnimationStopServerRpc(); 7 | 8 | public bool IsAbleToMove(SpringManAI enemy) => !CoilHeadController.GetStoppingMovement(enemy); 9 | 10 | public bool IsAbleToRotate(SpringManAI enemy) => !CoilHeadController.GetStoppingMovement(enemy); 11 | 12 | public float InteractRange(SpringManAI _) => 1.5f; 13 | } 14 | 15 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Modules/Possession/Controllers/CrawlerController.cs: -------------------------------------------------------------------------------- 1 | sealed class CrawlerController : IEnemyController { 2 | public float InteractRange(CrawlerAI _) => 1.5f; 3 | 4 | public bool SyncAnimationSpeedEnabled(CrawlerAI _) => false; 5 | 6 | public void UseSecondarySkill(CrawlerAI enemy) => enemy.MakeScreechNoiseServerRpc(); 7 | 8 | public void UsePrimarySkill(CrawlerAI enemy) => enemy.CollideWithWallServerRpc(); 9 | } 10 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Modules/Possession/Controllers/EarthLeviathanController.cs: -------------------------------------------------------------------------------- 1 | sealed class EarthLeviathanController : IEnemyController { 2 | static bool IsEmerged(SandWormAI enemy) => enemy.inEmergingState || enemy.emerged; 3 | 4 | public void UseSecondarySkill(SandWormAI enemy) { 5 | if (EarthLeviathanController.IsEmerged(enemy)) return; 6 | enemy.StartEmergeAnimation(); 7 | } 8 | 9 | public string GetSecondarySkillName(SandWormAI _) => "Emerge"; 10 | 11 | public bool CanUseEntranceDoors(SandWormAI _) => false; 12 | 13 | public float InteractRange(SandWormAI _) => 0.0f; 14 | 15 | public bool SyncAnimationSpeedEnabled(SandWormAI _) => false; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Modules/Possession/Controllers/EyelessDogController.cs: -------------------------------------------------------------------------------- 1 | enum DogState { 2 | ROAMING, 3 | SUSPICIOUS, 4 | CHASE, 5 | LUNGE 6 | } 7 | 8 | sealed class EyelessDogController : IEnemyController { 9 | public void OnMovement(MouthDogAI enemy, bool isMoving, bool isSprinting) { 10 | if (!isSprinting) { 11 | if (!isMoving) return; 12 | enemy.SetBehaviourState(DogState.ROAMING); 13 | } 14 | 15 | else { 16 | enemy.SetBehaviourState(DogState.CHASE); 17 | } 18 | } 19 | 20 | public void UseSecondarySkill(MouthDogAI enemy) => enemy.SetBehaviourState(DogState.LUNGE); 21 | 22 | public string GetSecondarySkillName(MouthDogAI _) => "Lunge"; 23 | 24 | public float InteractRange(MouthDogAI _) => 2.5f; 25 | } 26 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Modules/Possession/Controllers/ForestGiantController.cs: -------------------------------------------------------------------------------- 1 | enum GiantState { 2 | DEFAULT = 0, 3 | CHASE = 1 4 | } 5 | 6 | sealed class ForestGiantController : IEnemyController { 7 | bool IsUsingSecondarySkill { get; set; } 8 | 9 | public void OnMovement(ForestGiantAI enemy, bool isMoving, bool isSprinting) { 10 | if (!this.IsUsingSecondarySkill) { 11 | enemy.SetBehaviourState(GiantState.DEFAULT); 12 | } 13 | } 14 | 15 | public void OnSecondarySkillHold(ForestGiantAI enemy) { 16 | this.IsUsingSecondarySkill = true; 17 | enemy.SetBehaviourState(GiantState.CHASE); 18 | } 19 | 20 | public void ReleaseSecondarySkill(ForestGiantAI enemy) { 21 | this.IsUsingSecondarySkill = false; 22 | enemy.SetBehaviourState(GiantState.DEFAULT); 23 | } 24 | 25 | public bool IsAbleToMove(ForestGiantAI enemy) => !enemy.inEatingPlayerAnimation; 26 | 27 | public string GetSecondarySkillName(ForestGiantAI _) => "(HOLD) Chase"; 28 | 29 | public bool CanUseEntranceDoors(ForestGiantAI _) => false; 30 | 31 | public float InteractRange(ForestGiantAI _) => 0.0f; 32 | 33 | public void OnUnpossess(ForestGiantAI enemy) => this.IsUsingSecondarySkill = false; 34 | 35 | public bool SyncAnimationSpeedEnabled(ForestGiantAI _) => false; 36 | } 37 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Modules/Possession/Controllers/HygrodereController.cs: -------------------------------------------------------------------------------- 1 | sealed class HygrodereController : IEnemyController { 2 | static void SetTamedTimer(BlobAI enemy, float time) => enemy.tamedTimer = time; 3 | 4 | static void SetAngeredTimer(BlobAI enemy, float time) => enemy.angeredTimer = time; 5 | 6 | public void OnSecondarySkillHold(BlobAI enemy) { 7 | HygrodereController.SetAngeredTimer(enemy, 0.0f); 8 | HygrodereController.SetTamedTimer(enemy, 2.0f); 9 | } 10 | 11 | public void ReleaseSecondarySkill(BlobAI enemy) => HygrodereController.SetTamedTimer(enemy, 0.0f); 12 | 13 | public void UsePrimarySkill(BlobAI enemy) => HygrodereController.SetAngeredTimer(enemy, 18.0f); 14 | 15 | public float InteractRange(BlobAI _) => 3.5f; 16 | 17 | public float SprintMultiplier(BlobAI _) => 9.8f; 18 | } 19 | 20 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Modules/Possession/Controllers/JesterEnemyController.cs: -------------------------------------------------------------------------------- 1 | enum JesterState { 2 | CLOSED, 3 | CRANKING, 4 | OPEN 5 | } 6 | 7 | sealed class JesterController : IEnemyController { 8 | static void SetNoPlayerChasetimer(JesterAI enemy, float value) => enemy.noPlayersToChaseTimer = value; 9 | 10 | public void UsePrimarySkill(JesterAI enemy) { 11 | enemy.SetBehaviourState(JesterState.CLOSED); 12 | SetNoPlayerChasetimer(enemy, 0.0f); 13 | } 14 | 15 | public void OnSecondarySkillHold(JesterAI enemy) { 16 | if (!enemy.IsBehaviourState(JesterState.CLOSED)) return; 17 | enemy.SetBehaviourState(JesterState.CRANKING); 18 | } 19 | 20 | public void ReleaseSecondarySkill(JesterAI enemy) { 21 | if (!enemy.IsBehaviourState(JesterState.CRANKING)) return; 22 | enemy.SetBehaviourState(JesterState.OPEN); 23 | } 24 | 25 | public void Update(JesterAI enemy, bool isAIControlled) => JesterController.SetNoPlayerChasetimer(enemy, 100.0f); 26 | 27 | public void OnUnpossess(JesterAI enemy) => JesterController.SetNoPlayerChasetimer(enemy, 5.0f); 28 | 29 | public bool IsAbleToMove(JesterAI enemy) => !enemy.IsBehaviourState(JesterState.CRANKING); 30 | 31 | public bool IsAbleToRotate(JesterAI enemy) => !enemy.IsBehaviourState(JesterState.CRANKING); 32 | 33 | public string GetPrimarySkillName(JesterAI _) => "Close box"; 34 | 35 | public string GetSecondarySkillName(JesterAI _) => "(HOLD) Begin cranking"; 36 | 37 | public float InteractRange(JesterAI _) => 1.0f; 38 | } 39 | 40 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Modules/Possession/Controllers/LassoManController.cs: -------------------------------------------------------------------------------- 1 | sealed class LassoManController : IEnemyController { 2 | public void UsePrimarySkill(LassoManAI enemy) => enemy.MakeScreechNoiseServerRpc(); 3 | 4 | public bool SyncAnimationSpeedEnabled(LassoManAI enemy) => false; 5 | } 6 | 7 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Modules/Possession/Controllers/MaskedController.cs: -------------------------------------------------------------------------------- 1 | sealed class MaskedPlayerController : IEnemyController { 2 | public void UsePrimarySkill(MaskedPlayerEnemy enemy) => enemy.SetHandsOutServerRpc(!enemy.creatureAnimator.GetBool("HandsOut")); 3 | 4 | public void UseSecondarySkill(MaskedPlayerEnemy enemy) => enemy.SetCrouchingServerRpc(!enemy.creatureAnimator.GetBool("Crouching")); 5 | 6 | public float InteractRange(MaskedPlayerEnemy _) => 1.0f; 7 | 8 | public bool SyncAnimationSpeedEnabled(MaskedPlayerEnemy _) => false; 9 | } 10 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Modules/Possession/Controllers/NutcrackerController.cs: -------------------------------------------------------------------------------- 1 | enum NutcrackerState { 2 | WALKING, 3 | SENTRY 4 | } 5 | 6 | sealed class NutcrackerController : IEnemyController { 7 | bool InSentryMode { get; set; } 8 | 9 | public void Update(NutcrackerEnemyAI enemy, bool isAIControlled) { 10 | if (isAIControlled) return; 11 | if (this.InSentryMode) return; 12 | enemy.SwitchToBehaviourServerRpc(unchecked((int)NutcrackerState.WALKING)); // See #415 13 | } 14 | 15 | public bool IsAbleToRotate(NutcrackerEnemyAI enemy) => !enemy.IsBehaviourState(NutcrackerState.SENTRY); 16 | 17 | public bool IsAbleToMove(NutcrackerEnemyAI enemy) => !enemy.IsBehaviourState(NutcrackerState.SENTRY); 18 | 19 | public void UsePrimarySkill(NutcrackerEnemyAI enemy) { 20 | if (enemy.gun is not ShotgunItem shotgun) return; 21 | 22 | shotgun.gunShootAudio.volume = 0.25f; 23 | enemy.FireGunServerRpc(); 24 | } 25 | 26 | public void OnSecondarySkillHold(NutcrackerEnemyAI enemy) { 27 | enemy.SetBehaviourState(NutcrackerState.SENTRY); 28 | this.InSentryMode = true; 29 | } 30 | 31 | public void ReleaseSecondarySkill(NutcrackerEnemyAI enemy) { 32 | enemy.SetBehaviourState(NutcrackerState.WALKING); 33 | this.InSentryMode = false; 34 | } 35 | 36 | public void OnUnpossess(NutcrackerEnemyAI enemy) => this.InSentryMode = false; 37 | 38 | public string GetPrimarySkillName(NutcrackerEnemyAI enemy) => enemy.gun is null ? "" : "Fire"; 39 | 40 | public string GetSecondarySkillName(NutcrackerEnemyAI _) => "(HOLD) Sentry mode"; 41 | 42 | public float InteractRange(NutcrackerEnemyAI _) => 1.5f; 43 | } 44 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Modules/Possession/Controllers/SandSpiderController.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | sealed class SandSpiderController : IEnemyController { 4 | public void OnMovement(SandSpiderAI enemy, bool isMoving, bool isSprinting) { 5 | enemy.creatureAnimator.SetBool("moving", true); 6 | // spider is too slow, make it like 6f default, 8f sprinting 7 | float speed = isSprinting ? 8.0f : 6.0f; 8 | enemy.agent.speed = speed; 9 | enemy.spiderSpeed = speed; 10 | enemy.SyncMeshContainerPositionToClients(); 11 | } 12 | 13 | public void UsePrimarySkill(SandSpiderAI enemy) => SandSpiderController.PlaceWebTrap(enemy); 14 | 15 | static void PlaceWebTrap(SandSpiderAI enemy) { 16 | if (Helper.StartOfRound is not StartOfRound startOfRound) return; 17 | 18 | Vector3 randomDirection = Random.onUnitSphere; 19 | randomDirection.y = Mathf.Min(0.0f, randomDirection.y * Random.Range(0.5f, 1.0f)); 20 | 21 | Ray ray = new(enemy.abdomen.position + (Vector3.up * 0.4f), randomDirection); 22 | 23 | if (!Physics.Raycast(ray, out RaycastHit raycastHit, 7.0f, startOfRound.collidersAndRoomMask)) { 24 | return; 25 | } 26 | 27 | if (raycastHit.distance < 2.0f) { 28 | return; 29 | } 30 | 31 | if (!Physics.Raycast(enemy.abdomen.position, Vector3.down, out RaycastHit groundHit, 10.0f, startOfRound.collidersAndRoomMask)) { 32 | return; 33 | } 34 | 35 | Vector3 floorPosition = groundHit.point + (Vector3.up * 0.2f); 36 | enemy.SpawnWebTrapServerRpc(floorPosition, raycastHit.point); 37 | } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Modules/Possession/Controllers/SnareFleaController.cs: -------------------------------------------------------------------------------- 1 | enum SnareFleaState { 2 | SEARCHING, 3 | HIDING, 4 | CHASING, 5 | CLINGING 6 | } 7 | 8 | sealed class SnareFleaController : IEnemyController { 9 | static bool IsClingingToSomething(CentipedeAI enemy) { 10 | return enemy.clingingToPlayer is not null || enemy.inSpecialAnimation || 11 | enemy.clingingToDeadBody || 12 | enemy.clingingToCeiling || 13 | enemy.startedCeilingAnimationCoroutine || 14 | enemy.inDroppingOffPlayerAnim; 15 | } 16 | 17 | public void UsePrimarySkill(CentipedeAI enemy) { 18 | if (!enemy.IsBehaviourState(SnareFleaState.HIDING)) return; 19 | enemy.SetBehaviourState(SnareFleaState.CHASING); 20 | } 21 | 22 | public void UseSecondarySkill(CentipedeAI enemy) { 23 | if (SnareFleaController.IsClingingToSomething(enemy)) return; 24 | 25 | enemy.RaycastToCeiling(); 26 | enemy.SetBehaviourState(SnareFleaState.HIDING); 27 | } 28 | 29 | public bool IsAbleToMove(CentipedeAI enemy) => !IsClingingToSomething(enemy); 30 | 31 | public bool IsAbleToRotate(CentipedeAI enemy) => !IsClingingToSomething(enemy); 32 | 33 | public string GetPrimarySkillName(CentipedeAI _) => "Drop"; 34 | 35 | public string GetSecondarySkillName(CentipedeAI _) => "Attach to ceiling"; 36 | 37 | public float InteractRange(CentipedeAI _) => 1.5f; 38 | 39 | public bool CanUseEntranceDoors(CentipedeAI _) => false; 40 | 41 | public bool SyncAnimationSpeedEnabled(CentipedeAI _) => false; 42 | } 43 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Modules/Possession/Controllers/SporeLizardController.cs: -------------------------------------------------------------------------------- 1 | public enum SporeLizardState { 2 | IDLE, 3 | ALERTED, 4 | HOSTILE 5 | } 6 | 7 | sealed class SporeLizardController : IEnemyController { 8 | public void UsePrimarySkill(PufferAI enemy) { 9 | enemy.SetBehaviourState(SporeLizardState.HOSTILE); 10 | enemy.StompServerRpc(); 11 | } 12 | 13 | public void UseSecondarySkill(PufferAI enemy) { 14 | enemy.SetBehaviourState(SporeLizardState.HOSTILE); 15 | enemy.ShakeTailServerRpc(); 16 | } 17 | 18 | public string GetPrimarySkillName(PufferAI _) => "Stomp"; 19 | 20 | public string GetSecondarySkillName(PufferAI _) => "Smoke"; 21 | 22 | public float InteractRange(PufferAI _) => 2.5f; 23 | } 24 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Modules/Possession/Controllers/TestEnemyController.cs: -------------------------------------------------------------------------------- 1 | sealed class TestEnemyController : IEnemyController { 2 | public bool CanUseEntranceDoors(TestEnemy _) => true; 3 | 4 | public float InteractRange(TestEnemy _) => 4.5f; 5 | } 6 | 7 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Modules/Possession/Possession.cs: -------------------------------------------------------------------------------- 1 | sealed class Possession() { 2 | internal EnemyAI? Enemy { get; private set; } 3 | 4 | internal bool IsPossessed => this.Enemy is not null; 5 | 6 | internal void SetEnemy(EnemyAI enemy) => this.Enemy = enemy; 7 | 8 | internal void Clear() => this.Enemy = null; 9 | } 10 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Modules/SaneMod.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using GameNetcodeStuff; 3 | using UnityEngine; 4 | 5 | sealed class SaneMod : MonoBehaviour { 6 | IEnumerator SetSanity(object[] args) { 7 | WaitForEndOfFrame waitForEndOfFrame = new(); 8 | 9 | while (true) { 10 | if (Helper.LocalPlayer is not PlayerControllerB localPlayer) { 11 | yield return waitForEndOfFrame; 12 | continue; 13 | } 14 | 15 | localPlayer.playersManager.gameStats.allPlayerStats[localPlayer.playerClientId].turnAmount = 0; 16 | localPlayer.playersManager.fearLevel = 0.0f; 17 | localPlayer.playersManager.fearLevelIncreasing = false; 18 | localPlayer.insanityLevel = 0.0f; 19 | localPlayer.insanitySpeedMultiplier = 0.0f; 20 | 21 | yield return waitForEndOfFrame; 22 | } 23 | } 24 | 25 | void Start() => this.StartResilientCoroutine(this.SetSanity); 26 | } 27 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Modules/StaminaMod.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using UnityEngine; 3 | using GameNetcodeStuff; 4 | 5 | sealed class StaminaMod : MonoBehaviour { 6 | IEnumerator SetSprint(object[] args) { 7 | WaitForEndOfFrame waitForEndOfFrame = new(); 8 | WaitForSeconds waitForFiveSeconds = new(5.0f); 9 | 10 | while (true) { 11 | if (Helper.LocalPlayer is not PlayerControllerB player) { 12 | yield return waitForEndOfFrame; 13 | continue; 14 | } 15 | 16 | player.isSpeedCheating = false; 17 | player.isSprinting = false; 18 | player.isExhausted = false; 19 | player.sprintMeter = 1.0f; 20 | 21 | yield return waitForFiveSeconds; 22 | } 23 | } 24 | 25 | void Start() => this.StartResilientCoroutine(this.SetSprint); 26 | } 27 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Modules/StunClickMod.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using ZLinq; 3 | 4 | sealed class StunClickMod : MonoBehaviour { 5 | Collider[] Colliders { get; set; } = new Collider[100]; 6 | RaycastHit[] RaycastHits { get; set; } = new RaycastHit[100]; 7 | 8 | void OnEnable() => InputListener.OnLeftButtonPress += this.Stun; 9 | 10 | void OnDisable() => InputListener.OnLeftButtonPress -= this.Stun; 11 | 12 | static bool IsHoldingADefensiveWeapon() => 13 | Helper.LocalPlayer?.currentlyHeldObjectServer.itemProperties is { isDefensiveWeapon: true }; 14 | 15 | static void StunJam(Collider collider) { 16 | if (collider.TryGetComponent(out EnemyAICollisionDetect enemy)) { 17 | enemy.mainScript.SetEnemyStunned(true, 5.0f); 18 | } 19 | 20 | if (!collider.TryGetComponent(out Turret _) && !collider.TryGetComponent(out Landmine _)) { 21 | return; 22 | } 23 | 24 | if (!collider.TryGetComponent(out TerminalAccessibleObject terminalAccessibleObject)) { 25 | return; 26 | } 27 | 28 | terminalAccessibleObject.CallFunctionFromTerminal(); 29 | } 30 | 31 | void Stun() { 32 | if (!Setting.EnableStunOnLeftClick) return; 33 | if (Helper.CurrentCamera is not Camera camera) return; 34 | if (StunClickMod.IsHoldingADefensiveWeapon()) return; 35 | 36 | foreach (int i in this.RaycastHits.SphereCastForward(camera.transform).Range()) { 37 | Collider collider = this.RaycastHits[i].collider; 38 | StunClickMod.StunJam(collider); 39 | } 40 | 41 | foreach (int i in Physics.OverlapSphereNonAlloc(camera.transform.position, 5.0f, this.Colliders).Range()) { 42 | Collider collider = this.Colliders[i]; 43 | StunClickMod.StunJam(collider); 44 | } 45 | 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Modules/WeightMod.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using GameNetcodeStuff; 3 | using UnityEngine; 4 | 5 | sealed class WeightMod : MonoBehaviour { 6 | IEnumerator SetWeight(object[] args) { 7 | WaitForEndOfFrame waitForEndOfFrame = new(); 8 | WaitForSeconds waitForOneSecond = new(1.0f); 9 | 10 | while (true) { 11 | if (Helper.LocalPlayer is not PlayerControllerB player) { 12 | yield return waitForEndOfFrame; 13 | continue; 14 | } 15 | 16 | player.carryWeight = 1.0f; 17 | yield return waitForOneSecond; 18 | } 19 | } 20 | 21 | void Start() => this.StartResilientCoroutine(this.SetWeight); 22 | } 23 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Patches/AntiFlashPatch.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE1006 2 | 3 | using HarmonyLib; 4 | 5 | sealed class AntiFlashPatch { 6 | [HarmonyPatch(typeof(HUDManager), "Update")] 7 | static void Prefix(HUDManager __instance) => __instance.flashFilter = 0.0f; 8 | 9 | [HarmonyPatch(typeof(SoundManager), "SetEarsRinging")] 10 | static bool Prefix() => false; 11 | } 12 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Patches/AntiKickPatch.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE1006 2 | 3 | using HarmonyLib; 4 | using Steamworks; 5 | using GameNetcodeStuff; 6 | 7 | [HarmonyPatch(typeof(PlayerControllerB))] 8 | sealed class AntiKickPatch { 9 | [HarmonyPatch("SendNewPlayerValuesServerRpc")] 10 | static bool Prefix(PlayerControllerB __instance) { 11 | if (!Setting.EnableAntiKick) return true; 12 | 13 | Helper.GameNetworkManager?.currentLobby?.Members.ForEach((i, member) => { 14 | if (Helper.LocalPlayer?.playerSteamId == member.Id.Value) return; 15 | 16 | PlayerControllerB player = __instance.playersManager.allPlayerScripts[i]; 17 | player.playerSteamId = member.Id.Value; 18 | player.playerUsername = member.Name; 19 | player.usernameBillboardText.text = member.Name; 20 | __instance.playersManager.mapScreen.radarTargets[i].name = player.playerUsername; 21 | __instance.quickMenuManager.AddUserToPlayerList(player.playerSteamId, player.playerUsername, i); 22 | }); 23 | 24 | return false; 25 | } 26 | 27 | [HarmonyPatch("SendNewPlayerValuesClientRpc")] 28 | static void Postfix(PlayerControllerB __instance) { 29 | if (!Setting.EnableAntiKick) return; 30 | if (Helper.LocalPlayer is not PlayerControllerB localPlayer) return; 31 | 32 | localPlayer.playerSteamId = SteamClient.SteamId; 33 | localPlayer.playerUsername = SteamClient.Name; 34 | localPlayer.usernameBillboardText.text = SteamClient.Name; 35 | __instance.playersManager.mapScreen.radarTargets[localPlayer.PlayerIndex()].name = localPlayer.playerUsername; 36 | __instance.quickMenuManager.AddUserToPlayerList(localPlayer.playerSteamId, localPlayer.playerUsername, localPlayer.PlayerIndex()); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Patches/CommandPatch.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE1006 2 | 3 | using GameNetcodeStuff; 4 | using HarmonyLib; 5 | using ZLinq; 6 | 7 | [HarmonyPatch(typeof(HUDManager), "EnableChat_performed")] 8 | sealed class EnableChatPatch { 9 | static void Prefix(HUDManager __instance, ref bool __state) { 10 | if (__instance.localPlayer is not PlayerControllerB localPlayer) return; 11 | 12 | __instance.chatTextField.characterLimit = int.MaxValue; 13 | __state = localPlayer.isPlayerDead; 14 | localPlayer.isPlayerDead = false; 15 | } 16 | 17 | static void Postfix(ref PlayerControllerB ___localPlayer, bool __state) => ___localPlayer.isPlayerDead = __state; 18 | } 19 | 20 | [HarmonyBefore] 21 | [HarmonyPatch(typeof(HUDManager), "SubmitChat_performed")] 22 | sealed class SubmitChatPatch { 23 | static bool Prefix(HUDManager __instance, ref bool __state) { 24 | __state = __instance.localPlayer.isPlayerDead; 25 | __instance.localPlayer.isPlayerDead = false; 26 | 27 | if (Helper.HUDManager is not HUDManager hudManager) { 28 | return true; 29 | } 30 | 31 | if (!new[] { '!', State.CommandPrefix }.AsValueEnumerable().Any(hudManager.chatTextField.text.StartsWith)) { 32 | return true; 33 | } 34 | 35 | Chat.ExecuteCommand(hudManager.chatTextField.text.TrimEnd()); 36 | 37 | return false; 38 | } 39 | 40 | static void Postfix(HUDManager __instance, bool __state) => __instance.localPlayer.isPlayerDead = __state; 41 | } 42 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Patches/DeathNotificationPatch.cs: -------------------------------------------------------------------------------- 1 | using GameNetcodeStuff; 2 | using HarmonyLib; 3 | 4 | [HarmonyPatch(typeof(PlayerControllerB), "KillPlayerClientRpc")] 5 | sealed class DeathNotificationPatch { 6 | static void Postfix(int playerId, int causeOfDeath) { 7 | if (Helper.GetPlayer(playerId) is not PlayerControllerB player) return; 8 | if (player.IsSelf()) return; 9 | 10 | Helper.SendNotification( 11 | title: player.playerUsername, 12 | body: $"Killed by {(CauseOfDeath)causeOfDeath}", 13 | isWarning: true 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Patches/Dependencies/EnemyDependencyPatch.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE1006 2 | 3 | using HarmonyLib; 4 | 5 | [HarmonyPatch(typeof(EnemyAI))] 6 | sealed class EnemyDependencyPatch { 7 | [HarmonyPatch(nameof(EnemyAI.OnDestroy))] 8 | static void Prefix(EnemyAI __instance) => Helper.Enemies.Remove(__instance); 9 | 10 | [HarmonyPatch(nameof(EnemyAI.Start))] 11 | static void Postfix(EnemyAI __instance) => Helper.Enemies.Add(__instance); 12 | } 13 | 14 | [HarmonyPatch(typeof(MaskedPlayerEnemy))] 15 | sealed class MaskedDependencyPatch { 16 | [HarmonyPatch(nameof(MaskedPlayerEnemy.OnDestroy))] 17 | static void Prefix(MaskedPlayerEnemy __instance) => Helper.Enemies.Remove(__instance); 18 | 19 | [HarmonyPatch(nameof(MaskedPlayerEnemy.Start))] 20 | static void Postfix(MaskedPlayerEnemy __instance) => Helper.Enemies.Add(__instance); 21 | } 22 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Patches/Dependencies/GrabbableDependencyPatch.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE1006 2 | 3 | using Unity.Netcode; 4 | using HarmonyLib; 5 | 6 | [HarmonyPatch] 7 | sealed class GrabbableDependencyPatch { 8 | [HarmonyPatch(typeof(GrabbableObject), nameof(GrabbableObject.Update))] 9 | static void Postfix(GrabbableObject __instance) => Helper.Grabbables.Add(__instance); 10 | 11 | [HarmonyPatch(typeof(NetworkBehaviour), nameof(NetworkBehaviour.OnDestroy))] 12 | static void Prefix(NetworkBehaviour __instance) { 13 | if (__instance is GrabbableObject grabbableObject) { 14 | _ = Helper.Grabbables.Remove(grabbableObject); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Patches/Dependencies/LevelDependencyPatch.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using HarmonyLib; 3 | 4 | [HarmonyPatch(typeof(RoundManager), nameof(RoundManager.FinishGeneratingNewLevelClientRpc))] 5 | sealed class LevelDependencyPatch { 6 | internal static event Action? OnFinishLevelGeneration; 7 | 8 | static void Postfix() => LevelDependencyPatch.OnFinishLevelGeneration?.Invoke(); 9 | } 10 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Patches/Dependencies/LobbyDependencyPatch.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | using UnityEngine; 3 | using Steamworks; 4 | using Steamworks.Data; 5 | using HarmonyLib; 6 | 7 | [HarmonyPatch(typeof(GameNetworkManager), nameof(GameNetworkManager.JoinLobby))] 8 | sealed class LobbyDependencyPatch { 9 | static void Postfix(Lobby lobby, SteamId id) { 10 | if (State.DisconnectedVoluntarily && Regex.IsMatch(GUIUtility.systemCopyBuffer, @"^\d{17}$")) { 11 | GUIUtility.systemCopyBuffer = ""; 12 | } 13 | 14 | State.ConnectedLobby = new ConnectedLobby() { 15 | Lobby = lobby, 16 | SteamId = id 17 | }; 18 | 19 | State.DisconnectedVoluntarily = false; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Patches/Dependencies/MenuDependencyPatch.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | 3 | [HarmonyPatch(typeof(QuickMenuManager), nameof(QuickMenuManager.LeaveGameConfirm))] 4 | sealed class MenuDependencyPatch { 5 | static void Postfix() => State.DisconnectedVoluntarily = true; 6 | } 7 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Patches/DepthOfFieldPatch.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE1006 2 | 3 | using HarmonyLib; 4 | using UnityEngine.Rendering.HighDefinition; 5 | 6 | [HarmonyPatch(typeof(DepthOfField), nameof(DepthOfField.IsActive))] 7 | sealed class DepthOfFieldPatch { 8 | static bool Prefix(ref bool __result) { 9 | __result = false; 10 | return false; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Patches/EnemyPatch.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE1006 2 | 3 | using HarmonyLib; 4 | 5 | [HarmonyPatch(typeof(EnemyAI), nameof(EnemyAI.SyncPositionToClients))] 6 | sealed class EnemyPatch { 7 | static void Prefix(EnemyAI __instance) => __instance.updatePositionThreshold = 0.0f; 8 | } 9 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Patches/FakeDeathPatch.cs: -------------------------------------------------------------------------------- 1 | using GameNetcodeStuff; 2 | using HarmonyLib; 3 | 4 | [HarmonyPatch(typeof(PlayerControllerB), "KillPlayerClientRpc")] 5 | sealed class FakeDeathPatch { 6 | static bool Prefix(int playerId) { 7 | if (!Setting.EnableFakeDeath) return true; 8 | 9 | Setting.EnableFakeDeath = false; 10 | return Helper.LocalPlayer?.PlayerIndex() != playerId; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Patches/Fixes/HoarderBugAIFixPatch.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE1006 2 | 3 | using HarmonyLib; 4 | using Unity.Netcode; 5 | 6 | [HarmonyPatch(typeof(HoarderBugAI))] 7 | sealed class HoarderBugAIFixPatch { 8 | [HarmonyPatch(nameof(HoarderBugAI.HitEnemy))] 9 | static void Prefix(HoarderBugAI __instance) { 10 | if (!__instance.isEnemyDead) return; 11 | if (!__instance.heldItem.itemGrabbableObject.TryGetComponent(out NetworkObject networkObject)) return; 12 | 13 | __instance.DropItemAndCallDropRPC(networkObject, false); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Patches/Fixes/WaitForShipFixPatch.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using UnityEngine; 3 | using HarmonyLib; 4 | 5 | // Sometimes, especially in bigger lobbies, the ship stays forever deadlocked on "Wait for ship to land" because the server failed to receive a 6 | // PlayerHasRevivedServerRpc from one of the players at end of round. This patch sends another RPC a few seconds after round end, which increments 7 | // the playersRevived property, satisfying the server's "playersRevived >= GameNetworkManager.Instance.connectedPlayers" WaitUntil condition. 8 | 9 | [HarmonyPatch(typeof(StartOfRound), "EndOfGame")] 10 | sealed class WaitForShipFixPatch { 11 | static IEnumerator Postfix(IEnumerator endOfGame) { 12 | if (Helper.StartOfRound is not StartOfRound startOfRound) yield break; 13 | if (Helper.FindObject() is not StartMatchLever startMatchLever) yield break; 14 | 15 | while (endOfGame.MoveNext()) { 16 | yield return endOfGame.Current; 17 | } 18 | 19 | yield return new WaitUntil(() => !startOfRound.shipIsLeaving); 20 | yield return new WaitForSeconds(5.0f); // Wait a bit to give it a chance to fix itself 21 | bool isLeverBroken = true; 22 | 23 | while (isLeverBroken) { 24 | yield return new WaitForSeconds(Random.Range(2.0f, 5.0f)); // Make lc-hax users not send it all at the same time 25 | 26 | if (startOfRound.travellingToNewLevel) { 27 | continue; 28 | } 29 | 30 | if (startOfRound.inShipPhase && !startMatchLever.triggerScript.interactable) { 31 | startOfRound.PlayerHasRevivedServerRpc(); 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Patches/GiftBoxPatch.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE1006 2 | #pragma warning disable IDE0060 3 | 4 | using HarmonyLib; 5 | using Unity.Netcode; 6 | using UnityEngine; 7 | 8 | [HarmonyPatch(typeof(GiftBoxItem))] 9 | sealed class GiftBoxPatch { 10 | static bool LocalPlayerActivated { get; set; } 11 | 12 | [HarmonyPatch(nameof(GiftBoxItem.ItemActivate))] 13 | static bool Prefix(GiftBoxItem __instance) { 14 | GiftBoxPatch.LocalPlayerActivated = true; 15 | __instance.OpenGiftBoxServerRpc(); 16 | return false; 17 | } 18 | 19 | [HarmonyPatch(nameof(GiftBoxItem.OpenGiftBoxClientRpc))] 20 | static bool Prefix(NetworkObjectReference netObjectRef, int presentValue, Vector3 startFallingPos) { 21 | bool skipFlag = !GiftBoxPatch.LocalPlayerActivated; 22 | GiftBoxPatch.LocalPlayerActivated = false; 23 | return skipFlag; 24 | } 25 | 26 | [HarmonyPatch(nameof(GiftBoxItem.OpenGiftBoxNoPresentClientRpc))] 27 | static bool Prefix() => false; 28 | } 29 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Patches/GodModePatch.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE1006 2 | 3 | using GameNetcodeStuff; 4 | using HarmonyLib; 5 | 6 | [HarmonyPatch(typeof(PlayerControllerB), nameof(PlayerControllerB.AllowPlayerDeath))] 7 | sealed class GodModePatch { 8 | static bool Prefix(PlayerControllerB __instance, ref bool __result) { 9 | if (!Setting.EnableGodMode) return true; 10 | if (!__instance.IsSelf()) return true; 11 | 12 | __result = false; 13 | __instance.inAnimationWithEnemy = null; 14 | __instance.inSpecialInteractAnimation = false; 15 | 16 | return false; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Patches/GrabInLobbyPatch.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE1006 2 | 3 | using HarmonyLib; 4 | 5 | [HarmonyPatch(typeof(GrabbableObject), nameof(GrabbableObject.Update))] 6 | sealed class GrabInLobbyPatch { 7 | static void Postfix(GrabbableObject __instance) => __instance.itemProperties.canBeGrabbedBeforeGameStart = true; 8 | } 9 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Patches/HUDPatch.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | 3 | [HarmonyPatch(typeof(HUDManager), nameof(HUDManager.HideHUD))] 4 | sealed class HUDPatch { 5 | static void Prefix(ref bool hide) => hide = false; 6 | } 7 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Patches/HearPatch.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE1006 2 | 3 | using HarmonyLib; 4 | using UnityEngine; 5 | 6 | [HarmonyPatch(typeof(StartOfRound), nameof(StartOfRound.UpdatePlayerVoiceEffects))] 7 | sealed class HearPatch { 8 | static void Postfix(StartOfRound __instance) { 9 | if (!Setting.EnableEavesdrop) return; 10 | if (Helper.StartOfRound is { shipIsLeaving: true }) return; 11 | if (Helper.SoundManager is not SoundManager soundManager) return; 12 | 13 | __instance.allPlayerScripts.ForEach(player => { 14 | AudioSource currentVoiceChatAudioSource = player.currentVoiceChatAudioSource; 15 | 16 | if (!currentVoiceChatAudioSource.TryGetComponent(out AudioLowPassFilter audioLowPassFilter)) { 17 | return; 18 | } 19 | 20 | if (!currentVoiceChatAudioSource.TryGetComponent(out AudioHighPassFilter audioHighPassFilter)) { 21 | return; 22 | } 23 | 24 | audioLowPassFilter.enabled = false; 25 | audioHighPassFilter.enabled = false; 26 | currentVoiceChatAudioSource.panStereo = 0.0f; 27 | soundManager.playerVoicePitchTargets[player.playerClientId] = 1.0f; 28 | soundManager.SetPlayerPitch(1.0f, unchecked((int)player.playerClientId)); 29 | 30 | currentVoiceChatAudioSource.spatialBlend = 0.0f; 31 | player.currentVoiceChatIngameSettings.set2D = true; 32 | player.voicePlayerState.Volume = 1.0f; 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Patches/InfiniteAmmoPatch.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE1006 2 | 3 | using HarmonyLib; 4 | 5 | [HarmonyPatch(typeof(ShotgunItem), nameof(ShotgunItem.ItemActivate))] 6 | sealed class InfiniteShotgunAmmoPatch { 7 | static void Prefix(ShotgunItem __instance, ref EnemyAI? ___heldByEnemy) { 8 | if (__instance.isReloading) return; 9 | if (___heldByEnemy is not null) return; 10 | __instance.shellsLoaded = 3; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Patches/InfiniteBatteryPatch.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE1006 2 | 3 | using HarmonyLib; 4 | 5 | [HarmonyPatch(typeof(GrabbableObject), nameof(GrabbableObject.LateUpdate))] 6 | sealed class InfiniteBatteryPatch { 7 | static void Postfix(GrabbableObject __instance) { 8 | if (__instance.insertedBattery is not Battery battery) return; 9 | 10 | battery.charge = 1.0f; 11 | battery.empty = false; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Patches/InfiniteDepositPatch.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Reflection.Emit; 3 | using HarmonyLib; 4 | 5 | [HarmonyPatch(typeof(DepositItemsDesk), nameof(DepositItemsDesk.PlaceItemOnCounter))] 6 | sealed class InfiniteDepositPatch { 7 | static IEnumerable Transpiler(IEnumerable instructions) { 8 | foreach (CodeInstruction instruction in instructions) { 9 | yield return instruction.opcode == OpCodes.Ldc_I4_S && instruction.operand.Equals((sbyte)12) 10 | ? new CodeInstruction(OpCodes.Ldc_I4, int.MaxValue) 11 | : instruction; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Patches/InfiniteGrabPatch.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE1006 2 | 3 | using GameNetcodeStuff; 4 | using HarmonyLib; 5 | using UnityEngine; 6 | 7 | [HarmonyPatch(typeof(PlayerControllerB), "SetHoverTipAndCurrentInteractTrigger")] 8 | sealed class InfiniteGrabPatch { 9 | static void Postfix(PlayerControllerB __instance, ref int ___interactableObjectsMask) { 10 | ___interactableObjectsMask = LayerMask.GetMask(["Props", "InteractableObject"]); 11 | __instance.grabDistance = float.MaxValue; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Patches/InfiniteItemUsagePatch.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | 3 | [HarmonyPatch(typeof(GrabbableObject), nameof(GrabbableObject.DestroyObjectInHand))] 4 | sealed class InfiniteItemUsagePatch { 5 | static bool Prefix() => false; 6 | } 7 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Patches/InfiniteScanRangePatch.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE1006 2 | 3 | using System.Collections.Generic; 4 | using System.Reflection.Emit; 5 | using HarmonyLib; 6 | 7 | [HarmonyPatch(typeof(HUDManager))] 8 | sealed class InfiniteScanRangePatch { 9 | [HarmonyPatch("AssignNewNodes")] 10 | static IEnumerable Transpiler(IEnumerable instructions) { 11 | bool foundMaxDistance = false; 12 | 13 | foreach (CodeInstruction instruction in instructions) { 14 | if (instruction.opcode == OpCodes.Ldc_R4 && instruction.operand.Equals(20.0f)) { 15 | instruction.operand = 50.0f; 16 | } 17 | 18 | else if (!foundMaxDistance && instruction.opcode == OpCodes.Ldc_R4 && instruction.operand.Equals(80.0f)) { 19 | foundMaxDistance = true; 20 | instruction.operand = float.MaxValue; 21 | } 22 | 23 | yield return instruction; 24 | } 25 | } 26 | 27 | [HarmonyPatch("MeetsScanNodeRequirements")] 28 | static bool Prefix(ScanNodeProperties? node, ref bool __result) { 29 | if (node is null) return true; 30 | 31 | __result = true; 32 | return false; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Patches/InvisiblePatch.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE1006 2 | 3 | using GameNetcodeStuff; 4 | using HarmonyLib; 5 | using UnityEngine; 6 | 7 | [HarmonyPatch(typeof(PlayerControllerB))] 8 | sealed class InvisiblePatch { 9 | static Vector3 LastNewPos { get; set; } 10 | static bool LastInElevator { get; set; } 11 | static bool LastInShipRoom { get; set; } 12 | static bool LastExhausted { get; set; } 13 | static bool LastIsPlayerGrounded { get; set; } 14 | 15 | [HarmonyPatch("UpdatePlayerPositionServerRpc")] 16 | static void Prefix( 17 | ulong ___actualClientId, 18 | ref Vector3 newPos, 19 | ref bool inElevator, 20 | ref bool inShipRoom, 21 | ref bool exhausted, 22 | ref bool isPlayerGrounded 23 | ) { 24 | if (!Setting.EnableInvisible) return; 25 | if (Helper.LocalPlayer?.actualClientId != ___actualClientId) return; 26 | 27 | InvisiblePatch.LastNewPos = newPos; 28 | InvisiblePatch.LastInElevator = inElevator; 29 | InvisiblePatch.LastInShipRoom = inShipRoom; 30 | InvisiblePatch.LastExhausted = exhausted; 31 | InvisiblePatch.LastIsPlayerGrounded = isPlayerGrounded; 32 | 33 | newPos = new Vector3(0.0f, -100.0f, 0.0f); 34 | inElevator = false; 35 | inShipRoom = false; 36 | exhausted = false; 37 | isPlayerGrounded = true; 38 | } 39 | 40 | [HarmonyPatch("UpdatePlayerPositionClientRpc")] 41 | static void Prefix( 42 | PlayerControllerB __instance, 43 | ref Vector3 newPos, 44 | ref bool inElevator, 45 | ref bool isInShip, 46 | ref bool exhausted, 47 | ref bool isPlayerGrounded 48 | ) { 49 | if (!Setting.EnableInvisible) return; 50 | if (!__instance.IsSelf()) return; 51 | 52 | newPos = InvisiblePatch.LastNewPos; 53 | inElevator = InvisiblePatch.LastInElevator; 54 | isInShip = InvisiblePatch.LastInShipRoom; 55 | exhausted = InvisiblePatch.LastExhausted; 56 | isPlayerGrounded = InvisiblePatch.LastIsPlayerGrounded; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Patches/JebPatch.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE1006 2 | 3 | using HarmonyLib; 4 | 5 | [HarmonyPatch(typeof(DepositItemsDesk), nameof(DepositItemsDesk.AttackPlayersServerRpc))] 6 | sealed class JebPatch { 7 | static void Prefix(DepositItemsDesk __instance, ref bool __state) { 8 | __state = __instance.inGrabbingObjectsAnimation; 9 | __instance.attacking = false; 10 | __instance.inGrabbingObjectsAnimation = false; 11 | } 12 | 13 | static void Postfix(DepositItemsDesk __instance, bool __state) => __instance.inGrabbingObjectsAnimation = __state; 14 | } 15 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Patches/LobbyPatch.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE1006 2 | 3 | using HarmonyLib; 4 | using Steamworks.Data; 5 | using ZLinq; 6 | 7 | [HarmonyPatch(typeof(SteamLobbyManager))] 8 | sealed class LobbyPatch { 9 | [HarmonyPatch(nameof(SteamLobbyManager.RefreshServerListButton))] 10 | static void Prefix(SteamLobbyManager __instance, ref float ___refreshServerListTimer) { 11 | ___refreshServerListTimer = 1.0f; 12 | __instance.censorOffensiveLobbyNames = false; 13 | } 14 | 15 | [HarmonyPatch("loadLobbyListAndFilter")] 16 | static void Prefix(ref Lobby[] ___currentLobbyList) { 17 | ___currentLobbyList 18 | .AsValueEnumerable() 19 | .Where(lobby => lobby.GetData("name") 20 | .Contains('\u200b')) 21 | .ForEach(lobby => lobby.SetData("name", $"[CC] {lobby.GetData("name")}")); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Patches/LookDownPatch.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection.Emit; 2 | using System.Collections.Generic; 3 | using GameNetcodeStuff; 4 | using HarmonyLib; 5 | 6 | [HarmonyPatch(typeof(PlayerControllerB))] 7 | sealed class LookDownPatch { 8 | static IEnumerable CalculateLookingInputTranspiler(IEnumerable instructions) { 9 | foreach (CodeInstruction instruction in instructions) { 10 | if (instruction.opcode == OpCodes.Ldc_R4 && instruction.operand.Equals(60.0f)) { 11 | instruction.operand = 90.0f; 12 | } 13 | 14 | yield return instruction; 15 | } 16 | } 17 | 18 | [HarmonyTranspiler] 19 | [HarmonyPatch("CalculateSmoothLookingInput")] 20 | static IEnumerable SmoothLookingTranspiler(IEnumerable instructions) => 21 | LookDownPatch.CalculateLookingInputTranspiler(instructions); 22 | 23 | [HarmonyTranspiler] 24 | [HarmonyPatch("CalculateNormalLookingInput")] 25 | static IEnumerable NormalLookingTranspiler(IEnumerable instructions) => 26 | LookDownPatch.CalculateLookingInputTranspiler(instructions); 27 | } 28 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Patches/NoCooldownPatch.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE1006 2 | 3 | using System.Collections; 4 | using UnityEngine; 5 | using HarmonyLib; 6 | 7 | [HarmonyPatch] 8 | sealed class NoCooldownPatch { 9 | [HarmonyPatch(typeof(GrabbableObject), nameof(GrabbableObject.RequireCooldown))] 10 | static void Postfix(ref bool __result) { 11 | if (!Setting.EnableNoCooldown) return; 12 | __result = false; 13 | } 14 | 15 | [HarmonyPatch(typeof(Shovel), "reelUpShovel")] 16 | static IEnumerator Postfix(IEnumerator reelUpShovel) { 17 | while (reelUpShovel.MoveNext()) { 18 | if (Setting.EnableNoCooldown && reelUpShovel.Current is WaitForSeconds) continue; 19 | yield return reelUpShovel.Current; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Patches/OneHandedItemPatch.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE1006 2 | 3 | using GameNetcodeStuff; 4 | using HarmonyLib; 5 | 6 | [HarmonyPatch(typeof(PlayerControllerB), "LateUpdate")] 7 | sealed class OneHandedItemPatch { 8 | static void Postfix(PlayerControllerB __instance) => __instance.twoHanded = false; 9 | } 10 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Patches/PossessionPatch.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE1006 2 | 3 | using HarmonyLib; 4 | 5 | [HarmonyPatch] 6 | sealed class PossessionPatch { 7 | [HarmonyPatch(typeof(HUDManager), "CanPlayerScan")] 8 | static bool Prefix(ref bool __result) { 9 | if (PossessionMod.Instance is null or { IsPossessed: false }) return true; 10 | 11 | __result = false; 12 | return false; 13 | } 14 | 15 | [HarmonyPatch(typeof(HUDManager), "Update")] 16 | static void Prefix(HUDManager __instance, ref float ___holdButtonToEndGameEarlyHoldTime) { 17 | if (PossessionMod.Instance is null or { IsPossessed: false }) return; 18 | 19 | ___holdButtonToEndGameEarlyHoldTime = 0.0f; 20 | __instance.holdButtonToEndGameEarlyMeter?.gameObject.SetActive(false); 21 | } 22 | 23 | [HarmonyPatch(typeof(TimeOfDay), nameof(TimeOfDay.VoteShipToLeaveEarly))] 24 | static bool Prefix() => PossessionMod.Instance is null or { IsPossessed: false }; 25 | } 26 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Patches/RadarPatch.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE1006 2 | 3 | using HarmonyLib; 4 | 5 | [HarmonyPatch(typeof(ManualCameraRenderer), nameof(ManualCameraRenderer.SwitchRadarTargetClientRpc))] 6 | sealed class RadarPatch { 7 | static bool Prefix(ManualCameraRenderer __instance, int switchToIndex) { 8 | if (!Setting.EnableBlockRadar) return true; 9 | if (Helper.LocalPlayer?.PlayerIndex() != switchToIndex) return true; 10 | 11 | __instance.SwitchRadarTargetForward(true); 12 | return false; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Patches/ShotgunPatch.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE1006 2 | 3 | using GameNetcodeStuff; 4 | using HarmonyLib; 5 | 6 | [HarmonyPatch(typeof(ShotgunItem))] 7 | sealed class ShotgunPatch { 8 | static bool InterruptDestroyItem { get; set; } 9 | 10 | /// 11 | /// Prevents the shotgun from consuming ammo when firing 12 | /// 13 | [HarmonyPatch(typeof(ShotgunItem), "FindAmmoInInventory")] 14 | static bool Prefix(ref int __result, ref int ___ammoSlotToUse) { 15 | ___ammoSlotToUse = -2; 16 | __result = -2; 17 | return false; 18 | } 19 | 20 | /// 21 | /// Always allow the Shotgun to reload 22 | /// 23 | [HarmonyPatch(typeof(ShotgunItem), nameof(ShotgunItem.ItemInteractLeftRight))] 24 | static void Prefix(ShotgunItem __instance) { 25 | __instance.shellsLoaded = 1; 26 | ShotgunPatch.InterruptDestroyItem = true; 27 | } 28 | 29 | /// 30 | /// Resets the interrupt flag 31 | /// 32 | [HarmonyPatch(typeof(ShotgunItem), "StopUsingGun")] 33 | static void Postfix() => ShotgunPatch.InterruptDestroyItem = false; 34 | 35 | /// 36 | /// Prevents the shotgun shell from being destroyed 37 | /// 38 | [HarmonyPatch(typeof(PlayerControllerB), nameof(PlayerControllerB.DestroyItemInSlotAndSync))] 39 | static bool Prefix() => !ShotgunPatch.InterruptDestroyItem; 40 | 41 | /// 42 | /// Prevents the shotgun from misfiring 43 | /// 44 | [HarmonyPatch(nameof(ShotgunItem.Update))] 45 | static void Postfix(ref float ___misfireTimer, ref bool ___hasHitGroundWithSafetyOff) { 46 | ___misfireTimer = 30.0f; 47 | ___hasHitGroundWithSafetyOff = true; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Patches/ShovelPatch.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE1006 2 | 3 | using HarmonyLib; 4 | 5 | [HarmonyPatch(typeof(Shovel), nameof(Shovel.HitShovel))] 6 | sealed class ShovelPatch { 7 | static void Prefix(Shovel __instance) => __instance.shovelHitForce = State.ShovelHitForce; 8 | } 9 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Patches/TeleportWithItemsPatch.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE1006 2 | 3 | using GameNetcodeStuff; 4 | using HarmonyLib; 5 | 6 | [HarmonyPatch(typeof(PlayerControllerB), nameof(PlayerControllerB.DropAllHeldItems))] 7 | sealed class TeleportWithItemsPatch { 8 | static bool Prefix(PlayerControllerB __instance) => __instance.shipTeleporterId is not -1 or 1; 9 | } 10 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Patches/UnconstrainedBuildPatch.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE1006 2 | 3 | using HarmonyLib; 4 | 5 | [HarmonyPatch(typeof(ShipBuildModeManager), "PlayerMeetsConditionsToBuild")] 6 | sealed class UnconstrainedBuildPatch { 7 | static bool Prefix(ref bool __result, ref bool ___CanConfirmPosition, ref PlaceableShipObject? ___placingObject) { 8 | if (___placingObject is null) return true; 9 | 10 | ___placingObject.AllowPlacementOnCounters = true; 11 | ___placingObject.AllowPlacementOnWalls = true; 12 | ___CanConfirmPosition = true; 13 | __result = Helper.LocalPlayer is { inTerminalMenu: false }; 14 | 15 | return false; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Patches/UnlimitedJumpPatch.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE1006 2 | 3 | using GameNetcodeStuff; 4 | using HarmonyLib; 5 | using UnityEngine; 6 | 7 | [HarmonyPatch(typeof(PlayerControllerB), "Jump_performed")] 8 | sealed class UnlimitedJumpPatch { 9 | static bool Prefix(PlayerControllerB __instance) { 10 | if (!Setting.EnableUnlimitedJump) return true; 11 | if (!__instance.IsSelf()) return true; 12 | if (!__instance.isPlayerControlled) return false; 13 | if (__instance.inSpecialInteractAnimation) return false; 14 | if (__instance.isTypingChat) return false; 15 | if (__instance.quickMenuManager.isMenuOpen) return false; 16 | 17 | __instance.sprintMeter = Mathf.Clamp(__instance.sprintMeter - 0.08f, 0.0f, 1.0f); 18 | __instance.movementAudio.PlayOneShot(StartOfRound.Instance.playerJumpSFX); 19 | __instance.playerSlidingTimer = 0.0f; 20 | __instance.isJumping = true; 21 | 22 | Coroutine? jumpCoroutine = __instance.jumpCoroutine; 23 | 24 | if (jumpCoroutine is not null) { 25 | __instance.StopCoroutine(jumpCoroutine); 26 | } 27 | 28 | __instance.jumpCoroutine = __instance.StartCoroutine(__instance.PlayerJump()); 29 | 30 | return false; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Patches/UntargetablePatch.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE1006 2 | 3 | using GameNetcodeStuff; 4 | using HarmonyLib; 5 | 6 | [HarmonyPatch(typeof(EnemyAI))] 7 | sealed class UntargetableEnemyPatch { 8 | [HarmonyPatch(nameof(EnemyAI.PlayerIsTargetable))] 9 | static void Postfix(PlayerControllerB playerScript, ref bool __result) { 10 | if (!Setting.EnableUntargetable) return; 11 | if (!playerScript.IsSelf()) return; 12 | 13 | __result = false; 14 | } 15 | 16 | [HarmonyPatch(nameof(EnemyAI.Update))] 17 | static bool Prefix(EnemyAI __instance) { 18 | if (!Setting.EnableUntargetable) return true; 19 | if (!__instance.targetPlayer.IsSelf()) return true; 20 | 21 | __instance.targetPlayer = null; 22 | __instance.movingTowardsTargetPlayer = false; 23 | return false; 24 | } 25 | } 26 | 27 | [HarmonyPatch(typeof(NutcrackerEnemyAI), nameof(NutcrackerEnemyAI.CheckLineOfSightForLocalPlayer))] 28 | sealed class UntargetableNutcrackerPatch { 29 | static bool Prefix() => !Setting.EnableUntargetable; 30 | } 31 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Patches/ZapGunPatch.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE1006 2 | 3 | using HarmonyLib; 4 | 5 | [HarmonyPatch(typeof(PatcherTool), nameof(PatcherTool.ShiftBendRandomizer))] 6 | sealed class ZapGunPatch { 7 | static void Postfix(PatcherTool __instance) => __instance.bendMultiplier = 0.0f; 8 | } 9 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Static/CodeAnalysis.cs: -------------------------------------------------------------------------------- 1 | namespace System.Diagnostics.CodeAnalysis; 2 | 3 | [AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false, Inherited = false)] 4 | sealed class SetsRequiredMembersAttribute : Attribute { } 5 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Static/CompilerServices.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable CS9113 2 | 3 | namespace System.Runtime.CompilerServices; 4 | 5 | static class IsExternalInit { } 6 | 7 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)] 8 | sealed class RequiredMemberAttribute : Attribute { } 9 | 10 | [AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)] 11 | sealed class CompilerFeatureRequiredAttribute(string name) : Attribute { } 12 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Static/Logger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | static class Logger { 5 | const string LogFileName = "lc-hax.log"; 6 | static object LockObject { get; } = new(); 7 | 8 | internal static void Write(string message) { 9 | Console.WriteLine(message); 10 | 11 | lock (Logger.LockObject) { 12 | string timeNow = DateTime.Now.ToString("dd-MM-yy HH:mm:ss"); 13 | 14 | File.AppendAllText( 15 | LogFileName, 16 | $"[{timeNow}] {message}{Environment.NewLine}" 17 | ); 18 | } 19 | } 20 | 21 | internal static void Write(Exception exception) => Logger.Write(exception.ToString()); 22 | } 23 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Static/Setting.cs: -------------------------------------------------------------------------------- 1 | static class Setting { 2 | internal static bool EnableGodMode { get; set; } 3 | internal static bool EnableBlockCredits { get; set; } 4 | internal static bool EnableBlockRadar { get; set; } 5 | internal static bool EnableUntargetable { get; set; } 6 | internal static bool EnableStunOnLeftClick { get; set; } 7 | internal static bool EnableKillOnLeftClick { get; set; } 8 | internal static bool EnableInvisible { get; set; } 9 | internal static bool EnableNoCooldown { get; set; } 10 | internal static bool EnableAntiKick { get; set; } 11 | internal static bool EnablePhantom { get; set; } 12 | internal static bool EnableFakeDeath { get; set; } 13 | internal static bool EnableEavesdrop { get; set; } 14 | internal static bool EnableUnlimitedJump { get; set; } 15 | internal static bool InvertYAxis => IngamePlayerSettings.Instance.settings.invertYAxis; 16 | } 17 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Static/State.cs: -------------------------------------------------------------------------------- 1 | static class State { 2 | internal static char CommandPrefix { get; set; } = '/'; 3 | internal static int ShovelHitForce { get; set; } = 1; 4 | internal static bool DisconnectedVoluntarily { get; set; } 5 | internal static ConnectedLobby? ConnectedLobby { get; set; } 6 | } 7 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Structures/ConnectedLobby.cs: -------------------------------------------------------------------------------- 1 | using Steamworks; 2 | using Steamworks.Data; 3 | 4 | readonly record struct ConnectedLobby { 5 | internal required Lobby Lobby { get; init; } 6 | internal required SteamId SteamId { get; init; } 7 | } 8 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Structures/ObjectPlacement.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | readonly record struct ObjectPlacement() where T : Transform where M : MonoBehaviour { 4 | internal required T TargetObject { get; init; } 5 | internal required M GameObject { get; init; } 6 | internal Vector3 PositionOffset { get; init; } = new(); 7 | internal Vector3 RotationOffset { get; init; } = new(); 8 | } 9 | 10 | readonly record struct ObjectPlacements where T : Transform where M : MonoBehaviour { 11 | internal required ObjectPlacement Placement { get; init; } 12 | internal required ObjectPlacement PreviousPlacement { get; init; } 13 | } 14 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Structures/RendererPair.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | readonly record struct RendererPair where T : Object where R : Renderer { 4 | internal required T GameObject { get; init; } 5 | internal required R Renderer { get; init; } 6 | } 7 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Structures/Result.cs: -------------------------------------------------------------------------------- 1 | readonly record struct Result(bool Success = false, string? Message = null) { 2 | internal bool Success { get; init; } = Success; 3 | internal string? Message { get; init; } = Message; 4 | } 5 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Structures/Size.cs: -------------------------------------------------------------------------------- 1 | readonly ref struct Size { 2 | internal required float Width { get; init; } 3 | internal required float Height { get; init; } 4 | 5 | internal Size(float size) { 6 | this.Width = size; 7 | this.Height = size; 8 | } 9 | 10 | public static Size operator +(Size a, Size b) => new() { 11 | Width = a.Width + b.Width, 12 | Height = a.Height + b.Height 13 | }; 14 | 15 | public static Size operator -(Size a, Size b) => new() { 16 | Width = a.Width - b.Width, 17 | Height = a.Height - b.Height 18 | }; 19 | 20 | public static Size operator *(Size size, float multiplier) => new() { 21 | Width = size.Width * multiplier, 22 | Height = size.Height * multiplier 23 | }; 24 | 25 | public static Size operator /(Size size, float divider) => new() { 26 | Width = size.Width / divider, 27 | Height = size.Height / divider 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Structures/StringArray.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | 4 | sealed class Arguments { 5 | internal required string[] Span { private get; init; } 6 | 7 | internal int Length => this.Span.Length; 8 | 9 | internal string? this[uint index] => index >= this.Span.Length ? null : this.Span[index]; 10 | 11 | internal Arguments this[Range range] => new() { 12 | Span = this.Span[range] 13 | }; 14 | 15 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 16 | public static implicit operator ReadOnlySpan(Arguments stringArray) => stringArray.Span; 17 | 18 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 19 | public static implicit operator Arguments(string[] array) => new() { 20 | Span = array 21 | }; 22 | 23 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 24 | public static implicit operator string[](Arguments stringArray) => stringArray.Span; 25 | } 26 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Utils/MultiObjectPool.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using UnityEngine; 3 | using UnityObject = UnityEngine.Object; 4 | 5 | sealed class MultiObjectPool where T : UnityObject { 6 | internal T?[] Objects { get; private set; } = []; 7 | 8 | internal MultiObjectPool(MonoBehaviour self, float renewInterval = 1.0f) => self.StartCoroutine(this.RenewObjects(renewInterval)); 9 | 10 | internal void Renew() => this.Objects = UnityObject.FindObjectsByType(FindObjectsSortMode.None); 11 | 12 | IEnumerator RenewObjects(float renewInterval) { 13 | WaitForSeconds waitForRenewInterval = new(renewInterval); 14 | 15 | while (true) { 16 | this.Renew(); 17 | yield return waitForRenewInterval; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lc-hax/Scripts/Utils/SingleObjectPool..cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using UnityObject = UnityEngine.Object; 3 | using UnityEngine; 4 | 5 | sealed class SingleObjectPool where T : UnityObject { 6 | internal T? Object { get; private set; } 7 | 8 | internal SingleObjectPool(MonoBehaviour self, float renewInterval = 1.0f) => self.StartCoroutine(this.RenewObject(renewInterval)); 9 | 10 | internal void Renew() => this.Object = UnityObject.FindAnyObjectByType(); 11 | 12 | IEnumerator RenewObject(float renewInterval) { 13 | WaitForSeconds waitForRenewInterval = new(renewInterval); 14 | 15 | while (true) { 16 | this.Renew(); 17 | yield return waitForRenewInterval; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lc-hax/lc-hax.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | ../bin 6 | None 7 | true 8 | enable 9 | preview 10 | win-x64 11 | netstandard2.1 12 | lc-hax 13 | true 14 | true 15 | true 16 | true 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /lc-hax/nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /omnisharp.json: -------------------------------------------------------------------------------- 1 | { 2 | "script": { 3 | "enableScriptNuGetReferences": true, 4 | "defaultTargetFramework": "netstandard2.1" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "@biomejs/biome": "latest" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /requirements.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | git submodule update --init || pause && exit /b 4 | 5 | echo Building SharpMonoInjectorCore! Please be patient as this may take a few minutes! 6 | dotnet publish submodules/SharpMonoInjectorCore 7 | 8 | pause 9 | -------------------------------------------------------------------------------- /scripts/UpdateEmbeddedResource.csx: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env dotnet-script 2 | 3 | #nullable enable 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.IO; 8 | using System.Xml; 9 | using System.Xml.Linq; 10 | 11 | void Main() 12 | { 13 | string? csprojPath = Environment.GetEnvironmentVariable("CSPROJ_PATH"); 14 | 15 | if (!File.Exists(csprojPath)) 16 | { 17 | WriteLine("CSPROJ_PATH environment variable is not set or invalid."); 18 | return; 19 | } 20 | 21 | XDocument document = XDocument.Load(csprojPath, LoadOptions.PreserveWhitespace); 22 | 23 | Dictionary packageReferences = document.Descendants("PackageReference").ToDictionary( 24 | element => element.Attribute("Include")?.Value.ToLower() ?? "", 25 | element => element.Attribute("Version")?.Value ?? "" 26 | ); 27 | 28 | foreach (XElement embeddedResource in document.Descendants("EmbeddedResource")) 29 | { 30 | if (embeddedResource.Attribute("Include") is not XAttribute includeAttribute) 31 | { 32 | throw new Exception("EmbeddedResource element does not have an Include attribute."); 33 | } 34 | 35 | string[] path = includeAttribute.Value.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries); 36 | 37 | if (!packageReferences.TryGetValue(path[1].ToLower(), out string? version)) 38 | { 39 | continue; 40 | } 41 | 42 | path[2] = version; 43 | includeAttribute.SetValue(string.Join(Path.DirectorySeparatorChar, path)); 44 | } 45 | 46 | using XmlWriter xmlWriter = XmlWriter.Create(csprojPath, new XmlWriterSettings() { OmitXmlDeclaration = true }); 47 | document.Save(xmlWriter); 48 | } 49 | 50 | Main(); 51 | --------------------------------------------------------------------------------