GamePreferences.cs

Static preferences store for the Socer game. Reads and writes language, sound volume, text size, dialogue censorship, tutorial flags, club help bitmask and hotkey bindings to a local JSON file, saving after each change.

File Access
using Sandbox;
using System;
using System.Collections.Generic;

namespace Socer;

public sealed class GamePreferencesData
{
    public string Language {get;set;}="en";
    public int SoundVolume {get;set;}=70;
    public int TextSizePercent {get;set;}=100;
    public int DialogueCensorship {get;set;}=1;
    public bool TutorialSeen {get;set;}
    public bool MatchTutorialSeen {get;set;}
    public int ClubHelpSeen {get;set;}
    public Dictionary<string,string> Hotkeys {get;set;}=new();
}

// Stored independently of the career so changing a preference never touches match progress.
public static class GamePreferences
{
    // Kept private; editor acceptance can redirect to its own disposable file
    // while exercising the normal preference setters and automatic saving.
    static string FileName="socer-preferences-v1.json";
    public static string Language {get;private set;}="en";
    public static bool Russian=>Language=="ru";
    public static int SoundVolume {get;private set;}=70;
    public static int TextSizePercent {get;private set;}=100;
    public static DialogueCensorship Censorship {get;private set;}=DialogueCensorship.Masked;
    public static string TextSizeClass=>TextSizePercent switch {115=>"text-size-115",130=>"text-size-130",_=>"text-size-100"};
    public static int TextSizeRevision {get;private set;}
    public static bool TutorialSeen {get;private set;}
    public static bool MatchTutorialSeen {get;private set;}
    public static int ClubHelpSeen {get;private set;}
    public static int Revision {get;private set;}

    public static void Load()
    {
        try
        {
            var data=FileSystem.Data.ReadJson<GamePreferencesData>(FileName);
            Language=data?.Language=="ru"?"ru":"en";
            SoundVolume=Math.Clamp(data?.SoundVolume??70,0,100);
            TextSizePercent=NormalizeTextSize(data?.TextSizePercent??100);
            Censorship=DialogueFilter.Normalize(data?.DialogueCensorship??1);
            TutorialSeen=data?.TutorialSeen??false;
            MatchTutorialSeen=data?.MatchTutorialSeen??false;
            ClubHelpSeen=(data?.ClubHelpSeen??0)&ClubHelp.AllTopics;
            GameHotkeys.LoadBindings(data?.Hotkeys);
            if(data?.DialogueCensorship==2)Save();
        }
        catch(Exception e){Log.Warning($"Socer preferences: {e.Message}");Language="en";SoundVolume=70;TextSizePercent=100;Censorship=DialogueCensorship.Masked;TutorialSeen=false;MatchTutorialSeen=false;ClubHelpSeen=0;GameHotkeys.LoadBindings(null);}
        TextSizeRevision++;
        Revision++;
    }
    public static void SetLanguage(string language)
    {
        language=language=="ru"?"ru":"en";
        if(Language==language)return;
        Language=language;GameHotkeys.ClearStatus();Revision++;Save();
    }
    public static void SetSoundVolume(int volume)
    {
        volume=Math.Clamp(volume,0,100);
        if(SoundVolume==volume)return;
        SoundVolume=volume;Revision++;Save();
    }
    // Invalid old/custom values always resolve to one of the three supported layouts.
    public static void SetCensorship(DialogueCensorship value)
    {
        value=DialogueFilter.Normalize((int)value);
        if(Censorship==value)return;
        Censorship=value;Revision++;Save();
    }
    public static int NormalizeTextSize(int percent)
    {
        if(percent<100)return 100;
        if(percent>130)return 130;
        return percent<108?100:percent<123?115:130;
    }
    public static void SetTextSize(int percent)
    {
        percent=NormalizeTextSize(percent);
        if(TextSizePercent==percent)return;
        TextSizePercent=percent;TextSizeRevision++;Revision++;Save();
    }
    public static void MarkTutorialSeen()
    {
        if(TutorialSeen)return;
        TutorialSeen=true;Revision++;Save();
    }
    public static void MarkMatchTutorialSeen()
    {
        if(MatchTutorialSeen)return;
        MatchTutorialSeen=true;Revision++;Save();
    }
    public static bool HasSeenClubHelp(ClubHelpTopic topic)=>topic==ClubHelpTopic.None || (ClubHelpSeen&(int)topic)!=0;
    public static void MarkClubHelpSeen(ClubHelpTopic topic)
    {
        if(ClubHelp.Get(topic)==null || HasSeenClubHelp(topic))return;
        ClubHelpSeen|=(int)topic;Revision++;Save();
    }
    public static void ResetClubHelp()
    {
        if(ClubHelpSeen==0)return;
        ClubHelpSeen=0;Revision++;Save();
    }
    internal static bool SaveHotkeys()
    {
        bool saved=Save();if(saved)Revision++;return saved;
    }
    static bool Save()
    {
        try
        {
            var data=new GamePreferencesData{Language=Language,SoundVolume=SoundVolume,TextSizePercent=TextSizePercent,
                DialogueCensorship=(int)Censorship,TutorialSeen=TutorialSeen,MatchTutorialSeen=MatchTutorialSeen,
                ClubHelpSeen=ClubHelpSeen,Hotkeys=GameHotkeys.ExportBindings()};
            FileSystem.Data.WriteJson(FileName,data);return true;
        }
        catch(Exception e){Log.Warning($"Could not save Socer preferences: {e.Message}");return false;}
    }
}