17 Commits
Author SHA1 Message Date
Justin Oros e738eab74c Add downloads and latest version badges 2026-09-28 12:42:22 -07:00
Justin Oros 16fcce7c62 Bump mod version to 1.1.0 2026-09-15 09:43:55 -07:00
Justin Oros ee8d19dd8a Updated version 2026-09-15 09:40:22 -07:00
Justin Oros 2e687f0e8a Document the F1 console and how to update 2026-09-15 09:30:30 -07:00
Justin Oros 1ce33139fe Document the character console command 2026-09-15 09:17:30 -07:00
Justin Oros d8b9995924 Add CHARACTER console command alongside the F9 keybind 2026-09-15 09:14:02 -07:00
Justin Oros 7fa11aafe8 Add installer script that sets up RedLoader and the mod 2026-09-13 12:18:37 -07:00
Justin Oros f2d4cc9a26 Write manifest.json without a byte order mark 2026-09-13 11:56:08 -07:00
Justin Oros 51fc824e0a Correct the config file path in the readme 2026-09-13 11:38:11 -07:00
Justin Oros 0eff6cf70b Note that the building section is optional 2026-09-13 11:35:22 -07:00
Justin Oros 66f3398455 Replace readme with full rewrite 2026-09-13 11:30:51 -07:00
Justin Oros f186743171 Restructure installation into two clear steps 2026-09-13 11:28:55 -07:00
Justin Oros 59f196262b Restructure installation into two clear steps with RedLoader explained 2026-09-13 11:25:52 -07:00
Justin Oros a8af75721c Split install instructions into separate RedLoader and mod sections 2026-09-13 11:22:22 -07:00
Justin Oros 76da33cc6d Add RedLoader install steps for people new to modding 2026-09-13 11:19:54 -07:00
Justin Oros 39055569ad Reword the intro to say the choice is reapplied on spawn 2026-09-13 11:17:38 -07:00
Justin Oros 3e744a9a74 Link the dotnet SDK and RedLoader in the building section 2026-09-13 11:15:31 -07:00
4 changed files with 263 additions and 29 deletions
+55 -7
View File
@@ -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
View File
@@ -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 ''
+91 -20
View File
@@ -1,34 +1,82 @@
# Character Select
Pick your player appearance in Sons of the Forest and keep it across sessions.
[![Downloads](https://img.shields.io/github/downloads/JustinOros/sotf-character-select/total?label=downloads)](https://github.com/JustinOros/sotf-character-select/releases) [![Latest](https://img.shields.io/github/v/release/JustinOros/sotf-character-select?label=latest)](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
View File
@@ -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",