Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e738eab74c | ||
|
|
16fcce7c62 | ||
|
|
ee8d19dd8a | ||
|
|
2e687f0e8a | ||
|
|
1ce33139fe | ||
|
|
d8b9995924 | ||
|
|
7fa11aafe8 | ||
|
|
f2d4cc9a26 | ||
|
|
51fc824e0a | ||
|
|
0eff6cf70b | ||
|
|
66f3398455 | ||
|
|
f186743171 | ||
|
|
59f196262b | ||
|
|
a8af75721c | ||
|
|
76da33cc6d | ||
|
|
39055569ad | ||
|
|
3e744a9a74 |
+55
-7
@@ -1,9 +1,11 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using RedLoader;
|
||||
using RedLoader.Utils;
|
||||
using Sons.Wearable.Race;
|
||||
using SonsSdk;
|
||||
using SonsSdk.Attributes;
|
||||
using TheForest.Utils;
|
||||
using UnityEngine;
|
||||
|
||||
@@ -27,7 +29,7 @@ public class CharacterSelect : SonsMod
|
||||
_configPath = Path.Combine(LoaderEnvironment.UserDataDirectory, "CharacterSelect.txt");
|
||||
Load();
|
||||
SdkEvents.OnAfterSpawn.Subscribe(OnSpawned);
|
||||
RLog.Msg($"CharacterSelect loaded. Saved race: {_index} ({(PlayerRace.Race)_index}). F9 cycles.");
|
||||
RLog.Msg($"CharacterSelect loaded. Saved character: {_index} ({(PlayerRace.Race)_index}). Press F9 or type: character");
|
||||
}
|
||||
|
||||
private void OnSpawned()
|
||||
@@ -37,32 +39,78 @@ public class CharacterSelect : SonsMod
|
||||
|
||||
private void OnUpdate()
|
||||
{
|
||||
var system = LocalPlayer.RaceSystem;
|
||||
if (!system)
|
||||
if (!LocalPlayer.RaceSystem)
|
||||
return;
|
||||
|
||||
if (!_applied)
|
||||
{
|
||||
_applied = true;
|
||||
Apply(system, false);
|
||||
ApplyIndex(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Input.GetKeyDown(KeyCode.F9))
|
||||
{
|
||||
_index = (_index + 1) % RaceCount;
|
||||
Apply(system, true);
|
||||
ApplyIndex(true);
|
||||
Save();
|
||||
}
|
||||
}
|
||||
|
||||
private void Apply(PlayerRaceSystem system, bool announce)
|
||||
[DebugCommand("character")]
|
||||
private static void CharacterCommand(string args)
|
||||
{
|
||||
args = (args ?? string.Empty).Trim();
|
||||
|
||||
if (args.Length == 0)
|
||||
{
|
||||
ShowList();
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Equals("next", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_index = (_index + 1) % RaceCount;
|
||||
ApplyIndex(true);
|
||||
Save();
|
||||
return;
|
||||
}
|
||||
|
||||
if (int.TryParse(args, out var value) && value >= 0 && value < RaceCount)
|
||||
{
|
||||
_index = value;
|
||||
ApplyIndex(true);
|
||||
Save();
|
||||
return;
|
||||
}
|
||||
|
||||
SonsTools.ShowMessage("Usage: character [0-7] or character next", 5f);
|
||||
RLog.Msg("Usage: character [0-7] or character next");
|
||||
}
|
||||
|
||||
private static void ShowList()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine($"Current: {_index} ({(PlayerRace.Race)_index})");
|
||||
for (int i = 0; i < RaceCount; i++)
|
||||
sb.AppendLine($"{i}: {(PlayerRace.Race)i}");
|
||||
|
||||
var text = sb.ToString().TrimEnd();
|
||||
SonsTools.ShowMessage(text, 8f);
|
||||
RLog.Msg(text);
|
||||
}
|
||||
|
||||
private static void ApplyIndex(bool announce)
|
||||
{
|
||||
var system = LocalPlayer.RaceSystem;
|
||||
if (!system)
|
||||
return;
|
||||
|
||||
var race = (PlayerRace.Race)_index;
|
||||
system.ApplyRace(race);
|
||||
if (announce)
|
||||
SonsTools.ShowMessage($"{_index}: {race}");
|
||||
RLog.Msg($"Applied race {_index} ({race})");
|
||||
RLog.Msg($"Applied character {_index} ({race})");
|
||||
}
|
||||
|
||||
private static void Load()
|
||||
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$GameDir
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
function Write-Step {
|
||||
param([string]$Text)
|
||||
Write-Host ''
|
||||
Write-Host $Text -ForegroundColor Cyan
|
||||
}
|
||||
|
||||
function Find-GameDir {
|
||||
$steam = (Get-ItemProperty 'HKCU:\Software\Valve\Steam' -ErrorAction SilentlyContinue).SteamPath
|
||||
if (-not $steam) { return $null }
|
||||
$libs = @($steam)
|
||||
$vdf = Join-Path $steam 'steamapps\libraryfolders.vdf'
|
||||
if (Test-Path $vdf) {
|
||||
Select-String -Path $vdf -Pattern '"path"\s+"(.+?)"' -AllMatches |
|
||||
ForEach-Object { $_.Matches } |
|
||||
ForEach-Object { $libs += $_.Groups[1].Value.Replace('\\','\') }
|
||||
}
|
||||
foreach ($lib in $libs) {
|
||||
$candidate = Join-Path $lib 'steamapps\common\Sons Of The Forest'
|
||||
if (Test-Path (Join-Path $candidate 'SonsOfTheForest.exe')) { return $candidate }
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
function Get-ReleaseZip {
|
||||
param([string]$Repo, [string]$Pattern, [string]$OutFile)
|
||||
$release = Invoke-RestMethod "https://api.github.com/repos/$Repo/releases/latest" -Headers @{ 'User-Agent' = 'ps' }
|
||||
$asset = $release.assets | Where-Object { $_.name -match $Pattern } | Select-Object -First 1
|
||||
if (-not $asset) { throw "Could not find a download matching $Pattern in the latest $Repo release" }
|
||||
Write-Host " version $($release.tag_name)"
|
||||
Invoke-WebRequest -Uri $asset.browser_download_url -OutFile $OutFile -UseBasicParsing
|
||||
return $asset.name
|
||||
}
|
||||
|
||||
Write-Host ''
|
||||
Write-Host '====================================' -ForegroundColor Green
|
||||
Write-Host ' Character Select installer' -ForegroundColor Green
|
||||
Write-Host '====================================' -ForegroundColor Green
|
||||
|
||||
Write-Step 'Looking for Sons of the Forest'
|
||||
if (-not $GameDir) { $GameDir = Find-GameDir }
|
||||
if (-not $GameDir -or -not (Test-Path (Join-Path $GameDir 'SonsOfTheForest.exe'))) {
|
||||
Write-Host ' Could not find the game automatically.' -ForegroundColor Yellow
|
||||
Write-Host ' Find the folder that contains SonsOfTheForest.exe and paste the path below.'
|
||||
$GameDir = (Read-Host ' Game folder').Trim('"')
|
||||
if (-not (Test-Path (Join-Path $GameDir 'SonsOfTheForest.exe'))) {
|
||||
throw "SonsOfTheForest.exe was not found in $GameDir"
|
||||
}
|
||||
}
|
||||
Write-Host " found: $GameDir" -ForegroundColor Green
|
||||
|
||||
$needsFirstLaunch = $false
|
||||
|
||||
Write-Step 'Checking for RedLoader'
|
||||
if (Test-Path (Join-Path $GameDir '_RedLoader\net6\SonsSdk.dll')) {
|
||||
Write-Host ' already installed' -ForegroundColor Green
|
||||
}
|
||||
else {
|
||||
Write-Host ' not installed, downloading it now'
|
||||
$rlZip = Join-Path $env:TEMP 'RedLoader.zip'
|
||||
Get-ReleaseZip -Repo 'ToniMacaroni/RedLoader' -Pattern '^RedLoader.*\.zip$' -OutFile $rlZip | Out-Null
|
||||
|
||||
$rlStage = Join-Path $env:TEMP 'redloader-stage'
|
||||
if (Test-Path $rlStage) { Remove-Item $rlStage -Recurse -Force }
|
||||
Expand-Archive -Path $rlZip -DestinationPath $rlStage -Force
|
||||
Copy-Item -Path (Join-Path $rlStage '*') -Destination $GameDir -Recurse -Force
|
||||
Remove-Item $rlZip -Force -ErrorAction SilentlyContinue
|
||||
Remove-Item $rlStage -Recurse -Force -ErrorAction SilentlyContinue
|
||||
Write-Host ' installed' -ForegroundColor Green
|
||||
$needsFirstLaunch = $true
|
||||
}
|
||||
|
||||
Write-Step 'Downloading Character Select'
|
||||
$modZip = Join-Path $env:TEMP 'CharacterSelect.zip'
|
||||
Get-ReleaseZip -Repo 'JustinOros/sotf-character-select' -Pattern '^CharacterSelect\.zip$' -OutFile $modZip | Out-Null
|
||||
|
||||
Write-Step 'Installing Character Select'
|
||||
$modsDir = Join-Path $GameDir 'Mods'
|
||||
New-Item -ItemType Directory -Force -Path $modsDir | Out-Null
|
||||
Remove-Item (Join-Path $modsDir 'CharacterSelect.dll') -Force -ErrorAction SilentlyContinue
|
||||
Remove-Item (Join-Path $modsDir 'CharacterSelect') -Recurse -Force -ErrorAction SilentlyContinue
|
||||
Expand-Archive -Path $modZip -DestinationPath $modsDir -Force
|
||||
Remove-Item $modZip -Force -ErrorAction SilentlyContinue
|
||||
|
||||
if (-not (Test-Path (Join-Path $modsDir 'CharacterSelect.dll'))) {
|
||||
throw 'Something went wrong, CharacterSelect.dll is not in the Mods folder'
|
||||
}
|
||||
if (-not (Test-Path (Join-Path $modsDir 'CharacterSelect\manifest.json'))) {
|
||||
throw 'Something went wrong, manifest.json is not in the Mods\CharacterSelect folder'
|
||||
}
|
||||
Write-Host ' installed' -ForegroundColor Green
|
||||
|
||||
Write-Host ''
|
||||
Write-Host '====================================' -ForegroundColor Green
|
||||
Write-Host ' Done' -ForegroundColor Green
|
||||
Write-Host '====================================' -ForegroundColor Green
|
||||
Write-Host ''
|
||||
|
||||
if ($needsFirstLaunch) {
|
||||
Write-Host 'RedLoader was installed for the first time, so the next time you start'
|
||||
Write-Host 'the game it will take a few extra minutes to get ready. That is normal.'
|
||||
Write-Host ''
|
||||
}
|
||||
|
||||
Write-Host 'Start Sons of the Forest, then:'
|
||||
Write-Host ' 1. Check that MODS appears on the main menu and lists CharacterSelect'
|
||||
Write-Host ' 2. Load into a game'
|
||||
Write-Host ' 3. Press F9 to change your appearance'
|
||||
Write-Host ''
|
||||
@@ -1,34 +1,82 @@
|
||||
# Character Select
|
||||
|
||||
Pick your player appearance in Sons of the Forest and keep it across sessions.
|
||||
[](https://github.com/JustinOros/sotf-character-select/releases) [](https://github.com/JustinOros/sotf-character-select/releases/latest)
|
||||
|
||||
The game assigns one of eight player appearances at random when you join a server,
|
||||
and only remembers it if the world gets saved. This mod lets you choose, and
|
||||
reapplies your choice every time you spawn.
|
||||
Choose your player appearance in Sons of the Forest and keep it across sessions.
|
||||
|
||||
## Notes
|
||||
Sons of the Forest gives you a random appearance out of eight every time you join
|
||||
a server, and there is no way to choose. This mod lets you pick the one you want
|
||||
and automatically reapplies it every time your player spawns, so you stay the
|
||||
same character.
|
||||
|
||||
Client side only. Nothing needs to be installed on a dedicated server.
|
||||
## Multiplayer
|
||||
|
||||
Client side only. Nothing gets installed on a dedicated server.
|
||||
|
||||
Your appearance replicates through normal game networking, so other players see
|
||||
your choice even if they do not have the mod installed. They will not be able to
|
||||
change their own appearance without it.
|
||||
|
||||
## Requirements
|
||||
|
||||
RedLoader
|
||||
your choice even if they do not have the mod installed.
|
||||
|
||||
## Installation
|
||||
|
||||
1. Download the latest release
|
||||
2. Extract into your Sons of the Forest folder so you end up with:
|
||||
- `Mods/CharacterSelect.dll`
|
||||
- `Mods/CharacterSelect/manifest.json`
|
||||
### Step 1: Install RedLoader
|
||||
|
||||
[RedLoader](https://github.com/ToniMacaroni/RedLoader/releases/latest) is the mod
|
||||
loader for Sons of the Forest. The game cannot load any mod without it, including
|
||||
this one, so install it first. You only have to do this once.
|
||||
|
||||
1. Download `RedLoader.zip` from the
|
||||
[latest RedLoader release](https://github.com/ToniMacaroni/RedLoader/releases/latest)
|
||||
2. Extract it into your Sons of the Forest folder, the one containing
|
||||
`SonsOfTheForest.exe`, usually
|
||||
`C:\Program Files (x86)\Steam\steamapps\common\Sons Of The Forest`
|
||||
3. You should now see `version.dll` and a `_Redloader` folder sitting next to
|
||||
`SonsOfTheForest.exe`
|
||||
4. Launch the game once and wait until you reach the main menu. The first launch
|
||||
takes a few minutes while RedLoader processes the game files
|
||||
5. Check that `MODS` appears on the main menu, then quit
|
||||
|
||||
Ignore `RedManager.exe` if you see it. It is only a convenience installer and
|
||||
you do not need it.
|
||||
|
||||
### Step 2: Install Character Select
|
||||
|
||||
1. Download `CharacterSelect.zip` from the
|
||||
[latest release](https://github.com/JustinOros/sotf-character-select/releases/latest)
|
||||
2. Open the `Mods` folder inside your Sons of the Forest install, usually
|
||||
`C:\Program Files (x86)\Steam\steamapps\common\Sons Of The Forest\Mods`
|
||||
3. Extract the contents of the zip into that `Mods` folder
|
||||
|
||||
When you are done it should look like this:
|
||||
|
||||
```
|
||||
Sons Of The Forest/
|
||||
Mods/
|
||||
CharacterSelect.dll
|
||||
CharacterSelect/
|
||||
manifest.json
|
||||
```
|
||||
|
||||
## Updating
|
||||
|
||||
Run the installer again. It replaces the old version and keeps your saved
|
||||
appearance.
|
||||
|
||||
## Usage
|
||||
|
||||
Press F9 to cycle through the eight appearances. Your choice is saved to
|
||||
`_RedLoader/UserData/CharacterSelect.txt` and applied automatically on every spawn.
|
||||
Once you are in the world, press F9 to cycle through the eight appearances. The
|
||||
current one is shown on screen as you cycle.
|
||||
|
||||
If your keyboard has no F keys, or you would rather pick one directly, press F1
|
||||
to open the console and use the `character` command:
|
||||
|
||||
| Command | What it does |
|
||||
| --- | --- |
|
||||
| `character` | Shows your current appearance and the full list |
|
||||
| `character next` | Moves to the next appearance |
|
||||
| `character 3` | Switches straight to that appearance |
|
||||
|
||||
Your choice is saved to `UserData/CharacterSelect.txt` in your Sons of the Forest
|
||||
folder and applied automatically every time you spawn.
|
||||
|
||||
| Value | Name |
|
||||
| --- | --- |
|
||||
@@ -43,5 +91,28 @@ Press F9 to cycle through the eight appearances. Your choice is saved to
|
||||
|
||||
## Building
|
||||
|
||||
Requires the .NET 8 SDK and a RedLoader install that has been launched once, so
|
||||
the interop assemblies exist. The script offers to install both if they are missing.
|
||||
This section is only for those who want to modify the source or build it
|
||||
yourself.
|
||||
|
||||
Requires the [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) and a
|
||||
[RedLoader](https://github.com/ToniMacaroni/RedLoader/releases/latest) install
|
||||
that has been launched at least once, so the interop assemblies exist. The build
|
||||
script offers to install both if they are missing.
|
||||
|
||||
```
|
||||
.\build.ps1 -Install
|
||||
```
|
||||
|
||||
The game folder is found through Steam. Override it with `-GameDir "path"` or the
|
||||
`SOTF_PATH` environment variable.
|
||||
|
||||
To produce a release zip:
|
||||
|
||||
```
|
||||
.\build.ps1 -Package
|
||||
```
|
||||
|
||||
## Credits
|
||||
|
||||
Built against RedLoader by Toni Macaroni. The player race system was found by
|
||||
reading the source of ArmorMod by clairenheit.
|
||||
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
{
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/ToniMacaroni/RedLoader/main/MetadataSchema.json",
|
||||
"id": "CharacterSelect",
|
||||
"name": "CharacterSelect",
|
||||
"author": "JustinOros",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"description": "Pick your player appearance and keep it across sessions",
|
||||
"gameVersion": "1.0.0",
|
||||
"platform": "Client",
|
||||
|
||||
Reference in New Issue
Block a user