diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index ba8602508..ee56e460e 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -43,6 +43,7 @@ jobs:
mkdir -p staging
cp ${{ matrix.binary }} staging/
cp config.example.json staging/
+ cp config.reference.json staging/
cp -r www/ staging/www/
cp -r savedata/ staging/savedata/
# Schema is now embedded in the binary via server/migrations/
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index c67b44054..dc1206563 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -21,7 +21,7 @@ Thank you for your interest in contributing to Erupe! This guide will help you g
```
3. Set up the database following the [Installation guide](README.md#installation)
-4. Copy `config.example.json` to `config.json` and configure it
+4. Copy `config.example.json` to `config.json` and set your database password (see `config.reference.json` for all available options)
5. Install dependencies:
```bash
diff --git a/README.md b/README.md
index 6f4d3273f..099f9a543 100644
--- a/README.md
+++ b/README.md
@@ -156,7 +156,7 @@ Edit `config.json` before starting the server. The essential settings are:
| `BinPath` | Path to quest/scenario files |
| `Language` | `"en"` or `"jp"` |
-For the full configuration reference (gameplay multipliers, debug options, Discord integration, in-game commands, entrance/channel definitions), see [config.example.json](./config.example.json) and the [Erupe Wiki](https://github.com/Mezeporta/Erupe/wiki).
+`config.example.json` is intentionally minimal — all other settings have sane defaults built into the server. For the full configuration reference (gameplay multipliers, debug options, Discord integration, in-game commands, entrance/channel definitions), see [config.reference.json](./config.reference.json) and the [Erupe Wiki](https://github.com/Mezeporta/Erupe/wiki).
## Features
diff --git a/config.example.json b/config.example.json
index 0e1270e88..2b95d08b0 100644
--- a/config.example.json
+++ b/config.example.json
@@ -1,203 +1,5 @@
{
- "Host": "127.0.0.1",
- "BinPath": "bin",
- "Language": "en",
- "DisableSoftCrash": false,
- "HideLoginNotice": true,
- "LoginNotices": [
- "
Welcome to Erupe SU9.3!
Erupe is experimental software, we are not liable for any
issues caused by installing the software!
■Report bugs on Discord!
■Test everything!
■Don't talk to softlocking NPCs!
■Fork the code on GitHub!
Thank you to all of the contributors,
this wouldn't exist without you."
- ],
- "PatchServerManifest": "",
- "PatchServerFile": "",
- "Screenshots":{
- "Enabled":true,
- "Host":"127.0.0.1",
- "Port":8080,
- "OutputDir":"screenshots",
- "UploadQuality":100
- },
- "DeleteOnSaveCorruption": false,
- "ClientMode": "ZZ",
- "QuestCacheExpiry": 300,
- "CommandPrefix": "!",
- "AutoCreateAccount": true,
- "LoopDelay": 50,
- "DefaultCourses": [1, 23, 24],
- "EarthStatus": 0,
- "EarthID": 0,
- "EarthMonsters": [0, 0, 0, 0],
- "SaveDumps": {
- "Enabled": true,
- "RawEnabled": false,
- "OutputDir": "save-backups"
- },
- "Capture": {
- "Enabled": false,
- "OutputDir": "captures",
- "ExcludeOpcodes": [],
- "CaptureSign": true,
- "CaptureEntrance": true,
- "CaptureChannel": true
- },
- "DebugOptions": {
- "CleanDB": false,
- "MaxLauncherHR": false,
- "LogInboundMessages": false,
- "LogOutboundMessages": false,
- "LogMessageData": false,
- "MaxHexdumpLength": 256,
- "DivaOverride": 0,
- "FestaOverride": -1,
- "TournamentOverride": 0,
- "DisableTokenCheck": false,
- "QuestTools": false,
- "AutoQuestBackport": true,
- "ProxyPort": 0,
- "CapLink": {
- "Values": [51728, 20000, 51729, 1, 20000],
- "Key": "",
- "Host": "",
- "Port": 80
- }
- },
- "GameplayOptions": {
- "MinFeatureWeapons": 0,
- "MaxFeatureWeapons": 1,
- "MaximumNP": 100000,
- "MaximumRP": 50000,
- "MaximumFP": 120000,
- "TreasureHuntExpiry": 604800,
- "DisableLoginBoost": false,
- "DisableBoostTime": false,
- "BoostTimeDuration": 7200,
- "ClanMealDuration": 3600,
- "ClanMemberLimits": [[0, 30], [3, 40], [7, 50], [10, 60]],
- "BonusQuestAllowance": 3,
- "DailyQuestAllowance": 1,
- "LowLatencyRaviente": false,
- "RegularRavienteMaxPlayers": 8,
- "ViolentRavienteMaxPlayers": 8,
- "BerserkRavienteMaxPlayers": 32,
- "ExtremeRavienteMaxPlayers": 32,
- "SmallBerserkRavienteMaxPlayers": 8,
- "GUrgentRate": 0.10,
- "GCPMultiplier": 1.00,
- "HRPMultiplier": 1.00,
- "HRPMultiplierNC": 1.00,
- "SRPMultiplier": 1.00,
- "SRPMultiplierNC": 1.00,
- "GRPMultiplier": 1.00,
- "GRPMultiplierNC": 1.00,
- "GSRPMultiplier": 1.00,
- "GSRPMultiplierNC": 1.00,
- "ZennyMultiplier": 1.00,
- "ZennyMultiplierNC": 1.00,
- "GZennyMultiplier": 1.00,
- "GZennyMultiplierNC": 1.00,
- "MaterialMultiplier": 1.00,
- "MaterialMultiplierNC": 1.00,
- "GMaterialMultiplier": 1.00,
- "GMaterialMultiplierNC": 1.00,
- "ExtraCarves": 0,
- "ExtraCarvesNC": 0,
- "GExtraCarves": 0,
- "GExtraCarvesNC": 0,
- "DisableHunterNavi": false,
- "MezFesSoloTickets": 5,
- "MezFesGroupTickets": 1,
- "MezFesDuration": 172800,
- "MezFesSwitchMinigame": false,
- "EnableKaijiEvent": false,
- "EnableHiganjimaEvent": false,
- "EnableNierEvent": false,
- "DisableRoad": false,
- "SeasonOverride": false
- },
- "Discord": {
- "Enabled": false,
- "BotToken": "",
- "RelayChannel": {
- "Enabled": false,
- "MaxMessageLength": 183,
- "RelayChannelID": ""
- }
- },
- "Commands": [
- {
- "Name": "Help",
- "Enabled": true,
- "Description": "Show enabled chat commands",
- "Prefix": "help"
- }, {
- "Name": "Rights",
- "Enabled": false,
- "Description": "Overwrite the Rights value on your account",
- "Prefix": "rights"
- }, {
- "Name": "Raviente",
- "Enabled": true,
- "Description": "Various Raviente siege commands",
- "Prefix": "ravi"
- }, {
- "Name": "Teleport",
- "Enabled": false,
- "Description": "Teleport to specified coordinates",
- "Prefix": "tele"
- }, {
- "Name": "Reload",
- "Enabled": true,
- "Description": "Reload all players in your Land",
- "Prefix": "reload"
- }, {
- "Name": "KeyQuest",
- "Enabled": false,
- "Description": "Overwrite your HR Key Quest progress",
- "Prefix": "kqf"
- }, {
- "Name": "Course",
- "Enabled": true,
- "Description": "Toggle Courses on your account",
- "Prefix": "course"
- }, {
- "Name": "PSN",
- "Enabled": true,
- "Description": "Link a PlayStation Network ID to your account",
- "Prefix": "psn"
- }, {
- "Name": "Discord",
- "Enabled": true,
- "Description": "Generate a token to link your Discord account",
- "Prefix": "discord"
- }, {
- "Name": "Ban",
- "Enabled": false,
- "Description": "Ban/Temp Ban a user",
- "Prefix": "ban"
- }, {
- "Name": "Timer",
- "Enabled": true,
- "Description": "Toggle the Quest timer",
- "Prefix": "timer"
- }, {
- "Name": "Playtime",
- "Enabled": true,
- "Description": "Show your playtime",
- "Prefix": "playtime"
- }
- ],
- "Courses": [
- {"Name": "HunterLife", "Enabled": true},
- {"Name": "Extra", "Enabled": true},
- {"Name": "Premium", "Enabled": true},
- {"Name": "Assist", "Enabled": false},
- {"Name": "N", "Enabled": false},
- {"Name": "Hiden", "Enabled": false},
- {"Name": "HunterSupport", "Enabled": false},
- {"Name": "NBoost", "Enabled": false},
- {"Name": "NetCafe", "Enabled": true},
- {"Name": "HLRenewing", "Enabled": true},
- {"Name": "EXRenewing", "Enabled": true}
- ],
+ "Host": "",
"Database": {
"Host": "localhost",
"Port": 5432,
@@ -205,63 +7,6 @@
"Password": "",
"Database": "erupe"
},
- "Sign": {
- "Enabled": true,
- "Port": 53312
- },
- "API": {
- "Enabled": true,
- "Port": 8080,
- "PatchServer": "",
- "Banners": [],
- "Messages": [],
- "Links": [],
- "LandingPage": {
- "Enabled": true,
- "Title": "My Frontier Server",
- "Content": "Welcome! Download the client from our Discord.
"
- }
- },
- "Channel": {
- "Enabled": true
- },
- "Entrance": {
- "Enabled": true,
- "Port": 53310,
- "Entries": [
- {
- "Name": "Newbie", "Description": "", "IP": "", "Type": 3, "Recommended": 2, "AllowedClientFlags": 0,
- "Channels": [
- { "Port": 54001, "MaxPlayers": 100, "Enabled": true },
- { "Port": 54002, "MaxPlayers": 100, "Enabled": true }
- ]
- }, {
- "Name": "Normal", "Description": "", "IP": "", "Type": 1, "Recommended": 0, "AllowedClientFlags": 0,
- "Channels": [
- { "Port": 54003, "MaxPlayers": 100, "Enabled": true },
- { "Port": 54004, "MaxPlayers": 100, "Enabled": true }
- ]
- }, {
- "Name": "Cities", "Description": "", "IP": "", "Type": 2, "Recommended": 0, "AllowedClientFlags": 0,
- "Channels": [
- { "Port": 54005, "MaxPlayers": 100, "Enabled": true }
- ]
- }, {
- "Name": "Tavern", "Description": "", "IP": "", "Type": 4, "Recommended": 0, "AllowedClientFlags": 0,
- "Channels": [
- { "Port": 54006, "MaxPlayers": 100, "Enabled": true }
- ]
- }, {
- "Name": "Return", "Description": "", "IP": "", "Type": 5, "Recommended": 0, "AllowedClientFlags": 0,
- "Channels": [
- { "Port": 54007, "MaxPlayers": 100, "Enabled": true }
- ]
- }, {
- "Name": "MezFes", "Description": "", "IP": "", "Type": 6, "Recommended": 6, "AllowedClientFlags": 0,
- "Channels": [
- { "Port": 54008, "MaxPlayers": 100, "Enabled": true }
- ]
- }
- ]
- }
+ "ClientMode": "ZZ",
+ "AutoCreateAccount": true
}
diff --git a/config.reference.json b/config.reference.json
new file mode 100644
index 000000000..0e1270e88
--- /dev/null
+++ b/config.reference.json
@@ -0,0 +1,267 @@
+{
+ "Host": "127.0.0.1",
+ "BinPath": "bin",
+ "Language": "en",
+ "DisableSoftCrash": false,
+ "HideLoginNotice": true,
+ "LoginNotices": [
+ "Welcome to Erupe SU9.3!
Erupe is experimental software, we are not liable for any
issues caused by installing the software!
■Report bugs on Discord!
■Test everything!
■Don't talk to softlocking NPCs!
■Fork the code on GitHub!
Thank you to all of the contributors,
this wouldn't exist without you."
+ ],
+ "PatchServerManifest": "",
+ "PatchServerFile": "",
+ "Screenshots":{
+ "Enabled":true,
+ "Host":"127.0.0.1",
+ "Port":8080,
+ "OutputDir":"screenshots",
+ "UploadQuality":100
+ },
+ "DeleteOnSaveCorruption": false,
+ "ClientMode": "ZZ",
+ "QuestCacheExpiry": 300,
+ "CommandPrefix": "!",
+ "AutoCreateAccount": true,
+ "LoopDelay": 50,
+ "DefaultCourses": [1, 23, 24],
+ "EarthStatus": 0,
+ "EarthID": 0,
+ "EarthMonsters": [0, 0, 0, 0],
+ "SaveDumps": {
+ "Enabled": true,
+ "RawEnabled": false,
+ "OutputDir": "save-backups"
+ },
+ "Capture": {
+ "Enabled": false,
+ "OutputDir": "captures",
+ "ExcludeOpcodes": [],
+ "CaptureSign": true,
+ "CaptureEntrance": true,
+ "CaptureChannel": true
+ },
+ "DebugOptions": {
+ "CleanDB": false,
+ "MaxLauncherHR": false,
+ "LogInboundMessages": false,
+ "LogOutboundMessages": false,
+ "LogMessageData": false,
+ "MaxHexdumpLength": 256,
+ "DivaOverride": 0,
+ "FestaOverride": -1,
+ "TournamentOverride": 0,
+ "DisableTokenCheck": false,
+ "QuestTools": false,
+ "AutoQuestBackport": true,
+ "ProxyPort": 0,
+ "CapLink": {
+ "Values": [51728, 20000, 51729, 1, 20000],
+ "Key": "",
+ "Host": "",
+ "Port": 80
+ }
+ },
+ "GameplayOptions": {
+ "MinFeatureWeapons": 0,
+ "MaxFeatureWeapons": 1,
+ "MaximumNP": 100000,
+ "MaximumRP": 50000,
+ "MaximumFP": 120000,
+ "TreasureHuntExpiry": 604800,
+ "DisableLoginBoost": false,
+ "DisableBoostTime": false,
+ "BoostTimeDuration": 7200,
+ "ClanMealDuration": 3600,
+ "ClanMemberLimits": [[0, 30], [3, 40], [7, 50], [10, 60]],
+ "BonusQuestAllowance": 3,
+ "DailyQuestAllowance": 1,
+ "LowLatencyRaviente": false,
+ "RegularRavienteMaxPlayers": 8,
+ "ViolentRavienteMaxPlayers": 8,
+ "BerserkRavienteMaxPlayers": 32,
+ "ExtremeRavienteMaxPlayers": 32,
+ "SmallBerserkRavienteMaxPlayers": 8,
+ "GUrgentRate": 0.10,
+ "GCPMultiplier": 1.00,
+ "HRPMultiplier": 1.00,
+ "HRPMultiplierNC": 1.00,
+ "SRPMultiplier": 1.00,
+ "SRPMultiplierNC": 1.00,
+ "GRPMultiplier": 1.00,
+ "GRPMultiplierNC": 1.00,
+ "GSRPMultiplier": 1.00,
+ "GSRPMultiplierNC": 1.00,
+ "ZennyMultiplier": 1.00,
+ "ZennyMultiplierNC": 1.00,
+ "GZennyMultiplier": 1.00,
+ "GZennyMultiplierNC": 1.00,
+ "MaterialMultiplier": 1.00,
+ "MaterialMultiplierNC": 1.00,
+ "GMaterialMultiplier": 1.00,
+ "GMaterialMultiplierNC": 1.00,
+ "ExtraCarves": 0,
+ "ExtraCarvesNC": 0,
+ "GExtraCarves": 0,
+ "GExtraCarvesNC": 0,
+ "DisableHunterNavi": false,
+ "MezFesSoloTickets": 5,
+ "MezFesGroupTickets": 1,
+ "MezFesDuration": 172800,
+ "MezFesSwitchMinigame": false,
+ "EnableKaijiEvent": false,
+ "EnableHiganjimaEvent": false,
+ "EnableNierEvent": false,
+ "DisableRoad": false,
+ "SeasonOverride": false
+ },
+ "Discord": {
+ "Enabled": false,
+ "BotToken": "",
+ "RelayChannel": {
+ "Enabled": false,
+ "MaxMessageLength": 183,
+ "RelayChannelID": ""
+ }
+ },
+ "Commands": [
+ {
+ "Name": "Help",
+ "Enabled": true,
+ "Description": "Show enabled chat commands",
+ "Prefix": "help"
+ }, {
+ "Name": "Rights",
+ "Enabled": false,
+ "Description": "Overwrite the Rights value on your account",
+ "Prefix": "rights"
+ }, {
+ "Name": "Raviente",
+ "Enabled": true,
+ "Description": "Various Raviente siege commands",
+ "Prefix": "ravi"
+ }, {
+ "Name": "Teleport",
+ "Enabled": false,
+ "Description": "Teleport to specified coordinates",
+ "Prefix": "tele"
+ }, {
+ "Name": "Reload",
+ "Enabled": true,
+ "Description": "Reload all players in your Land",
+ "Prefix": "reload"
+ }, {
+ "Name": "KeyQuest",
+ "Enabled": false,
+ "Description": "Overwrite your HR Key Quest progress",
+ "Prefix": "kqf"
+ }, {
+ "Name": "Course",
+ "Enabled": true,
+ "Description": "Toggle Courses on your account",
+ "Prefix": "course"
+ }, {
+ "Name": "PSN",
+ "Enabled": true,
+ "Description": "Link a PlayStation Network ID to your account",
+ "Prefix": "psn"
+ }, {
+ "Name": "Discord",
+ "Enabled": true,
+ "Description": "Generate a token to link your Discord account",
+ "Prefix": "discord"
+ }, {
+ "Name": "Ban",
+ "Enabled": false,
+ "Description": "Ban/Temp Ban a user",
+ "Prefix": "ban"
+ }, {
+ "Name": "Timer",
+ "Enabled": true,
+ "Description": "Toggle the Quest timer",
+ "Prefix": "timer"
+ }, {
+ "Name": "Playtime",
+ "Enabled": true,
+ "Description": "Show your playtime",
+ "Prefix": "playtime"
+ }
+ ],
+ "Courses": [
+ {"Name": "HunterLife", "Enabled": true},
+ {"Name": "Extra", "Enabled": true},
+ {"Name": "Premium", "Enabled": true},
+ {"Name": "Assist", "Enabled": false},
+ {"Name": "N", "Enabled": false},
+ {"Name": "Hiden", "Enabled": false},
+ {"Name": "HunterSupport", "Enabled": false},
+ {"Name": "NBoost", "Enabled": false},
+ {"Name": "NetCafe", "Enabled": true},
+ {"Name": "HLRenewing", "Enabled": true},
+ {"Name": "EXRenewing", "Enabled": true}
+ ],
+ "Database": {
+ "Host": "localhost",
+ "Port": 5432,
+ "User": "postgres",
+ "Password": "",
+ "Database": "erupe"
+ },
+ "Sign": {
+ "Enabled": true,
+ "Port": 53312
+ },
+ "API": {
+ "Enabled": true,
+ "Port": 8080,
+ "PatchServer": "",
+ "Banners": [],
+ "Messages": [],
+ "Links": [],
+ "LandingPage": {
+ "Enabled": true,
+ "Title": "My Frontier Server",
+ "Content": "Welcome! Download the client from our Discord.
"
+ }
+ },
+ "Channel": {
+ "Enabled": true
+ },
+ "Entrance": {
+ "Enabled": true,
+ "Port": 53310,
+ "Entries": [
+ {
+ "Name": "Newbie", "Description": "", "IP": "", "Type": 3, "Recommended": 2, "AllowedClientFlags": 0,
+ "Channels": [
+ { "Port": 54001, "MaxPlayers": 100, "Enabled": true },
+ { "Port": 54002, "MaxPlayers": 100, "Enabled": true }
+ ]
+ }, {
+ "Name": "Normal", "Description": "", "IP": "", "Type": 1, "Recommended": 0, "AllowedClientFlags": 0,
+ "Channels": [
+ { "Port": 54003, "MaxPlayers": 100, "Enabled": true },
+ { "Port": 54004, "MaxPlayers": 100, "Enabled": true }
+ ]
+ }, {
+ "Name": "Cities", "Description": "", "IP": "", "Type": 2, "Recommended": 0, "AllowedClientFlags": 0,
+ "Channels": [
+ { "Port": 54005, "MaxPlayers": 100, "Enabled": true }
+ ]
+ }, {
+ "Name": "Tavern", "Description": "", "IP": "", "Type": 4, "Recommended": 0, "AllowedClientFlags": 0,
+ "Channels": [
+ { "Port": 54006, "MaxPlayers": 100, "Enabled": true }
+ ]
+ }, {
+ "Name": "Return", "Description": "", "IP": "", "Type": 5, "Recommended": 0, "AllowedClientFlags": 0,
+ "Channels": [
+ { "Port": 54007, "MaxPlayers": 100, "Enabled": true }
+ ]
+ }, {
+ "Name": "MezFes", "Description": "", "IP": "", "Type": 6, "Recommended": 6, "AllowedClientFlags": 0,
+ "Channels": [
+ { "Port": 54008, "MaxPlayers": 100, "Enabled": true }
+ ]
+ }
+ ]
+ }
+}
diff --git a/config/config.go b/config/config.go
index 4cc558cdc..cfff625f7 100644
--- a/config/config.go
+++ b/config/config.go
@@ -338,22 +338,195 @@ func getOutboundIP4() (net.IP, error) {
return localAddr.IP.To4(), nil
}
-// LoadConfig loads the given config toml file.
-func LoadConfig() (*Config, error) {
- viper.SetConfigName("config")
- viper.AddConfigPath(".")
+// registerDefaults sets all sane defaults via Viper so that a minimal
+// config.json (just database credentials) produces a fully working server.
+func registerDefaults() {
+ // Top-level settings
+ viper.SetDefault("BinPath", "bin")
+ viper.SetDefault("HideLoginNotice", true)
+ viper.SetDefault("LoginNotices", []string{
+ "Welcome to Erupe!",
+ })
+ viper.SetDefault("ClientMode", "ZZ")
+ viper.SetDefault("QuestCacheExpiry", 300)
+ viper.SetDefault("CommandPrefix", "!")
+ viper.SetDefault("AutoCreateAccount", true)
+ viper.SetDefault("LoopDelay", 50)
+ viper.SetDefault("DefaultCourses", []uint16{1, 23, 24})
+ viper.SetDefault("EarthMonsters", []int32{0, 0, 0, 0})
- viper.SetDefault("DevModeOptions.SaveDumps", SaveDumpOptions{
+ // SaveDumps
+ viper.SetDefault("SaveDumps", SaveDumpOptions{
Enabled: true,
OutputDir: "save-backups",
})
+
+ // Screenshots
+ viper.SetDefault("Screenshots", ScreenshotsOptions{
+ Enabled: true,
+ Host: "127.0.0.1",
+ Port: 8080,
+ OutputDir: "screenshots",
+ UploadQuality: 100,
+ })
+
+ // Capture
viper.SetDefault("Capture", CaptureOptions{
OutputDir: "captures",
CaptureSign: true,
CaptureEntrance: true,
CaptureChannel: true,
})
- viper.SetDefault("LoopDelay", 50)
+
+ // DebugOptions (dot-notation for per-field merge)
+ viper.SetDefault("DebugOptions.MaxHexdumpLength", 256)
+ viper.SetDefault("DebugOptions.FestaOverride", -1)
+ viper.SetDefault("DebugOptions.AutoQuestBackport", true)
+ viper.SetDefault("DebugOptions.CapLink", CapLinkOptions{
+ Values: []uint16{51728, 20000, 51729, 1, 20000},
+ Port: 80,
+ })
+
+ // GameplayOptions (dot-notation — critical to avoid zeroing multipliers)
+ viper.SetDefault("GameplayOptions.MaxFeatureWeapons", 1)
+ viper.SetDefault("GameplayOptions.MaximumNP", 100000)
+ viper.SetDefault("GameplayOptions.MaximumRP", uint16(50000))
+ viper.SetDefault("GameplayOptions.MaximumFP", uint32(120000))
+ viper.SetDefault("GameplayOptions.TreasureHuntExpiry", uint32(604800))
+ viper.SetDefault("GameplayOptions.BoostTimeDuration", 7200)
+ viper.SetDefault("GameplayOptions.ClanMealDuration", 3600)
+ viper.SetDefault("GameplayOptions.ClanMemberLimits", [][]uint8{{0, 30}, {3, 40}, {7, 50}, {10, 60}})
+ viper.SetDefault("GameplayOptions.BonusQuestAllowance", uint32(3))
+ viper.SetDefault("GameplayOptions.DailyQuestAllowance", uint32(1))
+ viper.SetDefault("GameplayOptions.RegularRavienteMaxPlayers", uint8(8))
+ viper.SetDefault("GameplayOptions.ViolentRavienteMaxPlayers", uint8(8))
+ viper.SetDefault("GameplayOptions.BerserkRavienteMaxPlayers", uint8(32))
+ viper.SetDefault("GameplayOptions.ExtremeRavienteMaxPlayers", uint8(32))
+ viper.SetDefault("GameplayOptions.SmallBerserkRavienteMaxPlayers", uint8(8))
+ viper.SetDefault("GameplayOptions.GUrgentRate", float64(0.10))
+ // All reward multipliers default to 1.0 — without this, Go's zero value
+ // (0.0) would zero out all quest rewards for minimal configs.
+ for _, key := range []string{
+ "GCPMultiplier", "HRPMultiplier", "HRPMultiplierNC",
+ "SRPMultiplier", "SRPMultiplierNC", "GRPMultiplier", "GRPMultiplierNC",
+ "GSRPMultiplier", "GSRPMultiplierNC", "ZennyMultiplier", "ZennyMultiplierNC",
+ "GZennyMultiplier", "GZennyMultiplierNC", "MaterialMultiplier", "MaterialMultiplierNC",
+ "GMaterialMultiplier", "GMaterialMultiplierNC",
+ } {
+ viper.SetDefault("GameplayOptions."+key, float64(1.0))
+ }
+ viper.SetDefault("GameplayOptions.MezFesSoloTickets", uint32(5))
+ viper.SetDefault("GameplayOptions.MezFesGroupTickets", uint32(1))
+ viper.SetDefault("GameplayOptions.MezFesDuration", 172800)
+
+ // Discord
+ viper.SetDefault("Discord.RelayChannel.MaxMessageLength", 183)
+
+ // Commands (whole-struct default — replaced entirely if user provides any)
+ viper.SetDefault("Commands", []Command{
+ {Name: "Help", Enabled: true, Description: "Show enabled chat commands", Prefix: "help"},
+ {Name: "Rights", Enabled: false, Description: "Overwrite the Rights value on your account", Prefix: "rights"},
+ {Name: "Raviente", Enabled: true, Description: "Various Raviente siege commands", Prefix: "ravi"},
+ {Name: "Teleport", Enabled: false, Description: "Teleport to specified coordinates", Prefix: "tele"},
+ {Name: "Reload", Enabled: true, Description: "Reload all players in your Land", Prefix: "reload"},
+ {Name: "KeyQuest", Enabled: false, Description: "Overwrite your HR Key Quest progress", Prefix: "kqf"},
+ {Name: "Course", Enabled: true, Description: "Toggle Courses on your account", Prefix: "course"},
+ {Name: "PSN", Enabled: true, Description: "Link a PlayStation Network ID to your account", Prefix: "psn"},
+ {Name: "Discord", Enabled: true, Description: "Generate a token to link your Discord account", Prefix: "discord"},
+ {Name: "Ban", Enabled: false, Description: "Ban/Temp Ban a user", Prefix: "ban"},
+ {Name: "Timer", Enabled: true, Description: "Toggle the Quest timer", Prefix: "timer"},
+ {Name: "Playtime", Enabled: true, Description: "Show your playtime", Prefix: "playtime"},
+ })
+
+ // Courses
+ viper.SetDefault("Courses", []Course{
+ {Name: "HunterLife", Enabled: true},
+ {Name: "Extra", Enabled: true},
+ {Name: "Premium", Enabled: true},
+ {Name: "Assist", Enabled: false},
+ {Name: "N", Enabled: false},
+ {Name: "Hiden", Enabled: false},
+ {Name: "HunterSupport", Enabled: false},
+ {Name: "NBoost", Enabled: false},
+ {Name: "NetCafe", Enabled: true},
+ {Name: "HLRenewing", Enabled: true},
+ {Name: "EXRenewing", Enabled: true},
+ })
+
+ // Database (Password deliberately has no default)
+ viper.SetDefault("Database.Host", "localhost")
+ viper.SetDefault("Database.Port", 5432)
+ viper.SetDefault("Database.User", "postgres")
+ viper.SetDefault("Database.Database", "erupe")
+
+ // Sign server
+ viper.SetDefault("Sign.Enabled", true)
+ viper.SetDefault("Sign.Port", 53312)
+
+ // API server
+ viper.SetDefault("API.Enabled", true)
+ viper.SetDefault("API.Port", 8080)
+ viper.SetDefault("API.LandingPage", LandingPage{
+ Enabled: true,
+ Title: "My Frontier Server",
+ Content: "Welcome! Server is running.
",
+ })
+
+ // Channel server
+ viper.SetDefault("Channel.Enabled", true)
+
+ // Entrance server
+ viper.SetDefault("Entrance.Enabled", true)
+ viper.SetDefault("Entrance.Port", uint16(53310))
+ boolTrue := true
+ viper.SetDefault("Entrance.Entries", []EntranceServerInfo{
+ {
+ Name: "Newbie", Type: 3, Recommended: 2,
+ Channels: []EntranceChannelInfo{
+ {Port: 54001, MaxPlayers: 100, Enabled: &boolTrue},
+ {Port: 54002, MaxPlayers: 100, Enabled: &boolTrue},
+ },
+ },
+ {
+ Name: "Normal", Type: 1,
+ Channels: []EntranceChannelInfo{
+ {Port: 54003, MaxPlayers: 100, Enabled: &boolTrue},
+ {Port: 54004, MaxPlayers: 100, Enabled: &boolTrue},
+ },
+ },
+ {
+ Name: "Cities", Type: 2,
+ Channels: []EntranceChannelInfo{
+ {Port: 54005, MaxPlayers: 100, Enabled: &boolTrue},
+ },
+ },
+ {
+ Name: "Tavern", Type: 4,
+ Channels: []EntranceChannelInfo{
+ {Port: 54006, MaxPlayers: 100, Enabled: &boolTrue},
+ },
+ },
+ {
+ Name: "Return", Type: 5,
+ Channels: []EntranceChannelInfo{
+ {Port: 54007, MaxPlayers: 100, Enabled: &boolTrue},
+ },
+ },
+ {
+ Name: "MezFes", Type: 6, Recommended: 6,
+ Channels: []EntranceChannelInfo{
+ {Port: 54008, MaxPlayers: 100, Enabled: &boolTrue},
+ },
+ },
+ })
+}
+
+// LoadConfig loads the given config toml file.
+func LoadConfig() (*Config, error) {
+ viper.SetConfigName("config")
+ viper.AddConfigPath(".")
+
+ registerDefaults()
err := viper.ReadInConfig()
if err != nil {
diff --git a/config/config_load_test.go b/config/config_load_test.go
index 1b493dbc4..d19359edc 100644
--- a/config/config_load_test.go
+++ b/config/config_load_test.go
@@ -2,8 +2,11 @@ package config
import (
"os"
+ "path/filepath"
"strings"
"testing"
+
+ "github.com/spf13/viper"
)
// TestLoadConfigNoFile tests LoadConfig when config file doesn't exist
@@ -497,3 +500,191 @@ func BenchmarkConfigCreation(b *testing.B) {
}
}
}
+
+// writeMinimalConfig writes a minimal config.json to dir and returns its path.
+func writeMinimalConfig(t *testing.T, dir, content string) {
+ t.Helper()
+ if err := os.WriteFile(filepath.Join(dir, "config.json"), []byte(content), 0644); err != nil {
+ t.Fatalf("writing config.json: %v", err)
+ }
+}
+
+// TestMinimalConfigDefaults verifies that a minimal config.json produces a fully
+// populated Config with sane defaults (multipliers not zero, entrance entries present, etc).
+func TestMinimalConfigDefaults(t *testing.T) {
+ viper.Reset()
+ dir := t.TempDir()
+ origDir, _ := os.Getwd()
+ defer func() { _ = os.Chdir(origDir) }()
+ if err := os.Chdir(dir); err != nil {
+ t.Fatal(err)
+ }
+
+ writeMinimalConfig(t, dir, `{
+ "Database": { "Password": "test" }
+ }`)
+
+ cfg, err := LoadConfig()
+ if err != nil {
+ t.Fatalf("LoadConfig() error: %v", err)
+ }
+
+ // Multipliers must be 1.0 (not Go's zero value 0.0)
+ multipliers := map[string]float32{
+ "HRPMultiplier": cfg.GameplayOptions.HRPMultiplier,
+ "SRPMultiplier": cfg.GameplayOptions.SRPMultiplier,
+ "GRPMultiplier": cfg.GameplayOptions.GRPMultiplier,
+ "ZennyMultiplier": cfg.GameplayOptions.ZennyMultiplier,
+ "MaterialMultiplier": cfg.GameplayOptions.MaterialMultiplier,
+ "GCPMultiplier": cfg.GameplayOptions.GCPMultiplier,
+ "GMaterialMultiplier": cfg.GameplayOptions.GMaterialMultiplier,
+ }
+ for name, val := range multipliers {
+ if val != 1.0 {
+ t.Errorf("%s = %v, want 1.0", name, val)
+ }
+ }
+
+ // Entrance entries should be present
+ if len(cfg.Entrance.Entries) != 6 {
+ t.Errorf("Entrance.Entries = %d, want 6", len(cfg.Entrance.Entries))
+ }
+
+ // Commands should be present
+ if len(cfg.Commands) != 12 {
+ t.Errorf("Commands = %d, want 12", len(cfg.Commands))
+ }
+
+ // Courses should be present
+ if len(cfg.Courses) != 11 {
+ t.Errorf("Courses = %d, want 11", len(cfg.Courses))
+ }
+
+ // Standard ports
+ if cfg.Sign.Port != 53312 {
+ t.Errorf("Sign.Port = %d, want 53312", cfg.Sign.Port)
+ }
+ if cfg.API.Port != 8080 {
+ t.Errorf("API.Port = %d, want 8080", cfg.API.Port)
+ }
+ if cfg.Entrance.Port != 53310 {
+ t.Errorf("Entrance.Port = %d, want 53310", cfg.Entrance.Port)
+ }
+
+ // Servers enabled by default
+ if !cfg.Sign.Enabled {
+ t.Error("Sign.Enabled should be true")
+ }
+ if !cfg.API.Enabled {
+ t.Error("API.Enabled should be true")
+ }
+ if !cfg.Channel.Enabled {
+ t.Error("Channel.Enabled should be true")
+ }
+ if !cfg.Entrance.Enabled {
+ t.Error("Entrance.Enabled should be true")
+ }
+
+ // Database defaults
+ if cfg.Database.Host != "localhost" {
+ t.Errorf("Database.Host = %q, want localhost", cfg.Database.Host)
+ }
+ if cfg.Database.Port != 5432 {
+ t.Errorf("Database.Port = %d, want 5432", cfg.Database.Port)
+ }
+
+ // ClientMode defaults to ZZ
+ if cfg.RealClientMode != ZZ {
+ t.Errorf("RealClientMode = %v, want ZZ", cfg.RealClientMode)
+ }
+
+ // BinPath default
+ if cfg.BinPath != "bin" {
+ t.Errorf("BinPath = %q, want bin", cfg.BinPath)
+ }
+
+ // Gameplay limits
+ if cfg.GameplayOptions.MaximumNP != 100000 {
+ t.Errorf("MaximumNP = %d, want 100000", cfg.GameplayOptions.MaximumNP)
+ }
+}
+
+// TestFullConfigBackwardCompat verifies that existing full configs still load correctly.
+func TestFullConfigBackwardCompat(t *testing.T) {
+ viper.Reset()
+ dir := t.TempDir()
+ origDir, _ := os.Getwd()
+ defer func() { _ = os.Chdir(origDir) }()
+ if err := os.Chdir(dir); err != nil {
+ t.Fatal(err)
+ }
+
+ // Read the reference config (the full original config.example.json).
+ // Look in the project root (one level up from config/).
+ refPath := filepath.Join(origDir, "..", "config.reference.json")
+ refData, err := os.ReadFile(refPath)
+ if err != nil {
+ t.Skipf("config.reference.json not found at %s, skipping backward compat test", refPath)
+ }
+ writeMinimalConfig(t, dir, string(refData))
+
+ cfg, err := LoadConfig()
+ if err != nil {
+ t.Fatalf("LoadConfig() with full config error: %v", err)
+ }
+
+ // Spot-check values from the reference config
+ if cfg.GameplayOptions.HRPMultiplier != 1.0 {
+ t.Errorf("HRPMultiplier = %v, want 1.0", cfg.GameplayOptions.HRPMultiplier)
+ }
+ if cfg.Sign.Port != 53312 {
+ t.Errorf("Sign.Port = %d, want 53312", cfg.Sign.Port)
+ }
+ if len(cfg.Entrance.Entries) != 6 {
+ t.Errorf("Entrance.Entries = %d, want 6", len(cfg.Entrance.Entries))
+ }
+ if len(cfg.Commands) != 12 {
+ t.Errorf("Commands = %d, want 12", len(cfg.Commands))
+ }
+ if cfg.GameplayOptions.MaximumNP != 100000 {
+ t.Errorf("MaximumNP = %d, want 100000", cfg.GameplayOptions.MaximumNP)
+ }
+}
+
+// TestSingleFieldOverride verifies that overriding one field in a dot-notation
+// section doesn't clobber other fields' defaults.
+func TestSingleFieldOverride(t *testing.T) {
+ viper.Reset()
+ dir := t.TempDir()
+ origDir, _ := os.Getwd()
+ defer func() { _ = os.Chdir(origDir) }()
+ if err := os.Chdir(dir); err != nil {
+ t.Fatal(err)
+ }
+
+ writeMinimalConfig(t, dir, `{
+ "Database": { "Password": "test" },
+ "GameplayOptions": { "HRPMultiplier": 2.0 }
+ }`)
+
+ cfg, err := LoadConfig()
+ if err != nil {
+ t.Fatalf("LoadConfig() error: %v", err)
+ }
+
+ // Overridden field
+ if cfg.GameplayOptions.HRPMultiplier != 2.0 {
+ t.Errorf("HRPMultiplier = %v, want 2.0", cfg.GameplayOptions.HRPMultiplier)
+ }
+
+ // Other multipliers should retain defaults
+ if cfg.GameplayOptions.SRPMultiplier != 1.0 {
+ t.Errorf("SRPMultiplier = %v, want 1.0 (should retain default)", cfg.GameplayOptions.SRPMultiplier)
+ }
+ if cfg.GameplayOptions.ZennyMultiplier != 1.0 {
+ t.Errorf("ZennyMultiplier = %v, want 1.0 (should retain default)", cfg.GameplayOptions.ZennyMultiplier)
+ }
+ if cfg.GameplayOptions.GCPMultiplier != 1.0 {
+ t.Errorf("GCPMultiplier = %v, want 1.0 (should retain default)", cfg.GameplayOptions.GCPMultiplier)
+ }
+}
diff --git a/docker/README.md b/docker/README.md
index bedd3faeb..c7208675f 100644
--- a/docker/README.md
+++ b/docker/README.md
@@ -8,7 +8,7 @@
cp config.example.json docker/config.json
```
- Edit `docker/config.json` — set `Database.Host` to `"db"` and match the password to `docker-compose.yml` (default: `password`).
+ Edit `docker/config.json` — set `Database.Host` to `"db"` and `Database.Password` to match `docker-compose.yml` (default: `password`). The example config is minimal; see `config.reference.json` for all available options.
2. Place your [quest/scenario files](https://files.catbox.moe/xf0l7w.7z) in `docker/bin/`.
diff --git a/server/channelserver/testhelpers_db.go b/server/channelserver/testhelpers_db.go
index 78d421c3b..4c4310deb 100644
--- a/server/channelserver/testhelpers_db.go
+++ b/server/channelserver/testhelpers_db.go
@@ -185,7 +185,7 @@ func CreateTestCharacter(t *testing.T, db *sqlx.DB, userID uint32, name string)
// Create minimal valid savedata (needs to be large enough for the game to parse)
// The name is at offset 88, and various game mode pointers extend up to ~147KB for ZZ mode
// We need at least 150KB to accommodate all possible pointer offsets
- saveData := make([]byte, 150000) // Large enough for all game modes
+ saveData := make([]byte, 150000) // Large enough for all game modes
copy(saveData[88:], append([]byte(name), 0x00)) // Name at offset 88 with null terminator
// Import the nullcomp package for compression
diff --git a/server/setup/wizard.go b/server/setup/wizard.go
index 96c9466b7..21f748af9 100644
--- a/server/setup/wizard.go
+++ b/server/setup/wizard.go
@@ -30,158 +30,13 @@ type FinishRequest struct {
AutoCreateAccount bool `json:"autoCreateAccount"`
}
-// buildDefaultConfig produces a config map matching config.example.json structure
-// with the user's values merged in.
+// buildDefaultConfig produces a minimal config map with only user-provided values.
+// All other settings are filled by Viper's registered defaults at load time.
func buildDefaultConfig(req FinishRequest) map[string]interface{} {
- config := map[string]interface{}{
- "Host": req.Host,
- "BinPath": "bin",
- "Language": "en",
- "DisableSoftCrash": false,
- "HideLoginNotice": true,
- "LoginNotices": []string{"Welcome to Erupe!"},
- "PatchServerManifest": "",
- "PatchServerFile": "",
- "DeleteOnSaveCorruption": false,
- "ClientMode": req.ClientMode,
- "QuestCacheExpiry": 300,
- "CommandPrefix": "!",
- "AutoCreateAccount": req.AutoCreateAccount,
- "LoopDelay": 50,
- "DefaultCourses": []int{1, 23, 24},
- "EarthStatus": 0,
- "EarthID": 0,
- "EarthMonsters": []int{0, 0, 0, 0},
- "Screenshots": map[string]interface{}{
- "Enabled": true,
- "Host": "127.0.0.1",
- "Port": 8080,
- "OutputDir": "screenshots",
- "UploadQuality": 100,
- },
- "SaveDumps": map[string]interface{}{
- "Enabled": true,
- "RawEnabled": false,
- "OutputDir": "save-backups",
- },
- "Capture": map[string]interface{}{
- "Enabled": false,
- "OutputDir": "captures",
- "ExcludeOpcodes": []int{},
- "CaptureSign": true,
- "CaptureEntrance": true,
- "CaptureChannel": true,
- },
- "DebugOptions": map[string]interface{}{
- "CleanDB": false,
- "MaxLauncherHR": false,
- "LogInboundMessages": false,
- "LogOutboundMessages": false,
- "LogMessageData": false,
- "MaxHexdumpLength": 256,
- "DivaOverride": 0,
- "FestaOverride": -1,
- "TournamentOverride": 0,
- "DisableTokenCheck": false,
- "QuestTools": false,
- "AutoQuestBackport": true,
- "ProxyPort": 0,
- "CapLink": map[string]interface{}{
- "Values": []int{51728, 20000, 51729, 1, 20000},
- "Key": "",
- "Host": "",
- "Port": 80,
- },
- },
- "GameplayOptions": map[string]interface{}{
- "MinFeatureWeapons": 0,
- "MaxFeatureWeapons": 1,
- "MaximumNP": 100000,
- "MaximumRP": 50000,
- "MaximumFP": 120000,
- "TreasureHuntExpiry": 604800,
- "DisableLoginBoost": false,
- "DisableBoostTime": false,
- "BoostTimeDuration": 7200,
- "ClanMealDuration": 3600,
- "ClanMemberLimits": [][]int{{0, 30}, {3, 40}, {7, 50}, {10, 60}},
- "BonusQuestAllowance": 3,
- "DailyQuestAllowance": 1,
- "LowLatencyRaviente": false,
- "RegularRavienteMaxPlayers": 8,
- "ViolentRavienteMaxPlayers": 8,
- "BerserkRavienteMaxPlayers": 32,
- "ExtremeRavienteMaxPlayers": 32,
- "SmallBerserkRavienteMaxPlayers": 8,
- "GUrgentRate": 0.10,
- "GCPMultiplier": 1.00,
- "HRPMultiplier": 1.00,
- "HRPMultiplierNC": 1.00,
- "SRPMultiplier": 1.00,
- "SRPMultiplierNC": 1.00,
- "GRPMultiplier": 1.00,
- "GRPMultiplierNC": 1.00,
- "GSRPMultiplier": 1.00,
- "GSRPMultiplierNC": 1.00,
- "ZennyMultiplier": 1.00,
- "ZennyMultiplierNC": 1.00,
- "GZennyMultiplier": 1.00,
- "GZennyMultiplierNC": 1.00,
- "MaterialMultiplier": 1.00,
- "MaterialMultiplierNC": 1.00,
- "GMaterialMultiplier": 1.00,
- "GMaterialMultiplierNC": 1.00,
- "ExtraCarves": 0,
- "ExtraCarvesNC": 0,
- "GExtraCarves": 0,
- "GExtraCarvesNC": 0,
- "DisableHunterNavi": false,
- "MezFesSoloTickets": 5,
- "MezFesGroupTickets": 1,
- "MezFesDuration": 172800,
- "MezFesSwitchMinigame": false,
- "EnableKaijiEvent": false,
- "EnableHiganjimaEvent": false,
- "EnableNierEvent": false,
- "DisableRoad": false,
- "SeasonOverride": false,
- },
- "Discord": map[string]interface{}{
- "Enabled": false,
- "BotToken": "",
- "RelayChannel": map[string]interface{}{
- "Enabled": false,
- "MaxMessageLength": 183,
- "RelayChannelID": "",
- },
- },
- "Commands": []map[string]interface{}{
- {"Name": "Help", "Enabled": true, "Description": "Show enabled chat commands", "Prefix": "help"},
- {"Name": "Rights", "Enabled": false, "Description": "Overwrite the Rights value on your account", "Prefix": "rights"},
- {"Name": "Raviente", "Enabled": true, "Description": "Various Raviente siege commands", "Prefix": "ravi"},
- {"Name": "Teleport", "Enabled": false, "Description": "Teleport to specified coordinates", "Prefix": "tele"},
- {"Name": "Reload", "Enabled": true, "Description": "Reload all players in your Land", "Prefix": "reload"},
- {"Name": "KeyQuest", "Enabled": false, "Description": "Overwrite your HR Key Quest progress", "Prefix": "kqf"},
- {"Name": "Course", "Enabled": true, "Description": "Toggle Courses on your account", "Prefix": "course"},
- {"Name": "PSN", "Enabled": true, "Description": "Link a PlayStation Network ID to your account", "Prefix": "psn"},
- {"Name": "Discord", "Enabled": true, "Description": "Generate a token to link your Discord account", "Prefix": "discord"},
- {"Name": "Ban", "Enabled": false, "Description": "Ban/Temp Ban a user", "Prefix": "ban"},
- {"Name": "Timer", "Enabled": true, "Description": "Toggle the Quest timer", "Prefix": "timer"},
- {"Name": "Playtime", "Enabled": true, "Description": "Show your playtime", "Prefix": "playtime"},
- },
- "Courses": []map[string]interface{}{
- {"Name": "HunterLife", "Enabled": true},
- {"Name": "Extra", "Enabled": true},
- {"Name": "Premium", "Enabled": true},
- {"Name": "Assist", "Enabled": false},
- {"Name": "N", "Enabled": false},
- {"Name": "Hiden", "Enabled": false},
- {"Name": "HunterSupport", "Enabled": false},
- {"Name": "NBoost", "Enabled": false},
- {"Name": "NetCafe", "Enabled": true},
- {"Name": "HLRenewing", "Enabled": true},
- {"Name": "EXRenewing", "Enabled": true},
- },
+ return map[string]interface{}{
+ "Host": req.Host,
+ "ClientMode": req.ClientMode,
+ "AutoCreateAccount": req.AutoCreateAccount,
"Database": map[string]interface{}{
"Host": req.DBHost,
"Port": req.DBPort,
@@ -189,73 +44,7 @@ func buildDefaultConfig(req FinishRequest) map[string]interface{} {
"Password": req.DBPassword,
"Database": req.DBName,
},
- "Sign": map[string]interface{}{
- "Enabled": true,
- "Port": 53312,
- },
- "API": map[string]interface{}{
- "Enabled": true,
- "Port": 8080,
- "PatchServer": "",
- "Banners": []interface{}{},
- "Messages": []interface{}{},
- "Links": []interface{}{},
- "LandingPage": map[string]interface{}{
- "Enabled": true,
- "Title": "My Frontier Server",
- "Content": "Welcome! Server is running.
",
- },
- },
- "Channel": map[string]interface{}{
- "Enabled": true,
- },
- "Entrance": map[string]interface{}{
- "Enabled": true,
- "Port": 53310,
- "Entries": []map[string]interface{}{
- {
- "Name": "Newbie", "Description": "", "IP": "", "Type": 3, "Recommended": 2, "AllowedClientFlags": 0,
- "Channels": []map[string]interface{}{
- {"Port": 54001, "MaxPlayers": 100, "Enabled": true},
- {"Port": 54002, "MaxPlayers": 100, "Enabled": true},
- },
- },
- {
- "Name": "Normal", "Description": "", "IP": "", "Type": 1, "Recommended": 0, "AllowedClientFlags": 0,
- "Channels": []map[string]interface{}{
- {"Port": 54003, "MaxPlayers": 100, "Enabled": true},
- {"Port": 54004, "MaxPlayers": 100, "Enabled": true},
- },
- },
- {
- "Name": "Cities", "Description": "", "IP": "", "Type": 2, "Recommended": 0, "AllowedClientFlags": 0,
- "Channels": []map[string]interface{}{
- {"Port": 54005, "MaxPlayers": 100, "Enabled": true},
- },
- },
- {
- "Name": "Tavern", "Description": "", "IP": "", "Type": 4, "Recommended": 0, "AllowedClientFlags": 0,
- "Channels": []map[string]interface{}{
- {"Port": 54006, "MaxPlayers": 100, "Enabled": true},
- },
- },
- {
- "Name": "Return", "Description": "", "IP": "", "Type": 5, "Recommended": 0, "AllowedClientFlags": 0,
- "Channels": []map[string]interface{}{
- {"Port": 54007, "MaxPlayers": 100, "Enabled": true},
- },
- },
- {
- "Name": "MezFes", "Description": "", "IP": "", "Type": 6, "Recommended": 6, "AllowedClientFlags": 0,
- "Channels": []map[string]interface{}{
- {"Port": 54008, "MaxPlayers": 100, "Enabled": true},
- },
- },
- },
- },
}
-
- return config
}
// writeConfig writes the config map to config.json with pretty formatting.
@@ -368,4 +157,3 @@ func createDatabase(host string, port int, user, password, dbName string) error
}
return nil
}
-
diff --git a/server/setup/wizard_test.go b/server/setup/wizard_test.go
index 9776cab99..d86b25f55 100644
--- a/server/setup/wizard_test.go
+++ b/server/setup/wizard_test.go
@@ -56,13 +56,9 @@ func TestBuildDefaultConfig(t *testing.T) {
t.Errorf("Database.Database = %v, want mydb", db["Database"])
}
- // Check that critical sections exist
- requiredKeys := []string{
- "Host", "BinPath", "Language", "ClientMode", "Database",
- "Sign", "API", "Channel", "Entrance", "DebugOptions",
- "GameplayOptions", "Discord", "Commands", "Courses",
- "SaveDumps", "Capture", "Screenshots",
- }
+ // Wizard config is now minimal — only user-provided values.
+ // Viper defaults fill the rest at load time.
+ requiredKeys := []string{"Host", "ClientMode", "AutoCreateAccount", "Database"}
for _, key := range requiredKeys {
if _, ok := cfg[key]; !ok {
t.Errorf("missing required key %q", key)
@@ -74,7 +70,7 @@ func TestBuildDefaultConfig(t *testing.T) {
if err != nil {
t.Fatalf("failed to marshal config: %v", err)
}
- if len(data) < 100 {
+ if len(data) < 50 {
t.Errorf("config JSON unexpectedly short: %d bytes", len(data))
}
}