Game/EditorSettings.cs
using System.Collections.Generic;
namespace BlockParty;
/// <summary>
/// Editor-only, persisted preferences for the in-editor <see cref="LevelEditorStage"/> — currently
/// just the alignment-grid overlay. Kept separate from the player-facing <see cref="GameSettings"/>
/// (different file, different audience) but stored the same way (JSON in the per-user data folder).
/// </summary>
public sealed class EditorSettings
{
/// <summary>Draw the white alignment grid over the schematic. Default off.</summary>
public bool ShowGrid { get; set; } = false;
/// <summary>Snap dragged geometry to nearby edges and visible grid lines. Whole-pixel placement
/// remains enforced when this is off. Default on.</summary>
public bool SnapEnabled { get; set; } = true;
/// <summary>Grid cell size in logical (arena) pixels. The grid is centred on the arena middle, so
/// a line always falls on the centre. Clamped to a sane range when drawn. Default 8.</summary>
public float GridSize { get; set; } = 8f;
/// <summary>Opacity of the white grid lines, slider-space 0..100 (converted to a 0..1 alpha when
/// drawn). Default 40.</summary>
public float GridOpacity { get; set; } = 40f;
/// <summary>Smart grid: lines are mirrored inward from BOTH inner wall faces at multiples of
/// <see cref="GridSize"/>, so geometry flush against either wall always lands on a line even though
/// the playable span (arena minus the 2px walls) doesn't divide evenly by common sizes like 40.
/// While on, the origin/offset options below are ignored. Default off.</summary>
public bool GridSmart { get; set; } = false;
/// <summary>When true the arena centre sits inside a grid CELL (lines offset by half a cell); when
/// false a line intersection lands on the centre. Default false (intersection on centre).</summary>
public bool GridCenterCell { get; set; } = false;
/// <summary>When true and <see cref="GridCenterCell"/> is false, grid lines start at the playable
/// bottom-left corner just inside the arena walls instead of radiating from the arena centre.</summary>
public bool GridBottomLeftOrigin { get; set; } = false;
/// <summary>Signed logical-pixel offsets applied to the grid's selected base origin.</summary>
public float GridOffsetX { get; set; } = 0f;
public float GridOffsetY { get; set; } = 0f;
/// <summary>Level-browser overlay view: true = mapped levels laid out in the level-select map
/// layout; false = one of the flat non-map grids selected by <see cref="LevelBrowserTestMode"/>.
/// Persisted so the browser reopens in the last-used mode.</summary>
public bool LevelBrowserMapMode { get; set; } = false;
/// <summary>When true, the non-map level browser shows levels explicitly flagged as tests instead
/// of ordinary non-map levels. Mutually exclusive with <see cref="LevelBrowserMapMode"/>.</summary>
public bool LevelBrowserTestMode { get; set; } = false;
/// <summary>When true, the level browser shows the daily-challenge obstacle templates grid instead
/// of a level view. Mutually exclusive with the map/test flags above (editor picker only).</summary>
public bool LevelBrowserTemplatesMode { get; set; } = false;
/// <summary>When true, the level browser's Other/Test view orders levels by newest file timestamp
/// first; otherwise it uses the stable alphabetical order.</summary>
public bool LevelBrowserOtherRecentFirst { get; set; } = false;
/// <summary>Sort order of the level browser's Templates view: 0 = alphabetical, 1 = newest file
/// first, 2 = highest pick weight first (the sort button cycles through these).</summary>
public int LevelBrowserTemplateSort { get; set; } = 0;
/// <summary>Editor-maintained level file timestamps keyed by level id. The game assembly cannot query
/// physical file times under the sbox whitelist, so the editor assembly refreshes this map.</summary>
public Dictionary<string, long> LevelBrowserLevelTimes { get; set; } = new();
/// <summary>Editor-maintained daily-template file timestamps keyed by template id, for the Templates
/// view's Recent sort (same whitelist workaround as <see cref="LevelBrowserLevelTimes"/>).</summary>
public Dictionary<string, long> LevelBrowserTemplateTimes { get; set; } = new();
/// <summary>Id of the last project level successfully opened in the editor. Empty means the scratch
/// level was edited most recently; a stale id also leaves the editor on a new blank level.</summary>
public string LastLevelId { get; set; } = "";
}
/// <summary>
/// Static accessor + JSON persistence for <see cref="EditorSettings"/>. Stored in the game's
/// per-user data folder via <see cref="FileSystem.Data"/> (a separate file from the player
/// <see cref="Settings"/>); loaded lazily on first access and re-saved by the editor UI on change.
/// </summary>
public static class EditorPrefs
{
public const string FilePath = "blockparty_editor_settings.json";
private static EditorSettings _current;
public static EditorSettings Current
{
get
{
if ( _current is null ) Load();
return _current;
}
}
/// <summary>Load editor settings from disk, falling back to defaults if the file is missing/invalid.</summary>
public static void Load()
{
_current = FileSystem.Data.ReadJsonOrDefault<EditorSettings>( FilePath, new EditorSettings() );
}
public static void Save()
{
FileSystem.Data.WriteJson( FilePath, Current );
}
}