Commit initial BossHealth, DamageNumbers, Horde, ThirdPerson mods
This commit is contained in:
1 parent
7bd44d1d99
commit
9c123865bc
176 files changed
+10367
No files matched your search
+140
@@ -0,0 +1,140 @@
|
||||
# Skopje modding (BepInEx 6 IL2CPP)
|
||||
|
||||
This game loads [BepInEx 6](https://docs.bepinex.dev/) plugins for Unity **IL2CPP**. Official hooks live in the interop assembly **`SkopjeAssembly`**, namespace `Skopje.Modding`.
|
||||
|
||||
## Requirements
|
||||
|
||||
| Item | Value |
|
||||
|------|--------|
|
||||
| Unity (game build) | 2022.3.62 |
|
||||
| Loader | BepInEx **6** for Unity **IL2CPP** |
|
||||
| Game assembly to reference | `SkopjeAssembly` (from a built game’s Il2CppInterop / dummy assemblies) |
|
||||
| Hook API | `Skopje.Modding.ModAPI` |
|
||||
|
||||
## Install layout
|
||||
|
||||
Install BepInEx next to the game executable (same folder as the `.exe`). Typical layout:
|
||||
|
||||
```text
|
||||
GameRoot/
|
||||
Game.exe
|
||||
winhttp.dll # Doorstop (Windows)
|
||||
doorstop_config.ini
|
||||
BepInEx/
|
||||
core/
|
||||
config/ # Local settings — NOT compared on join
|
||||
plugins/ # Your mod DLLs (+ shared data files)
|
||||
patchers/ # Optional patcher DLLs (+ shared data files)
|
||||
LogOutput.log
|
||||
```
|
||||
|
||||
Put gameplay plugins in `BepInEx/plugins/`. Shared override tables (JSON/CFG/etc.) must sit under `plugins/` or `patchers/` next to the DLL, **not** under `BepInEx/config`.
|
||||
|
||||
## Minimal plugin
|
||||
|
||||
Subscribe in `BasePlugin.Load` with `+=` (plugins load before the first scene):
|
||||
|
||||
```csharp
|
||||
using BepInEx;
|
||||
using BepInEx.Unity.IL2CPP;
|
||||
using Skopje.Modding;
|
||||
|
||||
[BepInPlugin("com.example.skopje.hello", "Hello Skopje", "1.0.0")]
|
||||
public class HelloPlugin : BasePlugin
|
||||
{
|
||||
public override void Load()
|
||||
{
|
||||
ModAPI.OnServerStarted += () => Log.LogInfo("Server started");
|
||||
ModAPI.OnGameModeStartedServer += gm => Log.LogInfo($"GameMode server: {gm}");
|
||||
ModAPI.OnModCheckFailed += msg => Log.LogError(msg);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
A full logging sample lives in [`Modding/SamplePlugin/`](../Modding/SamplePlugin/).
|
||||
|
||||
## ModAPI events
|
||||
|
||||
| Event | Where it runs | Notes |
|
||||
|-------|----------------|-------|
|
||||
| `OnServerStarted` / `OnServerStopped` | Dedicated server, listen host | FishNet local server state |
|
||||
| `OnClientStarted` / `OnClientStopped` | Pure client, listen host | FishNet local client state |
|
||||
| `OnPlayerStartedServer` / `OnPlayerStoppedServer` | Server / host | `SkopjePlayer` |
|
||||
| `OnPlayerStartedClient` / `OnPlayerStoppedClient` | Clients / host | `SkopjePlayer` |
|
||||
| `OnGameModeStartedServer` | Server / host | After `SkopjeGameMode.OnStartServer` |
|
||||
| `OnGameModeStartedClient` | Clients / host | After `SkopjeGameMode.OnStartClient` |
|
||||
| `OnGameStatusChanged` | Server/host and client-only | `NotStarted`, `Active`, `GameOver` |
|
||||
| `OnGameOver` | Same peers as status | When status becomes `GameOver` |
|
||||
| `OnNewDay` / `OnDay` / `OnNight` | Server and clients | Day/night cycle |
|
||||
| `OnModCheckFailed` | Local client | Join rejected; message lists path diffs |
|
||||
|
||||
A **listen host** raises both server and client events. Prefer **server** events for gameplay changes so dedicated servers work.
|
||||
|
||||
### ScriptableObject lookup helpers
|
||||
|
||||
```csharp
|
||||
if (ModAPI.TryGetGuidObject(123456, out GuidScriptableObject obj)) { /* ... */ }
|
||||
if (ModAPI.TryGetGuidObject<WeaponData>(123456, out var weapon)) { /* mutate fields */ }
|
||||
```
|
||||
|
||||
Call these after `OnServerStarted` or `OnGameModeStartedServer` so the GUID database is registered.
|
||||
|
||||
## ScriptableObject / item stat overrides
|
||||
|
||||
Items and many definitions are baked `ScriptableObject`s keyed by integer `Guid` in `GuidScriptableObjectDBManager`.
|
||||
|
||||
**Supported today**
|
||||
|
||||
- Mutate **public fields** on existing instances (`ItemData`, `WeaponData`, `UtilityData`, …) at runtime after the DB is ready.
|
||||
- Same plugin must run on the **dedicated server and every client** for gameplay stats.
|
||||
|
||||
**Not supported yet**
|
||||
|
||||
- Adding brand-new items/recipes as new assets.
|
||||
- Replacing loose `.asset` files on disk (IL2CPP does not load those as Mono would).
|
||||
|
||||
Example pattern (also commented in the sample plugin):
|
||||
|
||||
```csharp
|
||||
ModAPI.OnGameModeStartedServer += _ =>
|
||||
{
|
||||
if (ModAPI.TryGetGuidObject<WeaponData>(YOUR_GUID, out var weapon))
|
||||
{
|
||||
// weapon.SomePublicStatField = newValue;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Multiplayer contract
|
||||
|
||||
- FishNet is **server-authoritative**. Client-only stat changes do not define combat/inventory outcomes.
|
||||
- Gameplay mods must be **identical** on the host/dedicated server and every joining client.
|
||||
- Client-only mods (UI, camera, cosmetics) should subscribe only to **client** events and must not change server-owned results.
|
||||
- Put shared override data under `BepInEx/plugins/` (or `patchers/`) so the join check can see it. Local keybinds and BepInEx settings stay in `BepInEx/config/` and are **not** compared.
|
||||
|
||||
## Manifest join check
|
||||
|
||||
Before a remote client is authenticated, the game hashes:
|
||||
|
||||
- `BepInEx/plugins/**` and `BepInEx/patchers/**`
|
||||
- Extensions: `.dll`, `.json`, `.cfg`, `.txt`, `.xml`
|
||||
|
||||
Relative paths (forward slashes) + SHA-256 must match the server’s install. If `BepInEx` is missing on both sides, both look vanilla and match.
|
||||
|
||||
**Not hashed:** `BepInEx/core`, `BepInEx/config`, logs.
|
||||
|
||||
**Note:** A DLL present under `plugins/` still counts even if disabled in BepInEx config. Remove or rename the file to drop it from the check.
|
||||
|
||||
On mismatch the client is disconnected, `ModAPI.OnModCheckFailed` runs with a detail string, and a Doozy game event named **`ModCheckFailed`** is sent (UI can listen for that name).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Symptom | What to check |
|
||||
|---------|----------------|
|
||||
| Join rejected / disconnect | Log message and `OnModCheckFailed`; compare listed missing/extra/changed paths under `plugins`/`patchers` |
|
||||
| Plugin never loads | BepInEx 6 IL2CPP pack; `LogOutput.log`; Doorstop `winhttp.dll` next to the exe |
|
||||
| Overrides work solo but not in MP | Same plugin + sidecar files on **server and clients**; use server hooks for gameplay |
|
||||
| Different stats with “matching” mods | Shared data must live under `plugins/`, not only in `BepInEx/config/` |
|
||||
| Disabled mod still blocks join | File still hashed; remove the DLL/data file from `plugins` |
|
||||
|
||||
Logs: `BepInEx/LogOutput.log` and the Unity player log.
|
||||
Reference in new issue
Block a user