mirror of
https://github.com/DorkSquadRR/DorkNet.git
synced 2026-10-25 23:18:44 -07:00
Add game configuration management: implement endpoints for retrieving and updating discovered game configurations; enhance database schema and UI integration
This commit is contained in:
@@ -1531,6 +1531,19 @@ public class AdminController(
|
||||
return Ok(doors);
|
||||
}
|
||||
|
||||
[HttpGet("settings/gameconfigs")]
|
||||
public async Task<ActionResult> GetDiscoveredGameConfigs()
|
||||
=> Ok(await serverSettings.GetDiscoveredGameConfigsAsync());
|
||||
|
||||
[HttpPost("settings/gameconfigs")]
|
||||
public async Task<ActionResult> SetDiscoveredGameConfigs([FromBody] DiscoveredGameConfigSettings body)
|
||||
{
|
||||
var settings = await serverSettings.SetDiscoveredGameConfigsAsync(body);
|
||||
await LogAsync("gameconfigs_updated", "system", 0, "discovered");
|
||||
await db.SaveChangesAsync();
|
||||
return Ok(settings);
|
||||
}
|
||||
|
||||
// ── Signup codes ─────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>GET <c>api/admin/v1/signup-codes</c> — every issued code
|
||||
|
||||
@@ -611,6 +611,7 @@ public class DorkNetDbContext(DbContextOptions<DorkNetDbContext> options) : DbCo
|
||||
e.Property(s => s.WeeklyChallengeRewardJson).HasDefaultValue(string.Empty);
|
||||
e.Property(s => s.PlayMenuTagsJson).HasDefaultValue(string.Empty);
|
||||
e.Property(s => s.RecCenterDoorsJson).HasDefaultValue(string.Empty);
|
||||
e.Property(s => s.DiscoveredGameConfigsJson).HasDefaultValue(string.Empty);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<RoomKeyEntity>(e =>
|
||||
|
||||
@@ -48,5 +48,11 @@ public class ServerSettingsEntity
|
||||
/// Door.{Category}.Title and Door.{Category}.Query entries.</summary>
|
||||
public string RecCenterDoorsJson { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Admin-managed values for GameConfig keys confirmed in
|
||||
/// the 2020.12 decomp. Kept typed in the admin API; stored as JSON
|
||||
/// here so newly discovered built-in keys do not require a table
|
||||
/// migration each time.</summary>
|
||||
public string DiscoveredGameConfigsJson { get; set; } = string.Empty;
|
||||
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
@@ -66,10 +66,18 @@ public class ServerSettingsService(DorkNetDbContext db)
|
||||
return ToRecCenterDoors(row);
|
||||
}
|
||||
|
||||
public async Task<DiscoveredGameConfigSettings> GetDiscoveredGameConfigsAsync()
|
||||
{
|
||||
var row = await GetAsync();
|
||||
return ToDiscoveredGameConfigs(row);
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<GameConfigurationSetting>> GetGameConfigurationsAsync()
|
||||
{
|
||||
var doors = await GetRecCenterDoorsAsync();
|
||||
return ToGameConfigurations(doors);
|
||||
var row = await GetAsync();
|
||||
return ToGameConfigurations(
|
||||
ToRecCenterDoors(row),
|
||||
ToDiscoveredGameConfigs(row));
|
||||
}
|
||||
|
||||
public async Task<PlayMenuTagSettings> SetPlayMenuTagsAsync(
|
||||
@@ -95,6 +103,17 @@ public class ServerSettingsService(DorkNetDbContext db)
|
||||
return normalized with { UpdatedAt = existing.UpdatedAt };
|
||||
}
|
||||
|
||||
public async Task<DiscoveredGameConfigSettings> SetDiscoveredGameConfigsAsync(
|
||||
DiscoveredGameConfigSettings settings)
|
||||
{
|
||||
var normalized = NormalizeDiscoveredGameConfigs(settings);
|
||||
var existing = await GetTrackedRowAsync();
|
||||
existing.DiscoveredGameConfigsJson = JsonSerializer.Serialize(normalized, JsonOptions);
|
||||
existing.UpdatedAt = DateTime.UtcNow;
|
||||
await db.SaveChangesAsync();
|
||||
return normalized with { UpdatedAt = existing.UpdatedAt };
|
||||
}
|
||||
|
||||
public async Task<WeeklyChallengeSettings> SetWeeklyChallengesAsync(
|
||||
bool completedRequired,
|
||||
IReadOnlyList<WeeklyChallengeTemplate> challenges,
|
||||
@@ -209,6 +228,28 @@ public class ServerSettingsService(DorkNetDbContext db)
|
||||
return DefaultRecCenterDoors() with { UpdatedAt = row.UpdatedAt };
|
||||
}
|
||||
|
||||
private static DiscoveredGameConfigSettings ToDiscoveredGameConfigs(ServerSettingsEntity row)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(row.DiscoveredGameConfigsJson))
|
||||
{
|
||||
try
|
||||
{
|
||||
var parsed = JsonSerializer.Deserialize<DiscoveredGameConfigSettings>(
|
||||
row.DiscoveredGameConfigsJson,
|
||||
JsonOptions);
|
||||
if (parsed is not null)
|
||||
return NormalizeDiscoveredGameConfigs(parsed)
|
||||
with { UpdatedAt = row.UpdatedAt };
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Bad settings should not break gameconfig bootstrap.
|
||||
}
|
||||
}
|
||||
|
||||
return DefaultDiscoveredGameConfigs() with { UpdatedAt = row.UpdatedAt };
|
||||
}
|
||||
|
||||
private static List<WeeklyChallengeTemplate> NormalizeWeeklyChallenges(
|
||||
IReadOnlyList<WeeklyChallengeTemplate> challenges)
|
||||
{
|
||||
@@ -323,8 +364,27 @@ public class ServerSettingsService(DorkNetDbContext db)
|
||||
],
|
||||
UpdatedAt: DateTime.UtcNow);
|
||||
|
||||
public static DiscoveredGameConfigSettings DefaultDiscoveredGameConfigs() =>
|
||||
new(
|
||||
FriendsPostGamePromptUnderFriendCount: 3,
|
||||
FriendsSuggestFriendCodeOnFriendsScreenCount: 5,
|
||||
ScreensForceVerification: false,
|
||||
VrForceVerification: false,
|
||||
RewardsUseRewardSelection: false,
|
||||
RewardsSelectionTimeout: 30,
|
||||
RoomDetailsPhotoRollEnabled: false,
|
||||
LoadingNetworkTimeout: 30,
|
||||
RunningNetworkTimeout: 30,
|
||||
SynchronizedFieldRemoveDefaultEntries: false,
|
||||
RenderingDisableSrpBatcher: false,
|
||||
SplitTestSoftOverrides: "{}",
|
||||
SplitTestHardOverrides: "{}",
|
||||
SplitTestSegmentProbabilities: "{}",
|
||||
UpdatedAt: DateTime.UtcNow);
|
||||
|
||||
public static IReadOnlyList<GameConfigurationSetting> ToGameConfigurations(
|
||||
RecCenterDoorSettings doors)
|
||||
RecCenterDoorSettings doors,
|
||||
DiscoveredGameConfigSettings discovered)
|
||||
{
|
||||
var rows = new List<GameConfigurationSetting>();
|
||||
foreach (var door in NormalizeRecCenterDoors(doors.Doors).Doors)
|
||||
@@ -333,16 +393,22 @@ public class ServerSettingsService(DorkNetDbContext db)
|
||||
rows.Add(new($"Door.{door.Key}.Query", door.Query));
|
||||
}
|
||||
|
||||
var gameConfig = NormalizeDiscoveredGameConfigs(discovered);
|
||||
rows.AddRange([
|
||||
new("Rewards.UseRewardSelection", "false"),
|
||||
new("RoomDetails.PhotoRollEnabled", "false"),
|
||||
new("SynchronizedField.RemoveDefaultEntries", "false"),
|
||||
new("Rendering.DisableSrpBatcher", "false"),
|
||||
new("splitTestSoftOverrides", "{}"),
|
||||
new("splitTestHardOverrides", "{}"),
|
||||
new("splitTestSegmentProbabilities", "{}"),
|
||||
new("loadingNetworkTimeout", "30"),
|
||||
new("runningNetworkTimeout", "30"),
|
||||
new("Friends.PostGamePromptUnderFriendCount", gameConfig.FriendsPostGamePromptUnderFriendCount.ToString()),
|
||||
new("Friends.SuggestFriendCodeOnFriendsScreenCount", gameConfig.FriendsSuggestFriendCodeOnFriendsScreenCount.ToString()),
|
||||
new("Screens.ForceVerification", gameConfig.ScreensForceVerification ? "1" : "0"),
|
||||
new("VR.ForceVerification", gameConfig.VrForceVerification ? "1" : "0"),
|
||||
new("Rewards.UseRewardSelection", gameConfig.RewardsUseRewardSelection.ToString().ToLowerInvariant()),
|
||||
new("Rewards.SelectionTimeout", gameConfig.RewardsSelectionTimeout.ToString()),
|
||||
new("RoomDetails.PhotoRollEnabled", gameConfig.RoomDetailsPhotoRollEnabled.ToString().ToLowerInvariant()),
|
||||
new("loadingNetworkTimeout", gameConfig.LoadingNetworkTimeout.ToString(System.Globalization.CultureInfo.InvariantCulture)),
|
||||
new("runningNetworkTimeout", gameConfig.RunningNetworkTimeout.ToString(System.Globalization.CultureInfo.InvariantCulture)),
|
||||
new("SynchronizedField.RemoveDefaultEntries", gameConfig.SynchronizedFieldRemoveDefaultEntries.ToString().ToLowerInvariant()),
|
||||
new("Rendering.DisableSrpBatcher", gameConfig.RenderingDisableSrpBatcher.ToString().ToLowerInvariant()),
|
||||
new("splitTestSoftOverrides", gameConfig.SplitTestSoftOverrides),
|
||||
new("splitTestHardOverrides", gameConfig.SplitTestHardOverrides),
|
||||
new("splitTestSegmentProbabilities", gameConfig.SplitTestSegmentProbabilities),
|
||||
]);
|
||||
return rows;
|
||||
}
|
||||
@@ -415,6 +481,41 @@ public class ServerSettingsService(DorkNetDbContext db)
|
||||
normalized.Count > 0 ? normalized : DefaultRecCenterDoors().Doors,
|
||||
DateTime.UtcNow);
|
||||
}
|
||||
|
||||
private static DiscoveredGameConfigSettings NormalizeDiscoveredGameConfigs(
|
||||
DiscoveredGameConfigSettings settings)
|
||||
{
|
||||
static string JsonObjectOrDefault(string value)
|
||||
{
|
||||
value = string.IsNullOrWhiteSpace(value) ? "{}" : value.Trim();
|
||||
try
|
||||
{
|
||||
using var doc = JsonDocument.Parse(value);
|
||||
return doc.RootElement.ValueKind == JsonValueKind.Object ? value : "{}";
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "{}";
|
||||
}
|
||||
}
|
||||
|
||||
return new DiscoveredGameConfigSettings(
|
||||
FriendsPostGamePromptUnderFriendCount: Math.Clamp(settings.FriendsPostGamePromptUnderFriendCount, 0, 100),
|
||||
FriendsSuggestFriendCodeOnFriendsScreenCount: Math.Clamp(settings.FriendsSuggestFriendCodeOnFriendsScreenCount, 0, 100),
|
||||
ScreensForceVerification: settings.ScreensForceVerification,
|
||||
VrForceVerification: settings.VrForceVerification,
|
||||
RewardsUseRewardSelection: settings.RewardsUseRewardSelection,
|
||||
RewardsSelectionTimeout: Math.Clamp(settings.RewardsSelectionTimeout, 0, 300),
|
||||
RoomDetailsPhotoRollEnabled: settings.RoomDetailsPhotoRollEnabled,
|
||||
LoadingNetworkTimeout: Math.Clamp(settings.LoadingNetworkTimeout, 1, 300),
|
||||
RunningNetworkTimeout: Math.Clamp(settings.RunningNetworkTimeout, 1, 300),
|
||||
SynchronizedFieldRemoveDefaultEntries: settings.SynchronizedFieldRemoveDefaultEntries,
|
||||
RenderingDisableSrpBatcher: settings.RenderingDisableSrpBatcher,
|
||||
SplitTestSoftOverrides: JsonObjectOrDefault(settings.SplitTestSoftOverrides),
|
||||
SplitTestHardOverrides: JsonObjectOrDefault(settings.SplitTestHardOverrides),
|
||||
SplitTestSegmentProbabilities: JsonObjectOrDefault(settings.SplitTestSegmentProbabilities),
|
||||
UpdatedAt: DateTime.UtcNow);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record WeeklyChallengeSettings(
|
||||
@@ -461,3 +562,20 @@ public sealed record GameConfigurationSetting(
|
||||
string Value,
|
||||
DateTime? StartTime = null,
|
||||
DateTime? EndTime = null);
|
||||
|
||||
public sealed record DiscoveredGameConfigSettings(
|
||||
int FriendsPostGamePromptUnderFriendCount,
|
||||
int FriendsSuggestFriendCodeOnFriendsScreenCount,
|
||||
bool ScreensForceVerification,
|
||||
bool VrForceVerification,
|
||||
bool RewardsUseRewardSelection,
|
||||
int RewardsSelectionTimeout,
|
||||
bool RoomDetailsPhotoRollEnabled,
|
||||
float LoadingNetworkTimeout,
|
||||
float RunningNetworkTimeout,
|
||||
bool SynchronizedFieldRemoveDefaultEntries,
|
||||
bool RenderingDisableSrpBatcher,
|
||||
string SplitTestSoftOverrides,
|
||||
string SplitTestHardOverrides,
|
||||
string SplitTestSegmentProbabilities,
|
||||
DateTime UpdatedAt);
|
||||
|
||||
@@ -246,6 +246,8 @@ public static class DatabaseBootstrap
|
||||
@"ALTER TABLE ""ServerSettings"" ADD COLUMN IF NOT EXISTS ""PlayMenuTagsJson"" text NOT NULL DEFAULT '';");
|
||||
await db.Database.ExecuteSqlRawAsync(
|
||||
@"ALTER TABLE ""ServerSettings"" ADD COLUMN IF NOT EXISTS ""RecCenterDoorsJson"" text NOT NULL DEFAULT '';");
|
||||
await db.Database.ExecuteSqlRawAsync(
|
||||
@"ALTER TABLE ""ServerSettings"" ADD COLUMN IF NOT EXISTS ""DiscoveredGameConfigsJson"" text NOT NULL DEFAULT '';");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -272,6 +274,18 @@ public static class DatabaseBootstrap
|
||||
// ignore the duplicate-column error; any other schema problem
|
||||
// will surface when EF reads ServerSettings.
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await db.Database.ExecuteSqlRawAsync(
|
||||
@"ALTER TABLE ""ServerSettings"" ADD COLUMN ""DiscoveredGameConfigsJson"" TEXT NOT NULL DEFAULT '';");
|
||||
}
|
||||
catch
|
||||
{
|
||||
// SQLite has no ADD COLUMN IF NOT EXISTS. If it already exists,
|
||||
// ignore the duplicate-column error; any other schema problem
|
||||
// will surface when EF reads ServerSettings.
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>SQLite migration-history reconciliation. Handles two
|
||||
|
||||
@@ -54,9 +54,28 @@ interface RewardOptions {
|
||||
equipment: RewardOption[];
|
||||
}
|
||||
|
||||
interface DiscoveredGameConfigSettings {
|
||||
friendsPostGamePromptUnderFriendCount: number;
|
||||
friendsSuggestFriendCodeOnFriendsScreenCount: number;
|
||||
screensForceVerification: boolean;
|
||||
vrForceVerification: boolean;
|
||||
rewardsUseRewardSelection: boolean;
|
||||
rewardsSelectionTimeout: number;
|
||||
roomDetailsPhotoRollEnabled: boolean;
|
||||
loadingNetworkTimeout: number;
|
||||
runningNetworkTimeout: number;
|
||||
synchronizedFieldRemoveDefaultEntries: boolean;
|
||||
renderingDisableSrpBatcher: boolean;
|
||||
splitTestSoftOverrides: string;
|
||||
splitTestHardOverrides: string;
|
||||
splitTestSegmentProbabilities: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export function Settings({ embedded }: { embedded?: boolean } = {}) {
|
||||
const [settings, setSettings] = useState<ServerSettings | null>(null);
|
||||
const [weekly, setWeekly] = useState<WeeklyChallengeSettings | null>(null);
|
||||
const [gameConfigs, setGameConfigs] = useState<DiscoveredGameConfigSettings | null>(null);
|
||||
const [rewardOptions, setRewardOptions] = useState<RewardOptions>({
|
||||
avatarItems: [],
|
||||
consumables: [],
|
||||
@@ -65,18 +84,21 @@ export function Settings({ embedded }: { embedded?: boolean } = {}) {
|
||||
const [err, setErr] = useState<string | null>(null);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [weeklyBusy, setWeeklyBusy] = useState(false);
|
||||
const [gameConfigBusy, setGameConfigBusy] = useState(false);
|
||||
const toast = useToast();
|
||||
|
||||
const load = async () => {
|
||||
try {
|
||||
const [s, w, rewards] = await Promise.all([
|
||||
const [s, w, rewards, configs] = await Promise.all([
|
||||
get<ServerSettings>('/settings'),
|
||||
get<WeeklyChallengeSettings>('/settings/weekly-challenges'),
|
||||
get<RewardOptions>('/settings/weekly-challenges/reward-options'),
|
||||
get<DiscoveredGameConfigSettings>('/settings/gameconfigs'),
|
||||
]);
|
||||
setSettings(s);
|
||||
setWeekly(w);
|
||||
setRewardOptions(rewards);
|
||||
setGameConfigs(configs);
|
||||
setErr(null);
|
||||
} catch (e) {
|
||||
setErr((e as Error).message);
|
||||
@@ -252,6 +274,28 @@ export function Settings({ embedded }: { embedded?: boolean } = {}) {
|
||||
}
|
||||
};
|
||||
|
||||
const updateGameConfigs = (patch: Partial<DiscoveredGameConfigSettings>) => {
|
||||
setGameConfigs((current) => current ? { ...current, ...patch } : current);
|
||||
};
|
||||
|
||||
const saveGameConfigs = async (reset = false) => {
|
||||
if (!gameConfigs && !reset) return;
|
||||
setGameConfigBusy(true);
|
||||
try {
|
||||
const body = reset ? defaultGameConfigs() : gameConfigs;
|
||||
const updated = await api<DiscoveredGameConfigSettings>('/settings/gameconfigs', {
|
||||
method: 'POST',
|
||||
body,
|
||||
});
|
||||
setGameConfigs(updated);
|
||||
toast.push(reset ? 'GameConfig settings reset.' : 'GameConfig settings saved.', 'success');
|
||||
} catch (e) {
|
||||
toast.push((e as Error).message, 'error');
|
||||
} finally {
|
||||
setGameConfigBusy(false);
|
||||
}
|
||||
};
|
||||
|
||||
const refreshBtn = (
|
||||
<button onClick={load} className="btn-secondary text-xs" disabled={busy}>
|
||||
<RefreshCw className={busy ? 'animate-spin' : ''} />
|
||||
@@ -308,6 +352,16 @@ export function Settings({ embedded }: { embedded?: boolean } = {}) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{gameConfigs && (
|
||||
<DiscoveredGameConfigsPanel
|
||||
settings={gameConfigs}
|
||||
busy={gameConfigBusy}
|
||||
onChange={updateGameConfigs}
|
||||
onSave={() => saveGameConfigs(false)}
|
||||
onReset={() => saveGameConfigs(true)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{weekly && (
|
||||
<div className="card !p-5 max-w-5xl mt-5">
|
||||
<div className="flex flex-wrap items-start justify-between gap-4">
|
||||
@@ -507,3 +561,241 @@ export function Settings({ embedded }: { embedded?: boolean } = {}) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function DiscoveredGameConfigsPanel({
|
||||
settings,
|
||||
busy,
|
||||
onChange,
|
||||
onSave,
|
||||
onReset,
|
||||
}: {
|
||||
settings: DiscoveredGameConfigSettings;
|
||||
busy: boolean;
|
||||
onChange: (patch: Partial<DiscoveredGameConfigSettings>) => void;
|
||||
onSave: () => void;
|
||||
onReset: () => void;
|
||||
}) {
|
||||
const number = (key: keyof DiscoveredGameConfigSettings, value: string) => {
|
||||
const n = Number.parseFloat(value);
|
||||
onChange({ [key]: Number.isFinite(n) ? n : 0 } as Partial<DiscoveredGameConfigSettings>);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="card !p-5 max-w-5xl mt-5">
|
||||
<div className="flex flex-wrap items-start justify-between gap-4">
|
||||
<div className="min-w-0 flex-1">
|
||||
<h2 className="text-sm font-semibold text-ink-50">GameConfig</h2>
|
||||
<p className="mt-1 text-xs text-ink-400">
|
||||
Typed keys confirmed in the 2020.12 client decomp.
|
||||
</p>
|
||||
<p className="mt-2 text-[11px] text-ink-500">
|
||||
Last changed {new Date(settings.updatedAt).toLocaleString()}.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<button onClick={onReset} disabled={busy} className="btn-secondary text-xs">
|
||||
<RefreshCw />
|
||||
Defaults
|
||||
</button>
|
||||
<button onClick={onSave} disabled={busy} className="btn-primary text-xs">
|
||||
{busy ? 'Saving...' : 'Save'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-5 grid gap-4 lg:grid-cols-2">
|
||||
<div className="rounded-lg border border-ink-800 bg-ink-950/30 p-4">
|
||||
<h3 className="text-xs font-semibold uppercase tracking-wider text-ink-400">Social</h3>
|
||||
<div className="mt-3 grid gap-3 sm:grid-cols-2">
|
||||
<NumberConfig
|
||||
label="Friends.PostGamePromptUnderFriendCount"
|
||||
value={settings.friendsPostGamePromptUnderFriendCount}
|
||||
onChange={(v) => number('friendsPostGamePromptUnderFriendCount', v)}
|
||||
/>
|
||||
<NumberConfig
|
||||
label="Friends.SuggestFriendCodeOnFriendsScreenCount"
|
||||
value={settings.friendsSuggestFriendCodeOnFriendsScreenCount}
|
||||
onChange={(v) => number('friendsSuggestFriendCodeOnFriendsScreenCount', v)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border border-ink-800 bg-ink-950/30 p-4">
|
||||
<h3 className="text-xs font-semibold uppercase tracking-wider text-ink-400">Account gates</h3>
|
||||
<div className="mt-3 grid gap-3 sm:grid-cols-2">
|
||||
<BoolConfig
|
||||
label="Screens.ForceVerification"
|
||||
checked={settings.screensForceVerification}
|
||||
onChange={(v) => onChange({ screensForceVerification: v })}
|
||||
/>
|
||||
<BoolConfig
|
||||
label="VR.ForceVerification"
|
||||
checked={settings.vrForceVerification}
|
||||
onChange={(v) => onChange({ vrForceVerification: v })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border border-ink-800 bg-ink-950/30 p-4">
|
||||
<h3 className="text-xs font-semibold uppercase tracking-wider text-ink-400">Rewards</h3>
|
||||
<div className="mt-3 grid gap-3 sm:grid-cols-2">
|
||||
<BoolConfig
|
||||
label="Rewards.UseRewardSelection"
|
||||
checked={settings.rewardsUseRewardSelection}
|
||||
onChange={(v) => onChange({ rewardsUseRewardSelection: v })}
|
||||
/>
|
||||
<NumberConfig
|
||||
label="Rewards.SelectionTimeout"
|
||||
value={settings.rewardsSelectionTimeout}
|
||||
onChange={(v) => number('rewardsSelectionTimeout', v)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border border-ink-800 bg-ink-950/30 p-4">
|
||||
<h3 className="text-xs font-semibold uppercase tracking-wider text-ink-400">Room details</h3>
|
||||
<div className="mt-3">
|
||||
<BoolConfig
|
||||
label="RoomDetails.PhotoRollEnabled"
|
||||
checked={settings.roomDetailsPhotoRollEnabled}
|
||||
onChange={(v) => onChange({ roomDetailsPhotoRollEnabled: v })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border border-ink-800 bg-ink-950/30 p-4">
|
||||
<h3 className="text-xs font-semibold uppercase tracking-wider text-ink-400">Networking</h3>
|
||||
<div className="mt-3 grid gap-3 sm:grid-cols-2">
|
||||
<NumberConfig
|
||||
label="loadingNetworkTimeout"
|
||||
value={settings.loadingNetworkTimeout}
|
||||
onChange={(v) => number('loadingNetworkTimeout', v)}
|
||||
/>
|
||||
<NumberConfig
|
||||
label="runningNetworkTimeout"
|
||||
value={settings.runningNetworkTimeout}
|
||||
onChange={(v) => number('runningNetworkTimeout', v)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border border-ink-800 bg-ink-950/30 p-4">
|
||||
<h3 className="text-xs font-semibold uppercase tracking-wider text-ink-400">Rendering / sync</h3>
|
||||
<div className="mt-3 grid gap-3 sm:grid-cols-2">
|
||||
<BoolConfig
|
||||
label="SynchronizedField.RemoveDefaultEntries"
|
||||
checked={settings.synchronizedFieldRemoveDefaultEntries}
|
||||
onChange={(v) => onChange({ synchronizedFieldRemoveDefaultEntries: v })}
|
||||
/>
|
||||
<BoolConfig
|
||||
label="Rendering.DisableSrpBatcher"
|
||||
checked={settings.renderingDisableSrpBatcher}
|
||||
onChange={(v) => onChange({ renderingDisableSrpBatcher: v })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border border-ink-800 bg-ink-950/30 p-4 lg:col-span-2">
|
||||
<h3 className="text-xs font-semibold uppercase tracking-wider text-ink-400">Split tests</h3>
|
||||
<div className="mt-3 grid gap-3 lg:grid-cols-3">
|
||||
<JsonConfig
|
||||
label="splitTestSoftOverrides"
|
||||
value={settings.splitTestSoftOverrides}
|
||||
onChange={(v) => onChange({ splitTestSoftOverrides: v })}
|
||||
/>
|
||||
<JsonConfig
|
||||
label="splitTestHardOverrides"
|
||||
value={settings.splitTestHardOverrides}
|
||||
onChange={(v) => onChange({ splitTestHardOverrides: v })}
|
||||
/>
|
||||
<JsonConfig
|
||||
label="splitTestSegmentProbabilities"
|
||||
value={settings.splitTestSegmentProbabilities}
|
||||
onChange={(v) => onChange({ splitTestSegmentProbabilities: v })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function NumberConfig({
|
||||
label,
|
||||
value,
|
||||
onChange,
|
||||
}: {
|
||||
label: string;
|
||||
value: number;
|
||||
onChange: (value: string) => void;
|
||||
}) {
|
||||
return (
|
||||
<label className="block">
|
||||
<span className="label font-mono normal-case tracking-normal">{label}</span>
|
||||
<input className="input mt-1" type="number" value={value} onChange={(e) => onChange(e.target.value)} />
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
||||
function BoolConfig({
|
||||
label,
|
||||
checked,
|
||||
onChange,
|
||||
}: {
|
||||
label: string;
|
||||
checked: boolean;
|
||||
onChange: (value: boolean) => void;
|
||||
}) {
|
||||
return (
|
||||
<label className="flex min-h-10 items-center justify-between gap-3 rounded-md border border-ink-800 bg-ink-900/50 px-3 py-2">
|
||||
<span className="font-mono text-xs text-ink-100">{label}</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={checked}
|
||||
onChange={(e) => onChange(e.target.checked)}
|
||||
className="size-4 rounded border-ink-700 bg-ink-950 text-brand-500 focus:ring-brand-500/30"
|
||||
/>
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
||||
function JsonConfig({
|
||||
label,
|
||||
value,
|
||||
onChange,
|
||||
}: {
|
||||
label: string;
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
}) {
|
||||
return (
|
||||
<label className="block">
|
||||
<span className="label font-mono normal-case tracking-normal">{label}</span>
|
||||
<textarea
|
||||
className="input mt-1 min-h-28 font-mono text-xs"
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
/>
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
||||
function defaultGameConfigs(): DiscoveredGameConfigSettings {
|
||||
return {
|
||||
friendsPostGamePromptUnderFriendCount: 3,
|
||||
friendsSuggestFriendCodeOnFriendsScreenCount: 5,
|
||||
screensForceVerification: false,
|
||||
vrForceVerification: false,
|
||||
rewardsUseRewardSelection: false,
|
||||
rewardsSelectionTimeout: 30,
|
||||
roomDetailsPhotoRollEnabled: false,
|
||||
loadingNetworkTimeout: 30,
|
||||
runningNetworkTimeout: 30,
|
||||
synchronizedFieldRemoveDefaultEntries: false,
|
||||
renderingDisableSrpBatcher: false,
|
||||
splitTestSoftOverrides: '{}',
|
||||
splitTestHardOverrides: '{}',
|
||||
splitTestSegmentProbabilities: '{}',
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user