Radicade Public Code

This commit is contained in:
JogmansonReal
2026-10-16 23:33:13 -07:00
commit 55175acdf6
832 changed files with 363261 additions and 0 deletions
+12
View File
@@ -0,0 +1,12 @@
namespace RadicadeServer.RecNet.Config
{
public class AmplitudeConfig
{
public string AmplitudeKey { get; set; } = "";
public bool UseRudderStack { get; set; } = false;
public string RudderStackKey { get; set; } = "";
public bool UseStatSig { get; set; } = false;
public string StatSigKey { get; set; } = "";
public int StatSigEnvironment { get; set; } = 0;
}
}
+130
View File
@@ -0,0 +1,130 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using RadicadeServer.DB;
using RadicadeServer.RecNet.Config;
namespace RadicadeServer.API.Config
{
[Route("api/config")]
public class ConfigAPI : Controller
{
RecDBContext db;
public ConfigAPI(RecDBContext context)
{
this.db = context;
}
[HttpGet("v1/amplitude")]
public IActionResult GetAmplitudeConfig()
{
AmplitudeConfig amplitudeConfig = new AmplitudeConfig
{
AmplitudeKey = "e1693a1003671058b6abc356c8ba8d59",
UseRudderStack = true,
RudderStackKey = "23NiJHIgu3koaGNCZIiuYvIQNCu",
UseStatSig = true,
StatSigKey = "client-SBZkOrjD3r1Cat3f3W8K6sBd11WKlXZXIlCWj6l4Aje",
StatSigEnvironment = 0,
};
return Ok(amplitudeConfig);
}
[HttpGet("v1/freegiftbutton")]
public IActionResult GetFreeGiftButtonConfig()
{
return Ok(true);
}
[HttpGet("v1/azurespeech")]
public IActionResult GetAzureSpeechConfig()
{
return Ok(new
{
Key = "dce8de5b297747d9b5bddcc7f19e8c5b",
Region = "eastus",
Enabled = false,
});
}
[HttpGet("v1/backtrace")]
public IActionResult GetBacktraceConfig()
{
return Ok(new
{
ReportBudget = 125,
FilterType = 0,
SampleRate = 1,
LogLineCount = 50,
CaptureNativeCrashes = 1,
AMRThresholdMS = 0,
MessageCount = 1000,
MessageRegex = "^.*$",
VersionRegex = ".*",
});
}
[HttpGet("v2")]
public async Task<ActionResult<MainConfig>> GetConfig()
{
MainConfig? config = await db.Config.FirstOrDefaultAsync();
if (config == null) return StatusCode(500);
bool didChanges = false;
if (string.IsNullOrWhiteSpace(config.CdnBaseUri) || config.CdnBaseUri.Contains("localhost", StringComparison.OrdinalIgnoreCase))
{
config.CdnBaseUri = DBConfig.NameServer.CDN;
didChanges = true;
}
if (string.IsNullOrWhiteSpace(config.ShareBaseUrl) || config.ShareBaseUrl.Contains("localhost", StringComparison.OrdinalIgnoreCase))
{
config.ShareBaseUrl = DBConfig.NameServer.WWW + "{0}";
didChanges = true;
}
if (config.ExpectingReload) {
didChanges = true;
config.SetAsActive();
config.ExpectingReload = false;
}
if (config.RefreshConfig())
didChanges = true;
if (didChanges)
await db.SaveChangesAsync();
return Ok(config?.ToActualConfig());
}
[HttpPost("~/statsigUserProperties")]
public IActionResult GetStatsigUserProperties()
{
return Ok(new { success = true });
}
[HttpGet("~/voice/config")]
[HttpGet("~/moderation/voice/config")]
public IActionResult GetVoiceConfig()
{
return Ok(new
{
KOLPDOKFMGH = "",
OKHMLNBOBNP = (object?)null,
BEGBMPFDPIE = (object?)null,
BNOIPAAFAMJ = new
{
_AOMMGOMJHPE = "",
_NPEJEBGENBC = "",
PEFLHAPIDIF = "",
ALOMNDOKBLH = ""
},
PAFBLJGNBLN = new string[0],
HAOCNOKPKDH = new string[0],
MBEGDKGEOMJ = false
});
}
}
}
+16
View File
@@ -0,0 +1,16 @@
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace RadicadeServer.RecNet.Config
{
public class ConfigVal
{
public ConfigVal() { }
public ConfigVal(string key, string val) { Key = key; Value = val; }
[Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
public string Key { get; set; } = null!;
public string Value { get; set; } = null!;
}
}
+91
View File
@@ -0,0 +1,91 @@
using Microsoft.AspNetCore.Mvc;
using RadicadeServer.DB;
using RadicadeServer.RecNet.Config;
using RadicadeServer.RecNet.Keepsakes;
namespace RadicadeServer.API.Config
{
[Route("api/gameconfigs/")]
public class GameConfigAPI : Controller
{
private static readonly Dictionary<string, string> Local2023Overrides = new()
{
["RRUI.UseRRUIHomeScreen"] = "true",
["USE_NEW_HOME_SCREEN"] = "true",
["new_watch_architecture_enabled"] = "true",
["watch_layer.new_watch_architecture_enabled"] = "true",
["HAS_SEEN_HOME_SCREEN_CHOICE"] = "true",
["AntiHile.QD"] = "false",
["Radicade.Invites.UseRRUIDialog"] = "true",
["Radicade.Invites.ShowOnlineFriendsBeforeJoining"] = "true",
["Lifecycle.PSF.FriendRoomInviteRateLimitingCount"] = "2147483647",
["AndroidMobile.enforceFileWatcher"] = "0",
["AndroidMobile.enforceHashCheck"] = "0",
["AndroidMobile.runFileWatcher"] = "0",
["AndroidMobile.runHashCheck"] = "0",
["Desktop_Oculus.enforceFileWatcher"] = "0",
["Desktop_Oculus.enforceHashCheck"] = "0",
["Desktop_Oculus.runFileWatcher"] = "0",
["Desktop_Oculus.runHashCheck"] = "0",
["Desktop_Steam.enforceFileWatcher"] = "0",
["Desktop_Steam.enforceHashCheck"] = "0",
["Desktop_Steam.runFileWatcher"] = "0",
["Desktop_Steam.runHashCheck"] = "0",
["OculusQuest.enforceFileWatcher"] = "0",
["OculusQuest.enforceHashCheck"] = "0",
["OculusQuest.runFileWatcher"] = "0",
["OculusQuest.runHashCheck"] = "0",
["Steam.enforceFileWatcher"] = "0",
["Steam.enforceHashCheck"] = "0",
["Steam.runFileWatcher"] = "0",
["Steam.runHashCheck"] = "0",
};
private readonly RecDBContext db;
public GameConfigAPI(RecDBContext context)
{
db = context;
}
private static IEnumerable<KeyValuePair<string, string>> KeepsakeConfigValues()
{
var enabled = KeepsakeFeature.Enabled;
yield return new("KeepsakeFeatureEnabled", enabled ? "true" : "false");
yield return new("KeepsakeRoomLimit", enabled ? "100" : "0");
yield return new("ProgressionEvents.EnableKeepsakeManager", enabled ? "true" : "false");
}
[HttpGet("v1/all")]
public IActionResult GetAllGameConfigs()
{
var overrides = new Dictionary<string, string>(Local2023Overrides);
foreach (var (key, value) in KeepsakeConfigValues()) overrides[key] = value;
var configs = db.GameConfig
.AsEnumerable()
.Select(x => new ConfigVal(
x.Key,
overrides.TryGetValue(x.Key, out var value)
? value
: SplitTestConfigs.FilterHardOverridesFor2023Client(x.Key, x.Value)))
.GroupBy(x => x.Key)
.Select(group =>
{
if (overrides.TryGetValue(group.Key, out var value))
return new ConfigVal(group.Key, value);
return group.Last();
})
.ToList();
foreach (var (key, value) in overrides)
{
if (!configs.Any(x => x.Key == key))
configs.Add(new ConfigVal(key, value));
}
return Ok(configs);
}
}
}
+29
View File
@@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore;
using RadicadeServer.DB;
using RadicadeServer.RecNet.Config;
namespace RadicadeServer.Utils
{
public static class GameConfigSeeder
{
public static async Task<ConfigVal?> FindAsync(RecDBContext db, string key)
=> db.GameConfig.Local.FirstOrDefault(row => row.Key == key)
?? await db.GameConfig.FirstOrDefaultAsync(row => row.Key == key);
public static async Task<ConfigVal> UpsertAsync(RecDBContext db, string key, string value)
{
var existing = await FindAsync(db, key);
if (existing == null)
{
var added = new ConfigVal(key, value);
await db.GameConfig.AddAsync(added);
return added;
}
if (string.IsNullOrWhiteSpace(existing.Value))
existing.Value = value;
return existing;
}
}
}
+84
View File
@@ -0,0 +1,84 @@
using Microsoft.EntityFrameworkCore;
using Rubidium2018.Utils.Objectives;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using RadicadeServer.Extensions;
using RadicadeServer.RecNet.Leveling;
using RadicadeServer.RecNet.Maintenance;
using RadicadeServer.RecNet.Progression.Objectives;
namespace RadicadeServer.RecNet.Config
{
public class MainConfig
{
[Key]
public string FUCKYOUMICROSOFT { get; set; } = null!;
public string ShareBaseUrl { get; set; } = null!;
public string CdnBaseUri { get; set; } = null!;
public string LevelProgressionMaps { get; set; } = null!;
public string ServerMaintenance { get; set; } = null!;
public string DailyObjectives { get; set; } = null!;
public string AutoMicMutingConfig { get; set; } = null!;
public string StorefrontConfig { get; set; } = null!;
public string RoomKeyConfig { get; set; } = null!;
public string RoomCurrencyConfig { get; set; } = null!;
public string GiftDropStoreItems { get; set; } = null!;
public bool ExpectingReload { get; set; }
public DateTime LastRefresh_DailyObjectives { get; set; } = DateTime.Now;
public static MainConfig? ActiveConfig = null;
internal void SetAsActive()
{
ActiveConfig = this;
}
}
public static class MainConfigExt
{
public static bool RefreshConfig(this MainConfig config, bool force = false)
{
if (config == null)
return false;
bool changed = false;
if (DateTime.Now >= config.LastRefresh_DailyObjectives || force)
{
changed = true;
Objective[][] dailys =
[
[RbPresetDailys.CreateRandomObjective(), RbPresetDailys.CreateRandomObjective(), RbPresetDailys.CreateRandomObjective()],
[RbPresetDailys.CreateRandomObjective(), RbPresetDailys.CreateRandomObjective(), RbPresetDailys.CreateRandomObjective()],
[RbPresetDailys.CreateRandomObjective(), RbPresetDailys.CreateRandomObjective(), RbPresetDailys.CreateRandomObjective()],
[RbPresetDailys.CreateRandomObjective(), RbPresetDailys.CreateRandomObjective(), RbPresetDailys.CreateRandomObjective()],
[RbPresetDailys.CreateRandomObjective(), RbPresetDailys.CreateRandomObjective(), RbPresetDailys.CreateRandomObjective()],
[RbPresetDailys.CreateRandomObjective(), RbPresetDailys.CreateRandomObjective(), RbPresetDailys.CreateRandomObjective()],
[RbPresetDailys.CreateRandomObjective(), RbPresetDailys.CreateRandomObjective(), RbPresetDailys.CreateRandomObjective()]
];
config.DailyObjectives = dailys.ToJson();
config.LastRefresh_DailyObjectives = DateTime.Now.AddDays(7);
}
return changed;
}
public static RealMainConfig ToActualConfig(this MainConfig config)
{
return new RealMainConfig
{
ShareBaseUrl = config.ShareBaseUrl ?? string.Empty,
CdnBaseUri = config.CdnBaseUri ?? string.Empty,
StorefrontConfig = config.StorefrontConfig.FromJson<StorefrontConfig>() ?? new StorefrontConfig(),
AutoMicMutingConfig = config.AutoMicMutingConfig.FromJson<MicMuting>() ?? new MicMuting(),
RoomCurrencyConfig = config.RoomCurrencyConfig.FromJson<RoomCurrencyConfig>() ?? new RoomCurrencyConfig(),
RoomKeyConfig = config.RoomKeyConfig.FromJson<RoomKeyConfig>() ?? new RoomKeyConfig(),
DailyObjectives = config.DailyObjectives.FromJson<Objective[][]>() ?? Array.Empty<Objective[]>(),
LevelProgressionMaps = config.LevelProgressionMaps.FromJson<LevelProgressionMap[]>() ?? Array.Empty<LevelProgressionMap>(),
ServerMaintenance = config.ServerMaintenance.FromJson<ServerMaintenance>() ?? new ServerMaintenance { StartsInMinutes = 0 }
};
}
}
}
+14
View File
@@ -0,0 +1,14 @@
namespace RadicadeServer.RecNet.Config
{
public class MicMuting
{
public float MicSpamVolumeThreshold { get; set; }
public float MicVolumeSampleInterval { get; set; }
public float MicVolumeSampleRollingWindowLength { get; set; }
public float MicSpamSamplePercentageForWarning { get; set; }
public float MicSpamSamplePercentageForWarningToEnd { get; set; }
public float MicSpamSamplePercentageForForceMute { get; set; }
public float MicSpamSamplePercentageForForceMuteToEnd { get; set; }
public float MicSpamWarningStateVolumeMultiplier { get; set; }
}
}
+354
View File
@@ -0,0 +1,354 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Text.Json;
using RadicadeServer.DB;
using RadicadeServer.Extensions;
using RadicadeServer.RecNet.Accounts.Local;
namespace RadicadeServer.API.Config
{
[Route("api/settings")]
[Route("playersettings")]
public class PlayerSettingsAPI : Controller
{
private readonly RecDBContext db;
public PlayerSettingsAPI(RecDBContext context)
{
db = context;
}
[HttpGet(""), Authorize]
[HttpGet("v2"), Authorize]
[HttpGet("playersettings"), Authorize]
public List<Setting> GetSettings()
{
var userId = User.GetUserId();
if (userId == null)
return new List<Setting>();
var settings = db.PlayerSettings.FirstOrDefault(x => x.AccountId == userId.Value);
var list = settings?.DeserializeSettings() ?? GetDefaultSettings();
ApplyDefaultSettings(list);
var player = db.Players.AsNoTracking().FirstOrDefault(x => x.AccountId == userId.Value);
if (player != null && string.IsNullOrWhiteSpace(player.Hashword))
{
SetOrReplace(list, "Recroom.AccountCreation.HasCreatedPassword", "False");
SetOrReplace(list, "Recroom.AccountCreation.HasFinished", "False");
SetOrReplace(list, "Recroom.OOBE", "0");
}
ApplyResponseMetadata(list, userId.Value);
if (settings == null)
{
db.PlayerSettings.Add(new PlayerSettings
{
AccountId = userId.Value,
Settings = list.ToJson()
});
db.SaveChanges();
}
return list;
}
private static void SetOrReplace(List<Setting> list, string key, string value)
{
var existing = list.FirstOrDefault(x => string.Equals(x.Key, key, StringComparison.OrdinalIgnoreCase));
if (existing != null)
existing.Value = value;
else
list.Add(new Setting { Key = key, Value = value });
}
private static void ApplyDefaultSettings(List<Setting> list)
{
bool needsOrientation = list.Any(setting =>
string.Equals(setting.Key, "Radicade.NeedsOrientation", StringComparison.OrdinalIgnoreCase) &&
(setting.Value == "1" || string.Equals(setting.Value, "true", StringComparison.OrdinalIgnoreCase)));
foreach (var setting in GetDefaultSettings())
{
if (needsOrientation &&
(string.Equals(setting.Key, "HAS_COMPLETED_ORIENTATION", StringComparison.OrdinalIgnoreCase) ||
string.Equals(setting.Key, "OrientationCompletionTime", StringComparison.OrdinalIgnoreCase)))
continue;
if (list.All(existing => !string.Equals(existing.Key, setting.Key, StringComparison.OrdinalIgnoreCase)))
list.Add(setting);
}
SetOrReplace(list, "SplitTestAssignedSegments", "1|{}");
}
private static void ApplyResponseMetadata(List<Setting> list, long accountId)
{
for (var i = 0; i < list.Count; i++)
{
list[i].Id = i + 1;
list[i].AccountId = accountId;
}
}
public const int InviteLinkLifetimeInMinutes = 60;
[HttpGet("partyinvite")]
[HttpGet("~/chat/settings/partyinvite")]
public IActionResult GetPartyInviteSettings() => Ok(new { InviteLinkLifetimeInMinutes });
[HttpPost("v2/set"), Authorize]
[HttpPost(""), Authorize]
[HttpPost("playersettings/set"), Authorize]
public async Task<IActionResult> SetSettingRequest()
{
if (User.GetUserId() == null) return Unauthorized();
var body = await ReadJsonBodyAsync();
var updates = body.HasValue ? ReadSettings(body.Value) : new List<Setting>();
if (updates.Count != 1) return BadRequest("Expected one setting with Key and Value.");
return await SetSetting(updates[0]);
}
[NonAction]
public async Task<IActionResult> SetSetting(Setting val)
{
var userId = User.GetUserId();
if (userId == null)
return Unauthorized();
if (val == null || string.IsNullOrWhiteSpace(val.Key) || val.Value == null)
return BadRequest("Expected a setting with Key and Value.");
var settings = db.PlayerSettings.FirstOrDefault(x => x.AccountId == userId.Value);
if (settings == null)
{
settings = new PlayerSettings
{
AccountId = userId.Value,
Settings = DBConfig.DefaultSettings2023
};
await db.PlayerSettings.AddAsync(settings);
}
var actualSettings = settings.DeserializeSettings();
if (!await CanSaveOnboardingMarkerAsync(userId.Value, val, actualSettings, new[] { val }))
return Ok(Array.Empty<object>());
SetOrReplace(actualSettings, val.Key, val.Value);
if (IsOnboardingFinishedMarker(val))
SetOrReplace(actualSettings, "Radicade.NeedsOrientation", "1");
settings.Settings = actualSettings.ToJson();
await db.SaveChangesAsync();
await RecordCodeOfConductAsync(userId.Value, new[] { val }, actualSettings);
return Ok(Array.Empty<object>());
}
[HttpPut(""), Authorize]
[HttpPut("v2"), Authorize]
[HttpPut("playersettings"), Authorize]
public async Task<IActionResult> PutSettings()
{
var userId = User.GetUserId();
if (userId == null)
return Unauthorized();
var body = await ReadJsonBodyAsync();
var updates = body.HasValue ? ReadSettings(body.Value) : new List<Setting>();
if (updates.Count == 0)
return BadRequest("No valid settings were supplied.");
var settings = db.PlayerSettings.FirstOrDefault(x => x.AccountId == userId.Value);
if (settings == null)
{
settings = new PlayerSettings
{
AccountId = userId.Value,
Settings = DBConfig.DefaultSettings2023
};
await db.PlayerSettings.AddAsync(settings);
}
var actualSettings = settings.DeserializeSettings();
foreach (var update in updates)
{
if (string.IsNullOrWhiteSpace(update.Key) || update.Value == null ||
!await CanSaveOnboardingMarkerAsync(userId.Value, update, actualSettings, updates))
continue;
SetOrReplace(actualSettings, update.Key, update.Value);
if (IsOnboardingFinishedMarker(update))
SetOrReplace(actualSettings, "Radicade.NeedsOrientation", "1");
}
settings.Settings = actualSettings.ToJson();
await db.SaveChangesAsync();
await RecordCodeOfConductAsync(userId.Value, updates, actualSettings);
ApplyResponseMetadata(actualSettings, userId.Value);
return Ok(actualSettings);
}
private async Task RecordCodeOfConductAsync(
long accountId,
IEnumerable<Setting> incomingSettings,
IEnumerable<Setting> savedSettings)
{
bool accepted = incomingSettings.Concat(savedSettings).Any(setting =>
string.Equals(setting.Key, "Recroom.AccountCreation.AcceptedCodeOfConduct", StringComparison.OrdinalIgnoreCase) &&
string.Equals(setting.Value, "true", StringComparison.OrdinalIgnoreCase));
if (!accepted) return;
var player = await db.Players.FirstOrDefaultAsync(x => x.AccountId == accountId);
if (player == null || player.CodeOfConductAccepted) return;
player.CodeOfConductAccepted = true;
await db.SaveChangesAsync();
}
private static bool IsOnboardingFinishedMarker(Setting setting) =>
(string.Equals(setting.Key, "Recroom.OOBE", StringComparison.OrdinalIgnoreCase) && setting.Value == "100") ||
(string.Equals(setting.Key, "Recroom.AccountCreation.HasFinished", StringComparison.OrdinalIgnoreCase) &&
string.Equals(setting.Value, "true", StringComparison.OrdinalIgnoreCase));
private async Task<bool> CanSaveOnboardingMarkerAsync(
long accountId,
Setting update,
IReadOnlyCollection<Setting> savedSettings,
IReadOnlyCollection<Setting> incomingSettings)
{
bool marksPasswordCreated =
string.Equals(update.Key, "Recroom.AccountCreation.HasCreatedPassword", StringComparison.OrdinalIgnoreCase) &&
string.Equals(update.Value, "true", StringComparison.OrdinalIgnoreCase);
if (!marksPasswordCreated && !IsOnboardingFinishedMarker(update))
return true;
var player = await db.Players.AsNoTracking().FirstOrDefaultAsync(x => x.AccountId == accountId);
if (player == null || string.IsNullOrWhiteSpace(player.Hashword))
{
return false;
}
if (marksPasswordCreated)
return true;
var allSettings = incomingSettings.Concat(savedSettings).ToList();
var profile = await db.Profiles.AsNoTracking().FirstOrDefaultAsync(x => x.accountId == accountId);
bool hasBirthday = player.Birthday.HasValue && player.Birthday.Value.Year > 1900;
bool hasChosenUsername = allSettings.Any(setting =>
string.Equals(setting.Key, "Recroom.AccountCreation.HasChosenUsername", StringComparison.OrdinalIgnoreCase) &&
string.Equals(setting.Value, "true", StringComparison.OrdinalIgnoreCase));
bool hasAcceptedCodeOfConduct = player.CodeOfConductAccepted || allSettings.Any(setting =>
string.Equals(setting.Key, "Recroom.AccountCreation.AcceptedCodeOfConduct", StringComparison.OrdinalIgnoreCase) &&
string.Equals(setting.Value, "true", StringComparison.OrdinalIgnoreCase));
if (profile == null || string.IsNullOrWhiteSpace(profile.username))
{
return false;
}
if (!hasChosenUsername)
{
return false;
}
if (!hasAcceptedCodeOfConduct)
{
return false;
}
return true;
}
private static List<Setting> GetDefaultSettings()
{
try
{
return DBConfig.DefaultSettings2023.FromJson<List<Setting>>() ?? new List<Setting>();
}
catch (Exception)
{
return new List<Setting>();
}
}
private async Task<JsonElement?> ReadJsonBodyAsync()
{
if (Request.Body == null)
return null;
if (Request.ContentLength == 0)
return null;
try
{
if (Request.HasFormContentType)
{
var form = await Request.ReadFormAsync();
return JsonSerializer.SerializeToElement(form.ToDictionary(pair => pair.Key, pair => pair.Value.ToString()));
}
using var doc = await JsonDocument.ParseAsync(Request.Body);
return doc.RootElement.Clone();
}
catch (JsonException)
{
return null;
}
}
private static List<Setting> ReadSettings(JsonElement body)
{
if (body.ValueKind == JsonValueKind.Array)
return ReadSettingsArray(body);
if (body.ValueKind != JsonValueKind.Object)
return new List<Setting>();
foreach (var key in new[] { "settings", "Settings", "playerSettings", "PlayerSettings" })
{
if (body.TryGetProperty(key, out var nested) && nested.ValueKind == JsonValueKind.Array)
return ReadSettingsArray(nested);
}
var setting = ReadSettingObject(body);
return setting == null ? new List<Setting>() : new List<Setting> { setting };
}
private static List<Setting> ReadSettingsArray(JsonElement array)
{
var settings = new List<Setting>();
foreach (var item in array.EnumerateArray())
{
var setting = ReadSettingObject(item);
if (setting != null)
settings.Add(setting);
}
return settings;
}
private static Setting? ReadSettingObject(JsonElement item)
{
if (item.ValueKind != JsonValueKind.Object)
return null;
var key = ReadString(item, "Key", "key", "Name", "name");
var value = ReadString(item, "Value", "value");
if (string.IsNullOrWhiteSpace(key) || value == null)
return null;
return new Setting { Key = key, Value = value };
}
private static string? ReadString(JsonElement item, params string[] names)
{
foreach (var name in names)
{
if (!item.TryGetProperty(name, out var value))
continue;
return value.ValueKind switch
{
JsonValueKind.String => value.GetString(),
JsonValueKind.Number or JsonValueKind.True or JsonValueKind.False => value.ToString(),
_ => null,
};
}
return null;
}
}
}
+19
View File
@@ -0,0 +1,19 @@
using RadicadeServer.RecNet.Leveling;
using RadicadeServer.RecNet.Maintenance;
using RadicadeServer.RecNet.Progression.Objectives;
namespace RadicadeServer.RecNet.Config
{
public class RealMainConfig
{
public string ShareBaseUrl { get; set; } = null!;
public string CdnBaseUri { get; set; } = null!;
public LevelProgressionMap[] LevelProgressionMaps { get; set; } = null!;
public ServerMaintenance ServerMaintenance { get; set; } = null!;
public Objective[][] DailyObjectives { get; set; } = null!;
public MicMuting AutoMicMutingConfig { get; set; } = null!;
public StorefrontConfig StorefrontConfig { get; set; } = null!;
public RoomKeyConfig RoomKeyConfig { get; set; } = null!;
public RoomCurrencyConfig RoomCurrencyConfig { get; set; } = null!;
}
}
+7
View File
@@ -0,0 +1,7 @@
namespace RadicadeServer.RecNet.Config
{
public class RoomCurrencyConfig
{
public float AwardCurrencyCooldownSeconds { get; set; }
}
}
+7
View File
@@ -0,0 +1,7 @@
namespace RadicadeServer.RecNet.Config
{
public class RoomKeyConfig
{
public int MaxKeysPerRoom { get; set; }
}
}
+16
View File
@@ -0,0 +1,16 @@
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace RadicadeServer.RecNet.Config
{
public class ServerConfigVal
{
public ServerConfigVal() { }
public ServerConfigVal(string key, string val) { Key = key; Value = val; }
[Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
public string Key { get; set; } = null!;
public string Value { get; set; } = null!;
}
}
+54
View File
@@ -0,0 +1,54 @@
namespace RadicadeServer.RecNet.Config
{
public static class SplitTestConfigs
{
public const string HardOverrides =
"IOSAppPermissions_2019_10_02=Immediate;GottaGoFastNUX_2020_09_04=Off;RecNetImageCacheRefCount_2020_09_18=On;Curated_Rooms_2020_08_06=On;PlayMenuRRUI_2021_01_11=On;PlayMenuRRUI_HotRoomsCarousel_2021_02_09=On;PlayMenuRRUI_CarouselOrdering_2021_02_09=PlayMenuCarouselOrdering_1;CreationCommands_2021_02_16=Off;RecRoomPlus_DormMirrorButton_2021_03_09=ButtonOn;CurrentContestWinnersCarouselVisibility_2021_04_15=Off;MutualFriends_2021_04_22=On;PlayMenuRRUI_DefaultTab_2021_05_13=Highlight;DefaultNewPlayersToPlayMenu_2021_05_24=On;ShowNewRRPlusIcon_2021_06_01=NewIcon;ParallelNetworkingInitialRoomLoad_2021_06_28=ParallelLoad;ShowNewRRPlusBenefitsPage_2021_06_14=OldPage;WatchStoreHomepageLayout_2021_06_14=OldStoreLayout;NotificationPermissionsExplanationText_2021_06_25=Control;PlayMenuRRUI_TopCreatorsCarousel_2021_09_01=Off;UseSets_2021_08_06=UseSets;RoomSimilarities_2021_10_29=On;ProfileOnClick_2021_04_30=On;RoomRecommendations_2020_05_06=On;FriendImportLogin_2021_09_23=On;RudderStackAnalyticsDestinations_2022_02_10=RudderStackOnly;ObjectModel_EnableForRoom_2022_07_01=Disallowed;PlayMenuCarouselOrdering_2021_11_01=PlayMenuCarouselOrdering_1;PlayMenuBanner_2021_11_01=Off;";
public const string TestsAvailable =
"RoomRecommendations_2020_05_06=On,Off;Curated_Rooms_2020_08_06=Off,On;RecNetImageCacheRefCount_2020_09_18=On,Off;GottaGoFastNUX_2020_09_04=Off,On;Curated_Rooms_Tab_Placement_2020_09_22=First,Second;PlayMenuRRUI_2021_01_11=Off,On;PlayMenuRRUI_HotRoomsCarousel_2021_02_09=Off,On;PlayMenuRRUI_CarouselOrdering_2021_02_09=PlayMenuCarouselOrdering_1,PlayMenuCarouselOrdering_2,PlayMenuCarouselOrdering_3;CreationCommands_2021_02_16=Off,On;RecRoomPlus_DormMirrorButton_2021_03_09=ButtonOn,ButtonOff;CurrentContestWinnersCarouselVisibility_2021_04_15=Off,On;MutualFriends_2021_04_22=Off,On;ProfileOnClick_2021_04_30=Off,On;PlayMenuRRUI_DefaultTab_2021_05_13=Highlight,RRO,Hot;DefaultNewPlayersToPlayMenu_2021_05_24=Off,On;ShowNewRRPlusIcon_2021_06_01=OldIcon,NewIcon;ShowNewRRPlusBenefitsPage_2021_06_14=OldPage,NewPage;WatchStoreHomepageLayout_2021_06_14=OldStoreLayout,NewStoreLayout;NotificationPermissionsExplanationText_2021_06_25=Control,SpecificValueText;ParallelNetworkingInitialRoomLoad_2021_06_28=SequentialLoad,ParallelLoad;UseSets_2021_08_06=IgnoreSets,UseSets;PlayMenuRRUI_TopCreatorsCarousel_2021_09_01=Off,On;FriendImportLogin_2021_09_23=Off,On;Purchase_Campaigns_AB_2021_09_13=Purchase_Campaign_AB_A,Purchase_Campaign_AB_B;Purchase_Campaigns_ABCD_2021_09_13=Purchase_Campaign_ABCD_A,Purchase_Campaign_ABCD_B,Purchase_Campaign_ABCD_C,Purchase_Campaign_ABCD_D;RoomSimilarities_2021_10_29=On,Off;PlayMenuBanner_2021_11_01=Off,On;PlayMenuCarouselOrdering_2021_11_01=PlayMenuCarouselOrdering_1,PlayMenuCarouselOrdering_2,PlayMenuCarouselOrdering_3;RudderStackAnalyticsDestinations_2022_02_10=Disabled,RudderStackOnly,Both;ObjectModel_EnableForRoom_2022_07_01=Disallowed,Allowed;";
public const string SegmentProbabilities =
"{\"RoomRecommendations_2020_05_06\":[{\"SegmentName\":\"On\",\"Probability\":0.25},{\"SegmentName\":\"Off\",\"Probability\":0.75}],\"CurrentContestWinnersCarouselVisibility_2021_04_15\":[{\"SegmentName\":\"Off\",\"Probability\":1.0},{\"SegmentName\":\"On\",\"Probability\":0.0}],\"PlayMenuBanner_2021_11_01\":[{\"SegmentName\":\"Off\",\"Probability\":0.5},{\"SegmentName\":\"On\",\"Probability\":0.5}],\"FriendImportLogin_2021_09_23\":[{\"SegmentName\":\"Off\",\"Probability\":0.5},{\"SegmentName\":\"On\",\"Probability\":0.5}],\"PlayMenuCarouselOrdering_2021_11_01\":[{\"SegmentName\":\"PlayMenuCarouselOrdering_1\",\"Probability\":0.33},{\"SegmentName\":\"PlayMenuCarouselOrdering_2\",\"Probability\":0.33},{\"SegmentName\":\"PlayMenuCarouselOrdering_3\",\"Probability\":0.34}]}";
public static readonly IReadOnlyDictionary<string, IReadOnlyList<string>> Client2023Catalog =
new Dictionary<string, IReadOnlyList<string>>(StringComparer.Ordinal)
{
["Example_Test_2020_06_06"] = new[] { "On", "Off" },
["RoomRecommendations_2020_05_06"] = new[] { "On", "Off" },
["RoomRecommendationsType_2020_08_14"] = new[] { "June22", "Aug14JuniorSpecificModel", "Aug14JuniorAccessibleModelForAll", "Aug14MinVisitors350", "Aug14MinVisitors3500", "Aug14MinVisitors35000" },
["GottaGoFastNUX_2020_09_04"] = new[] { "Off", "On" },
["PlayMenuRRUI_HotRoomsCarousel_2021_02_09"] = new[] { "Off", "On" },
["PlayMenuRRUI_CarouselOrdering_2021_02_09"] = new[] { "PlayMenuCarouselOrdering_1", "PlayMenuCarouselOrdering_2", "PlayMenuCarouselOrdering_3" },
["MutualFriends_2021_04_22"] = new[] { "Off", "On" },
["CurrentContestWinnersCarouselVisibility_2021_04_15"] = new[] { "Off", "On" },
["PlayMenuRRUI_DefaultTab_2021_05_13"] = new[] { "Highlight", "RRO", "Hot" },
["ProfileOnClick_2021_04_30"] = new[] { "Off", "On" },
["DefaultNewPlayersToPlayMenu_2021_05_24"] = new[] { "Off", "On" },
["ParallelNetworkingInitialRoomLoad_2021_06_28"] = new[] { "SequentialLoad", "ParallelLoad" },
["PlayMenuRRUI_TopCreatorsCarousel_2021_09_01"] = new[] { "Off", "On" },
["FriendImportLogin_2021_09_23"] = new[] { "Off", "On" },
["RoomSimilarities_2021_10_29"] = new[] { "On", "Off" },
["PlayMenuBanner_2021_11_01"] = new[] { "Off", "On" },
["PlayMenuCarouselOrdering_2021_11_01"] = new[] { "PlayMenuCarouselOrdering_1", "PlayMenuCarouselOrdering_2", "PlayMenuCarouselOrdering_3" },
["RudderStackAnalyticsDestinations_2022_02_10"] = new[] { "Disabled", "RudderStackOnly", "Both" },
["ObjectModel_EnableForRoom_2022_07_01"] = new[] { "Disallowed", "Allowed" },
};
public static string FilterHardOverridesFor2023Client(string key, string value)
{
if (key != "splitTestHardOverrides" || string.IsNullOrWhiteSpace(value))
return value;
var kept = value.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.Select(entry => entry.Split('=', 2))
.Where(pair => pair.Length == 2
&& Client2023Catalog.TryGetValue(pair[0], out var segments)
&& segments.Contains(pair[1], StringComparer.Ordinal))
.Select(pair => pair[0] + "=" + pair[1]);
var keptList = kept.ToList();
return keptList.Count > 0 ? string.Join(";", keptList) + ";" : string.Empty;
}
}
}
+7
View File
@@ -0,0 +1,7 @@
namespace RadicadeServer.RecNet.Config
{
public class StorefrontConfig
{
public int MinPlayerLevelForGifting { get; set; }
}
}
+23
View File
@@ -0,0 +1,23 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using RadicadeServer.DB;
using RadicadeServer.Extensions;
using RadicadeServer.Utils;
namespace RadicadeServer.API.Config
{
[Route("api/voicechat")]
public class VoiceChatRestrictionAPI(RecDBContext db) : Controller
{
[HttpGet("restriction"), Authorize]
public async Task<IActionResult> GetRestriction()
{
var userId = User.GetUserId();
if (userId == null)
return Unauthorized(new { restricted = false });
var restriction = await VoiceChatRestrictions.ActiveAsync(db, userId.Value);
return Ok(VoiceChatRestrictions.Payload(restriction));
}
}
}