├── .github
└── workflows
│ ├── build.yml
│ └── publish.yml
├── .gitignore
├── CHANGELOG.md
├── LC-API.sln
├── LC-API
├── BundleAPI
│ ├── BundleLoader.cs
│ └── LoadedAssetBundle.cs
├── CheatDatabase.cs
├── ClientAPI
│ └── CommandHandler.cs
├── Comp
│ └── LC_APIManager.cs
├── Data
│ ├── NetworkBroadcastDataType.cs
│ └── ShipState.cs
├── Exceptions
│ └── NoAuthorityException.cs
├── Extensions
│ └── DelegateExtensions.cs
├── GameInterfaceAPI
│ ├── Events
│ │ ├── Cache
│ │ │ └── Player.cs
│ │ ├── EventArgs
│ │ │ └── Player
│ │ │ │ ├── DiedEventArgs.cs
│ │ │ │ ├── DroppingItemEventArgs.cs
│ │ │ │ ├── DyingEventArgs.cs
│ │ │ │ ├── GrabbedItemEventArgs.cs
│ │ │ │ ├── GrabbingItemEventArgs.cs
│ │ │ │ ├── HurtEventArgs.cs
│ │ │ │ ├── HurtingEventArgs.cs
│ │ │ │ ├── JoinedEventArgs.cs
│ │ │ │ ├── LeftEventArgs.cs
│ │ │ │ └── StartGrabbingItemEventArgs.cs
│ │ ├── EventExtensions.cs
│ │ ├── Events.cs
│ │ ├── Handlers
│ │ │ └── Player.cs
│ │ └── Patches
│ │ │ ├── Internal
│ │ │ ├── DisplayTipPatch.cs
│ │ │ ├── GameNetworkManagerStartPatch.cs
│ │ │ └── PlayerControllerBStartPatch.cs
│ │ │ └── Player
│ │ │ ├── Die.cs
│ │ │ ├── DropItem.cs
│ │ │ ├── GrabItem.cs
│ │ │ ├── Hurt.cs
│ │ │ ├── Joined.cs
│ │ │ └── Left.cs
│ ├── Features
│ │ ├── Item.cs
│ │ ├── Player.cs
│ │ └── Tip.cs
│ ├── GameState.cs
│ └── GameTips.cs
├── LC-API.csproj
├── ManualPatches
│ └── ServerPatch.cs
├── Networking
│ ├── Network.cs
│ └── Serializers.cs
├── Plugin.cs
├── ServerAPI
│ ├── ModdedServer.cs
│ └── Networking.cs
└── Utils.cs
├── LICENSE
├── README.md
└── assets
├── bundles
└── networking
├── icons
└── icon.png
└── thunderstore.toml
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | on:
4 | # Trigger the workflow on pushes to only the 'main' branch (this avoids duplicate checks being run e.g., for dependabot pull requests)
5 | push:
6 | branches: [ main ]
7 | # Trigger the workflow on any pull request
8 | pull_request:
9 |
10 | jobs:
11 | build:
12 | name: Build
13 | runs-on: ubuntu-latest
14 | steps:
15 | - name: Fetch Sources
16 | uses: actions/checkout@v4
17 | with:
18 | fetch-depth: 0
19 | filter: tree:0
20 |
21 | - name: Setup .NET environment
22 | uses: actions/setup-dotnet@v3
23 | with:
24 | dotnet-version: "8.0.100"
25 |
26 | - name: Restore solution
27 | run: |
28 | dotnet restore
29 |
30 | - name: Setup Netcode Patcher
31 | id: setup-netcode-patcher
32 | uses: Lordfirespeed/setup-netcode-patcher@v0
33 | with:
34 | netcode-patcher-version: 2.4.0
35 | deps-packages: '[{"id": "UnityEngine.Modules", "version": "2022.3.9"}, {"id": "LethalCompany.GameLibs.Steam", "version": "49.0.0-alpha.1"}]'
36 | target-framework: "netstandard2.1"
37 |
38 | - name: Install Thunderstore CLI
39 | run: |
40 | dotnet tool install -g tcli
41 |
42 | - name: Build solution
43 | run: |
44 | dotnet build -p:NETCODE_PATCHER_DIR="${{ steps.setup-netcode-patcher.outputs.netcode-patcher-directory }}"
45 |
46 | - name: Upload Artifacts
47 | uses: actions/upload-artifact@v4
48 | with:
49 | name: build-artifacts
50 | path: "dist/*.zip"
51 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish
2 |
3 | on:
4 | release:
5 | types: [prereleased, released]
6 |
7 | jobs:
8 | build:
9 | name: Build
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: Fetch Sources
13 | uses: actions/checkout@v4
14 | with:
15 | ref: ${{ github.event.release.tag_name }}
16 | fetch-depth: 0
17 | filter: tree:0
18 |
19 | - name: Setup .NET environment
20 | uses: actions/setup-dotnet@v3
21 | with:
22 | dotnet-version: "8.0.100"
23 |
24 | - name: Restore solution
25 | run: |
26 | dotnet restore
27 |
28 | - name: Setup Netcode Patcher
29 | id: setup-netcode-patcher
30 | uses: Lordfirespeed/setup-netcode-patcher@v0
31 | with:
32 | netcode-patcher-version: 2.4.0
33 | deps-packages: '[{"id": "UnityEngine.Modules", "version": "2022.3.9"}, {"id": "LethalCompany.GameLibs.Steam", "version": "49.0.0-alpha.1"}]'
34 | target-framework: "netstandard2.1"
35 |
36 | - name: Install Thunderstore CLI
37 | run: |
38 | dotnet tool install -g tcli
39 |
40 | - name: Build and pack solution
41 | run: |
42 | dotnet pack -p:NETCODE_PATCHER_DIR="${{ steps.setup-netcode-patcher.outputs.netcode-patcher-directory }}"
43 |
44 | - name: Upload Thunderstore artifact
45 | uses: actions/upload-artifact@v4
46 | with:
47 | name: thunderstore-build
48 | path: dist/*.zip
49 |
50 | - name: Upload nupkg artifact
51 | uses: actions/upload-artifact@v4
52 | with:
53 | name: nupkg-build
54 | path: ./*/bin/*.nupkg
55 |
56 | upload-release-artifacts:
57 | name: Upload Release Artifacts
58 | needs: build
59 | runs-on: ubuntu-latest
60 | steps:
61 | - name: Fetch Sources
62 | uses: actions/checkout@v4
63 |
64 | - name: Download all artifacts
65 | uses: actions/download-artifact@v4
66 |
67 | - name: Upload artifacts to Release
68 | env:
69 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
70 | run: gh release upload ${{ github.event.release.tag_name }} thunderstore-build/*.zip nupkg-build/*/bin/*.nupkg
71 |
72 | deploy-nuget:
73 | name: Deploy to NuGet
74 | needs: build
75 | runs-on: ubuntu-latest
76 | steps:
77 | - name: Fetch Sources
78 | uses: actions/checkout@v4
79 |
80 | - name: Download nupkg artifact
81 | uses: actions/download-artifact@v4
82 | with:
83 | name: nupkg-build
84 |
85 | - name: Setup .NET environment
86 | uses: actions/setup-dotnet@v3
87 | with:
88 | dotnet-version: "8.0.100"
89 |
90 | - name: Publish to NuGet.org
91 | run: |
92 | dotnet nuget push ./*/bin/*.nupkg --api-key ${{ secrets.NUGET_API_TOKEN }} --source https://api.nuget.org/v3/index.json
93 |
94 | deploy-thunderstore:
95 | name: Deploy to Thunderstore
96 | needs: build
97 | runs-on: ubuntu-latest
98 | steps:
99 | - name: Fetch Sources
100 | uses: actions/checkout@v4
101 |
102 | - name: Download Thunderstore artifact
103 | uses: actions/download-artifact@v4
104 | with:
105 | name: thunderstore-build
106 | path: ./dist
107 |
108 | - name: Validate artifact exists
109 | run: test -d ./dist
110 |
111 | - name: Setup .NET environment
112 | uses: actions/setup-dotnet@v3
113 | with:
114 | dotnet-version: "8.0.100"
115 |
116 | - name: Install Thunderstore CLI
117 | run: |
118 | dotnet tool install -g tcli
119 |
120 | - name: Publish to Thunderstore
121 | env:
122 | TCLI_AUTH_TOKEN: ${{ secrets.THUNDERSTORE_API_TOKEN }}
123 | run: |
124 | tcli publish --config-path ./assets/thunderstore.toml --file dist/*.zip
125 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Exclude everything
2 | /*
3 |
4 | # Specific includes
5 |
6 | ## Build System/Scripts
7 | !NuGet.Config
8 | !Directory.Build.props
9 | !build.sh
10 | !build.ps1
11 | !build.cmd
12 |
13 | ## Markdowns
14 | !*.md
15 | !LICENSE
16 |
17 | ## important Git files
18 | !.gitmodules
19 | !.gitignore
20 | !.gitattributes
21 | !.git-blame-ignore-revs
22 |
23 | ## GitHub specific
24 | !.github/
25 |
26 | ## Assets
27 | !assets/
28 |
29 | ## Thunderstore metadata
30 | !thunderstore.toml
31 |
32 | ## Thunderstore Assets
33 | !Thunderstore
34 | LC_API.dll
35 |
36 | ## Solution
37 | !LC-API.sln
38 |
39 | ### LC-API Project
40 | !LC-API/
41 | LC-API/[Bb]in/
42 | LC-API/[Oo]bj/
43 |
44 | # Explicit exceptions/ignores
45 | **.user
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7 |
8 | ## [Unreleased]
9 |
10 | N/A
11 |
12 | ## Version [3.4.5]
13 | - Fixed an issue where bodies would not ragdoll after death when using vanilla support.
14 | - Fixed an issue where hurting sometimes did not work while using vanilla support.
15 |
16 | ## Version [3.4.4]
17 | - Fixed an issue where dropping/grabbing items would not work properly if vanilla support was enabled.
18 |
19 | ## Version [3.4.3]
20 | - Fixed an issue where intro tips would play regardless of if you've seen them or not.
21 |
22 | ## Version [3.4.2]
23 | - Fixed object placement not rotating with the player properly.
24 |
25 | ## Version [3.4.1]
26 | - Fixed players not taking damage or dying.
27 |
28 | ## Version [3.4.0]
29 | - Added `Player.Died` and `Player.Hurt` *past-tense* events.
30 | - Added `Player.StartGrabbingItem` and `Player.GrabbingItem` and `Player.GrabbedItem` events.
31 | - Added `Player.DroppingItem` event.
32 | - Added new `Player.ShowTip(string header, string message, float duration, bool isWarning, bool useSave, string prefsKey)` and `Player.QueueTip(string header, string message, float duration, int priority, bool isWarning, bool useSave, string prefsKey)`
33 | - `Player.ShowTip` bypasses the new tip queue.
34 | - Base game tips (or mods that use the `HUDManager.DisplayTip` method) will be treated as max priority tips for compatibility.
35 | - Tips now have a configurable duration in which they will stay on the screen.
36 | - The host can show tips to anyone, however local clients can only show tips to themselves.
37 | - The `int priority` parameter of the `QueueTip` method allows you to set a priority, where higher means it will be shown sooner.
38 | - Tips will always go to the end of their equivalent priority "list" meaning tips added after another that have the same priority will be shown after the ones added previously.
39 | - When a tip bypasses the queue for any reason, the currently showing tip will continue afterwards with whatever time left it had remaining as long as there were >= 1.5 seconds remaining.
40 | - This is the only "breaking" change as it will also affect tips shown by other plugins using other methods, which usually won't requeue the current tip. This will be monitored in case it needs to be removed.
41 | - With this, `GameTips` class has been deprecated, but will still work as expected.
42 | - Fixed `Player.Username` not updating on the radar or escape menu.
43 | - Added `Player.PlayerObjectId` which returns the player's specified player object index in places like `allPlayerScripts`.
44 | - Fixed an issue where players would be added twice to the player's dictionary causing an error.
45 | - Fixed an issue where giving an item to a player but not switching to it would cause it to not be held properly.
46 |
47 | ## Version [3.3.2]
48 |
49 | - Fixed `Player.Postion` setter for v47.
50 |
51 | ## Version [3.3.1]
52 | - Fixed `Player.Username` not updating on the radar or escape menu.
53 | - Because of this, `Player.PlayerObjectId` has been added which returns the player's specified player object index in places like `allPlayerScripts`.
54 | - Fixed `Player.ActiveList` not resetting.
55 | - Fixed `Player.Dictionary` not resetting.
56 |
57 | ## Version [3.3.0]
58 |
59 | - Fixed an issue where the old networking (`ServerAPI.Networking`) would not work properly.
60 | - Fixed issues with the build workflow.
61 | - Fixed the file structure in the README for manual installation.
62 | - Fixed an issue where network messages wouldn't properly be re-registered when rejoining.
63 | - Added a "Vanilla Support" config option that will disable installing the network bundle so you can play on vanilla servers.
64 | - The `Player` and `Item` classes will be completely disabled and this will probably break mods! Please just consider everyone in your game having LC API.
65 |
66 | ## Version [3.2.4]
67 |
68 | - Fixed an issue with the `Player.Joined` and `Player.Left` events where they wouldn't work on LAN due to not having a steam ID.
69 |
70 | ## Version [3.2.3]
71 |
72 | - Fixed package references being added as transitive dependencies when using the NuGet package.
73 |
74 | ## Version [3.2.2]
75 |
76 | - Remove directory validation when pushing to NuGet (dotnet validates for us)
77 | - Pack readme, changelog and license into nuget package
78 |
79 | ## Version [3.2.1]
80 |
81 | - Fix nupkg-build artifact pathing in Publish workflow
82 | - [Lordfirespeed](https://github.com/Lordfirespeed) sucks.
83 |
84 | ## Version [3.2.0]
85 |
86 | - Added `Networking` namespace
87 | - Provides much better networking that the previous `ServerAPI.Networking` class, which still exists for backwards compatibility.
88 | - See the [wiki](https://github.com/steven4547466/LC-API/wiki/Networking) for usage instructions.
89 | - Added CI/CD github actions.
90 | - Changed a hard-coded file location to be dynamically based off of where the plugin file is to prevent an issue with manual installation.
91 | - Significantly revamped project structure.
92 |
93 | ## Version [3.1.0]
94 |
95 | - Added `Item` class for interacting with grabbable objects easily.
96 | - The `Player` class now has multiple new properties for inventory management.
97 | - `Player.Inventory` will return a `PlayerInventory` for this.
98 | - The `Player.Joined` event should now work properly on LAN.
99 |
100 | ## Version [3.0.2]
101 |
102 | - Fixed the command handler "eating" messages if they started with the command prefix, but weren't registered as commands.
103 |
104 | ## Version [3.0.1]
105 |
106 | - Fixed `Player.HostPlayer`.
107 |
108 | ## Version [3.0.0]
109 |
110 | - Removed automated bundle loading.
111 | - Legacy loading will still automatically load bundles if wanted.
112 | - Added event system.
113 | - More events to be added in future.
114 | - Added `Player` class for interacting with players easily.
115 | - `ModdedServer.GameVersion` will now contain the base game version even if LC API modified the version to set modded only.
116 |
117 | ## Version [2.2.0]
118 |
119 | - Added a command handler.
120 | - The bundle loader will only attempt to load actual bundles.
121 | - Added a temporary fix for lethal expansion bundles. Will be looking into a long term solution in the next update.
122 | - The local player now will NOT receive data that they broadcast. The bool value on the receive delegates is also gone. If you were using Networking, you will need to ajust your code.
123 |
124 | ## Version [2.1.2]
125 |
126 | - Updated to game version 45.
127 |
128 | ## Version [2.1.1]
129 |
130 | - Actually fixed the BundleLodaer loading assets twice.
131 | - Added new CheatDatabase with the purpose of catching users trying to join non-modded servers with cheaty mods. The CheatDatabase also allows for the host to view all mods installed by people joining. (As long as they have LC_API installed).
132 |
133 | ## Version [2.1.0]
134 |
135 | - Fixed the BundleLodaer loading assets twice.
136 |
137 | ## Version [2.0.0]
138 |
139 | - Changes to the BundleLoader to stop conflicts with other plugins loading assets without the BundleLoader.
140 | - Changes to Networking and GameState events, plugins using these will need to be rebuilt.
141 | - Added GameTips to GameInterfaceAPI. GameTips uses a tip que system to ensure no popup tip messages overlap with eachother.
142 | - Changed the CheatDatabase to now (in theory) work for all players, not just the host.
143 | - Changes the CheatDatabase to use GameTips to display information. It will still output information to the logs.
144 |
145 | ## Version [1.4.2]
146 | - Fix for the new config option causing the API to fail to initialize.
147 |
148 |
149 | ## Version [1.4.1]
150 |
151 | - LC_API should now be able to load no matter if the Hide Manager GameObject option is on or off.
152 | - A config option has been added that will disable the BundleLoader.
153 |
154 |
155 | ## Version [1.4.0]
156 |
157 | - Changed how the BundleLoader in the BundleAPI loads assets to fix issues with certain languages. This will break some mods, but a config option is included to revert to the old system so you can still use older mods.
158 | - If you are a plugin developer, use GetLoadedAsset to get an asset, instead of using the asset dictionary. This ensures that your plugin will still work even when changes like this are made.
159 |
160 |
161 | ## Version [1.3.0]
162 |
163 | - Changed how the BundleLoader in the BundleAPI loads assets to fix issues caused by downloading mods from mod managers. The path BepInEx > Bundles is outdated and should not be used anymore.
164 |
165 | ## Version [1.2.1]
166 |
167 | - Adjusted README formatting.
168 |
169 |
170 | ## Version [1.2.0]
171 |
172 | - Added new GameInterfaceAPI. Documentation will be created soon.
173 |
174 | ## Version [1.1.1]
175 |
176 | - General bug fixes for Networking
177 |
178 | ## Version [1.1.0]
179 |
180 | - General bug fixes for Networking
181 |
182 | ## Version [1.0.0]
183 |
184 | - Release
185 |
--------------------------------------------------------------------------------
/LC-API.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.6.33829.357
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LC-API", "LC-API/LC-API.csproj", "{D1BF6DDB-3AEE-4EF0-B6C8-4FBED65C75B9}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {D1BF6DDB-3AEE-4EF0-B6C8-4FBED65C75B9}.Debug|Any CPU.ActiveCfg = Release|Any CPU
15 | {D1BF6DDB-3AEE-4EF0-B6C8-4FBED65C75B9}.Debug|Any CPU.Build.0 = Release|Any CPU
16 | {D1BF6DDB-3AEE-4EF0-B6C8-4FBED65C75B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {D1BF6DDB-3AEE-4EF0-B6C8-4FBED65C75B9}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {EB6BFF56-A2F5-4195-9F89-775EB386EDC2}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/LC-API/BundleAPI/BundleLoader.cs:
--------------------------------------------------------------------------------
1 | using BepInEx;
2 | using LC_API.Extensions;
3 | using System;
4 | using System.Collections.Concurrent;
5 | using System.Collections.Generic;
6 | using System.IO;
7 | using System.Linq;
8 | using System.Text;
9 | using UnityEngine;
10 |
11 | //todo: force all paths to lowercase after privating 'assets' to ensure all paths get normalized when accessing.
12 |
13 | #pragma warning disable CS0618 // Type or member is obsolete - we're the ones obsoleting things.
14 | namespace LC_API.BundleAPI
15 | {
16 | ///
17 | /// Use the BundleLoader to load and get custom assets. A folder for placing custom assets will be generated in BepInEx/Bundles.
18 | ///
19 | public static class BundleLoader
20 | {
21 | ///
22 | /// Is set to if there are any files not ending in '.manifest' in the legacy bundle dir. if there are no files or only .manifest files.
23 | ///
24 | public static bool AssetsInLegacyDirectory { get; private set; }
25 |
26 | ///
27 | /// Is set to if asset loading legacy bundle dir was enabled. if assets were not allowed to be loaded from the legacy bundle dir.
28 | ///
29 | public static bool LegacyLoadingEnabled { get; private set; }
30 |
31 | // i think eventually 'assets' should be privatized and changed to a Dictionary. Unity is comically thread-unsafe, so using a thread-safe ConcurrentDict is overkill.
32 | [Obsolete("Use GetLoadedAsset instead. This will be removed/private in a future update.")]
33 | public static ConcurrentDictionary assets = new ConcurrentDictionary();
34 |
35 | [Obsolete("Use OnLoadedBundles instead. This will be removed/private in a future update.")]
36 | public delegate void OnLoadedAssetsDelegate();
37 | ///
38 | /// This is called when the BundleLoader finishes loading assets.
39 | ///
40 | [Obsolete("Use OnLoadedBundles instead. This will be removed/private in a future update.")]
41 | public static OnLoadedAssetsDelegate OnLoadedAssets = LoadAssetsCompleted;
42 |
43 | ///
44 | /// This is called when the BundleLoader finishes loading assets.
45 | ///
46 | public static event Action OnLoadedBundles = LoadAssetsCompleted;
47 |
48 | private static Dictionary loadedAssetBundles = new Dictionary();
49 |
50 | internal static void Load(bool legacyLoading)
51 | {
52 | LegacyLoadingEnabled = legacyLoading;
53 | if (LegacyLoadingEnabled)
54 | {
55 | Plugin.Log.LogMessage("BundleAPI will now load all asset bundles...");
56 | string bundleDir = Path.Combine(Paths.BepInExRootPath, "Bundles");
57 | if (!Directory.Exists(bundleDir))
58 | {
59 | Directory.CreateDirectory(bundleDir);
60 | Plugin.Log.LogMessage("BundleAPI Created legacy bundle directory in BepInEx/Bundles");
61 | }
62 | string[] bundles = Directory.GetFiles(bundleDir, "*", SearchOption.AllDirectories).Where(x => !x.EndsWith(".manifest", StringComparison.CurrentCultureIgnoreCase)).ToArray();
63 |
64 | AssetsInLegacyDirectory = bundles.Length != 0;
65 |
66 | if (!AssetsInLegacyDirectory)
67 | {
68 | Plugin.Log.LogMessage("BundleAPI got no assets to load from legacy directory");
69 | }
70 |
71 | if (AssetsInLegacyDirectory)
72 | {
73 | Plugin.Log.LogWarning("The path BepInEx > Bundles is outdated and should not be used anymore! Bundles will be loaded from BepInEx > plugins from now on");
74 | LoadAllAssetsFromDirectory(bundles, legacyLoading);
75 | }
76 |
77 | string[] invalidEndings = { ".dll", ".json", ".png", ".md", ".old", ".txt", ".exe", ".lem" };
78 | bundleDir = Path.Combine(Paths.BepInExRootPath, "plugins");
79 | bundles = Directory.GetFiles(bundleDir, "*", SearchOption.AllDirectories).Where(file => !invalidEndings.Any(ending => file.EndsWith(ending, StringComparison.CurrentCultureIgnoreCase))).ToArray();
80 |
81 | byte[] bundleStart = Encoding.ASCII.GetBytes("UnityFS");
82 |
83 | List properBundles = new List();
84 |
85 | foreach (string path in bundles)
86 | {
87 | byte[] buffer = new byte[bundleStart.Length];
88 |
89 | try
90 | {
91 | using (FileStream fs = File.Open(path, FileMode.Open))
92 | {
93 | fs.Read(buffer, 0, buffer.Length);
94 | }
95 | }
96 | catch (IOException ioException)
97 | {
98 | Plugin.Log.LogError($"BundleAPI failed to open file \"{path}\": {ioException.Message}\n{ioException.StackTrace}");
99 | }
100 |
101 | if (buffer.SequenceEqual(bundleStart))
102 | {
103 | properBundles.Add(path);
104 | }
105 | }
106 |
107 | bundles = properBundles.ToArray();
108 |
109 | if (bundles.Length == 0)
110 | {
111 | Plugin.Log.LogMessage("BundleAPI got no assets to load from plugins folder");
112 | }
113 | else
114 | {
115 | LoadAllAssetsFromDirectory(bundles, legacyLoading);
116 | }
117 |
118 | OnLoadedAssets.InvokeParameterlessDelegate();
119 | OnLoadedBundles.InvokeActionSafe();
120 | }
121 | }
122 |
123 | private static void LoadAllAssetsFromDirectory(string[] array, bool legacyLoading)
124 | {
125 | if (legacyLoading)
126 | {
127 | Plugin.Log.LogMessage("BundleAPI got " + array.Length.ToString() + " AssetBundles to load!");
128 |
129 | for (int i = 0; i < array.Length; i++)
130 | {
131 | try
132 | {
133 | SaveAsset(array[i], legacyLoading);
134 | }
135 | catch (Exception ex)
136 | {
137 | Plugin.Log.LogError("Failed to load an assetbundle! Path: " + array[i]);
138 | }
139 | }
140 | }
141 | else
142 | {
143 | Plugin.Log.LogMessage("BundleAPI got " + array.Length.ToString() + " AssetBundles to load!");
144 |
145 | for (int i = 0; i < array.Length; i++)
146 | {
147 | try
148 | {
149 | SaveAsset(array[i], legacyLoading);
150 | }
151 | catch (Exception ex)
152 | {
153 | Plugin.Log.LogError("Failed to load an assetbundle! Path: " + array[i]);
154 | }
155 | }
156 | }
157 |
158 | }
159 |
160 | ///
161 | /// Saves all assets from a bundle at the given path. It is not recommended to call this method, as it can introduce instability.
162 | ///
163 | public static void SaveAsset(string path, bool legacyLoad)
164 | {
165 | AssetBundle bundle = AssetBundle.LoadFromFile(path);
166 | try
167 | {
168 | string[] assetPaths = bundle.GetAllAssetNames();
169 | //Plugin.Log.LogMessage("Assets: [" + string.Join(", ", array) + "]");
170 | foreach (string assetPath in assetPaths)
171 | {
172 | Plugin.Log.LogMessage("Got asset for load: " + assetPath);
173 | UnityEngine.Object loadedAsset = bundle.LoadAsset(assetPath);
174 | if (loadedAsset == null)
175 | {
176 | Plugin.Log.LogWarning($"Skipped/failed loading an asset (from bundle '{bundle.name}') - Asset path: {loadedAsset}");
177 | continue;
178 | }
179 |
180 | string text2 = legacyLoad ? assetPath.ToUpper() : assetPath.ToLower();
181 |
182 | if (assets.ContainsKey(text2))
183 | {
184 | Plugin.Log.LogError("BundleAPI got duplicate asset!");
185 | return;
186 | }
187 | assets.TryAdd(text2, loadedAsset);
188 | Plugin.Log.LogMessage("Loaded asset: " + loadedAsset.name);
189 | }
190 | }
191 | finally
192 | {
193 | bundle?.Unload(false);
194 | }
195 | }
196 |
197 | ///
198 | /// Get an asset from all the loaded assets. Use this to access your custom assets. Returned value may be null.
199 | ///
200 | public static TAsset GetLoadedAsset(string itemPath) where TAsset : UnityEngine.Object
201 | {
202 | UnityEngine.Object? asset = null;
203 | if (LegacyLoadingEnabled)
204 | assets.TryGetValue(itemPath.ToUpper(), out asset);
205 |
206 | if (asset == null)
207 | assets.TryGetValue(itemPath.ToLower(), out asset);
208 |
209 | return (TAsset)asset;
210 | }
211 |
212 | ///
213 | /// Loads an entire asset bundle. It is recommended to use this to load asset bundles.
214 | ///
215 | /// The file system path of the asset bundle.
216 | /// Whether or not to cache the loaded bundle. Set to if you cache it yourself.
217 | /// A containing all assets from the bundle mapped to their path in lowercase.
218 | public static LoadedAssetBundle LoadAssetBundle(string filePath, bool cache = true)
219 | {
220 | if (loadedAssetBundles.TryGetValue(filePath, out LoadedAssetBundle _assets))
221 | return _assets;
222 |
223 | Dictionary assetPairs = new Dictionary();
224 |
225 | AssetBundle bundle = AssetBundle.LoadFromFile(filePath);
226 | try
227 | {
228 | string[] assetPaths = bundle.GetAllAssetNames();
229 | //Plugin.Log.LogMessage("Assets: [" + string.Join(", ", array) + "]");
230 | foreach (string assetPath in assetPaths)
231 | {
232 | UnityEngine.Object loadedAsset = bundle.LoadAsset(assetPath);
233 | if (loadedAsset == null)
234 | {
235 | Plugin.Log.LogWarning($"Skipped/failed loading an asset (from bundle '{bundle.name}') - Asset path: {loadedAsset}");
236 | continue;
237 | }
238 |
239 | string path = assetPath.ToLower();
240 |
241 | if (assets.ContainsKey(path))
242 | {
243 | Plugin.Log.LogError("BundleAPI got duplicate asset!");
244 | return null;
245 | }
246 |
247 | assets.TryAdd(path, loadedAsset);
248 |
249 | assetPairs.Add(path, loadedAsset);
250 | }
251 | }
252 | finally
253 | {
254 | bundle?.Unload(false);
255 | }
256 |
257 | if (cache) loadedAssetBundles.Add(filePath, new LoadedAssetBundle(assetPairs));
258 |
259 | return new LoadedAssetBundle(assetPairs);
260 | }
261 |
262 | private static void LoadAssetsCompleted()
263 | {
264 | Plugin.Log.LogMessage("BundleAPI finished loading all assets.");
265 | }
266 | }
267 | }
268 | #pragma warning restore CS0618 // Type or member is obsolete - we're the ones obsoleting things.
269 |
--------------------------------------------------------------------------------
/LC-API/BundleAPI/LoadedAssetBundle.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace LC_API.BundleAPI
4 | {
5 | ///
6 | /// A loaded asset bundle.
7 | ///
8 | public class LoadedAssetBundle
9 | {
10 | private Dictionary _loadedAssets;
11 |
12 | internal LoadedAssetBundle(Dictionary loadedAssets)
13 | {
14 | _loadedAssets = loadedAssets;
15 | }
16 |
17 | ///
18 | /// Gets an asset from the bundle.
19 | ///
20 | /// The type of the asset. Usually .
21 | /// The path to the asset in the bundle.
22 | /// The asset.
23 | public TAsset GetAsset(string path) where TAsset : UnityEngine.Object
24 | {
25 | string lowerPath = path.ToLower();
26 |
27 | if (_loadedAssets.TryGetValue(lowerPath, out UnityEngine.Object obj))
28 | {
29 | return obj as TAsset;
30 | }
31 |
32 | return null;
33 | }
34 |
35 | ///
36 | /// Tries to get an asset from the bundle.
37 | ///
38 | /// The type of the asset. Usually .
39 | /// The path to the asset in the bundle.
40 | /// Outputs the asset.
41 | /// if the asset is found. if the asset isn't found, or couldn't be casted to TAsset
42 | public bool TryGetAsset(string path, out TAsset asset) where TAsset : UnityEngine.Object
43 | {
44 | string lowerPath = path.ToLower();
45 |
46 | asset = null;
47 |
48 | if (_loadedAssets.TryGetValue(lowerPath, out UnityEngine.Object obj))
49 | {
50 | if (obj is TAsset tasset) asset = tasset;
51 | }
52 |
53 | return asset != null;
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/LC-API/CheatDatabase.cs:
--------------------------------------------------------------------------------
1 | using BepInEx;
2 | using BepInEx.Bootstrap;
3 | using LC_API.GameInterfaceAPI.Features;
4 | using LC_API.Networking;
5 | using System.Collections.Generic;
6 |
7 | namespace LC_API
8 | {
9 | internal static class CheatDatabase
10 | {
11 | const string SIG_REQ_GUID = "LC_API_ReqGUID";
12 | const string SIG_SEND_MODS = "LC_APISendMods";
13 |
14 | private static Dictionary PluginsLoaded = new Dictionary();
15 |
16 | public static void RunLocalCheatDetector()
17 | {
18 | PluginsLoaded = Chainloader.PluginInfos;
19 |
20 | foreach (PluginInfo info in PluginsLoaded.Values)
21 | {
22 | switch (info.Metadata.GUID)
23 | {
24 | case "mikes.lethalcompany.mikestweaks":
25 | case "mom.llama.enhancer":
26 | case "Posiedon.GameMaster":
27 | case "LethalCompanyScalingMaster":
28 | case "verity.amberalert":
29 | ServerAPI.ModdedServer.SetServerModdedOnly();
30 | break;
31 | }
32 | }
33 |
34 | }
35 |
36 | public static void OtherPlayerCheatDetector()
37 | {
38 | Plugin.Log.LogWarning("Asking all other players for their mod list..");
39 | Player.LocalPlayer.QueueTip("Mod List:", "Asking all other players for installed mods..");
40 | Player.LocalPlayer.QueueTip("Mod List:", "Check the logs for more detailed results.\n(Note that if someone doesnt show up on the list, they may not have LC_API installed)");
41 | Network.Broadcast(SIG_REQ_GUID);
42 | }
43 |
44 | [NetworkMessage(SIG_SEND_MODS)]
45 | internal static void ReceivedModListHandler(ulong senderId, List mods)
46 | {
47 | Player player = Player.Get(senderId);
48 | string data = $"{player.Username} responded with these mods:\n{string.Join("\n", mods)}";
49 | Player.LocalPlayer.QueueTip("Mod List:", data);
50 | Plugin.Log.LogWarning(data);
51 | }
52 |
53 | [NetworkMessage(SIG_REQ_GUID)]
54 | internal static void ReceivedModListHandler(ulong senderId)
55 | {
56 | List mods = new List();
57 | foreach (PluginInfo info in PluginsLoaded.Values)
58 | {
59 | mods.Add(info.Metadata.GUID);
60 | }
61 |
62 | Network.Broadcast(SIG_SEND_MODS, mods);
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/LC-API/ClientAPI/CommandHandler.cs:
--------------------------------------------------------------------------------
1 | using BepInEx;
2 | using BepInEx.Configuration;
3 | using GameNetcodeStuff;
4 | using HarmonyLib;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Linq;
8 | using System.Reflection;
9 | using System.Reflection.Emit;
10 | using UnityEngine.EventSystems;
11 |
12 | namespace LC_API.ClientAPI
13 | {
14 | ///
15 | /// Provides an easy way for developers to add chat-based commands.
16 | ///
17 | public static class CommandHandler
18 | {
19 | internal static ConfigEntry commandPrefix;
20 |
21 | internal static Dictionary> CommandHandlers = new Dictionary>();
22 |
23 | internal static Dictionary> CommandAliases = new Dictionary>();
24 |
25 | ///
26 | /// Registers a command with no aliases.
27 | ///
28 | /// The command string. No spaces.
29 | /// The handler itself. Passes a string[] of arguments.
30 | /// Whether or not the command handler was added.
31 | public static bool RegisterCommand(string command, Action handler)
32 | {
33 | // The handler is not capable of handling commands with spaces.
34 | if (command.Contains(" ") || CommandHandlers.ContainsKey(command)) return false;
35 |
36 | CommandHandlers.Add(command, handler);
37 |
38 | return true;
39 | }
40 |
41 | ///
42 | /// Registers a command with aliases.
43 | ///
44 | /// The command string. No spaces.
45 | /// A list of aliases. None of them can have spaces, and a handler cannot exist with that string.
46 | /// The handler itself. Passes a string[] of arguments.
47 | ///
48 | public static bool RegisterCommand(string command, List aliases, Action handler)
49 | {
50 | // The handler is not capable of handling commands with spaces.
51 | if (command.Contains(" ") || GetCommandHandler(command) != null) return false;
52 |
53 | foreach (string alias in aliases)
54 | {
55 | if (alias.Contains(" ") || GetCommandHandler(alias) != null) return false;
56 | }
57 |
58 | CommandHandlers.Add(command, handler);
59 |
60 | CommandAliases.Add(command, aliases);
61 |
62 | return true;
63 | }
64 |
65 | ///
66 | /// Unregisters a command.
67 | ///
68 | /// The command string to unregister.
69 | /// true if the command existed and was unregistered, false otherwise.
70 | public static bool UnregisterCommand(string command)
71 | {
72 | CommandAliases.Remove(command);
73 | return CommandHandlers.Remove(command);
74 | }
75 |
76 | internal static Action GetCommandHandler(string command)
77 | {
78 | if (CommandHandlers.TryGetValue(command, out var handler)) return handler;
79 |
80 | foreach (var alias in CommandAliases)
81 | {
82 | if (alias.Value.Contains(command)) return CommandHandlers[alias.Key];
83 | }
84 |
85 | return null;
86 | }
87 |
88 | internal static bool TryGetCommandHandler(string command, out Action handler)
89 | {
90 | handler = GetCommandHandler(command);
91 | return handler != null;
92 | }
93 |
94 | internal static class SubmitChatPatch
95 | {
96 | private static bool HandleMessage(HUDManager manager)
97 | {
98 | string message = manager.chatTextField.text;
99 |
100 | if (!message.IsNullOrWhiteSpace() && message.StartsWith(commandPrefix.Value))
101 | {
102 | string[] split = message.Split(' ');
103 |
104 | string command = split[0].Substring(commandPrefix.Value.Length);
105 |
106 | if (TryGetCommandHandler(command, out var handler))
107 | {
108 | string[] arguments = split.Skip(1).ToArray();
109 | try
110 | {
111 | handler.Invoke(arguments);
112 | }
113 | catch (Exception ex)
114 | {
115 | Plugin.Log.LogError($"Error handling command: {command}");
116 | Plugin.Log.LogError(ex);
117 | }
118 |
119 | manager.localPlayer.isTypingChat = false;
120 | manager.chatTextField.text = "";
121 | EventSystem.current.SetSelectedGameObject(null);
122 | manager.typingIndicator.enabled = false;
123 |
124 | return true;
125 | }
126 | }
127 |
128 | return false;
129 | }
130 |
131 | internal static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator)
132 | {
133 | List newInstructions = new List(instructions);
134 |
135 | Label returnLabel = generator.DefineLabel();
136 |
137 | newInstructions[newInstructions.Count - 1].labels.Add(returnLabel);
138 |
139 | int index = newInstructions.FindIndex(i => i.opcode == OpCodes.Ldfld &&
140 | (FieldInfo)i.operand == AccessTools.Field(typeof(PlayerControllerB), nameof(PlayerControllerB.isPlayerDead))) - 2;
141 |
142 | newInstructions.InsertRange(index, new CodeInstruction[]
143 | {
144 | // if (SubmitChatPatch.HandleMessage(this)) return;
145 | new CodeInstruction(OpCodes.Ldarg_0).MoveLabelsFrom(newInstructions[index]),
146 | new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(SubmitChatPatch), nameof(SubmitChatPatch.HandleMessage))),
147 | new CodeInstruction(OpCodes.Brtrue, returnLabel)
148 | });
149 |
150 | for (int z = 0; z < newInstructions.Count; z++) yield return newInstructions[z];
151 | }
152 | }
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/LC-API/Comp/LC_APIManager.cs:
--------------------------------------------------------------------------------
1 | using LC_API.GameInterfaceAPI;
2 | using LC_API.GameInterfaceAPI.Features;
3 | using LC_API.ServerAPI;
4 | using System.Collections;
5 | using UnityEngine;
6 | using UnityEngine.SceneManagement;
7 |
8 | namespace LC_API.Comp
9 | {
10 | internal class LC_APIManager : MonoBehaviour
11 | {
12 | public static MenuManager MenuManager;
13 | private static int playerCount;
14 | private static bool wanttoCheckMods;
15 | private static float lobbychecktimer;
16 |
17 | public void Awake()
18 | {
19 | SceneManager.sceneLoaded += OnSceneLoaded;
20 | }
21 |
22 | public void Update()
23 | {
24 | GameState.GSUpdate();
25 | GameTips.UpdateInternal();
26 | if (!ModdedServer.setModdedOnly)
27 | {
28 | ModdedServer.OnSceneLoaded();
29 | }
30 | else if (ModdedServer.ModdedOnly)
31 | {
32 | if (MenuManager != null)
33 | {
34 | if (MenuManager.versionNumberText)
35 | {
36 | MenuManager.versionNumberText.text = $"v{GameNetworkManager.Instance.gameVersionNum - 16440}\nMOD";
37 | }
38 | }
39 | }
40 |
41 | if (GameNetworkManager.Instance != null)
42 | {
43 | if (playerCount < GameNetworkManager.Instance.connectedPlayers)
44 | {
45 | lobbychecktimer = -4.5f;
46 | wanttoCheckMods = true;
47 | }
48 | playerCount = GameNetworkManager.Instance.connectedPlayers;
49 | }
50 | if (lobbychecktimer < 0)
51 | {
52 | lobbychecktimer += Time.deltaTime;
53 | }
54 | else if (wanttoCheckMods && HUDManager.Instance != null && Player.HostPlayer != null && Player.LocalPlayer != null)
55 | {
56 | wanttoCheckMods = false;
57 | CD();
58 | }
59 | }
60 |
61 | // For pre-placed items
62 | internal void OnSceneLoaded(Scene scene, LoadSceneMode mode)
63 | {
64 | foreach (GrabbableObject grabbable in FindObjectsOfType())
65 | {
66 | if (!grabbable.TryGetComponent(out GameInterfaceAPI.Features.Item _))
67 | {
68 | grabbable.gameObject.AddComponent();
69 | }
70 | }
71 | }
72 |
73 | private void CD()
74 | {
75 | CheatDatabase.OtherPlayerCheatDetector();
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/LC-API/Data/NetworkBroadcastDataType.cs:
--------------------------------------------------------------------------------
1 | namespace LC_API.Data
2 | {
3 | internal enum NetworkBroadcastDataType
4 | {
5 | Unknown,
6 | BDint,
7 | BDfloat,
8 | BDvector3,
9 | BDstring,
10 | BDlistString
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/LC-API/Data/ShipState.cs:
--------------------------------------------------------------------------------
1 | namespace LC_API.Data
2 | {
3 | ///
4 | /// Describes the state the company ship is currently in.
5 | ///
6 | public enum ShipState
7 | {
8 | ///
9 | /// The ship is currently in orbit.
10 | /// Players are likely either goofing off, AFK, or picking the next moon to visit.
11 | ///
12 | InOrbit,
13 | ///
14 | /// The ship is currently on a moon.
15 | /// Players are likely getting torn limb from limb, getting , or witnessing horrors target their fellow man.
16 | ///
17 | OnMoon,
18 | ///
19 | /// The ship has been told to leave and it is currently doing so.
20 | /// This could either be caused by ghosts of players' pasts (dead players) telling the autopilot to do so, or a living player pulling the lever. Either way, the ship is taking off.
21 | ///
22 | LeavingMoon,
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/LC-API/Exceptions/NoAuthorityException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace LC_API.Exceptions
4 | {
5 | internal class NoAuthorityException : Exception
6 | {
7 | internal NoAuthorityException(string message) : base(message) { }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/LC-API/Extensions/DelegateExtensions.cs:
--------------------------------------------------------------------------------
1 | using BepInEx;
2 | using BepInEx.Logging;
3 | using HarmonyLib;
4 | using System;
5 | using System.Linq;
6 | using System.Reflection;
7 |
8 | namespace LC_API.Extensions
9 | {
10 | ///
11 | /// Extension methods for "Delegates". Basically makes working with 'callable' (invokable) things easier.
12 | ///
13 | public static class DelegateExtensions
14 | {
15 | private static readonly PropertyInfo PluginGetLogger = AccessTools.Property(typeof(BaseUnityPlugin), "Logger");
16 |
17 | ///
18 | /// Call an action (usually a list of hook callbacks) in a way that logs errors instead of throwing them. This avoids only executing some callbacks if one errors out.
19 | ///
20 | public static void InvokeActionSafe(this Action action)
21 | {
22 | if (action == null) return;
23 |
24 | foreach (Delegate invoker in action.GetInvocationList())
25 | {
26 | try
27 | {
28 | Action call = (Action)invoker;
29 | call();
30 | }
31 | catch (Exception ex)
32 | {
33 | Plugin.Log.LogError("Exception while invoking hook callback!");
34 |
35 | string asmName = invoker.GetMethodInfo().DeclaringType.Assembly.FullName;
36 | PluginInfo plugin = BepInEx.Bootstrap.Chainloader.PluginInfos.Values.FirstOrDefault(pi => pi.Instance.GetType().Assembly.FullName == asmName);
37 | if (plugin == null)
38 | {
39 | Plugin.Log.LogError(ex.ToString());
40 | return;
41 | }
42 |
43 | var pluginsLogger = (ManualLogSource)PluginGetLogger.GetValue(plugin.Instance);
44 | pluginsLogger.LogError(ex.ToString());
45 | }
46 | }
47 | }
48 |
49 |
50 | ///
51 | /// Call an action (usually a list of hook callbacks) in a way that logs errors instead of throwing them. This avoids only executing some callbacks if one errors out.
52 | ///
53 | public static void InvokeActionSafe(this Action action, T param)
54 | {
55 | if (action == null) return;
56 |
57 | foreach (Delegate invoker in action.GetInvocationList())
58 | {
59 | try
60 | {
61 | Action call = (Action)invoker;
62 | call(param);
63 | }
64 | catch (Exception ex)
65 | {
66 | Plugin.Log.LogError("Exception while invoking hook callback!");
67 |
68 | string asmName = invoker.GetMethodInfo().DeclaringType.Assembly.FullName;
69 | PluginInfo plugin = BepInEx.Bootstrap.Chainloader.PluginInfos.Values.FirstOrDefault(pi => pi.Instance.GetType().Assembly.FullName == asmName);
70 | if (plugin == null)
71 | {
72 | Plugin.Log.LogError(ex.ToString());
73 | return;
74 | }
75 |
76 | var pluginsLogger = (ManualLogSource)PluginGetLogger.GetValue(plugin.Instance);
77 | pluginsLogger.LogError(ex.ToString());
78 | }
79 | }
80 | }
81 |
82 | ///
83 | /// Call an action (usually a list of hook callbacks) in a way that logs errors instead of throwing them. This avoids only executing some callbacks if one errors out.
84 | ///
85 | public static void InvokeActionSafe(this Action action, T1 param1, T2 param2)
86 | {
87 | if (action == null) return;
88 |
89 | foreach (Delegate invoker in action.GetInvocationList())
90 | {
91 | try
92 | {
93 | Action call = (Action)invoker;
94 | call(param1, param2);
95 | }
96 | catch (Exception ex)
97 | {
98 | Plugin.Log.LogError("Exception while invoking hook callback!");
99 |
100 | string asmName = invoker.GetMethodInfo().DeclaringType.Assembly.FullName;
101 | PluginInfo plugin = BepInEx.Bootstrap.Chainloader.PluginInfos.Values.FirstOrDefault(pi => pi.Instance.GetType().Assembly.FullName == asmName);
102 | if (plugin == null)
103 | {
104 | Plugin.Log.LogError(ex.ToString());
105 | return;
106 | }
107 |
108 | var pluginsLogger = (ManualLogSource)PluginGetLogger.GetValue(plugin.Instance);
109 | pluginsLogger.LogError(ex.ToString());
110 | }
111 | }
112 | }
113 |
114 |
115 | // internal because it uses the slow DynamicInvoke, and almost any event delegate can be an Action(//etc), at least in modding.
116 | internal static void InvokeParameterlessDelegate(this T paramlessDelegate) where T : Delegate
117 | {
118 | if (paramlessDelegate == null) return;
119 |
120 | foreach (Delegate invoker in paramlessDelegate.GetInvocationList())
121 | {
122 | try
123 | {
124 | T call = (T)invoker;
125 | call.DynamicInvoke();
126 | }
127 | catch (Exception ex)
128 | {
129 | Plugin.Log.LogError("Exception while invoking hook callback!");
130 |
131 | string asmName = invoker.GetMethodInfo().DeclaringType.Assembly.FullName;
132 | PluginInfo plugin = BepInEx.Bootstrap.Chainloader.PluginInfos.Values.FirstOrDefault(pi => pi.Instance.GetType().Assembly.FullName == asmName);
133 | if (plugin == null)
134 | {
135 | Plugin.Log.LogError(ex.ToString());
136 | return;
137 | }
138 |
139 | var pluginsLogger = (ManualLogSource)PluginGetLogger.GetValue(plugin.Instance);
140 | pluginsLogger.LogError(ex.ToString());
141 | }
142 | }
143 | }
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/LC-API/GameInterfaceAPI/Events/Cache/Player.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace LC_API.GameInterfaceAPI.Events.Cache
4 | {
5 | internal static class Player
6 | {
7 | internal static List ConnectedPlayers { get; private set; } = new List();
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/LC-API/GameInterfaceAPI/Events/EventArgs/Player/DiedEventArgs.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 |
3 | namespace LC_API.GameInterfaceAPI.Events.EventArgs.Player
4 | {
5 | ///
6 | /// Contains all the information after a dies.
7 | ///
8 | public class DiedEventArgs : System.EventArgs
9 | {
10 | ///
11 | /// Gets the player that died.
12 | ///
13 | public Features.Player Player { get; }
14 |
15 | ///
16 | /// Gets the force that was added to the ragdoll.
17 | ///
18 | public Vector3 Force { get; }
19 |
20 | ///
21 | /// Gets whether or not a ragdoll was spawned.
22 | ///
23 | public bool SpawnBody { get; }
24 |
25 | ///
26 | /// Gets the cause of death.
27 | ///
28 | public CauseOfDeath CauseOfDeath { get; }
29 |
30 | ///
31 | /// Gets the death animation index.
32 | ///
33 | public int DeathAnimation { get; }
34 |
35 | ///
36 | /// Initializes a new instance of the class.
37 | ///
38 | ///
39 | ///
40 | ///
41 | ///
42 | ///
43 | public DiedEventArgs(Features.Player player, Vector3 force, bool spawnBody, CauseOfDeath causeOfDeath, int deathAnimation)
44 | {
45 | Player = player;
46 | Force = force;
47 | SpawnBody = spawnBody;
48 | CauseOfDeath = causeOfDeath;
49 | DeathAnimation = deathAnimation;
50 | }
51 | }
52 | }
--------------------------------------------------------------------------------
/LC-API/GameInterfaceAPI/Events/EventArgs/Player/DroppingItemEventArgs.cs:
--------------------------------------------------------------------------------
1 | using Unity.Netcode;
2 | using UnityEngine;
3 |
4 | namespace LC_API.GameInterfaceAPI.Events.EventArgs.Player
5 | {
6 | public class DroppingItemEventArgs : System.EventArgs
7 | {
8 | public Features.Player Player { get; }
9 |
10 | public Features.Item Item { get; }
11 |
12 | public bool Placing { get; }
13 |
14 | public Vector3 TargetPosition { get; set; }
15 |
16 | public int FloorYRotation { get; set; }
17 |
18 | public NetworkObject ParentObjectTo { get; set; }
19 |
20 | public bool MatchRotationOfParent { get; set; }
21 |
22 | public bool DroppedInShip { get; set; }
23 |
24 | public bool IsAllowed { get; set; } = true;
25 |
26 | public DroppingItemEventArgs(Features.Player player, Features.Item item, bool placeObject, Vector3 targetPosition,
27 | int floorYRotation, NetworkObject parentObjectTo, bool matchRotationOfParent, bool droppedInShip)
28 | {
29 | Player = player;
30 | Item = item;
31 | Placing = placeObject;
32 | TargetPosition = targetPosition;
33 | FloorYRotation = floorYRotation;
34 | ParentObjectTo = parentObjectTo;
35 | MatchRotationOfParent = matchRotationOfParent;
36 | DroppedInShip = droppedInShip;
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/LC-API/GameInterfaceAPI/Events/EventArgs/Player/DyingEventArgs.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 |
3 | namespace LC_API.GameInterfaceAPI.Events.EventArgs.Player
4 | {
5 | ///
6 | /// Contains all the information before a is dies.
7 | ///
8 | public class DyingEventArgs : System.EventArgs
9 | {
10 | ///
11 | /// Gets the player that is dying.
12 | ///
13 | public Features.Player Player { get; }
14 |
15 | ///
16 | /// Gets or sets the force to add to the ragdoll.
17 | ///
18 | public Vector3 Force { get; set; }
19 |
20 | ///
21 | /// Gets or sets whether or not to spawn a ragdoll.
22 | ///
23 | public bool SpawnBody { get; set; }
24 |
25 | ///
26 | /// Gets or sets the cause of death.
27 | ///
28 | public CauseOfDeath CauseOfDeath { get; set; }
29 |
30 | ///
31 | /// Gets or sets the death animation index.
32 | ///
33 | public int DeathAnimation { get; set; }
34 |
35 | ///
36 | /// Gets or sets whether this death is allowed to occur.
37 | ///
38 | public bool IsAllowed { get; set; } = true;
39 |
40 | ///
41 | /// Initializes a new instance of the class.
42 | ///
43 | ///
44 | ///
45 | ///
46 | ///
47 | ///
48 | public DyingEventArgs(Features.Player player, Vector3 force, bool spawnBody, CauseOfDeath causeOfDeath, int deathAnimation)
49 | {
50 | Player = player;
51 | Force = force;
52 | SpawnBody = spawnBody;
53 | CauseOfDeath = causeOfDeath;
54 | DeathAnimation = deathAnimation;
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/LC-API/GameInterfaceAPI/Events/EventArgs/Player/GrabbedItemEventArgs.cs:
--------------------------------------------------------------------------------
1 | namespace LC_API.GameInterfaceAPI.Events.EventArgs.Player
2 | {
3 | public class GrabbedItemEventArgs : System.EventArgs
4 | {
5 | public Features.Player Player { get; }
6 |
7 | public Features.Item Item { get; }
8 |
9 | public GrabbedItemEventArgs(Features.Player player, Features.Item item)
10 | {
11 | Player = player;
12 | Item = item;
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/LC-API/GameInterfaceAPI/Events/EventArgs/Player/GrabbingItemEventArgs.cs:
--------------------------------------------------------------------------------
1 | namespace LC_API.GameInterfaceAPI.Events.EventArgs.Player
2 | {
3 | ///
4 | /// Contains all the information before a picks up an .
5 | ///
6 | public class GrabbingItemEventArgs : System.EventArgs
7 | {
8 | ///
9 | /// The picking up the .
10 | ///
11 | public Features.Player Player { get; }
12 |
13 | ///
14 | /// The being picked up.
15 | ///
16 | public Features.Item Item { get; }
17 |
18 | ///
19 | /// Gets or sets whether this is allowed to occur.
20 | ///
21 | public bool IsAllowed { get; set; } = true;
22 |
23 | ///
24 | /// Initializes a new instance of the class.
25 | ///
26 | ///
27 | ///
28 | public GrabbingItemEventArgs(Features.Player player, Features.Item item)
29 | {
30 | Player = player;
31 | Item = item;
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/LC-API/GameInterfaceAPI/Events/EventArgs/Player/HurtEventArgs.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 |
3 | namespace LC_API.GameInterfaceAPI.Events.EventArgs.Player
4 | {
5 | ///
6 | /// Contains all the information after a is hurt.
7 | ///
8 | public class HurtEventArgs : System.EventArgs
9 | {
10 | ///
11 | /// Gets the player that is taking damage.
12 | ///
13 | public Features.Player Player { get; }
14 |
15 | ///
16 | /// Gets the amount of damage the took.
17 | ///
18 | public int Damage { get; }
19 |
20 | ///
21 | /// Gets or sets whether or not this damage will play sfx, if it has any.
22 | ///
23 | public bool HasSFX { get; set; }
24 |
25 | ///
26 | /// Gets or sets the cause of death.
27 | ///
28 | public CauseOfDeath CauseOfDeath { get; set; }
29 |
30 | ///
31 | /// Gets or sets the death animation index.
32 | ///
33 | public int DeathAnimation { get; set; }
34 |
35 | ///
36 | /// Gets or sets whether or not this damage is considered fall damage.
37 | ///
38 | public bool FallDamage { get; set; }
39 |
40 | ///
41 | /// Gets or sets the force to add to the ragdoll if the player is killed.
42 | ///
43 | public Vector3 Force { get; set; }
44 |
45 | ///
46 | /// Gets whether or not this damage will kill the player.
47 | ///
48 | public bool Killing => Player.Health <= 0;
49 |
50 | ///
51 | /// Initializes a new instance of the class.
52 | ///
53 | ///
54 | ///
55 | ///
56 | ///
57 | ///
58 | ///
59 | ///
60 | public HurtEventArgs(Features.Player player, int damage, bool hasSFX, CauseOfDeath causeOfDeath, int deathAnimation,
61 | bool fallDamage, Vector3 force)
62 | {
63 | Player = player;
64 | Damage = damage;
65 | HasSFX = hasSFX;
66 | CauseOfDeath = causeOfDeath;
67 | DeathAnimation = deathAnimation;
68 | FallDamage = fallDamage;
69 | Force = force;
70 | }
71 | }
72 | }
--------------------------------------------------------------------------------
/LC-API/GameInterfaceAPI/Events/EventArgs/Player/HurtingEventArgs.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 |
3 | namespace LC_API.GameInterfaceAPI.Events.EventArgs.Player
4 | {
5 | ///
6 | /// Contains all the information before a is hurt.
7 | ///
8 | public class HurtingEventArgs : System.EventArgs
9 | {
10 | ///
11 | /// Gets the player that is taking damage.
12 | ///
13 | public Features.Player Player { get; }
14 |
15 | ///
16 | /// Gets or sets the amount of damage the will take.
17 | ///
18 | public int Damage { get; set; }
19 |
20 | ///
21 | /// Gets or sets whether or not this damage will play sfx, if it has any.
22 | ///
23 | public bool HasSFX { get; set; }
24 |
25 | ///
26 | /// Gets or sets the cause of death.
27 | ///
28 | public CauseOfDeath CauseOfDeath { get; set; }
29 |
30 | ///
31 | /// Gets or sets the death animation index.
32 | ///
33 | public int DeathAnimation { get; set; }
34 |
35 | ///
36 | /// Gets or sets whether or not this damage is considered fall damage.
37 | ///
38 | public bool FallDamage { get; set; }
39 |
40 | ///
41 | /// Gets or sets the force to add to the ragdoll if the player is killed.
42 | ///
43 | public Vector3 Force { get; set; }
44 |
45 | ///
46 | /// Gets or sets whether this damage is allowed to occur.
47 | ///
48 | public bool IsAllowed { get; set; } = true;
49 |
50 | ///
51 | /// Initializes a new instance of the class.
52 | ///
53 | ///
54 | ///
55 | ///
56 | ///
57 | ///
58 | ///
59 | ///
60 | public HurtingEventArgs(Features.Player player, int damage, bool hasSFX, CauseOfDeath causeOfDeath, int deathAnimation,
61 | bool fallDamage, Vector3 force)
62 | {
63 | Player = player;
64 | Damage = damage;
65 | HasSFX = hasSFX;
66 | CauseOfDeath = causeOfDeath;
67 | DeathAnimation = deathAnimation;
68 | FallDamage = fallDamage;
69 | Force = force;
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/LC-API/GameInterfaceAPI/Events/EventArgs/Player/JoinedEventArgs.cs:
--------------------------------------------------------------------------------
1 | namespace LC_API.GameInterfaceAPI.Events.EventArgs.Player
2 | {
3 | ///
4 | /// Contains all the information after a joined the server. Including the host.
5 | ///
6 | public class JoinedEventArgs : System.EventArgs
7 | {
8 | ///
9 | /// Gets the joined player.
10 | ///
11 | public Features.Player Player { get; }
12 |
13 | ///
14 | /// Initializes a new instance of the class.
15 | ///
16 | ///
17 | public JoinedEventArgs(Features.Player player)
18 | {
19 | Player = player;
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/LC-API/GameInterfaceAPI/Events/EventArgs/Player/LeftEventArgs.cs:
--------------------------------------------------------------------------------
1 | namespace LC_API.GameInterfaceAPI.Events.EventArgs.Player
2 | {
3 | ///
4 | /// Contains all the information right before a leaves the server. Including the local client.
5 | ///
6 | public class LeftEventArgs : System.EventArgs
7 | {
8 | ///
9 | /// Gets the player that is leaving.
10 | ///
11 | public Features.Player Player { get; }
12 |
13 | ///
14 | /// Initializes a new instance of the class.
15 | ///
16 | ///
17 | public LeftEventArgs(Features.Player player)
18 | {
19 | Player = player;
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/LC-API/GameInterfaceAPI/Events/EventArgs/Player/StartGrabbingItemEventArgs.cs:
--------------------------------------------------------------------------------
1 | namespace LC_API.GameInterfaceAPI.Events.EventArgs.Player
2 | {
3 | public class StartGrabbingItemEventArgs : System.EventArgs
4 | {
5 | public Features.Player Player { get; }
6 |
7 | public Features.Item Item { get; }
8 |
9 | public bool IsAllowed { get; set; } = true;
10 |
11 | public StartGrabbingItemEventArgs(Features.Player player, Features.Item item)
12 | {
13 | Player = player;
14 | Item = item;
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/LC-API/GameInterfaceAPI/Events/EventExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace LC_API.GameInterfaceAPI.Events
4 | {
5 | #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
6 | public static class EventExtensions
7 | {
8 | public static void InvokeSafely(this Events.CustomEventHandler ev, T arg)
9 | where T : System.EventArgs
10 | {
11 | if (ev == null)
12 | return;
13 |
14 | foreach (Events.CustomEventHandler handler in ev.GetInvocationList())
15 | {
16 | try
17 | {
18 | handler(arg);
19 | }
20 | catch (Exception ex)
21 | {
22 | Plugin.Log.LogError(ex);
23 | }
24 | }
25 | }
26 |
27 | public static void InvokeSafely(this Events.CustomEventHandler ev)
28 | {
29 | if (ev == null)
30 | return;
31 |
32 | foreach (Events.CustomEventHandler handler in ev.GetInvocationList())
33 | {
34 | try
35 | {
36 | handler();
37 | }
38 | catch (Exception ex)
39 | {
40 | Plugin.Log.LogError(ex);
41 | }
42 | }
43 | }
44 | }
45 | }
46 | #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
--------------------------------------------------------------------------------
/LC-API/GameInterfaceAPI/Events/Events.cs:
--------------------------------------------------------------------------------
1 | using HarmonyLib;
2 | using System;
3 | using System.Diagnostics;
4 |
5 | namespace LC_API.GameInterfaceAPI.Events
6 | {
7 | #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
8 | public static class Events
9 | {
10 | public delegate void CustomEventHandler(TEventArgs ev)
11 | where TEventArgs : System.EventArgs;
12 |
13 | public delegate void CustomEventHandler();
14 |
15 | internal static void Patch(Harmony harmony)
16 | {
17 | var method = new StackTrace().GetFrame(1).GetMethod();
18 | var assembly = method.ReflectedType.Assembly;
19 | foreach (Type t in AccessTools.GetTypesFromAssembly(assembly))
20 | {
21 | if (t.Namespace.StartsWith("LC_API.GameInterfaceAPI.Events.Patches"))
22 | {
23 | harmony.CreateClassProcessor(t).Patch();
24 | }
25 | }
26 | }
27 | }
28 | }
29 | #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
30 |
--------------------------------------------------------------------------------
/LC-API/GameInterfaceAPI/Events/Handlers/Player.cs:
--------------------------------------------------------------------------------
1 | using LC_API.GameInterfaceAPI.Events.EventArgs.Player;
2 | using static LC_API.GameInterfaceAPI.Events.Events;
3 |
4 | namespace LC_API.GameInterfaceAPI.Events.Handlers
5 | {
6 | ///
7 | /// Provides event handlers relating to Players.
8 | ///
9 | public static class Player
10 | {
11 | ///
12 | /// Invoked after a joined the server, including the host.
13 | ///
14 | public static event CustomEventHandler Joined;
15 |
16 | ///
17 | /// Invoked right before a leaves the server, including the local client.
18 | ///
19 | public static event CustomEventHandler Left;
20 |
21 | ///
22 | /// Invoked before a is hurt.
23 | ///
24 | public static event CustomEventHandler Hurting;
25 |
26 | ///
27 | /// Invoked after a is hurt.
28 | ///
29 | public static event CustomEventHandler Hurt;
30 |
31 | ///
32 | /// Invoked before a dies.
33 | ///
34 | public static event CustomEventHandler Dying;
35 |
36 | ///
37 | /// Invoked after a dies.
38 | ///
39 | public static event CustomEventHandler Died;
40 |
41 | ///
42 | /// Invoked before a starts grabbing an .
43 | ///
44 | public static event CustomEventHandler StartGrabbingItem;
45 |
46 | ///
47 | /// Invoked before a completes a grab on an .
48 | ///
49 | public static event CustomEventHandler GrabbingItem;
50 |
51 | ///
52 | /// Invoked after a completes a grab on an .
53 | ///
54 | public static event CustomEventHandler GrabbedItem;
55 |
56 | ///
57 | /// Invoked before a drops an .
58 | ///
59 | public static event CustomEventHandler DroppingItem;
60 |
61 | ///
62 | /// Called after a joined the server, including the host.
63 | ///
64 | /// The event arguments.
65 | public static void OnJoined(JoinedEventArgs ev) => Joined.InvokeSafely(ev);
66 |
67 | ///
68 | /// Called right before a leaves the server, including the local client.
69 | ///
70 | /// The event arguments.
71 | public static void OnLeft(LeftEventArgs ev) => Left.InvokeSafely(ev);
72 |
73 | ///
74 | /// Called before a is hurt.
75 | ///
76 | /// The event arguments.
77 | public static void OnHurting(HurtingEventArgs ev) => Hurting.InvokeSafely(ev);
78 |
79 | ///
80 | /// Called after a is hurt.
81 | ///
82 | /// The event arguments.
83 | public static void OnHurt(HurtEventArgs ev) => Hurt.InvokeSafely(ev);
84 |
85 | ///
86 | /// Called before a dies.
87 | ///
88 | /// The event arguments.
89 | public static void OnDying(DyingEventArgs ev) => Dying.InvokeSafely(ev);
90 |
91 | ///
92 | /// Called after a dies.
93 | ///
94 | /// The event arguments.
95 | public static void OnDied(DiedEventArgs ev) => Died.InvokeSafely(ev);
96 |
97 | ///
98 | /// Called before a starts grabbing an .
99 | ///
100 | /// The event arguments.
101 | public static void OnStartGrabbingItem(StartGrabbingItemEventArgs ev) => StartGrabbingItem.InvokeSafely(ev);
102 |
103 | ///
104 | /// Called before a completes a grab on an .
105 | ///
106 | /// The event arguments.
107 | public static void OnGrabbingItem(GrabbingItemEventArgs ev) => GrabbingItem.InvokeSafely(ev);
108 |
109 | ///
110 | /// Called after a completes a grab on an .
111 | ///
112 | /// The event arguments.
113 | public static void OnGrabbedItem(GrabbedItemEventArgs ev) => GrabbedItem.InvokeSafely(ev);
114 |
115 | ///
116 | /// Called before a drops an .
117 | ///
118 | /// The event arguments.
119 | public static void OnDroppingItem(DroppingItemEventArgs ev) => DroppingItem.InvokeSafely(ev);
120 | }
121 | }
--------------------------------------------------------------------------------
/LC-API/GameInterfaceAPI/Events/Patches/Internal/DisplayTipPatch.cs:
--------------------------------------------------------------------------------
1 | using HarmonyLib;
2 | using LC_API.GameInterfaceAPI.Features;
3 |
4 | namespace LC_API.GameInterfaceAPI.Events.Patches.Internal
5 | {
6 | [HarmonyPatch(typeof(HUDManager), nameof(HUDManager.DisplayTip))]
7 | class DisplayTipPatch
8 | {
9 | // Normally we wouldn't prefix return false, however, we need all uses of `DisplayTip` to go through
10 | // our system in order to not cause improper timing. All uses of base game's `DisplayTip` will bypass queue
11 | // and immediately be shown, but preserving the currently showing tip, if there is one, and continuing it after
12 | // the tip is complete.
13 | private static bool Prefix(string headerText, string bodyText, bool isWarning, bool useSave, string prefsKey)
14 | {
15 | Features.Player player = Features.Player.LocalPlayer;
16 |
17 | if (player == null) return true;
18 |
19 | player.ShowTip(headerText, bodyText, 5, isWarning, useSave, prefsKey);
20 |
21 | return false;
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/LC-API/GameInterfaceAPI/Events/Patches/Internal/GameNetworkManagerStartPatch.cs:
--------------------------------------------------------------------------------
1 | using HarmonyLib;
2 | using LC_API.BundleAPI;
3 | using System;
4 | using System.IO;
5 | using Unity.Netcode;
6 | using UnityEngine;
7 |
8 | namespace LC_API.GameInterfaceAPI.Events.Patches.Internal
9 | {
10 | [HarmonyPatch(typeof(GameNetworkManager), nameof(GameNetworkManager.Start))]
11 | class GameNetworkManagerStartPatch
12 | {
13 | private static readonly string BUNDLE_PATH = Path.Combine(Plugin.Instance.Info.Location.Substring(0, Plugin.Instance.Info.Location.LastIndexOf(Path.DirectorySeparatorChar)), "Bundles", "networking");
14 |
15 | private const string PLAYER_NETWORKING_ASSET_LOCATION = "assets/lc_api/playernetworkingprefab.prefab";
16 |
17 | private static void Postfix(GameNetworkManager __instance)
18 | {
19 | if (Plugin.configVanillaSupport.Value) return;
20 |
21 | if (!File.Exists(BUNDLE_PATH))
22 | {
23 | throw new Exception("Networking bundle not found at expected path.");
24 | }
25 |
26 | NetworkManager networkManager = __instance.GetComponent();
27 |
28 | LoadedAssetBundle assets = BundleLoader.LoadAssetBundle(BUNDLE_PATH, false);
29 |
30 | GameObject playerObj = assets.GetAsset(PLAYER_NETWORKING_ASSET_LOCATION);
31 | playerObj.AddComponent();
32 | playerObj.AddComponent();
33 | networkManager.AddNetworkPrefab(playerObj);
34 | Features.Player.PlayerNetworkPrefab = playerObj;
35 |
36 | foreach (NetworkPrefab prefab in networkManager.NetworkConfig.Prefabs.Prefabs)
37 | {
38 | if (prefab.Prefab.GetComponent() != null)
39 | {
40 | prefab.Prefab.AddComponent();
41 | }
42 | }
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/LC-API/GameInterfaceAPI/Events/Patches/Internal/PlayerControllerBStartPatch.cs:
--------------------------------------------------------------------------------
1 | using GameNetcodeStuff;
2 | using HarmonyLib;
3 | using Unity.Netcode;
4 | using UnityEngine;
5 |
6 | namespace LC_API.GameInterfaceAPI.Events.Patches.Internal
7 | {
8 | [HarmonyPatch(typeof(PlayerControllerB), nameof(PlayerControllerB.Start))]
9 | class PlayerControllerBStartPatch
10 | {
11 | private static void Postfix(PlayerControllerB __instance)
12 | {
13 | if (Plugin.configVanillaSupport.Value) return;
14 |
15 | if (__instance.IsServer && !Features.Player.TryGet(__instance, out Features.Player _))
16 | {
17 | GameObject go = UnityEngine.Object.Instantiate(Features.Player.PlayerNetworkPrefab);
18 | go.SetActive(true);
19 | go.GetComponent().PlayerController = __instance;
20 | go.GetComponent().Spawn(false);
21 | }
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/LC-API/GameInterfaceAPI/Events/Patches/Player/Die.cs:
--------------------------------------------------------------------------------
1 | using GameNetcodeStuff;
2 | using HarmonyLib;
3 | using LC_API.GameInterfaceAPI.Events.EventArgs.Player;
4 | using System.Collections.Generic;
5 | using System.Reflection.Emit;
6 | using UnityEngine;
7 |
8 | namespace LC_API.GameInterfaceAPI.Events.Patches.Player
9 | {
10 | [HarmonyPatch(typeof(PlayerControllerB), nameof(PlayerControllerB.KillPlayer))]
11 | internal class Dying
12 | {
13 | private static DyingEventArgs CallEvent(PlayerControllerB playerController, Vector3 force, bool spawnBody,
14 | CauseOfDeath causeOfDeath, int deathAnimation)
15 | {
16 | if (Plugin.configVanillaSupport.Value) return null;
17 |
18 | DyingEventArgs ev = new DyingEventArgs(Features.Player.GetOrAdd(playerController), force, spawnBody,
19 | causeOfDeath, deathAnimation);
20 |
21 | Handlers.Player.OnDying(ev);
22 |
23 | return ev;
24 | }
25 |
26 | private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator)
27 | {
28 | List newInstructions = new List(instructions);
29 | const int offset = 3;
30 |
31 | int index = newInstructions.FindLastIndex(i => i.OperandIs(AccessTools.Method(typeof(PlayerControllerB),
32 | nameof(PlayerControllerB.AllowPlayerDeath)))) + offset;
33 |
34 | Label nullLabel = generator.DefineLabel();
35 | Label notAllowedLabel = generator.DefineLabel();
36 | Label skipLabel = generator.DefineLabel();
37 |
38 | CodeInstruction[] inst = new CodeInstruction[]
39 | {
40 | // DyingEventArgs ev = Dying.CallEvent(PlayerControllerB, Vector3, bool, CauseOfDeath, int)
41 | new CodeInstruction(OpCodes.Ldarg_0).MoveLabelsFrom(newInstructions[index]),
42 | new CodeInstruction(OpCodes.Ldarg_1),
43 | new CodeInstruction(OpCodes.Ldarg_2),
44 | new CodeInstruction(OpCodes.Ldarg_3),
45 | new CodeInstruction(OpCodes.Ldarg, 4),
46 | new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(Dying), nameof(Dying.CallEvent))),
47 | new CodeInstruction(OpCodes.Dup),
48 |
49 | // if (ev is null) -> base game code
50 | new CodeInstruction(OpCodes.Dup),
51 | new CodeInstruction(OpCodes.Brfalse_S, nullLabel),
52 |
53 | // if (!ev.IsAllowed) return
54 | new CodeInstruction(OpCodes.Call, AccessTools.PropertyGetter(typeof(DyingEventArgs), nameof(DyingEventArgs.IsAllowed))),
55 | new CodeInstruction(OpCodes.Brfalse_S, notAllowedLabel),
56 |
57 | // Duplicating the stack is more memory efficient than making a local
58 | new CodeInstruction(OpCodes.Dup),
59 | new CodeInstruction(OpCodes.Dup),
60 | new CodeInstruction(OpCodes.Dup),
61 |
62 | // bodyVelocity = ev.Force
63 | new CodeInstruction(OpCodes.Call, AccessTools.PropertyGetter(typeof(DyingEventArgs), nameof(DyingEventArgs.Force))),
64 | new CodeInstruction(OpCodes.Starg_S, 1),
65 |
66 | // spawnBody = ev.SpawnBody
67 | new CodeInstruction(OpCodes.Call, AccessTools.PropertyGetter(typeof(DyingEventArgs), nameof(DyingEventArgs.SpawnBody))),
68 | new CodeInstruction(OpCodes.Starg_S, 2),
69 |
70 | // causeOfDeath = ev.CauseOfDeath
71 | new CodeInstruction(OpCodes.Call, AccessTools.PropertyGetter(typeof(DyingEventArgs), nameof(DyingEventArgs.CauseOfDeath))),
72 | new CodeInstruction(OpCodes.Starg_S, 3),
73 |
74 | // deathAnimation = ev.DeathAnimation
75 | new CodeInstruction(OpCodes.Call, AccessTools.PropertyGetter(typeof(DyingEventArgs), nameof(DyingEventArgs.DeathAnimation))),
76 | new CodeInstruction(OpCodes.Starg_S, 4),
77 |
78 | new CodeInstruction(OpCodes.Br, skipLabel),
79 | new CodeInstruction(OpCodes.Pop).WithLabels(nullLabel),
80 | new CodeInstruction(OpCodes.Pop),
81 | new CodeInstruction(OpCodes.Br, skipLabel),
82 | new CodeInstruction(OpCodes.Pop).WithLabels(notAllowedLabel),
83 | new CodeInstruction(OpCodes.Ret)
84 | };
85 |
86 | newInstructions.InsertRange(index, inst);
87 |
88 | newInstructions[index + inst.Length].labels.Add(skipLabel);
89 |
90 | for (int i = 0; i < newInstructions.Count; i++) yield return newInstructions[i];
91 | }
92 | }
93 |
94 | [HarmonyPatch(typeof(PlayerControllerB), nameof(PlayerControllerB.KillPlayerClientRpc))]
95 | internal class Died
96 | {
97 | private static void Prefix(PlayerControllerB __instance, bool spawnBody, Vector3 bodyVelocity,
98 | int causeOfDeath, int deathAnimation)
99 | {
100 | if (Plugin.configVanillaSupport.Value) return;
101 |
102 | Features.Player player = Features.Player.GetOrAdd(__instance);
103 |
104 | // The local player Dying event has already been fired
105 | if (player.IsLocalPlayer) return;
106 |
107 | Handlers.Player.OnDying(new DyingEventArgs(player, bodyVelocity,
108 | spawnBody, (CauseOfDeath)causeOfDeath, deathAnimation));
109 | }
110 |
111 | private static void Postfix(PlayerControllerB __instance, bool spawnBody, Vector3 bodyVelocity,
112 | int causeOfDeath, int deathAnimation)
113 | {
114 | if (Plugin.configVanillaSupport.Value) return;
115 |
116 | Handlers.Player.OnDied(new DiedEventArgs(Features.Player.GetOrAdd(__instance), bodyVelocity,
117 | spawnBody, (CauseOfDeath)causeOfDeath, deathAnimation));
118 | }
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/LC-API/GameInterfaceAPI/Events/Patches/Player/DropItem.cs:
--------------------------------------------------------------------------------
1 | using GameNetcodeStuff;
2 | using HarmonyLib;
3 | using LC_API.GameInterfaceAPI.Events.EventArgs.Player;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Reflection.Emit;
7 | using System.Text;
8 | using Unity.Netcode;
9 | using UnityEngine;
10 |
11 | namespace LC_API.GameInterfaceAPI.Events.Patches.Player
12 | {
13 | [HarmonyPatch(typeof(PlayerControllerB), nameof(PlayerControllerB.DiscardHeldObject))]
14 | internal class DroppingItem
15 | {
16 | internal static DroppingItemEventArgs CallEvent(PlayerControllerB playerController, bool placeObject, Vector3 targetPosition,
17 | int floorYRotation, NetworkObject parentObjectTo, bool matchRotationOfParent, bool droppedInShip)
18 | {
19 | if (Plugin.configVanillaSupport.Value) return null;
20 |
21 | Features.Player player = Features.Player.GetOrAdd(playerController);
22 |
23 | Features.Item item = Features.Item.GetOrAdd(playerController.currentlyHeldObjectServer);
24 |
25 | DroppingItemEventArgs ev = new DroppingItemEventArgs(player, item, placeObject, targetPosition, floorYRotation, parentObjectTo, matchRotationOfParent, droppedInShip);
26 |
27 | Handlers.Player.OnDroppingItem(ev);
28 |
29 | player.CallDroppingItemOnOtherClients(item, placeObject, targetPosition, floorYRotation, parentObjectTo, matchRotationOfParent, droppedInShip);
30 |
31 | return ev;
32 | }
33 |
34 | private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator)
35 | {
36 | List newInstructions = new List(instructions);
37 |
38 | int animIndex = newInstructions.FindIndex(i => i.opcode == OpCodes.Ldarg_1);
39 |
40 | CodeInstruction[] animatorStuff = newInstructions.GetRange(0, animIndex).ToArray();
41 |
42 | newInstructions.RemoveRange(0, animIndex);
43 |
44 | LocalBuilder isInShipLocal = generator.DeclareLocal(typeof(bool));
45 |
46 | {
47 | const int offset = 1;
48 |
49 | int index = newInstructions.FindIndex(i => i.opcode == OpCodes.Stloc_0) + offset;
50 |
51 | Label nullLabel = generator.DefineLabel();
52 | Label notAllowedLabel = generator.DefineLabel();
53 | Label skipLabel = generator.DefineLabel();
54 |
55 | CodeInstruction[] inst = new CodeInstruction[]
56 | {
57 | // DroppingItemEventArgs ev = DroppingItem.CallEvent(PlayerControllerB, bool, Vector3,
58 | // int, NetworkObject, bool, bool)
59 | new CodeInstruction(OpCodes.Ldarg_0).MoveLabelsFrom(newInstructions[index]),
60 | new CodeInstruction(OpCodes.Ldarg_1),
61 | new CodeInstruction(OpCodes.Ldarg_3),
62 | new CodeInstruction(OpCodes.Ldloc, 4),
63 | new CodeInstruction(OpCodes.Ldarg_2),
64 | new CodeInstruction(OpCodes.Ldarg, 4),
65 | new CodeInstruction(OpCodes.Ldarg_0),
66 | new CodeInstruction(OpCodes.Ldfld, AccessTools.Field(typeof(PlayerControllerB), nameof(PlayerControllerB.isInHangarShipRoom))),
67 | new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(DroppingItem), nameof(DroppingItem.CallEvent))),
68 |
69 | // if (ev is null) -> base game code
70 | new CodeInstruction(OpCodes.Dup),
71 | new CodeInstruction(OpCodes.Brfalse_S, nullLabel),
72 |
73 | new CodeInstruction(OpCodes.Dup),
74 |
75 | // if (!ev.IsAllowed) return
76 | new CodeInstruction(OpCodes.Call, AccessTools.PropertyGetter(typeof(DroppingItemEventArgs), nameof(DroppingItemEventArgs.IsAllowed))),
77 | new CodeInstruction(OpCodes.Brfalse_S, notAllowedLabel),
78 |
79 | new CodeInstruction(OpCodes.Dup),
80 | new CodeInstruction(OpCodes.Dup),
81 | new CodeInstruction(OpCodes.Dup),
82 | new CodeInstruction(OpCodes.Dup),
83 |
84 | // placePosition = ev.TargetPosition
85 | new CodeInstruction(OpCodes.Call, AccessTools.PropertyGetter(typeof(DroppingItemEventArgs), nameof(DroppingItemEventArgs.TargetPosition))),
86 | new CodeInstruction(OpCodes.Starg, 3),
87 |
88 | // floorYRot2 = ev.FloorYRotation
89 | new CodeInstruction(OpCodes.Call, AccessTools.PropertyGetter(typeof(DroppingItemEventArgs), nameof(DroppingItemEventArgs.FloorYRotation))),
90 | new CodeInstruction(OpCodes.Stloc, 4),
91 |
92 | // parentObjectTo = ev.ParentObjectTo
93 | new CodeInstruction(OpCodes.Call, AccessTools.PropertyGetter(typeof(DroppingItemEventArgs), nameof(DroppingItemEventArgs.ParentObjectTo))),
94 | new CodeInstruction(OpCodes.Starg, 2),
95 |
96 | // matchRotationOfParent = ev.MatchRotationOfParent
97 | new CodeInstruction(OpCodes.Call, AccessTools.PropertyGetter(typeof(DroppingItemEventArgs), nameof(DroppingItemEventArgs.MatchRotationOfParent))),
98 | new CodeInstruction(OpCodes.Starg, 4),
99 |
100 | // droppedInShip = ev.DroppedInShip
101 | new CodeInstruction(OpCodes.Call, AccessTools.PropertyGetter(typeof(DroppingItemEventArgs), nameof(DroppingItemEventArgs.DroppedInShip))),
102 | new CodeInstruction(OpCodes.Stloc, isInShipLocal.LocalIndex),
103 | };
104 |
105 | inst = inst.AddRangeToArray(animatorStuff);
106 |
107 | inst = inst.AddRangeToArray(new CodeInstruction[]
108 | {
109 | new CodeInstruction(OpCodes.Br, skipLabel),
110 | new CodeInstruction(OpCodes.Pop).WithLabels(nullLabel),
111 | new CodeInstruction(OpCodes.Ldarg_0),
112 | new CodeInstruction(OpCodes.Ldfld, AccessTools.Field(typeof(PlayerControllerB), nameof(PlayerControllerB.isInHangarShipRoom))),
113 | new CodeInstruction(OpCodes.Stloc, isInShipLocal.LocalIndex),
114 | });
115 |
116 | inst = inst.AddRangeToArray(animatorStuff);
117 |
118 | inst = inst.AddRangeToArray(new CodeInstruction[]
119 | {
120 | new CodeInstruction(OpCodes.Br, skipLabel),
121 | new CodeInstruction(OpCodes.Pop).WithLabels(notAllowedLabel),
122 | new CodeInstruction(OpCodes.Ldarg_0),
123 | new CodeInstruction(OpCodes.Ldc_I4_0),
124 | new CodeInstruction(OpCodes.Stfld, AccessTools.Field(typeof(PlayerControllerB), nameof(PlayerControllerB.throwingObject))),
125 | new CodeInstruction(OpCodes.Ret)
126 | });
127 |
128 | newInstructions.InsertRange(index, inst);
129 |
130 | newInstructions[index + inst.Length].labels.Add(skipLabel);
131 |
132 | newInstructions.RemoveRange(index + inst.Length + 1, 4);
133 |
134 | newInstructions.InsertRange(index + inst.Length + 1, new CodeInstruction[]
135 | {
136 | new CodeInstruction(OpCodes.Ldloc, isInShipLocal.LocalIndex),
137 | new CodeInstruction(OpCodes.Ldloc, isInShipLocal.LocalIndex)
138 | });
139 | }
140 |
141 | {
142 | const int offset = 1;
143 |
144 | int index = newInstructions.FindLastIndex(i => i.opcode == OpCodes.Stloc_S) + offset;
145 |
146 | Label nullLabel = generator.DefineLabel();
147 | Label notAllowedLabel = generator.DefineLabel();
148 | Label skipLabel = generator.DefineLabel();
149 |
150 | CodeInstruction[] inst = new CodeInstruction[]
151 | {
152 | // DroppingItemEventArgs ev = DroppingItem.CallEvent(PlayerControllerB, bool, Vector3,
153 | // int, NetworkObject, bool, bool)
154 | new CodeInstruction(OpCodes.Ldarg_0).MoveLabelsFrom(newInstructions[index]),
155 | new CodeInstruction(OpCodes.Ldarg_1),
156 | new CodeInstruction(OpCodes.Ldloc_1),
157 | new CodeInstruction(OpCodes.Ldloc_0),
158 | new CodeInstruction(OpCodes.Ldarg_2),
159 | new CodeInstruction(OpCodes.Ldarg, 4),
160 | new CodeInstruction(OpCodes.Ldloc_2),
161 | new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(DroppingItem), nameof(DroppingItem.CallEvent))),
162 |
163 | // if (ev is null) -> base game code
164 | new CodeInstruction(OpCodes.Dup),
165 | new CodeInstruction(OpCodes.Brfalse_S, nullLabel),
166 |
167 | new CodeInstruction(OpCodes.Dup),
168 |
169 | // if (!ev.IsAllowed) return
170 | new CodeInstruction(OpCodes.Call, AccessTools.PropertyGetter(typeof(DroppingItemEventArgs), nameof(DroppingItemEventArgs.IsAllowed))),
171 | new CodeInstruction(OpCodes.Brfalse_S, notAllowedLabel),
172 |
173 | new CodeInstruction(OpCodes.Dup),
174 | new CodeInstruction(OpCodes.Dup),
175 | new CodeInstruction(OpCodes.Dup),
176 | new CodeInstruction(OpCodes.Dup),
177 |
178 | // targetFloorPosition = ev.TargetPosition
179 | new CodeInstruction(OpCodes.Call, AccessTools.PropertyGetter(typeof(DroppingItemEventArgs), nameof(DroppingItemEventArgs.TargetPosition))),
180 | new CodeInstruction(OpCodes.Stloc_1),
181 |
182 | // floorYRot = ev.FloorYRotation
183 | new CodeInstruction(OpCodes.Call, AccessTools.PropertyGetter(typeof(DroppingItemEventArgs), nameof(DroppingItemEventArgs.FloorYRotation))),
184 | new CodeInstruction(OpCodes.Stloc_0),
185 |
186 | // parentObjectTo = ev.ParentObjectTo
187 | new CodeInstruction(OpCodes.Call, AccessTools.PropertyGetter(typeof(DroppingItemEventArgs), nameof(DroppingItemEventArgs.ParentObjectTo))),
188 | new CodeInstruction(OpCodes.Starg, 2),
189 |
190 | // matchRotationOfParent = ev.MatchRotationOfParent
191 | new CodeInstruction(OpCodes.Call, AccessTools.PropertyGetter(typeof(DroppingItemEventArgs), nameof(DroppingItemEventArgs.MatchRotationOfParent))),
192 | new CodeInstruction(OpCodes.Starg, 4),
193 |
194 | // droppedInShip = ev.DroppedInShip
195 | new CodeInstruction(OpCodes.Call, AccessTools.PropertyGetter(typeof(DroppingItemEventArgs), nameof(DroppingItemEventArgs.DroppedInShip))),
196 | new CodeInstruction(OpCodes.Stloc, isInShipLocal.LocalIndex),
197 | };
198 |
199 | inst = inst.AddRangeToArray(animatorStuff);
200 |
201 | inst = inst.AddRangeToArray(new CodeInstruction[]
202 | {
203 | new CodeInstruction(OpCodes.Br, skipLabel),
204 | new CodeInstruction(OpCodes.Pop).WithLabels(nullLabel),
205 | new CodeInstruction(OpCodes.Ldloc_2),
206 | new CodeInstruction(OpCodes.Stloc, isInShipLocal.LocalIndex),
207 | });
208 |
209 | inst = inst.AddRangeToArray(animatorStuff);
210 |
211 | inst = inst.AddRangeToArray(new CodeInstruction[]
212 | {
213 | new CodeInstruction(OpCodes.Br, skipLabel),
214 | new CodeInstruction(OpCodes.Pop).WithLabels(notAllowedLabel),
215 | new CodeInstruction(OpCodes.Ldarg_0),
216 | new CodeInstruction(OpCodes.Ldc_I4_0),
217 | new CodeInstruction(OpCodes.Stfld, AccessTools.Field(typeof(PlayerControllerB), nameof(PlayerControllerB.throwingObject))),
218 | new CodeInstruction(OpCodes.Ret)
219 | });
220 |
221 | newInstructions.InsertRange(index, inst);
222 |
223 | newInstructions[index + inst.Length].labels.Add(skipLabel);
224 |
225 | newInstructions.RemoveRange(index + inst.Length + 1, 3);
226 |
227 | newInstructions.InsertRange(index + inst.Length + 1, new CodeInstruction[]
228 | {
229 | new CodeInstruction(OpCodes.Ldloc, isInShipLocal.LocalIndex),
230 | new CodeInstruction(OpCodes.Ldloc, isInShipLocal.LocalIndex)
231 | });
232 | }
233 |
234 | for (int i = 0; i < newInstructions.Count; i++) yield return newInstructions[i];
235 | }
236 | }
237 | }
238 |
--------------------------------------------------------------------------------
/LC-API/GameInterfaceAPI/Events/Patches/Player/GrabItem.cs:
--------------------------------------------------------------------------------
1 | using GameNetcodeStuff;
2 | using HarmonyLib;
3 | using LC_API.GameInterfaceAPI.Events.EventArgs.Player;
4 | using System.Collections.Generic;
5 | using System.Reflection.Emit;
6 | using Unity.Netcode;
7 | using UnityEngine;
8 |
9 | namespace LC_API.GameInterfaceAPI.Events.Patches.Player
10 | {
11 | [HarmonyPatch(typeof(PlayerControllerB), nameof(PlayerControllerB.BeginGrabObject))]
12 | internal class GrabbingItem
13 | {
14 | internal static StartGrabbingItemEventArgs CallStartGrabbingItem(PlayerControllerB playerController, GrabbableObject grabbableObject)
15 | {
16 | if (Plugin.configVanillaSupport.Value) return null;
17 |
18 | StartGrabbingItemEventArgs ev = new StartGrabbingItemEventArgs(Features.Player.GetOrAdd(playerController),
19 | Features.Item.GetOrAdd(grabbableObject));
20 |
21 | Handlers.Player.OnStartGrabbingItem(ev);
22 |
23 | return ev;
24 | }
25 |
26 | internal static GrabbingItemEventArgs CallGrabbingItem(PlayerControllerB playerController, GrabbableObject grabbableObject)
27 | {
28 | if (Plugin.configVanillaSupport.Value) return null;
29 |
30 | GrabbingItemEventArgs ev = new GrabbingItemEventArgs(Features.Player.GetOrAdd(playerController),
31 | Features.Item.GetOrAdd(grabbableObject));
32 |
33 | Handlers.Player.OnGrabbingItem(ev);
34 |
35 | return ev;
36 | }
37 |
38 | private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator)
39 | {
40 | List newInstructions = new List(instructions);
41 |
42 | {
43 | const int offset = 15;
44 |
45 | int index = newInstructions.FindLastIndex(i => i.opcode == OpCodes.Stfld &&
46 | i.OperandIs(AccessTools.Field(typeof(PlayerControllerB), nameof(PlayerControllerB.currentlyGrabbingObject)))) + offset;
47 |
48 | Label nullLabel = generator.DefineLabel();
49 | Label notAllowedLabel = generator.DefineLabel();
50 | Label skipLabel = generator.DefineLabel();
51 |
52 | CodeInstruction[] inst = new CodeInstruction[]
53 | {
54 | // StartGrabbingItemEventArgs ev = GrabbingItem.CallStartGrabbingItem(PlayerControllerB, GrabbableObject)
55 | new CodeInstruction(OpCodes.Ldarg_0).MoveLabelsFrom(newInstructions[index]),
56 | new CodeInstruction(OpCodes.Ldarg_0),
57 | new CodeInstruction(OpCodes.Ldfld, AccessTools.Field(typeof(PlayerControllerB), nameof(PlayerControllerB.currentlyGrabbingObject))),
58 | new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(GrabbingItem), nameof(GrabbingItem.CallStartGrabbingItem))),
59 |
60 | // if (ev is null) -> base game code
61 | new CodeInstruction(OpCodes.Dup),
62 | new CodeInstruction(OpCodes.Brfalse_S, nullLabel),
63 |
64 | // if (!ev.IsAllowed) return
65 | new CodeInstruction(OpCodes.Call, AccessTools.PropertyGetter(typeof(StartGrabbingItemEventArgs), nameof(StartGrabbingItemEventArgs.IsAllowed))),
66 | new CodeInstruction(OpCodes.Brfalse_S, notAllowedLabel),
67 |
68 | new CodeInstruction(OpCodes.Br, skipLabel),
69 | new CodeInstruction(OpCodes.Pop).WithLabels(nullLabel),
70 | new CodeInstruction(OpCodes.Br, skipLabel),
71 | new CodeInstruction(OpCodes.Ret).WithLabels(notAllowedLabel)
72 | };
73 |
74 | newInstructions.InsertRange(index, inst);
75 |
76 | newInstructions[index + inst.Length].labels.Add(skipLabel);
77 | }
78 |
79 | {
80 | const int offset = -2;
81 |
82 | int index = newInstructions.FindLastIndex(i => i.opcode == OpCodes.Ldstr &&
83 | i.OperandIs("GrabInvalidated")) + offset;
84 |
85 | Label nullLabel = generator.DefineLabel();
86 | Label notAllowedLabel = generator.DefineLabel();
87 | Label skipLabel = generator.DefineLabel();
88 |
89 | CodeInstruction[] inst = new CodeInstruction[]
90 | {
91 | // GrabbingItemEventArgs ev = GrabbingItem.CallGrabbingItem(PlayerControllerB, GrabbableObject)
92 | new CodeInstruction(OpCodes.Ldarg_0).MoveLabelsFrom(newInstructions[index]),
93 | new CodeInstruction(OpCodes.Ldarg_0),
94 | new CodeInstruction(OpCodes.Ldfld, AccessTools.Field(typeof(PlayerControllerB), nameof(PlayerControllerB.currentlyGrabbingObject))),
95 | new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(GrabbingItem), nameof(GrabbingItem.CallGrabbingItem))),
96 |
97 | // if (ev is null) -> base game code
98 | new CodeInstruction(OpCodes.Dup),
99 | new CodeInstruction(OpCodes.Brfalse_S, nullLabel),
100 |
101 | // if (!ev.IsAllowed) return
102 | new CodeInstruction(OpCodes.Call, AccessTools.PropertyGetter(typeof(StartGrabbingItemEventArgs), nameof(StartGrabbingItemEventArgs.IsAllowed))),
103 | new CodeInstruction(OpCodes.Brfalse_S, notAllowedLabel),
104 |
105 | new CodeInstruction(OpCodes.Br, skipLabel),
106 | new CodeInstruction(OpCodes.Pop).WithLabels(nullLabel),
107 | new CodeInstruction(OpCodes.Br, skipLabel),
108 | new CodeInstruction(OpCodes.Ret).WithLabels(notAllowedLabel)
109 | };
110 |
111 | newInstructions.InsertRange(index, inst);
112 |
113 | newInstructions[index + inst.Length].labels.Add(skipLabel);
114 | }
115 |
116 | for (int i = 0; i < newInstructions.Count; i++) yield return newInstructions[i];
117 | }
118 | }
119 |
120 | [HarmonyPatch(typeof(PlayerControllerB), nameof(PlayerControllerB.GrabObjectClientRpc))]
121 | internal class GrabbedItem
122 | {
123 | internal static void CallEvent(PlayerControllerB player)
124 | {
125 | if (Plugin.configVanillaSupport.Value) return;
126 |
127 | Handlers.Player.OnGrabbedItem(new GrabbedItemEventArgs(Features.Player.GetOrAdd(player),
128 | Features.Item.GetOrAdd(player.currentlyHeldObjectServer)));
129 | }
130 |
131 | private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator)
132 | {
133 | List newInstructions = new List(instructions);
134 |
135 | {
136 | const int offset = 1;
137 |
138 | int index = newInstructions.FindLastIndex(i => i.opcode == OpCodes.Callvirt &&
139 | i.OperandIs(AccessTools.Method(typeof(AudioSource),
140 | nameof(AudioSource.PlayOneShot), new[] { typeof(AudioClip), typeof(float) }))) + offset;
141 |
142 | CodeInstruction[] inst = new CodeInstruction[]
143 | {
144 | // GrabbedItem.CallEvent(PlayerControllerB)
145 | new CodeInstruction(OpCodes.Ldarg_0).MoveLabelsFrom(newInstructions[index]),
146 | new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(GrabbedItem), nameof(GrabbedItem.CallEvent))),
147 | };
148 |
149 | newInstructions.InsertRange(index, inst);
150 | }
151 |
152 | {
153 | const int offset = 1;
154 |
155 | int index = newInstructions.FindIndex(i => i.opcode == OpCodes.Call &&
156 | i.OperandIs(AccessTools.PropertyGetter(typeof(NetworkBehaviour), nameof(NetworkBehaviour.IsOwner)))) + offset;
157 |
158 | Label skipToLabel = generator.DefineLabel();
159 |
160 | newInstructions[index].operand = skipToLabel;
161 |
162 | CodeInstruction[] inst = new CodeInstruction[]
163 | {
164 | new CodeInstruction(OpCodes.Br, newInstructions[newInstructions.Count - 1].labels[0]),
165 |
166 | // GrabbedItem.CallEvent(PlayerControllerB)
167 | new CodeInstruction(OpCodes.Ldarg_0).WithLabels(skipToLabel),
168 | new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(GrabbedItem), nameof(GrabbedItem.CallEvent))),
169 | };
170 |
171 | newInstructions.InsertRange(newInstructions.Count - 1, inst);
172 | }
173 |
174 | for (int i = 0; i < newInstructions.Count; i++) yield return newInstructions[i];
175 | }
176 | }
177 | }
178 |
--------------------------------------------------------------------------------
/LC-API/GameInterfaceAPI/Events/Patches/Player/Hurt.cs:
--------------------------------------------------------------------------------
1 | using GameNetcodeStuff;
2 | using HarmonyLib;
3 | using LC_API.GameInterfaceAPI.Events.EventArgs.Player;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Reflection.Emit;
7 | using UnityEngine;
8 |
9 | namespace LC_API.GameInterfaceAPI.Events.Patches.Player
10 | {
11 | [HarmonyPatch(typeof(PlayerControllerB), nameof(PlayerControllerB.DamagePlayer))]
12 | internal class Hurt
13 | {
14 | private static HurtingEventArgs CallHurtingEvent(PlayerControllerB playerController, int damage, bool hasSFX, CauseOfDeath causeOfDeath,
15 | int deathAnimation, bool fallDamage, Vector3 force)
16 | {
17 | if (Plugin.configVanillaSupport.Value) return null;
18 |
19 | Features.Player player = Features.Player.GetOrAdd(playerController);
20 |
21 | HurtingEventArgs ev = new HurtingEventArgs(player, damage, hasSFX,
22 | causeOfDeath, deathAnimation, fallDamage, force);
23 |
24 | Handlers.Player.OnHurting(ev);
25 |
26 | player.CallHurtingOnOtherClients(damage, hasSFX, causeOfDeath, deathAnimation, fallDamage, force);
27 |
28 | return ev;
29 | }
30 |
31 | private static HurtEventArgs CallHurtEvent(PlayerControllerB playerController, int damage, bool hasSFX, CauseOfDeath causeOfDeath,
32 | int deathAnimation, bool fallDamage, Vector3 force)
33 | {
34 | if (Plugin.configVanillaSupport.Value) return null;
35 |
36 | Features.Player player = Features.Player.GetOrAdd(playerController);
37 |
38 | HurtEventArgs ev = new HurtEventArgs(player, damage, hasSFX,
39 | causeOfDeath, deathAnimation, fallDamage, force);
40 |
41 | Handlers.Player.OnHurt(ev);
42 |
43 | player.CallHurtOnOtherClients(damage, hasSFX, causeOfDeath, deathAnimation, fallDamage, force);
44 |
45 | return ev;
46 | }
47 |
48 | private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator)
49 | {
50 | List newInstructions = new List(instructions);
51 |
52 | {
53 | const int offset = 3;
54 |
55 | int index = newInstructions.FindLastIndex(i => i.OperandIs(AccessTools.Method(typeof(PlayerControllerB),
56 | nameof(PlayerControllerB.AllowPlayerDeath)))) + offset;
57 |
58 | Label nullLabel = generator.DefineLabel();
59 | Label notAllowedLabel = generator.DefineLabel();
60 | Label skipLabel = generator.DefineLabel();
61 |
62 | CodeInstruction[] inst = new CodeInstruction[]
63 | {
64 | // HurtingEventArgs ev = Hurt.CallHurtingEvent(PlayerControllerB, int, bool, CauseOfDeath, int, bool, Vector3)
65 | new CodeInstruction(OpCodes.Ldarg_0).MoveLabelsFrom(newInstructions[index]),
66 | new CodeInstruction(OpCodes.Ldarg_1),
67 | new CodeInstruction(OpCodes.Ldarg_2),
68 | new CodeInstruction(OpCodes.Ldarg, 4),
69 | new CodeInstruction(OpCodes.Ldarg, 5),
70 | new CodeInstruction(OpCodes.Ldarg, 6),
71 | new CodeInstruction(OpCodes.Ldarg, 7),
72 | new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(Hurt), nameof(Hurt.CallHurtingEvent))),
73 | new CodeInstruction(OpCodes.Dup),
74 |
75 | // if (ev is null) -> base game code
76 | new CodeInstruction(OpCodes.Dup),
77 | new CodeInstruction(OpCodes.Brfalse_S, nullLabel),
78 |
79 | // if (!ev.IsAllowed) return
80 | new CodeInstruction(OpCodes.Call, AccessTools.PropertyGetter(typeof(HurtingEventArgs), nameof(HurtingEventArgs.IsAllowed))),
81 | new CodeInstruction(OpCodes.Brfalse_S, notAllowedLabel),
82 |
83 | // Duplicating the stack is more memory efficient than making a local
84 | new CodeInstruction(OpCodes.Dup),
85 | new CodeInstruction(OpCodes.Dup),
86 | new CodeInstruction(OpCodes.Dup),
87 | new CodeInstruction(OpCodes.Dup),
88 | new CodeInstruction(OpCodes.Dup),
89 |
90 | // damage = ev.Damage
91 | new CodeInstruction(OpCodes.Call, AccessTools.PropertyGetter(typeof(HurtingEventArgs), nameof(HurtingEventArgs.Damage))),
92 | new CodeInstruction(OpCodes.Starg_S, 1),
93 |
94 | // hasDamageSFX = ev.HasSFX
95 | new CodeInstruction(OpCodes.Call, AccessTools.PropertyGetter(typeof(HurtingEventArgs), nameof(HurtingEventArgs.HasSFX))),
96 | new CodeInstruction(OpCodes.Starg_S, 2),
97 |
98 | // causeOfDeath = ev.CauseOfDeath
99 | new CodeInstruction(OpCodes.Call, AccessTools.PropertyGetter(typeof(HurtingEventArgs), nameof(HurtingEventArgs.CauseOfDeath))),
100 | new CodeInstruction(OpCodes.Starg_S, 4),
101 |
102 | // deathAnimation = ev.DeathAnimation
103 | new CodeInstruction(OpCodes.Call, AccessTools.PropertyGetter(typeof(HurtingEventArgs), nameof(HurtingEventArgs.DeathAnimation))),
104 | new CodeInstruction(OpCodes.Starg_S, 5),
105 |
106 | // fallDamage = ev.FallDamage
107 | new CodeInstruction(OpCodes.Call, AccessTools.PropertyGetter(typeof(HurtingEventArgs), nameof(HurtingEventArgs.FallDamage))),
108 | new CodeInstruction(OpCodes.Starg_S, 6),
109 |
110 | // force = ev.Force
111 | new CodeInstruction(OpCodes.Call, AccessTools.PropertyGetter(typeof(HurtingEventArgs), nameof(HurtingEventArgs.Force))),
112 | new CodeInstruction(OpCodes.Starg_S, 7),
113 | new CodeInstruction(OpCodes.Br, skipLabel),
114 |
115 | new CodeInstruction(OpCodes.Pop).WithLabels(nullLabel),
116 | new CodeInstruction(OpCodes.Pop),
117 | new CodeInstruction(OpCodes.Br, skipLabel),
118 | new CodeInstruction(OpCodes.Pop).WithLabels(notAllowedLabel),
119 | new CodeInstruction(OpCodes.Ret)
120 | };
121 |
122 | newInstructions.InsertRange(index, inst);
123 |
124 | newInstructions[index + inst.Length].labels.Add(skipLabel);
125 | }
126 |
127 | {
128 | const int offset = 1;
129 |
130 | int index = newInstructions.FindLastIndex(i => i.opcode == OpCodes.Stfld
131 | && i.OperandIs(AccessTools.Field(typeof(PlayerControllerB), nameof(PlayerControllerB.health)))) + offset;
132 |
133 | Label nullLabel = generator.DefineLabel();
134 | Label skipLabel = generator.DefineLabel();
135 |
136 | CodeInstruction[] inst = new CodeInstruction[]
137 | {
138 | // HurtEventArgs ev = Hurt.CallHurtEvent(PlayerControllerB, int, bool, CauseOfDeath, int, bool, Vector3)
139 | new CodeInstruction(OpCodes.Ldarg_0).MoveLabelsFrom(newInstructions[index]),
140 | new CodeInstruction(OpCodes.Ldarg_1),
141 | new CodeInstruction(OpCodes.Ldarg_2),
142 | new CodeInstruction(OpCodes.Ldarg, 4),
143 | new CodeInstruction(OpCodes.Ldarg, 5),
144 | new CodeInstruction(OpCodes.Ldarg, 6),
145 | new CodeInstruction(OpCodes.Ldarg, 7),
146 | new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(Hurt), nameof(Hurt.CallHurtEvent))),
147 |
148 | // if (ev is null) -> base game code
149 | new CodeInstruction(OpCodes.Dup),
150 | new CodeInstruction(OpCodes.Brfalse_S, nullLabel),
151 |
152 | // Duplicating the stack is more memory efficient than making a local
153 | new CodeInstruction(OpCodes.Dup),
154 | new CodeInstruction(OpCodes.Dup),
155 | new CodeInstruction(OpCodes.Dup),
156 | new CodeInstruction(OpCodes.Dup),
157 |
158 | // hasDamageSFX = ev.HasSFX
159 | new CodeInstruction(OpCodes.Call, AccessTools.PropertyGetter(typeof(HurtingEventArgs), nameof(HurtingEventArgs.HasSFX))),
160 | new CodeInstruction(OpCodes.Starg_S, 2),
161 |
162 | // causeOfDeath = ev.CauseOfDeath
163 | new CodeInstruction(OpCodes.Call, AccessTools.PropertyGetter(typeof(HurtingEventArgs), nameof(HurtingEventArgs.CauseOfDeath))),
164 | new CodeInstruction(OpCodes.Starg_S, 4),
165 |
166 | // deathAnimation = ev.DeathAnimation
167 | new CodeInstruction(OpCodes.Call, AccessTools.PropertyGetter(typeof(HurtingEventArgs), nameof(HurtingEventArgs.DeathAnimation))),
168 | new CodeInstruction(OpCodes.Starg_S, 5),
169 |
170 | // fallDamage = ev.FallDamage
171 | new CodeInstruction(OpCodes.Call, AccessTools.PropertyGetter(typeof(HurtingEventArgs), nameof(HurtingEventArgs.FallDamage))),
172 | new CodeInstruction(OpCodes.Starg_S, 6),
173 |
174 | // force = ev.Force
175 | new CodeInstruction(OpCodes.Call, AccessTools.PropertyGetter(typeof(HurtingEventArgs), nameof(HurtingEventArgs.Force))),
176 | new CodeInstruction(OpCodes.Starg_S, 7),
177 |
178 | new CodeInstruction(OpCodes.Br, skipLabel),
179 | new CodeInstruction(OpCodes.Pop).WithLabels(nullLabel),
180 | };
181 |
182 | newInstructions.InsertRange(index, inst);
183 |
184 | newInstructions[index + inst.Length].labels.Add(skipLabel);
185 | }
186 |
187 | for (int i = 0; i < newInstructions.Count; i++) yield return newInstructions[i];
188 | }
189 | }
190 | }
--------------------------------------------------------------------------------
/LC-API/GameInterfaceAPI/Events/Patches/Player/Joined.cs:
--------------------------------------------------------------------------------
1 | using GameNetcodeStuff;
2 | using HarmonyLib;
3 | using LC_API.GameInterfaceAPI.Events.EventArgs.Player;
4 | using System.Collections;
5 | using Unity.Netcode;
6 | using UnityEngine;
7 |
8 | namespace LC_API.GameInterfaceAPI.Events.Patches.Player
9 | {
10 | [HarmonyPatch(typeof(StartOfRound), nameof(StartOfRound.OnPlayerConnectedClientRpc))]
11 | internal static class Joined
12 | {
13 | private static void Prefix(ulong clientId, int assignedPlayerObjectId)
14 | {
15 | if (Plugin.configVanillaSupport.Value) return;
16 |
17 | if (NetworkManager.Singleton.IsServer || NetworkManager.Singleton.IsHost)
18 | {
19 | PlayerControllerB playerController = StartOfRound.Instance.allPlayerScripts[assignedPlayerObjectId];
20 | Features.Player.GetOrAdd(playerController).NetworkClientId.Value = clientId;
21 | }
22 | }
23 |
24 | private static void Postfix(ulong clientId, int assignedPlayerObjectId)
25 | {
26 | if (Plugin.configVanillaSupport.Value) return;
27 |
28 | PlayerControllerB playerController = StartOfRound.Instance.allPlayerScripts[assignedPlayerObjectId];
29 | if (!Cache.Player.ConnectedPlayers.Contains(clientId))
30 | {
31 | Cache.Player.ConnectedPlayers.Add(clientId);
32 |
33 | playerController.StartCoroutine(JoinedCoroutine(playerController));
34 | }
35 | }
36 |
37 | // Since we have to wait for players' client id to sync to the player instance, we have to constantly check
38 | // if the player and its controller were linked yet. Very annoying.
39 | internal static IEnumerator JoinedCoroutine(PlayerControllerB controller)
40 | {
41 | yield return new WaitUntil(() => StartOfRound.Instance.localPlayerController != null);
42 |
43 | Features.Player player = Features.Player.GetOrAdd(controller);
44 |
45 | while (player == null)
46 | {
47 | yield return new WaitForSeconds(0.1f);
48 |
49 | player = Features.Player.GetOrAdd(controller);
50 | }
51 |
52 | if (player.IsLocalPlayer)
53 | {
54 | Features.Player.LocalPlayer = player;
55 | }
56 |
57 | Handlers.Player.OnJoined(new JoinedEventArgs(player));
58 | }
59 | }
60 |
61 | [HarmonyPatch(typeof(PlayerControllerB), nameof(PlayerControllerB.ConnectClientToPlayerObject))]
62 | internal static class Joined2
63 | {
64 | private static void Postfix(PlayerControllerB __instance)
65 | {
66 | if (Plugin.configVanillaSupport.Value) return;
67 |
68 | if (NetworkManager.Singleton.IsServer || NetworkManager.Singleton.IsHost)
69 | {
70 | Features.Player.GetOrAdd(__instance).NetworkClientId.Value = __instance.actualClientId;
71 | }
72 |
73 | if (!Cache.Player.ConnectedPlayers.Contains(__instance.actualClientId))
74 | {
75 | Cache.Player.ConnectedPlayers.Add(__instance.actualClientId);
76 |
77 | __instance.StartCoroutine(Joined.JoinedCoroutine(__instance));
78 | }
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/LC-API/GameInterfaceAPI/Events/Patches/Player/Left.cs:
--------------------------------------------------------------------------------
1 | using GameNetcodeStuff;
2 | using HarmonyLib;
3 | using LC_API.GameInterfaceAPI.Events.EventArgs.Player;
4 |
5 | namespace LC_API.GameInterfaceAPI.Events.Patches.Player
6 | {
7 | [HarmonyPatch(typeof(StartOfRound), nameof(StartOfRound.OnPlayerDC))]
8 | internal static class Left
9 | {
10 | private static void Prefix(StartOfRound __instance, int playerObjectNumber, ulong clientId)
11 | {
12 | if (Plugin.configVanillaSupport.Value) return;
13 |
14 | PlayerControllerB player = __instance.allPlayerScripts[playerObjectNumber];
15 | if (__instance.ClientPlayerList.ContainsKey(clientId) &&
16 | Cache.Player.ConnectedPlayers.Contains(player.actualClientId))
17 | {
18 | Cache.Player.ConnectedPlayers.Remove(player.actualClientId);
19 | Handlers.Player.OnLeft(new LeftEventArgs(Features.Player.GetOrAdd(player)));
20 | }
21 | }
22 | }
23 |
24 | [HarmonyPatch(typeof(StartOfRound), nameof(StartOfRound.OnLocalDisconnect))]
25 | internal static class LocalLeft
26 | {
27 | private static void Prefix(StartOfRound __instance)
28 | {
29 | if (Plugin.configVanillaSupport.Value) return;
30 |
31 | PlayerControllerB player = __instance.localPlayerController;
32 | Handlers.Player.OnLeft(new LeftEventArgs(Features.Player.GetOrAdd(player)));
33 | Cache.Player.ConnectedPlayers.Clear();
34 |
35 | foreach (PlayerControllerB playerController in StartOfRound.Instance.allPlayerScripts)
36 | {
37 | playerController.isPlayerDead = false;
38 | playerController.isPlayerControlled = false;
39 | }
40 |
41 | Features.Player.Dictionary.Clear();
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/LC-API/GameInterfaceAPI/Features/Item.cs:
--------------------------------------------------------------------------------
1 | using LC_API.Exceptions;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using Unity.Netcode;
5 | using UnityEngine;
6 |
7 | namespace LC_API.GameInterfaceAPI.Features
8 | {
9 | ///
10 | /// Encapsulates a for easier interacting.
11 | ///
12 | public class Item : NetworkBehaviour
13 | {
14 | internal static GameObject ItemNetworkPrefab { get; set; }
15 |
16 | ///
17 | /// Gets a dictionary containing all s that are currently spawned in the world or in s' inventories.
18 | ///
19 | public static Dictionary Dictionary { get; } = new Dictionary();
20 |
21 | ///
22 | /// Gets a list containing all s.
23 | ///
24 | public static IReadOnlyCollection- List => Dictionary.Values;
25 |
26 | ///
27 | /// Gets the encapsulated
28 | ///
29 | public GrabbableObject GrabbableObject { get; private set; }
30 |
31 | ///
32 | /// Gets the 's item properties.
33 | /// These do not network, it is recommended to use the getters/setters on the itself.
34 | ///
35 | public global::Item ItemProperties => GrabbableObject.itemProperties;
36 |
37 | ///
38 | /// Gets the 's .
39 | /// These do not network, it is recommended to use the getters/setters on the itself.
40 | ///
41 | public ScanNodeProperties ScanNodeProperties { get; set; }
42 |
43 | ///
44 | /// Gets whether or not this is currently being held.
45 | ///
46 | public bool IsHeld => GrabbableObject.isHeld;
47 |
48 | ///
49 | /// Gets whether or not this is two handed.
50 | ///
51 | public bool IsTwoHanded => ItemProperties.twoHanded;
52 |
53 | ///
54 | /// Gets the that is currently holding this . if not held.
55 | ///
56 | public Player Holder => IsHeld ? Player.Dictionary.TryGetValue(GrabbableObject.playerHeldBy, out Player p) ? p : null : null;
57 |
58 | ///
59 | /// Gets or sets the 's name.
60 | ///
61 | /// Thrown when attempting to set item name from the client.
62 | public string Name
63 | {
64 | get
65 | {
66 | return ItemProperties.itemName;
67 | }
68 | set
69 | {
70 | if (!(NetworkManager.Singleton.IsServer || NetworkManager.Singleton.IsHost))
71 | {
72 | throw new NoAuthorityException("Tried to set item name on client.");
73 | }
74 |
75 | string current = ItemProperties.itemName.ToLower();
76 |
77 | CloneProperties();
78 |
79 | ItemProperties.itemName = value;
80 | OverrideTooltips(current, value.ToLower());
81 |
82 | ScanNodeProperties.headerText = value;
83 |
84 | SetGrabbableNameClientRpc(value);
85 | }
86 | }
87 |
88 | [ClientRpc]
89 | private void SetGrabbableNameClientRpc(string name)
90 | {
91 | string current = ItemProperties.itemName.ToLower();
92 |
93 | CloneProperties();
94 |
95 | ItemProperties.itemName = name;
96 | OverrideTooltips(current, name.ToLower());
97 |
98 | ScanNodeProperties.headerText = name;
99 | }
100 |
101 | private void OverrideTooltips(string oldName, string newName)
102 | {
103 | for (int i = 0; i < ItemProperties.toolTips.Length; i++)
104 | {
105 | ItemProperties.toolTips[i] = ItemProperties.toolTips[i].ReplaceWithCase(oldName, newName);
106 | }
107 |
108 | if (IsHeld && Holder == Player.LocalPlayer) GrabbableObject.SetControlTipsForItem();
109 | }
110 |
111 | ///
112 | /// Gets or sets the position of this .
113 | ///
114 | /// Thrown when attempting to set the item's position from the client.
115 | public Vector3 Position
116 | {
117 | get
118 | {
119 | return GrabbableObject.transform.position;
120 | }
121 | set
122 | {
123 | if (!(NetworkManager.Singleton.IsServer || NetworkManager.Singleton.IsHost))
124 | {
125 | throw new NoAuthorityException("Tried to set item position on client.");
126 | }
127 |
128 | GrabbableObject.startFallingPosition = value;
129 | GrabbableObject.targetFloorPosition = value;
130 | GrabbableObject.transform.position = value;
131 |
132 | SetItemPositionClientRpc(value);
133 | }
134 | }
135 |
136 | [ClientRpc]
137 | private void SetItemPositionClientRpc(Vector3 pos)
138 | {
139 | GrabbableObject.startFallingPosition = pos;
140 | GrabbableObject.targetFloorPosition = pos;
141 | GrabbableObject.transform.position = pos;
142 | }
143 |
144 | ///
145 | /// Gets or sets the rotation of this . Does not sync
146 | ///
147 | public Quaternion Rotation
148 | {
149 | get
150 | {
151 | return GrabbableObject.transform.rotation;
152 | }
153 | set
154 | {
155 | GrabbableObject.transform.rotation = value;
156 | }
157 | }
158 |
159 | ///
160 | /// Sets the 's rotation and syncs it across all clients.
161 | ///
162 | /// The desired rotation.
163 | /// Thrown when attempting to sync rotation to other clients while not being the host.
164 | public void SetAndSyncRotation(Quaternion rotation)
165 | {
166 | if (!(NetworkManager.Singleton.IsServer || NetworkManager.Singleton.IsHost))
167 | {
168 | throw new NoAuthorityException("Tried to sync item rotation from client.");
169 | }
170 |
171 | SetItemRotationClientRpc(rotation);
172 | }
173 |
174 | [ClientRpc]
175 | private void SetItemRotationClientRpc(Quaternion rotation)
176 | {
177 | Rotation = rotation;
178 | }
179 |
180 | ///
181 | /// Sets the scale of the . Does not sync.
182 | ///
183 | public Vector3 Scale
184 | {
185 | get
186 | {
187 | return GrabbableObject.transform.localScale;
188 | }
189 | set
190 | {
191 | GrabbableObject.transform.localScale = value;
192 | }
193 | }
194 |
195 | ///
196 | /// Sets the 's scale and syncs it across all clients.
197 | ///
198 | /// The desired scale.
199 | /// Thrown when attempting to sync scale to other clients while not being the host.
200 | public void SetAndSyncScale(Vector3 scale)
201 | {
202 | if (!(NetworkManager.Singleton.IsServer || NetworkManager.Singleton.IsHost))
203 | {
204 | throw new NoAuthorityException("Tried to sync item scale from client.");
205 | }
206 |
207 | SetItemScaleClientRpc(scale);
208 | }
209 |
210 | [ClientRpc]
211 | private void SetItemScaleClientRpc(Vector3 scale)
212 | {
213 | Scale = scale;
214 | }
215 |
216 | ///
217 | /// Gets or sets whether this should be considered scrap.
218 | ///
219 | /// Thrown when attempting to set isScrap from the client.
220 | public bool IsScrap
221 | {
222 | get
223 | {
224 | return ItemProperties.isScrap;
225 | }
226 | set
227 | {
228 | if (!(NetworkManager.Singleton.IsServer || NetworkManager.Singleton.IsHost))
229 | {
230 | throw new NoAuthorityException("Tried to set item name on client.");
231 | }
232 |
233 | CloneProperties();
234 |
235 | ItemProperties.isScrap = value;
236 |
237 | SetIsScrapClientRpc(value);
238 | }
239 | }
240 |
241 | [ClientRpc]
242 | private void SetIsScrapClientRpc(bool isScrap)
243 | {
244 | CloneProperties();
245 |
246 | ItemProperties.isScrap = isScrap;
247 | }
248 |
249 | ///
250 | /// Gets or sets this 's scrap value.
251 | ///
252 | /// Thrown when attempting to set scrap value from the client.
253 | public int ScrapValue
254 | {
255 | get
256 | {
257 | return GrabbableObject.scrapValue;
258 | }
259 | set
260 | {
261 | if (!(NetworkManager.Singleton.IsServer || NetworkManager.Singleton.IsHost))
262 | {
263 | throw new NoAuthorityException("Tried to set scrap value on client.");
264 | }
265 |
266 | GrabbableObject.SetScrapValue(value);
267 |
268 | SetScrapValueClientRpc(value);
269 | }
270 | }
271 |
272 | [ClientRpc]
273 | private void SetScrapValueClientRpc(int scrapValue)
274 | {
275 | GrabbableObject.SetScrapValue(scrapValue);
276 | }
277 |
278 | ///
279 | /// Removes the from its current holder.
280 | ///
281 | /// The position to place the object after removing.
282 | /// The rotation the object should have after removing.
283 | public void RemoveFromHolder(Vector3 position = default, Quaternion rotation = default)
284 | {
285 | if (!(NetworkManager.Singleton.IsServer || NetworkManager.Singleton.IsHost))
286 | {
287 | throw new NoAuthorityException("Tried to remove item from player on client.");
288 | }
289 |
290 | if (!IsHeld) return;
291 |
292 | NetworkObject.RemoveOwnership();
293 |
294 | Holder.Inventory.RemoveItem(this);
295 |
296 | RemoveFromHolderClientRpc();
297 |
298 | Position = position;
299 | Rotation = rotation;
300 | }
301 |
302 | [ClientRpc]
303 | private void RemoveFromHolderClientRpc()
304 | {
305 | if (!IsHeld) return;
306 |
307 | Holder.Inventory.RemoveItem(this);
308 | }
309 |
310 | ///
311 | /// Enables/disables the 's physics.
312 | ///
313 | /// to enable physics, otherwise.
314 | public void EnablePhysics(bool enable) => GrabbableObject.EnablePhysics(enable);
315 |
316 | ///
317 | /// Enables/disables the 's meshes.
318 | ///
319 | /// to enable meshes, otherwise.
320 | public void EnableMeshes(bool enable) => GrabbableObject.EnableItemMeshes(enable);
321 |
322 | ///
323 | /// Start the falling to the ground.
324 | ///
325 | /// Whether or not to add some randomness to the position.
326 | public void FallToGround(bool randomizePosition = false)
327 | {
328 | GrabbableObject.FallToGround(randomizePosition);
329 | }
330 |
331 | ///
332 | /// Pockets the by disabling its meshes. Plays pocket sound effects.
333 | ///
334 | /// if the was able to be pocketed, otherwise.
335 | public bool PocketItem()
336 | {
337 | // We can only pocket items that are currently being held in a player's hand. Two handed
338 | // objects cannot be pocketed.
339 | if (!IsHeld || Holder.HeldItem != this || IsTwoHanded) return false;
340 |
341 | GrabbableObject.PocketItem();
342 |
343 | return true;
344 | }
345 |
346 | ///
347 | /// Gives this to the specific player. Deleting it from another 's inventory, if necessary.
348 | ///
349 | /// The player to give the item to.
350 | /// Whether or not to switch to the item. Forced for 2 handed items.
351 | /// if the player had an open slot to add the item to, otherwise.
352 | public bool GiveTo(Player player, bool switchTo = true)
353 | {
354 | if (!(NetworkManager.Singleton.IsServer || NetworkManager.Singleton.IsHost))
355 | {
356 | throw new NoAuthorityException("Tried to give item to player on client.");
357 | }
358 |
359 | return player.Inventory.TryAddItem(this, switchTo);
360 | }
361 |
362 | ///
363 | /// Initializes the with base game scrap values.
364 | ///
365 | public void InitializeScrap()
366 | {
367 | if (RoundManager.Instance.AnomalyRandom != null) InitializeScrap((int)(RoundManager.Instance.AnomalyRandom.Next(ItemProperties.minValue, ItemProperties.maxValue) * RoundManager.Instance.scrapValueMultiplier));
368 | else InitializeScrap((int)(UnityEngine.Random.Range(ItemProperties.minValue, ItemProperties.maxValue) * RoundManager.Instance.scrapValueMultiplier));
369 | }
370 |
371 | ///
372 | /// Initializes the with a specific scrap value.
373 | ///
374 | /// The desired scrap value.
375 | /// Thrown when trying to initialize scrap from the client.
376 | public void InitializeScrap(int scrapValue)
377 | {
378 | if (!(NetworkManager.Singleton.IsServer || NetworkManager.Singleton.IsHost))
379 | {
380 | throw new NoAuthorityException("Tried to initialize scrap on client.");
381 | }
382 |
383 | ScrapValue = scrapValue;
384 |
385 | InitializeScrapClientRpc();
386 | }
387 |
388 | [ClientRpc]
389 | private void InitializeScrapClientRpc()
390 | {
391 | if (GrabbableObject.gameObject.TryGetComponent(out MeshFilter filter)
392 | && ItemProperties.meshVariants != null && ItemProperties.meshVariants.Length != 0)
393 | {
394 | if (RoundManager.Instance.ScrapValuesRandom != null)
395 | filter.mesh = ItemProperties.meshVariants[RoundManager.Instance.ScrapValuesRandom.Next(ItemProperties.meshVariants.Length)];
396 | else
397 | filter.mesh = ItemProperties.meshVariants[0];
398 | }
399 |
400 | if (GrabbableObject.gameObject.TryGetComponent(out MeshRenderer renderer)
401 | && ItemProperties.materialVariants != null && ItemProperties.materialVariants.Length != 0)
402 | {
403 | if (RoundManager.Instance.ScrapValuesRandom != null)
404 | renderer.sharedMaterial = ItemProperties.materialVariants[RoundManager.Instance.ScrapValuesRandom.Next(ItemProperties.materialVariants.Length)];
405 | else
406 | renderer.sharedMaterial = ItemProperties.materialVariants[0];
407 | }
408 | }
409 |
410 | ///
411 | /// Creates and spawns an in the world.
412 | ///
413 | /// The item's name. Uses a simple Contains check to see if the provided item name is contained in the actual item's name. Case insensitive.
414 | /// Whether or not to initialize the item after spawning.
415 | /// The position to spawn at.
416 | /// The rotation to spawn at.
417 | /// A new , or if the provided item name is not found.
418 | /// Thrown when trying to spawn an on the client.
419 | public static Item CreateAndSpawnItem(string itemName, bool andInitialize = true, Vector3 position = default, Quaternion rotation = default)
420 | {
421 | if (!(NetworkManager.Singleton.IsServer || NetworkManager.Singleton.IsHost))
422 | {
423 | throw new NoAuthorityException("Tried to create and spawn item on client.");
424 | }
425 |
426 | string name = itemName.ToLower();
427 |
428 | GameObject go = StartOfRound.Instance.allItemsList.itemsList.FirstOrDefault(i => i.itemName.ToLower().Contains(name))?.spawnPrefab;
429 | if (go != null)
430 | {
431 | GameObject instantiated = Instantiate(go, position, rotation);
432 |
433 | instantiated.GetComponent().Spawn();
434 |
435 | Item item = instantiated.GetComponent
- ();
436 |
437 | if (item.IsScrap && andInitialize) item.InitializeScrap();
438 |
439 | return item;
440 | }
441 |
442 | return null;
443 | }
444 |
445 | ///
446 | /// Creates an and gives it to a specific .
447 | ///
448 | /// The item's name. Uses a simple Contains check to see if the provided item name is contained in the actual item's name. Case insensitive.
449 | /// The to give the to.
450 | /// Whether or not to initialize this item after spawning.
451 | /// Whether or not to switch to the item. Forced for 2 handed items.
452 | /// A new , or if the provided item name is not found.
453 | /// Thrown when trying to spawn an on the client.
454 | public static Item CreateAndGiveItem(string itemName, Player player, bool andInitialize = true, bool switchTo = true)
455 | {
456 | if (!(NetworkManager.Singleton.IsServer || NetworkManager.Singleton.IsHost))
457 | {
458 | throw new NoAuthorityException("Tried to create and give item on client.");
459 | }
460 |
461 | string name = itemName.ToLower();
462 |
463 | GameObject go = StartOfRound.Instance.allItemsList.itemsList.FirstOrDefault(i => i.itemName.ToLower().Contains(name))?.spawnPrefab;
464 | if (go != null)
465 | {
466 | GameObject instantiated = Instantiate(go, Vector3.zero, default);
467 |
468 | instantiated.GetComponent().Spawn();
469 |
470 | Item item = instantiated.GetComponent
- ();
471 |
472 | if (item.IsScrap && andInitialize) item.InitializeScrap();
473 |
474 | item.GiveTo(player, switchTo);
475 |
476 | return item;
477 | }
478 |
479 | return null;
480 | }
481 |
482 | #region Unity related things
483 | private void Awake()
484 | {
485 | GrabbableObject = GetComponent();
486 | ScanNodeProperties = GrabbableObject.gameObject.GetComponentInChildren();
487 |
488 | Dictionary.Add(GrabbableObject, this);
489 | }
490 |
491 | // All items have the same properties, so if we change it, we need to clone it.
492 | private bool hasNewProps = false;
493 | private void CloneProperties()
494 | {
495 | global::Item newProps = Instantiate(ItemProperties);
496 |
497 | // Don't want to destroy any that aren't custom
498 | if (hasNewProps) Destroy(ItemProperties);
499 |
500 | GrabbableObject.itemProperties = newProps;
501 |
502 | hasNewProps = true;
503 | }
504 |
505 | ///
506 | /// For internal use. Do not use.
507 | ///
508 | public override void OnDestroy()
509 | {
510 | Dictionary.Remove(GrabbableObject);
511 |
512 | base.OnDestroy();
513 | }
514 | #endregion
515 |
516 | #region Item getters
517 | public static Item GetOrAdd(GrabbableObject grabbableObject)
518 | {
519 | if (Dictionary.TryGetValue(grabbableObject, out Item item))
520 | return item;
521 |
522 | return grabbableObject.gameObject.AddComponent
- (); ;
523 | }
524 |
525 | public static Item Get(GrabbableObject grabbableObject)
526 | {
527 | if (Dictionary.TryGetValue(grabbableObject, out Item item))
528 | return item;
529 |
530 | return null;
531 | }
532 |
533 | public static bool TryGet(GrabbableObject grabbableObject, out Item item)
534 | {
535 | return Dictionary.TryGetValue(grabbableObject, out item);
536 | }
537 |
538 | public static Item Get(ulong netId)
539 | {
540 | return List.FirstOrDefault(i => i.NetworkObjectId == netId);
541 | }
542 |
543 | public static bool TryGet(ulong netId, out Item item)
544 | {
545 | item = Get(netId);
546 |
547 | return item != null;
548 | }
549 | #endregion
550 | }
551 | }
552 |
--------------------------------------------------------------------------------
/LC-API/GameInterfaceAPI/Features/Tip.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace LC_API.GameInterfaceAPI.Features
4 | {
5 | public class Tip : IComparable
6 | {
7 | public string Header { get; }
8 |
9 | public string Message { get; }
10 |
11 | public float Duration { get; }
12 |
13 | public float TimeLeft { get; internal set; }
14 |
15 | public int Priority { get; } = 0;
16 |
17 | public bool IsWarning { get; set; } = false;
18 |
19 | public bool UseSave { get; set; } = false;
20 |
21 | public string PreferenceKey { get; set; } = "LC_Tip1";
22 |
23 | internal int TipId { get; set; }
24 |
25 | public Tip(string header, string message, float duration, int priority, bool isWarning, bool useSave, string prefsKey, int tipId)
26 | {
27 | Header = header;
28 | Message = message;
29 | Duration = duration;
30 | TimeLeft = duration;
31 | Priority = priority;
32 |
33 | IsWarning = isWarning;
34 | UseSave = useSave;
35 | PreferenceKey = prefsKey;
36 |
37 | TipId = tipId;
38 | }
39 |
40 | public int CompareTo(Tip other)
41 | {
42 | int diff = other.Priority - Priority;
43 |
44 | if (diff < 0) return -1;
45 |
46 | if (diff > 0) return 1;
47 |
48 | return TipId - other.TipId;
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/LC-API/GameInterfaceAPI/GameState.cs:
--------------------------------------------------------------------------------
1 | using LC_API.Data;
2 | using LC_API.Extensions;
3 | using System;
4 |
5 | namespace LC_API.GameInterfaceAPI
6 | {
7 | ///
8 | /// Contains callbacks and information about the game's current state.
9 | ///
10 | public static class GameState
11 | {
12 | // stops the compiler whining about nullability
13 | private readonly static Action NothingAction = () => { };
14 |
15 | ///
16 | /// Provides the count of living players, as gotten through
17 | ///
18 | public static int AlivePlayerCount { get; private set; }
19 | ///
20 | /// The state the ship is currently in. See .
21 | ///
22 | public static ShipState ShipState { get; private set; }
23 |
24 | ///
25 | /// Executes the frame after a player dies (determined by a living player count comparison)
26 | ///
27 | public static event Action PlayerDied = NothingAction;
28 | ///
29 | /// Executes the frame after the ship has landed.
30 | ///
31 | public static event Action LandOnMoon = NothingAction;
32 | ///
33 | /// Executes when the ship reaches orbit.
34 | ///
35 | public static event Action WentIntoOrbit = NothingAction;
36 | ///
37 | /// Executes when the ship starts taking off.
38 | ///
39 | public static event Action ShipStartedLeaving = NothingAction;
40 |
41 | internal static void GSUpdate()
42 | {
43 | if (StartOfRound.Instance == null)
44 | return;
45 |
46 | if (StartOfRound.Instance.shipHasLanded && ShipState != ShipState.OnMoon)
47 | {
48 | ShipState = ShipState.OnMoon;
49 | LandOnMoon.InvokeActionSafe();
50 | }
51 |
52 | if (StartOfRound.Instance.inShipPhase && ShipState != ShipState.InOrbit)
53 | {
54 | ShipState = ShipState.InOrbit;
55 | WentIntoOrbit.InvokeActionSafe();
56 | }
57 |
58 | if (StartOfRound.Instance.shipIsLeaving && ShipState != ShipState.LeavingMoon)
59 | {
60 | ShipState = ShipState.LeavingMoon;
61 | ShipStartedLeaving.InvokeActionSafe();
62 | }
63 |
64 | int aliveP = AlivePlayerCount;
65 | if (aliveP < StartOfRound.Instance.livingPlayers)
66 | {
67 | PlayerDied.InvokeActionSafe();
68 | }
69 | AlivePlayerCount = StartOfRound.Instance.livingPlayers;
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/LC-API/GameInterfaceAPI/GameTips.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 |
5 | namespace LC_API.GameInterfaceAPI
6 | {
7 | ///
8 | /// Allows for displaying information through the tip box on the players screen, without any tips overlapping.
9 | ///
10 | [Obsolete("Use Player::QueueTip instead.")]
11 | public class GameTips
12 | {
13 | private static List tipHeaders = new List();
14 | private static List tipBodys = new List();
15 | private static float lastMessageTime;
16 |
17 | ///
18 | /// Add a tip to the tip que.
19 | ///
20 | public static void ShowTip(string header, string body)
21 | {
22 | tipHeaders.Add(header);
23 | tipBodys.Add(body);
24 | }
25 |
26 | public static void UpdateInternal()
27 | {
28 | lastMessageTime -= Time.deltaTime;
29 | if (tipHeaders.Count > 0 & lastMessageTime < 0)
30 | {
31 | lastMessageTime = 5f;
32 | if (HUDManager.Instance != null)
33 | {
34 | HUDManager.Instance.DisplayTip(tipHeaders[0], tipBodys[0]);
35 | }
36 | tipHeaders.RemoveAt(0);
37 | tipBodys.RemoveAt(0);
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/LC-API/LC-API.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.1;net472
5 | LC_API
6 | Lethal Company API
7 | Utilities for plugin devs
8 | true
9 | latest
10 |
11 | https://api.nuget.org/v3/index.json;
12 | https://nuget.bepinex.dev/v3/index.json;
13 | https://nuget.samboy.dev/v3/index.json
14 |
15 | LC_API
16 | True
17 | v
18 |
19 |
20 |
21 |
22 | LethalCompany.LC-API
23 | 2018,steven4547466
24 | 2018
25 | LICENSE
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | bin
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | runtime; build; native; contentfiles; analyzers; buildtransitive
51 | all
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | $(LETHAL_COMPANY_DIR)\Lethal Company_Data\Managed\Assembly-CSharp.dll
64 |
65 |
66 | $(LETHAL_COMPANY_DIR)\Lethal Company_Data\Managed\Assembly-CSharp-firstpass.dll
67 |
68 |
69 | $(LETHAL_COMPANY_DIR)\Lethal Company_Data\Managed\Facepunch.Steamworks.Win64.dll
70 |
71 |
72 | $(LETHAL_COMPANY_DIR)\Lethal Company_Data\Managed\Newtonsoft.Json.dll
73 |
74 |
75 | $(LETHAL_COMPANY_DIR)\Lethal Company_Data\Managed\Unity.InputSystem.dll
76 |
77 |
78 | $(LETHAL_COMPANY_DIR)\Lethal Company_Data\Managed\Unity.Netcode.Runtime.dll
79 |
80 |
81 | $(LETHAL_COMPANY_DIR)\Lethal Company_Data\Managed\Unity.TextMeshPro.dll
82 |
83 |
84 | $(LETHAL_COMPANY_DIR)\Lethal Company_Data\Managed\UnityEngine.UI.dll
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 | true
94 |
95 |
96 |
97 |
98 |
99 | $(MinVerMajor).$(MinVerMinor).$(MinVerPatch)
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 | $(MinVerMajor).$(MinVerMinor).$(MinVerPatch)
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
--------------------------------------------------------------------------------
/LC-API/ManualPatches/ServerPatch.cs:
--------------------------------------------------------------------------------
1 | using LC_API.Comp;
2 | using LC_API.ServerAPI;
3 | using Steamworks;
4 | using Steamworks.Data;
5 | using UnityEngine;
6 | using UnityEngine.InputSystem;
7 |
8 | namespace LC_API.ManualPatches
9 | {
10 | internal static class ServerPatch
11 | {
12 | internal static bool OnLobbyCreate(GameNetworkManager __instance, Result result, Lobby lobby)
13 | {
14 | if (result != Result.OK)
15 | {
16 | Debug.LogError(string.Format("Lobby could not be created! {0}", result), __instance);
17 | }
18 | __instance.lobbyHostSettings.lobbyName = "[MODDED]" + __instance.lobbyHostSettings.lobbyName.ToString();
19 | Plugin.Log.LogMessage("server pre-setup success");
20 | return (true);
21 | }
22 |
23 | internal static bool CacheMenuManager(MenuManager __instance)
24 | {
25 | LC_APIManager.MenuManager = __instance;
26 | return true;
27 | }
28 |
29 | internal static bool ChatCommands(HUDManager __instance, InputAction.CallbackContext context)
30 | {
31 | if (__instance.chatTextField.text.ToLower().Contains("/modcheck"))
32 | {
33 | CheatDatabase.OtherPlayerCheatDetector();
34 | return false;
35 | }
36 | return true;
37 | }
38 |
39 | internal static void GameNetworkManagerAwake(GameNetworkManager __instance)
40 | {
41 | if (GameNetworkManager.Instance == null) ModdedServer.GameVersion = __instance.gameVersionNum;
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/LC-API/Networking/Serializers.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using UnityEngine;
3 |
4 | namespace LC_API.Networking.Serializers
5 | {
6 | #pragma warning disable
7 | ///
8 | /// Serializes a .
9 | ///
10 | public struct Vector2S
11 | {
12 | public float x { get; set; }
13 | public float y { get; set; }
14 |
15 | public Vector2S(float x, float y)
16 | {
17 | this.x = x;
18 | this.y = y;
19 | }
20 |
21 | public static implicit operator Vector2(Vector2S vector2S) => vector2S.vector2;
22 |
23 | public static implicit operator Vector2S(Vector2 vector2) => new Vector2S(vector2.x, vector2.y);
24 |
25 |
26 | private Vector2? v2 = null;
27 |
28 | [JsonIgnore]
29 | public Vector2 vector2
30 | {
31 | get
32 | {
33 | if (!v2.HasValue) v2 = new Vector2(x, y);
34 | return v2.Value;
35 | }
36 | }
37 | }
38 |
39 | ///
40 | /// Serializes a .
41 | ///
42 | public struct Vector2IntS
43 | {
44 | public int x { get; set; }
45 | public int y { get; set; }
46 |
47 | public Vector2IntS(int x, int y)
48 | {
49 | this.x = x;
50 | this.y = y;
51 | }
52 |
53 | public static implicit operator Vector2Int(Vector2IntS vector2S) => vector2S.vector2;
54 |
55 | public static implicit operator Vector2IntS(Vector2Int vector2) => new Vector2IntS(vector2.x, vector2.y);
56 |
57 |
58 | private Vector2Int? v2 = null;
59 |
60 | [JsonIgnore]
61 | public Vector2Int vector2
62 | {
63 | get
64 | {
65 | if (!v2.HasValue) v2 = new Vector2Int(x, y);
66 | return v2.Value;
67 | }
68 | }
69 | }
70 |
71 | ///
72 | /// Serializes a .
73 | ///
74 | public struct Vector3S
75 | {
76 | public float x { get; set; }
77 | public float y { get; set; }
78 | public float z { get; set; }
79 |
80 | public Vector3S(float x, float y, float z)
81 | {
82 | this.x = x;
83 | this.y = y;
84 | this.z = z;
85 | }
86 |
87 | public static implicit operator Vector3(Vector3S vector3S) => vector3S.vector3;
88 |
89 | public static implicit operator Vector3S(Vector3 vector3) => new Vector3S(vector3.x, vector3.y, vector3.z);
90 |
91 |
92 | private Vector3? v3 = null;
93 |
94 | [JsonIgnore]
95 | public Vector3 vector3
96 | {
97 | get
98 | {
99 | if (!v3.HasValue) v3 = new Vector3(x, y, z);
100 | return v3.Value;
101 | }
102 | }
103 | }
104 |
105 | ///
106 | /// Serializes a .
107 | ///
108 | public struct Vector3IntS
109 | {
110 | public int x { get; set; }
111 | public int y { get; set; }
112 | public int z { get; set; }
113 |
114 | public Vector3IntS(int x, int y, int z)
115 | {
116 | this.x = x;
117 | this.y = y;
118 | this.z = z;
119 | }
120 |
121 | public static implicit operator Vector3Int(Vector3IntS vector3S) => vector3S.vector3;
122 |
123 | public static implicit operator Vector3IntS(Vector3Int vector3) => new Vector3IntS(vector3.x, vector3.y, vector3.z);
124 |
125 |
126 | private Vector3Int? v3 = null;
127 |
128 | [JsonIgnore]
129 | public Vector3Int vector3
130 | {
131 | get
132 | {
133 | if (!v3.HasValue) v3 = new Vector3Int(x, y, z);
134 | return v3.Value;
135 | }
136 | }
137 | }
138 |
139 | ///
140 | /// Serializes a .
141 | ///
142 | public struct Vector4S
143 | {
144 | public float x { get; set; }
145 | public float y { get; set; }
146 | public float z { get; set; }
147 | public float w { get; set; }
148 |
149 | public Vector4S(float x, float y, float z, float w)
150 | {
151 | this.x = x;
152 | this.y = y;
153 | this.z = z;
154 | this.w = w;
155 | }
156 |
157 | public static implicit operator Vector4(Vector4S vector4S) => vector4S.Vector4;
158 |
159 | public static implicit operator Vector4S(Vector4 vector4) => new Vector4S(vector4.x, vector4.y, vector4.z, vector4.w);
160 |
161 |
162 | private Vector4? v4 = null;
163 |
164 | [JsonIgnore]
165 | public Vector4 Vector4
166 | {
167 | get
168 | {
169 | if (!v4.HasValue) v4 = new Vector4(x, y, z, w);
170 | return v4.Value;
171 | }
172 | }
173 | }
174 |
175 | ///
176 | /// Serializes a .
177 | ///
178 | public struct QuaternionS
179 | {
180 | public float x { get; set; }
181 | public float y { get; set; }
182 | public float z { get; set; }
183 | public float w { get; set; }
184 |
185 | public QuaternionS(float x, float y, float z, float w)
186 | {
187 | this.x = x;
188 | this.y = y;
189 | this.z = z;
190 | this.w = w;
191 | }
192 |
193 | public static implicit operator Quaternion(QuaternionS quaternionS) => quaternionS.Quaternion;
194 |
195 | public static implicit operator QuaternionS(Quaternion quaternion) => new QuaternionS(quaternion.x, quaternion.y, quaternion.z, quaternion.w);
196 |
197 |
198 | private Quaternion? q = null;
199 |
200 | [JsonIgnore]
201 | public Quaternion Quaternion
202 | {
203 | get
204 | {
205 | if (!q.HasValue) q = new Quaternion(x, y, z, w);
206 | return q.Value;
207 | }
208 | }
209 | }
210 |
211 | ///
212 | /// Serializes a .
213 | ///
214 | public struct ColorS
215 | {
216 | public float r { get; set; }
217 | public float g { get; set; }
218 | public float b { get; set; }
219 | public float a { get; set; }
220 |
221 | public ColorS(float r, float g, float b, float a)
222 | {
223 | this.r = r;
224 | this.g = g;
225 | this.b = b;
226 | this.a = a;
227 | }
228 |
229 | public static implicit operator Color(ColorS colorS) => colorS.Color;
230 |
231 | public static implicit operator ColorS(Color color) => new ColorS(color.r, color.g, color.b, color.a);
232 |
233 |
234 | private Color? c = null;
235 |
236 | [JsonIgnore]
237 | public Color Color
238 | {
239 | get
240 | {
241 | if (!c.HasValue) c = new Color(r, g, b, a);
242 | return c.Value;
243 | }
244 | }
245 | }
246 |
247 | ///
248 | /// Serializes a .
249 | ///
250 | public struct Color32S
251 | {
252 | public byte r { get; set; }
253 | public byte g { get; set; }
254 | public byte b { get; set; }
255 | public byte a { get; set; }
256 |
257 | public Color32S(byte r, byte g, byte b, byte a)
258 | {
259 | this.r = r;
260 | this.g = g;
261 | this.b = b;
262 | this.a = a;
263 | }
264 |
265 | public static implicit operator Color32(Color32S colorS) => colorS.Color;
266 |
267 | public static implicit operator Color32S(Color32 color) => new Color32S(color.r, color.g, color.b, color.a);
268 |
269 |
270 | private Color32? c = null;
271 |
272 | [JsonIgnore]
273 | public Color32 Color
274 | {
275 | get
276 | {
277 | if (!c.HasValue) c = new Color32(r, g, b, a);
278 | return c.Value;
279 | }
280 | }
281 | }
282 |
283 | ///
284 | /// Serializes a .
285 | ///
286 | public struct RayS
287 | {
288 | public Vector3S origin { get; set; }
289 | public Vector3S direction { get; set; }
290 |
291 | public RayS(Vector3 origin, Vector3 direction)
292 | {
293 | this.origin = origin;
294 | this.direction = direction;
295 | }
296 |
297 | public static implicit operator Ray(RayS rayS) => rayS.Ray;
298 |
299 | public static implicit operator RayS(Ray ray) => new RayS(ray.origin, ray.direction);
300 |
301 |
302 | private Ray? r = null;
303 |
304 | [JsonIgnore]
305 | public Ray Ray
306 | {
307 | get
308 | {
309 | if (!r.HasValue) r = new Ray(origin, direction);
310 | return r.Value;
311 | }
312 | }
313 | }
314 |
315 | ///
316 | /// Serializes a .
317 | ///
318 | public struct Ray2DS
319 | {
320 | public Vector2S origin { get; set; }
321 | public Vector2S direction { get; set; }
322 |
323 | public Ray2DS(Vector2 origin, Vector2 direction)
324 | {
325 | this.origin = origin;
326 | this.direction = direction;
327 | }
328 |
329 | public static implicit operator Ray2D(Ray2DS ray2DS) => ray2DS.Ray;
330 |
331 | public static implicit operator Ray2DS(Ray2D ray2D) => new Ray2DS(ray2D.origin, ray2D.direction);
332 |
333 |
334 | private Ray2D? r = null;
335 |
336 | [JsonIgnore]
337 | public Ray2D Ray
338 | {
339 | get
340 | {
341 | if (!r.HasValue) r = new Ray2D(origin, direction);
342 | return r.Value;
343 | }
344 | }
345 | }
346 | #pragma warning restore
347 | }
348 |
--------------------------------------------------------------------------------
/LC-API/Plugin.cs:
--------------------------------------------------------------------------------
1 | using BepInEx;
2 | using BepInEx.Configuration;
3 | using BepInEx.Logging;
4 | using HarmonyLib;
5 | using LC_API.ClientAPI;
6 | using LC_API.Comp;
7 | using LC_API.GameInterfaceAPI.Events;
8 | using LC_API.ManualPatches;
9 | using LC_API.Networking;
10 | using LC_API.ServerAPI;
11 | using System.Reflection;
12 | using Unity.Netcode;
13 | using UnityEngine;
14 |
15 | namespace LC_API
16 | {
17 | // .____ _________ _____ __________ .___
18 | // | | \_ ___ \ / _ \ \______ \| |
19 | // | | / \ \/ / /_\ \ | ___/| |
20 | // | |___\ \____ / | \| | | |
21 | // |_______ \\______ /______\____|__ /|____| |___|
22 | // \/ \//_____/ \/
23 | ///
24 | /// The Lethal Company modding API plugin!
25 | ///
26 | [BepInPlugin(MyPluginInfo.PLUGIN_GUID, MyPluginInfo.PLUGIN_NAME, MyPluginInfo.PLUGIN_VERSION)]
27 | public sealed class Plugin : BaseUnityPlugin
28 | {
29 | internal static Plugin Instance { get; private set; }
30 | ///
31 | /// Runs after the LC API plugin's "Awake" method is finished.
32 | ///
33 | public static bool Initialized { get; private set; }
34 |
35 | #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
36 | internal static ManualLogSource Log;
37 |
38 | private ConfigEntry configOverrideModServer;
39 | private ConfigEntry configLegacyAssetLoading;
40 | private ConfigEntry configDisableBundleLoader;
41 | internal static ConfigEntry configVanillaSupport;
42 | #pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
43 |
44 |
45 | internal static Harmony Harmony;
46 |
47 | private void Awake()
48 | {
49 | Instance = this;
50 | configOverrideModServer = Config.Bind("General", "Force modded server browser", false, "Should the API force you into the modded server browser?");
51 | configLegacyAssetLoading = Config.Bind("General", "Legacy asset bundle loading", false, "Should the BundleLoader use legacy asset loading? Turning this on may help with loading assets from older plugins.");
52 | configDisableBundleLoader = Config.Bind("General", "Disable BundleLoader", false, "Should the BundleLoader be turned off? Enable this if you are having problems with mods that load assets using a different method from LC_API's BundleLoader.");
53 | configVanillaSupport = Config.Bind("Compatibility", "Vanilla Compatibility", false, "Allows you to join vanilla servers, but disables many networking-related things and could cause mods to not work properly.");
54 | CommandHandler.commandPrefix = Config.Bind("General", "Prefix", "/", "Command prefix");
55 |
56 | Log = Logger;
57 | // Plugin startup logic
58 | Logger.LogWarning("\n.____ _________ _____ __________ .___ \r\n| | \\_ ___ \\ / _ \\ \\______ \\| | \r\n| | / \\ \\/ / /_\\ \\ | ___/| | \r\n| |___\\ \\____ / | \\| | | | \r\n|_______ \\\\______ /______\\____|__ /|____| |___| \r\n \\/ \\//_____/ \\/ \r\n ");
59 | Logger.LogInfo($"LC_API Starting up..");
60 | if (configOverrideModServer.Value)
61 | {
62 | ModdedServer.SetServerModdedOnly();
63 | }
64 |
65 | if (configVanillaSupport.Value) Logger.LogInfo("LC_API is starting with VANILLA SUPPORT ENABLED.");
66 |
67 | Harmony = new Harmony("ModAPI");
68 | MethodInfo originalLobbyCreated = AccessTools.Method(typeof(GameNetworkManager), "SteamMatchmaking_OnLobbyCreated");
69 | MethodInfo originalLobbyJoinable = AccessTools.Method(typeof(GameNetworkManager), "LobbyDataIsJoinable");
70 |
71 | MethodInfo patchLobbyCreate = AccessTools.Method(typeof(ServerPatch), nameof(ServerPatch.OnLobbyCreate));
72 |
73 | MethodInfo originalMenuAwake = AccessTools.Method(typeof(MenuManager), "Awake");
74 |
75 | MethodInfo patchCacheMenuMgr = AccessTools.Method(typeof(ServerPatch), nameof(ServerPatch.CacheMenuManager));
76 |
77 | MethodInfo originalAddChatMsg = AccessTools.Method(typeof(HUDManager), "AddChatMessage");
78 |
79 | MethodInfo originalSubmitChat = AccessTools.Method(typeof(HUDManager), "SubmitChat_performed");
80 |
81 | MethodInfo patchSubmitChat = AccessTools.Method(typeof(CommandHandler.SubmitChatPatch), nameof(CommandHandler.SubmitChatPatch.Transpiler));
82 |
83 | MethodInfo originalGameManagerAwake = AccessTools.Method(typeof(GameNetworkManager), nameof(GameNetworkManager.Awake));
84 |
85 | MethodInfo patchGameManagerAwake = AccessTools.Method(typeof(ServerPatch), nameof(ServerPatch.GameNetworkManagerAwake));
86 |
87 | MethodInfo originalStartClient = AccessTools.Method(typeof(NetworkManager), nameof(NetworkManager.StartClient));
88 | MethodInfo originalStartHost = AccessTools.Method(typeof(NetworkManager), nameof(NetworkManager.StartHost));
89 | MethodInfo originalShutdown = AccessTools.Method(typeof(NetworkManager), nameof(NetworkManager.Shutdown));
90 |
91 | MethodInfo registerPatch = AccessTools.Method(typeof(RegisterPatch), nameof(RegisterPatch.Postfix));
92 | MethodInfo unregisterPatch = AccessTools.Method(typeof(UnregisterPatch), nameof(UnregisterPatch.Postfix));
93 |
94 | Harmony.Patch(originalMenuAwake, new HarmonyMethod(patchCacheMenuMgr));
95 | Harmony.Patch(originalLobbyCreated, new HarmonyMethod(patchLobbyCreate));
96 | Harmony.Patch(originalSubmitChat, null, null, new HarmonyMethod(patchSubmitChat));
97 | Harmony.Patch(originalGameManagerAwake, new HarmonyMethod(patchGameManagerAwake));
98 |
99 | Harmony.Patch(originalStartClient, null, new HarmonyMethod(registerPatch));
100 | Harmony.Patch(originalStartHost, null, new HarmonyMethod(registerPatch));
101 |
102 | Harmony.Patch(originalShutdown, null, new HarmonyMethod(unregisterPatch));
103 |
104 | Network.Init();
105 | Events.Patch(Harmony);
106 | }
107 |
108 | internal void Start()
109 | {
110 | Initialize();
111 | }
112 |
113 | internal void OnDestroy()
114 | {
115 | Initialize();
116 | }
117 |
118 | internal void Initialize()
119 | {
120 | if (!Initialized)
121 | {
122 | Initialized = true;
123 | if (!configDisableBundleLoader.Value)
124 | {
125 | BundleAPI.BundleLoader.Load(configLegacyAssetLoading.Value);
126 | }
127 | GameObject gameObject = new GameObject("API");
128 | DontDestroyOnLoad(gameObject);
129 | gameObject.AddComponent();
130 | Logger.LogInfo($"LC_API Started!");
131 | CheatDatabase.RunLocalCheatDetector();
132 | }
133 | }
134 |
135 | internal static void PatchMethodManual(MethodInfo method, MethodInfo patch, Harmony harmony)
136 | {
137 | harmony.Patch(method, new HarmonyMethod(patch));
138 | }
139 | }
140 | }
--------------------------------------------------------------------------------
/LC-API/ServerAPI/ModdedServer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | #pragma warning disable CS0618 // Member is obsolete
4 | namespace LC_API.ServerAPI
5 | {
6 | ///
7 | /// You're probably here for
8 | ///
9 | public static class ModdedServer
10 | {
11 | private static bool moddedOnly;
12 | [Obsolete("Use SetServerModdedOnly() instead. This will be removed/private in a future update.")]
13 | public static bool setModdedOnly; // obsolete for the purposes of getting peoples' IDE's to bitch at them.
14 |
15 | public static int GameVersion { get; internal set; }
16 |
17 | ///
18 | /// Has the user been placed in modded only servers?
19 | ///
20 | public static bool ModdedOnly
21 | {
22 | get { return moddedOnly; }
23 | }
24 |
25 | ///
26 | /// Call this method to make your plugin place the user in modded only servers.
27 | ///
28 | public static void SetServerModdedOnly()
29 | {
30 | moddedOnly = true;
31 | Plugin.Log.LogMessage("A plugin has set your game to only allow you to play with other people who have mods!");
32 | }
33 |
34 | ///
35 | /// For internal use. Do not call this method.
36 | ///
37 | public static void OnSceneLoaded()
38 | {
39 | if (GameNetworkManager.Instance && ModdedOnly)
40 | {
41 | GameNetworkManager.Instance.gameVersionNum += 16440;
42 | setModdedOnly = true;
43 | }
44 | }
45 | }
46 | }
47 | #pragma warning restore CS0618 // Member is obsolete
48 |
--------------------------------------------------------------------------------
/LC-API/ServerAPI/Networking.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using UnityEngine;
5 |
6 | namespace LC_API.ServerAPI
7 | {
8 | ///
9 | /// Networking solution to broadcast and receive data over the server. Use the delegates GetString, GetInt, GetFloat, and GetVector3 for receiving data. Note that the local player will not receive data that they broadcast.
10 | /// The second parameter for each of the events is the signature string.
11 | ///
12 | [Obsolete("ServerAPI.Networking is obsolete and will be removed in future versions. Use LC_API.Networking.Network.")]
13 | public static class Networking
14 | {
15 | private const string StringMessageRegistrationName = "LCAPI_NET_LEGACY_STRING";
16 | private const string ListStringMessageRegistrationName = "LCAPI_NET_LEGACY_LISTSTRING";
17 | private const string IntMessageRegistrationName = "LCAPI_NET_LEGACY_INT";
18 | private const string FloatMessageRegistrationName = "LCAPI_NET_LEGACY_FLOAT";
19 | private const string Vector3MessageRegistrationName = "LCAPI_NET_LEGACY_VECTOR3";
20 |
21 | private const string SyncVarMessageRegistrationName = "LCAPI_NET_LEGACY_SYNCVAR_SET";
22 |
23 | ///
24 | /// Delegate for receiving a string value. Second parameter is the signature.
25 | /// (that signature would have been the signature given to , for example)
26 | ///
27 | public static Action GetString = (_, _) => { };
28 | ///
29 | /// Delegate for receiving a list of string values. Second parameter is the signature.
30 | /// (that signature would have been the signature given to , for example)
31 | ///
32 | public static Action
, string> GetListString = (_, _) => { };
33 | ///
34 | /// Delegate for receiving a int value. Second parameter is the signature.
35 | /// (that signature would have been the signature given to , for example)
36 | ///
37 | public static Action GetInt = (_, _) => { };
38 | ///
39 | /// Delegate for receiving a float value. Second parameter is the signature.
40 | /// (that signature would have been the signature given to , for example)
41 | ///
42 | public static Action GetFloat = (_, _) => { };
43 | ///
44 | /// Delegate for receiving a Vector3 value. Second parameter is the signature.
45 | /// (that signature would have been the signature given to , for example)
46 | ///
47 | public static Action GetVector3 = (_, _) => { };
48 |
49 | private static Dictionary syncStringVars = new Dictionary();
50 |
51 | ///
52 | /// Send data across the network. The signature is an identifier for use when receiving data.
53 | ///
54 | public static void Broadcast(string data, string signature)
55 | {
56 | if (data.Contains("/"))
57 | {
58 | Plugin.Log.LogError("Invalid character in broadcasted string event! ( / )");
59 | return;
60 | }
61 | LC_API.Networking.Network.Broadcast(StringMessageRegistrationName, new Data(signature: signature, value: data));
62 | }
63 |
64 | ///
65 | /// Send data across the network. The signature is an identifier for use when receiving data.
66 | ///
67 | public static void Broadcast(List data, string signature)
68 | {
69 | string dataFormatted = "";
70 | foreach (var item in data)
71 | {
72 | if (item.Contains("/"))
73 | {
74 | Plugin.Log.LogError("Invalid character in broadcasted string event! ( / )");
75 | return;
76 | }
77 | if (item.Contains("\n"))
78 | {
79 | Plugin.Log.LogError("Invalid character in broadcasted string event! ( NewLine )");
80 | return;
81 | }
82 | dataFormatted += item + "\n";
83 | }
84 | LC_API.Networking.Network.Broadcast(ListStringMessageRegistrationName, new Data(signature: signature, value: dataFormatted));
85 | }
86 |
87 | ///
88 | /// Send data across the network. The signature is an identifier for use when receiving data.
89 | ///
90 | public static void Broadcast(int data, string signature)
91 | {
92 | LC_API.Networking.Network.Broadcast(IntMessageRegistrationName, new Data(signature: signature, value: data));
93 | }
94 |
95 | ///
96 | /// Send data across the network. The signature is an identifier for use when receiving data.
97 | ///
98 | public static void Broadcast(float data, string signature)
99 | {
100 | LC_API.Networking.Network.Broadcast(FloatMessageRegistrationName, new Data(signature: signature, value: data));
101 | }
102 |
103 | ///
104 | /// Send data across the network. The signature is an identifier for use when receiving data.
105 | ///
106 | public static void Broadcast(Vector3 data, string signature)
107 | {
108 | LC_API.Networking.Network.Broadcast(Vector3MessageRegistrationName, new Data(signature: signature, value: data));
109 | }
110 |
111 | ///
112 | /// Register a Sync Variable. Currently Sync Variables can only store string values.
113 | ///
114 | public static void RegisterSyncVariable(string name)
115 | {
116 | if (!syncStringVars.ContainsKey(name))
117 | {
118 | syncStringVars.Add(name, "");
119 | }
120 | else
121 | {
122 | Plugin.Log.LogError("Cannot register Sync Variable! A Sync Variable has already been registered with name " + name);
123 | }
124 | }
125 |
126 | ///
127 | /// Set the value of a Sync Variable.
128 | ///
129 | public static void SetSyncVariable(string name, string value)
130 | {
131 | if (syncStringVars.ContainsKey(name))
132 | {
133 | syncStringVars[name] = value;
134 | List syncString = new List();
135 | syncString.Add(name);
136 | syncString.Add(value);
137 | Broadcast(syncString, SyncVarMessageRegistrationName);
138 | }
139 | else
140 | {
141 | Plugin.Log.LogError("Cannot set the value of Sync Variable " + name + " as it is not registered!");
142 | }
143 | }
144 |
145 | private static void SetSyncVariableB(string name, string value)
146 | {
147 | if (syncStringVars.ContainsKey(name))
148 | {
149 | syncStringVars[name] = value;
150 | }
151 | else
152 | {
153 | Plugin.Log.LogError("Cannot set the value of Sync Variable " + name + " as it is not registered!");
154 | }
155 | }
156 |
157 | internal static void LCAPI_NET_SYNCVAR_SET(List list, string arg2)
158 | {
159 | if (arg2 == SyncVarMessageRegistrationName)
160 | {
161 | SetSyncVariableB(list[0], list[1]);
162 | }
163 | }
164 |
165 | ///
166 | /// Get the value of a Sync Variable.
167 | ///
168 | public static string GetSyncVariable(string name)
169 | {
170 | if (syncStringVars.ContainsKey(name))
171 | {
172 | return syncStringVars[name];
173 | }
174 | else
175 | {
176 | Plugin.Log.LogError("Cannot get the value of Sync Variable " + name + " as it is not registered!");
177 | return "";
178 | }
179 | }
180 |
181 | private sealed class Data
182 | {
183 | public readonly string Signature;
184 | public readonly T Value;
185 |
186 | public Data(string signature, T value)
187 | {
188 | Signature = signature;
189 | Value = value;
190 | }
191 | }
192 |
193 | internal static void InitializeLegacyNetworking()
194 | {
195 | GetListString += LCAPI_NET_SYNCVAR_SET;
196 |
197 | LC_API.Networking.Network.RegisterMessage(
198 | StringMessageRegistrationName,
199 | false,
200 | (ulong senderId, Data data) => GetString(data.Value, data.Signature));
201 | LC_API.Networking.Network.RegisterMessage(
202 | ListStringMessageRegistrationName,
203 | false,
204 | (ulong senderId, Data data) => GetListString(data.Value.Split('\n').ToList(), data.Signature));
205 | LC_API.Networking.Network.RegisterMessage(
206 | IntMessageRegistrationName,
207 | false,
208 | (ulong senderId, Data data) => GetInt(data.Value, data.Signature));
209 | LC_API.Networking.Network.RegisterMessage(
210 | FloatMessageRegistrationName,
211 | false,
212 | (ulong senderId, Data data) => GetFloat(data.Value, data.Signature));
213 | LC_API.Networking.Network.RegisterMessage(
214 | Vector3MessageRegistrationName,
215 | false,
216 | (ulong senderId, Data data) => GetVector3(data.Value, data.Signature));
217 | }
218 | }
219 | }
--------------------------------------------------------------------------------
/LC-API/Utils.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using System.Text.RegularExpressions;
4 |
5 | namespace LC_API
6 | {
7 | ///
8 | /// Provides a few useful utilities for the API.
9 | ///
10 | public static class Utils
11 | {
12 | // Thanks Elias https://stackoverflow.com/a/11105164
13 | ///
14 | /// Replaces a string and attempts to keep its casing.
15 | ///
16 | /// The input .
17 | /// The to replace.
18 | /// The replacement .
19 | /// The with all instances of toReplace replaced with replacement.
20 | public static string ReplaceWithCase(this string input, string toReplace, string replacement)
21 | {
22 | Dictionary map = new Dictionary
23 | {
24 | { toReplace, replacement }
25 | };
26 |
27 | return input.ReplaceWithCase(map);
28 | }
29 |
30 | // Thanks Elias https://stackoverflow.com/a/11105164
31 | ///
32 | /// Replaces a string and attempts to keep its casing.
33 | ///
34 | /// The input .
35 | /// A of strings to replace and their replacement.
36 | /// The completed .
37 | public static string ReplaceWithCase(this string input, Dictionary map)
38 | {
39 | string temp = input;
40 | foreach (var entry in map)
41 | {
42 | string key = entry.Key;
43 | string value = entry.Value;
44 | temp = Regex.Replace(temp, key, match =>
45 | {
46 | bool isFirstUpper, isEachUpper, isAllUpper;
47 |
48 | string sentence = match.Value;
49 | char[] sentenceArray = sentence.ToCharArray();
50 |
51 | string[] words = sentence.Split(' ');
52 |
53 | isFirstUpper = char.IsUpper(sentenceArray[0]);
54 |
55 | isEachUpper = words.All(w => char.IsUpper(w[0]) || !char.IsLetter(w[0]));
56 |
57 | isAllUpper = sentenceArray.All(c => char.IsUpper(c) || !char.IsLetter(c));
58 |
59 | if (isAllUpper)
60 | return value.ToUpper();
61 |
62 | if (isEachUpper)
63 | {
64 | string capitalized = Regex.Replace(value, @"\b\w", charMatch => charMatch.Value.ToUpper());
65 | return capitalized;
66 | }
67 |
68 |
69 | char[] result = value.ToCharArray();
70 | result[0] = isFirstUpper
71 | ? char.ToUpper(result[0])
72 | : char.ToLower(result[0]);
73 | return new string(result);
74 | }, RegexOptions.IgnoreCase);
75 | }
76 |
77 | return temp;
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # LC-API
2 |
3 | [](https://github.com/steven4547466/LC-API/actions/workflows/build.yml)
4 | [](https://thunderstore.io/c/lethal-company/p/2018/LC_API)
5 | [](https://thunderstore.io/c/lethal-company/p/2018/LC_API)
6 |
7 | The definitive Lethal Company modding API. Includes some very useful features to make modding life easier.
8 |
9 | # For Developers
10 | If you want to use the API in your plugin, add the LC_API.dll as a project reference!
11 |
12 | ## Contributing
13 | If you wish to contribute to this project, you will need [unity netcode weaver](https://github.com/EvaisaDev/UnityNetcodeWeaver/releases)
14 | to implement custom networking properly. Follow their instructions to get NetcodeWeaver set-up for patching Lethal Company mods
15 | and keep note of the filepath where you chose to install it.
16 |
17 | Once you have forked and cloned the repository, you will need to create a file in the solution folder called `LC-API.csproj.user`
18 | to set paths to build dependencies. Here's a template for that file's contents:
19 | ```xml
20 |
21 |
22 |
23 | F:/SteamLibrary/steamapps/common/Lethal Company
24 | $(APPDATA)/r2modmanPlus-local/LethalCompany/profiles/Test LC API
25 | $(SolutionDir)NetcodeWeaver
26 |
27 |
28 |
30 |
31 |
35 |
36 |
37 |
38 | ```
39 |
40 | It is vital that you change the `NETCODE_PATCHER_DIR` property to the location of your local NetcodeWeaver installation.
41 |
42 | Ensure your Assembly CSharp is set `Publicize="true"` in the .csproj file to ensure it gets publicized.
43 |
44 | Once you have completed these steps, you will be able to properly build the solution.
45 |
46 | ## Making a PR
47 | Your [pull request](https://github.com/steven4547466/LC-API/pulls) should target the [dev branch](https://github.com/steven4547466/LC-API/tree/dev). This is because the `main` branch is reserved for tested features that are ready for release. Basically if someone were to clone the repo, they should be able to build `main` and use it without any fear of broken things.
48 |
49 | The `dev` branch, however, may contain untested features and is used to build release candidates. Before releases, the `dev` branch will be frozen and tested for issues, when it passes its testing then it will be merged into `main` and a release will be made. Pre-releases may come from the `dev` branch for release candidates and testing. These will be generally stable, but may still contain broken features before testing is done.
50 |
51 | Pull requests targeting the `main` branch will not be merged in most circumstances. Most merges to `main` will be directly from the frozen `dev` branch after testing.
52 |
53 | # Features
54 | AssetBundle loading - Put asset bundles in BepInEx > Bundles and load them using BundleAPI.BundleLoader.GetLoadedAsset
55 |
56 | ServerAPI - Utilities relating to the network and server. This includes:
57 |
58 | ModdedServer - Automatically alerts other users when you host a server that your server is modded.
59 | It also lets mod authors make their mods put users in special matchmaking where they can only play with other modded users
60 |
61 | Networking - Easily send data across the network to sync data between clients
62 |
63 | # Installation
64 |
65 | ## R2ModMan or Thunderstore Manager (highly recommended)
66 |
67 | ### R2ModMan
68 | 1. Go to the [thunderstore page](https://thunderstore.io/c/lethal-company/p/2018/LC_API)
69 | 2. Click `Install with Mod Manager`
70 |
71 | ### Thunderstore Manager
72 | (if the above doesn't work for you, open up the Thunderstore App to do the following)
73 | 1. Click `Get mods`/`Online` (whatever it happens to be called)
74 | 2. Search for LC API
75 | 3. Download it
76 |
77 | ## Manual
78 | 1. Go to the [thunderstore page](https://thunderstore.io/c/lethal-company/p/2018/LC_API)
79 | 2. Click `Manual Download`
80 | 3. Unzip files
81 | 4. Navigate to `2018-LC_API-VERSION/BepinEx/plugins` and copy the contents
82 | 5. Find your BepinEx installation's plugin folder, by default it would be in steamapps: `steamapps\common\Lethal Company\BepInEx\plugins`
83 | 6. Create a folder titled `2018-LC_API`
84 | 7. Paste the contents into that folder
85 |
86 | **DO NOT PLACE THE BUNDLES FOLDER IN THE PREMADE `BepinEx/Bundles` FOLDER!** It must be in the `2018-LC_API` folder.
87 |
88 | If you did all of this correctly, it should load properly.
89 |
90 | The resulting file structure should look like this:
91 | ```
92 | BepinEx
93 | ├───Bundles
94 | ├───cache
95 | ├───config
96 | ├───core
97 | ├───patchers
98 | └───plugins
99 | └───2018-LC_API
100 | ├───Bundles
101 | │ └───networking
102 | └───LC_API.dll
103 | ```
104 |
105 | # TODO
106 | - Picking up and dropping item events
107 | - Including `Player.StartGrabbingItem`, `Player.GrabbingItem`, `Player.GrabbedItem`, `Player.DroppingItem`, and `Player.DroppedItem`
108 | - The present-tense versions are cancellable, `StartGrabbingItem`, `GrabbingItem` and `DroppingItem`.
109 | - `StartGrabbingItem` exists for items that take time to pickup.
110 | - Using item events
111 | - For shovels, that's hitting
112 | - For boomboxes, that's activating music
113 | - etc. though this would all fall under one event probably just `Item.Using` or `Item.Activating`
114 | - Player begin and end moving events
115 | - Player crouch and jump events
116 | - Changing item event
117 | - Selling items event
118 | - Probably one for selling many items on the counter which will also call an event for each individual item being sold
119 | - Item spawning event
120 | - Enemy stuff is a bit further out, but here's some events that will be useful for them (as well as their past tense versions):
121 | - Spawning
122 | - Hurting
123 | - Dying
124 | - Attacking (when the enemy attacks a player)
125 | - There may be ones specific to some kind of enemy as well
126 | - `Player.EnteringFacility` and `Player.LeavingFacility`
127 | - With a bool for using fire escape
--------------------------------------------------------------------------------
/assets/bundles/networking:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/u-2018/LC-API/cfbc25acc43f0dd8eb5fd18546b404786b32267e/assets/bundles/networking
--------------------------------------------------------------------------------
/assets/icons/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/u-2018/LC-API/cfbc25acc43f0dd8eb5fd18546b404786b32267e/assets/icons/icon.png
--------------------------------------------------------------------------------
/assets/thunderstore.toml:
--------------------------------------------------------------------------------
1 | [config]
2 | schemaVersion = "0.0.1"
3 |
4 | [general]
5 | repository = "https://thunderstore.io"
6 |
7 | [package]
8 | namespace = "2018"
9 | name = "LC_API"
10 | description = "Multipurpose modding API for Lethal Company"
11 | websiteUrl = "https://github.com/steven4547466/LC-API"
12 | containsNsfwContent = false
13 |
14 | [package.dependencies]
15 | BepInEx-BepInExPack = "5.4.2100"
16 |
17 | [build]
18 | icon = "icons/icon.png"
19 | readme = "../README.md"
20 | outdir = "../dist"
21 |
22 | [[build.copy]]
23 | source = "../LC-API/bin/netstandard2.1/LC_API.dll"
24 | target = "BepInEx/plugins/LC_API.dll"
25 |
26 | [[build.copy]]
27 | source = "./bundles"
28 | target = "BepInEx/plugins/Bundles/"
29 |
30 | [[build.copy]]
31 | source = "../CHANGELOG.md"
32 | target = "CHANGELOG.md"
33 |
34 | [[build.copy]]
35 | source = "../LICENSE"
36 | target = "LICENSE"
37 |
38 | [publish]
39 | communities = ["lethal-company"]
40 |
41 | [publish.categories]
42 | lethal-company = ["bepinex", "libraries", "tools"]
--------------------------------------------------------------------------------