Repositories / GSConfigTool / GsConfigTool / ViewModels / ArkConfigViewModel.cs
ArkConfigViewModel.cs 37.6 KB
git clone https://xarmina.eu/git/gsconfigtool Raw Download
using GsConfigTool.Helpers;
using GsConfigTool.Models;
using GsConfigTool.Services;
using Microsoft.Win32;
using System.Collections.ObjectModel;
using System.IO;
using System.Text.Json;
using System.Windows;
using System.Windows.Input;

namespace GsConfigTool.ViewModels;

public class ArkConfigViewModel : BaseViewModel
{
    private readonly DatabaseService _db;
    private readonly MainViewModel _main;
    private ServerProject _project;
    private ArkServerSettings _settings;
    private string _generatedGameIni = "";
    private string _generatedGUS = "";
    private int _selectedTab;
    private string _projectName;
    private string _projectDescription;
    private LootDropOverride? _selectedDrop;
    private LootItemSet? _selectedSet;
    private EngramOverride? _selectedEngram;
    private CreatureMultiplier? _selectedCreature;
    private StackSizeOverride? _selectedStackSize;
    private HarvestMultiplier? _selectedHarvest;
    private SpawnOverride? _selectedSpawn;
    private bool _isDirty;
    private string _engramSearch = "";
    private string _creatureSearch = "";

    public ArkConfigViewModel(DatabaseService db, MainViewModel main, GameType game, ServerProject? project = null)
    {
        _db = db;
        _main = main;

        if (project != null)
        {
            _project = project;
            _settings = JsonSerializer.Deserialize<ArkServerSettings>(project.ConfigJson) ?? new ArkServerSettings();
        }
        else
        {
            _project = new ServerProject { Game = game, Name = $"Neuer {(game == GameType.ArkSurvivalAscended ? "ASA" : "ASE")} Server" };
            _settings = new ArkServerSettings();
        }

        Game = game;
        _projectName = _project.Name;
        _projectDescription = _project.Description;

        LootDrops = new ObservableCollection<LootDropOverride>(_settings.LootDropOverrides);
        Engrams = new ObservableCollection<EngramOverride>(_settings.EngramOverrides);
        Creatures = new ObservableCollection<CreatureMultiplier>(_settings.CreatureMultipliers);
        StackSizes = new ObservableCollection<StackSizeOverride>(_settings.StackSizeOverrides);
        Harvests = new ObservableCollection<HarvestMultiplier>(_settings.HarvestMultipliers);
        SpawnOverrides = new ObservableCollection<SpawnOverride>(_settings.SpawnOverrides);
        PerLevelRows = BuildPerLevelRows();

        SaveCommand = new RelayCommand(async () => await SaveAsync());
        SaveAsFileCommand = new RelayCommand(SaveAsFile);
        CopyGameIniCommand = new RelayCommand(() => CopyToClipboard(GeneratedGameIni));
        CopyGUSCommand = new RelayCommand(() => CopyToClipboard(GeneratedGUS));
        GoBackCommand = new RelayCommand(() => _main.NavigateTo("Home"));

        AddLootDropCommand = new RelayCommand(AddLootDrop);
        RemoveLootDropCommand = new RelayCommand(RemoveLootDrop, () => SelectedDrop != null);
        AddLootSetCommand = new RelayCommand(AddLootSet, () => SelectedDrop != null);
        RemoveLootSetCommand = new RelayCommand(RemoveLootSet, () => SelectedSet != null);
        AddLootItemCommand = new RelayCommand(AddLootItem, () => SelectedSet != null);

        AddEngramCommand = new RelayCommand(AddEngram);
        RemoveEngramCommand = new RelayCommand(RemoveEngram, () => SelectedEngram != null);
        AddCreatureCommand = new RelayCommand(AddCreature);
        RemoveCreatureCommand = new RelayCommand(RemoveCreature, () => SelectedCreature != null);

        AddStackSizeCommand = new RelayCommand(AddStackSize);
        RemoveStackSizeCommand = new RelayCommand(RemoveStackSize, () => SelectedStackSize != null);
        AddHarvestCommand = new RelayCommand(AddHarvest);
        RemoveHarvestCommand = new RelayCommand(RemoveHarvest, () => SelectedHarvest != null);
        AddSpawnCommand = new RelayCommand(AddSpawn);
        RemoveSpawnCommand = new RelayCommand(RemoveSpawn, () => SelectedSpawn != null);

        LoadTemplateCommand = new RelayCommand<ServerTemplate>(LoadTemplate);
        ImportConfigCommand = new RelayCommand(ImportConfig);
        ResetPerLevelStatsCommand = new RelayCommand(ResetPerLevelStats);

        GeneratePreview();
    }

    // ── Identity ──────────────────────────────────────────────────────────
    public GameType Game { get; }
    public string GameTitle => Game == GameType.ArkSurvivalAscended
        ? "ARK: Survival Ascended" : "ARK: Survival Evolved";
    public string GameColor => Game == GameType.ArkSurvivalAscended ? "#E04040" : "#FF6B35";

    public bool IsDirty { get => _isDirty; set => SetField(ref _isDirty, value); }

    public string ProjectName
    {
        get => _projectName;
        set { SetField(ref _projectName, value); IsDirty = true; }
    }
    public string ProjectDescription
    {
        get => _projectDescription;
        set { SetField(ref _projectDescription, value); IsDirty = true; }
    }

    // ── Session ───────────────────────────────────────────────────────────
    public string ServerName { get => _settings.ServerName; set { _settings.ServerName = value; OnPropertyChanged(); MarkDirty(); } }
    public string ServerPassword { get => _settings.ServerPassword; set { _settings.ServerPassword = value; OnPropertyChanged(); MarkDirty(); } }
    public string AdminPassword { get => _settings.AdminPassword; set { _settings.AdminPassword = value; OnPropertyChanged(); MarkDirty(); } }
    public int MaxPlayers { get => _settings.MaxPlayers; set { _settings.MaxPlayers = value; OnPropertyChanged(); MarkDirty(); } }
    public int QueryPort { get => _settings.QueryPort; set { _settings.QueryPort = value; OnPropertyChanged(); MarkDirty(); } }
    public int GamePort { get => _settings.GamePort; set { _settings.GamePort = value; OnPropertyChanged(); MarkDirty(); } }
    public string Map { get => _settings.Map; set { _settings.Map = value; OnPropertyChanged(); MarkDirty(); } }
    public string ActiveMods { get => _settings.ActiveMods; set { _settings.ActiveMods = value; OnPropertyChanged(); MarkDirty(); } }

    // ── Mode ──────────────────────────────────────────────────────────────
    public bool PvE { get => _settings.PvE; set { _settings.PvE = value; OnPropertyChanged(); MarkDirty(); } }
    public bool ServerHardcore { get => _settings.ServerHardcore; set { _settings.ServerHardcore = value; OnPropertyChanged(); MarkDirty(); } }
    public bool CrosshairEnabled { get => _settings.CrosshairEnabled; set { _settings.CrosshairEnabled = value; OnPropertyChanged(); MarkDirty(); } }
    public bool MapPlayerLocation { get => _settings.MapPlayerLocation; set { _settings.MapPlayerLocation = value; OnPropertyChanged(); MarkDirty(); } }
    public bool PreventOfflinePvP { get => _settings.PreventOfflinePvP; set { _settings.PreventOfflinePvP = value; OnPropertyChanged(); MarkDirty(); } }
    public bool PreventTribeAlliances { get => _settings.PreventTribeAlliances; set { _settings.PreventTribeAlliances = value; OnPropertyChanged(); MarkDirty(); } }
    public bool AllowThirdPersonPlayer { get => _settings.AllowThirdPersonPlayer; set { _settings.AllowThirdPersonPlayer = value; OnPropertyChanged(); MarkDirty(); } }
    public float KickIdlePlayersPeriod { get => _settings.KickIdlePlayersPeriod; set { _settings.KickIdlePlayersPeriod = value; OnPropertyChanged(); MarkDirty(); } }

    // ── Rates ─────────────────────────────────────────────────────────────
    public float XPMultiplier { get => _settings.XPMultiplier; set { _settings.XPMultiplier = value; OnPropertyChanged(); MarkDirty(); } }
    public float TamingSpeedMultiplier { get => _settings.TamingSpeedMultiplier; set { _settings.TamingSpeedMultiplier = value; OnPropertyChanged(); MarkDirty(); } }
    public float HarvestAmountMultiplier { get => _settings.HarvestAmountMultiplier; set { _settings.HarvestAmountMultiplier = value; OnPropertyChanged(); MarkDirty(); } }
    public float HarvestHealthMultiplier { get => _settings.HarvestHealthMultiplier; set { _settings.HarvestHealthMultiplier = value; OnPropertyChanged(); MarkDirty(); } }
    public float ResourcesRespawnPeriodMultiplier { get => _settings.ResourcesRespawnPeriodMultiplier; set { _settings.ResourcesRespawnPeriodMultiplier = value; OnPropertyChanged(); MarkDirty(); } }
    public float DayCycleSpeedScale { get => _settings.DayCycleSpeedScale; set { _settings.DayCycleSpeedScale = value; OnPropertyChanged(); MarkDirty(); } }
    public float DayTimeSpeedScale { get => _settings.DayTimeSpeedScale; set { _settings.DayTimeSpeedScale = value; OnPropertyChanged(); MarkDirty(); } }
    public float NightTimeSpeedScale { get => _settings.NightTimeSpeedScale; set { _settings.NightTimeSpeedScale = value; OnPropertyChanged(); MarkDirty(); } }
    public float DifficultyOffset { get => _settings.DifficultyOffset; set { _settings.DifficultyOffset = value; OnPropertyChanged(); MarkDirty(); } }
    public float OverrideOfficialDifficulty { get => _settings.OverrideOfficialDifficulty; set { _settings.OverrideOfficialDifficulty = value; OnPropertyChanged(); MarkDirty(); } }

    // ── Breeding ──────────────────────────────────────────────────────────
    public float MatingIntervalMultiplier { get => _settings.MatingIntervalMultiplier; set { _settings.MatingIntervalMultiplier = value; OnPropertyChanged(); MarkDirty(); } }
    public float MatingSpeedMultiplier { get => _settings.MatingSpeedMultiplier; set { _settings.MatingSpeedMultiplier = value; OnPropertyChanged(); MarkDirty(); } }
    public float EggHatchSpeedMultiplier { get => _settings.EggHatchSpeedMultiplier; set { _settings.EggHatchSpeedMultiplier = value; OnPropertyChanged(); MarkDirty(); } }
    public float BabyMatureSpeedMultiplier { get => _settings.BabyMatureSpeedMultiplier; set { _settings.BabyMatureSpeedMultiplier = value; OnPropertyChanged(); MarkDirty(); } }
    public float BabyFoodConsumptionSpeedMultiplier { get => _settings.BabyFoodConsumptionSpeedMultiplier; set { _settings.BabyFoodConsumptionSpeedMultiplier = value; OnPropertyChanged(); MarkDirty(); } }
    public float BabyCuddleIntervalMultiplier { get => _settings.BabyCuddleIntervalMultiplier; set { _settings.BabyCuddleIntervalMultiplier = value; OnPropertyChanged(); MarkDirty(); } }
    public float BabyImprintAmountMultiplier { get => _settings.BabyImprintAmountMultiplier; set { _settings.BabyImprintAmountMultiplier = value; OnPropertyChanged(); MarkDirty(); } }
    public float LayEggIntervalMultiplier { get => _settings.LayEggIntervalMultiplier; set { _settings.LayEggIntervalMultiplier = value; OnPropertyChanged(); MarkDirty(); } }

    // ── Player ────────────────────────────────────────────────────────────
    public float PlayerDamageMultiplier { get => _settings.PlayerDamageMultiplier; set { _settings.PlayerDamageMultiplier = value; OnPropertyChanged(); MarkDirty(); } }
    public float PlayerResistanceMultiplier { get => _settings.PlayerResistanceMultiplier; set { _settings.PlayerResistanceMultiplier = value; OnPropertyChanged(); MarkDirty(); } }
    public float PlayerWaterDrainMultiplier { get => _settings.PlayerWaterDrainMultiplier; set { _settings.PlayerWaterDrainMultiplier = value; OnPropertyChanged(); MarkDirty(); } }
    public float PlayerFoodDrainMultiplier { get => _settings.PlayerFoodDrainMultiplier; set { _settings.PlayerFoodDrainMultiplier = value; OnPropertyChanged(); MarkDirty(); } }
    public float PlayerStaminaDrainMultiplier { get => _settings.PlayerStaminaDrainMultiplier; set { _settings.PlayerStaminaDrainMultiplier = value; OnPropertyChanged(); MarkDirty(); } }
    public float PlayerHealthRecoveryMultiplier { get => _settings.PlayerHealthRecoveryMultiplier; set { _settings.PlayerHealthRecoveryMultiplier = value; OnPropertyChanged(); MarkDirty(); } }
    public float PlayerHarvestingDamageMultiplier { get => _settings.PlayerHarvestingDamageMultiplier; set { _settings.PlayerHarvestingDamageMultiplier = value; OnPropertyChanged(); MarkDirty(); } }
    public float PlayerTurretDamageMultiplier { get => _settings.PlayerTurretDamageMultiplier; set { _settings.PlayerTurretDamageMultiplier = value; OnPropertyChanged(); MarkDirty(); } }
    public int OverrideMaxExperiencePointsPlayer { get => _settings.OverrideMaxExperiencePointsPlayer; set { _settings.OverrideMaxExperiencePointsPlayer = value; OnPropertyChanged(); MarkDirty(); } }

    // ── Dino ──────────────────────────────────────────────────────────────
    public float DinoDamageMultiplier { get => _settings.DinoDamageMultiplier; set { _settings.DinoDamageMultiplier = value; OnPropertyChanged(); MarkDirty(); } }
    public float DinoResistanceMultiplier { get => _settings.DinoResistanceMultiplier; set { _settings.DinoResistanceMultiplier = value; OnPropertyChanged(); MarkDirty(); } }
    public float TamedDinoDamageMultiplier { get => _settings.TamedDinoDamageMultiplier; set { _settings.TamedDinoDamageMultiplier = value; OnPropertyChanged(); MarkDirty(); } }
    public float TamedDinoResistanceMultiplier { get => _settings.TamedDinoResistanceMultiplier; set { _settings.TamedDinoResistanceMultiplier = value; OnPropertyChanged(); MarkDirty(); } }
    public float DinoHarvestingDamageMultiplier { get => _settings.DinoHarvestingDamageMultiplier; set { _settings.DinoHarvestingDamageMultiplier = value; OnPropertyChanged(); MarkDirty(); } }
    public float DinoTurretDamageMultiplier { get => _settings.DinoTurretDamageMultiplier; set { _settings.DinoTurretDamageMultiplier = value; OnPropertyChanged(); MarkDirty(); } }
    public int MaxTamedDinos { get => _settings.MaxTamedDinos; set { _settings.MaxTamedDinos = value; OnPropertyChanged(); MarkDirty(); } }
    public int MaxPersonalTamedDinos { get => _settings.MaxPersonalTamedDinos; set { _settings.MaxPersonalTamedDinos = value; OnPropertyChanged(); MarkDirty(); } }
    public int OverrideMaxExperiencePointsDino { get => _settings.OverrideMaxExperiencePointsDino; set { _settings.OverrideMaxExperiencePointsDino = value; OnPropertyChanged(); MarkDirty(); } }

    // ── Structures ────────────────────────────────────────────────────────
    public bool DisableStructureDecayPvE { get => _settings.DisableStructureDecayPvE; set { _settings.DisableStructureDecayPvE = value; OnPropertyChanged(); MarkDirty(); } }
    public float StructureResistanceMultiplier { get => _settings.StructureResistanceMultiplier; set { _settings.StructureResistanceMultiplier = value; OnPropertyChanged(); MarkDirty(); } }
    public float PerPlatformMaxStructuresMultiplier { get => _settings.PerPlatformMaxStructuresMultiplier; set { _settings.PerPlatformMaxStructuresMultiplier = value; OnPropertyChanged(); MarkDirty(); } }
    public bool AllowFlyerCarryPvE { get => _settings.AllowFlyerCarryPvE; set { _settings.AllowFlyerCarryPvE = value; OnPropertyChanged(); MarkDirty(); } }
    public int MaxNumberOfPlayersInTribe { get => _settings.MaxNumberOfPlayersInTribe; set { _settings.MaxNumberOfPlayersInTribe = value; OnPropertyChanged(); MarkDirty(); } }

    // ── Items ─────────────────────────────────────────────────────────────
    public float GlobalSpoilingTimeMultiplier { get => _settings.GlobalSpoilingTimeMultiplier; set { _settings.GlobalSpoilingTimeMultiplier = value; OnPropertyChanged(); MarkDirty(); } }
    public float GlobalItemDecompositionTimeMultiplier { get => _settings.GlobalItemDecompositionTimeMultiplier; set { _settings.GlobalItemDecompositionTimeMultiplier = value; OnPropertyChanged(); MarkDirty(); } }
    public float GlobalCorpseDecompositionTimeMultiplier { get => _settings.GlobalCorpseDecompositionTimeMultiplier; set { _settings.GlobalCorpseDecompositionTimeMultiplier = value; OnPropertyChanged(); MarkDirty(); } }
    public float CropGrowthSpeedMultiplier { get => _settings.CropGrowthSpeedMultiplier; set { _settings.CropGrowthSpeedMultiplier = value; OnPropertyChanged(); MarkDirty(); } }
    public float FuelConsumptionIntervalMultiplier { get => _settings.FuelConsumptionIntervalMultiplier; set { _settings.FuelConsumptionIntervalMultiplier = value; OnPropertyChanged(); MarkDirty(); } }
    public float CustomRecipeEffectivenessMultiplier { get => _settings.CustomRecipeEffectivenessMultiplier; set { _settings.CustomRecipeEffectivenessMultiplier = value; OnPropertyChanged(); MarkDirty(); } }
    public float PassiveTameFoodConsumptionMultiplier { get => _settings.PassiveTameFoodConsumptionMultiplier; set { _settings.PassiveTameFoodConsumptionMultiplier = value; OnPropertyChanged(); MarkDirty(); } }
    public bool UnlockAllEngrams { get => _settings.UnlockAllEngrams; set { _settings.UnlockAllEngrams = value; OnPropertyChanged(); MarkDirty(); } }

    // ── Collections ───────────────────────────────────────────────────────
    public ObservableCollection<LootDropOverride> LootDrops { get; }
    public ObservableCollection<EngramOverride> Engrams { get; }
    public ObservableCollection<CreatureMultiplier> Creatures { get; }

    public ObservableCollection<LootItemSet>? CurrentSets =>
        SelectedDrop != null ? new ObservableCollection<LootItemSet>(SelectedDrop.ItemSets) : null;

    public ObservableCollection<LootItem>? CurrentItems =>
        SelectedSet != null ? new ObservableCollection<LootItem>(SelectedSet.Items) : null;

    public LootDropOverride? SelectedDrop
    {
        get => _selectedDrop;
        set { SetField(ref _selectedDrop, value); OnPropertyChanged(nameof(CurrentSets)); }
    }
    public LootItemSet? SelectedSet
    {
        get => _selectedSet;
        set { SetField(ref _selectedSet, value); OnPropertyChanged(nameof(CurrentItems)); }
    }
    public EngramOverride? SelectedEngram { get => _selectedEngram; set => SetField(ref _selectedEngram, value); }
    public CreatureMultiplier? SelectedCreature { get => _selectedCreature; set => SetField(ref _selectedCreature, value); }
    public StackSizeOverride? SelectedStackSize { get => _selectedStackSize; set => SetField(ref _selectedStackSize, value); }
    public HarvestMultiplier? SelectedHarvest { get => _selectedHarvest; set => SetField(ref _selectedHarvest, value); }
    public SpawnOverride? SelectedSpawn { get => _selectedSpawn; set => SetField(ref _selectedSpawn, value); }

    public ObservableCollection<StackSizeOverride> StackSizes { get; }
    public ObservableCollection<HarvestMultiplier> Harvests { get; }
    public ObservableCollection<SpawnOverride> SpawnOverrides { get; }
    public ObservableCollection<PerLevelStatRow> PerLevelRows { get; private set; } = new();

    public bool UseCustomPerLevelStats
    {
        get => _settings.UseCustomPerLevelStats;
        set { _settings.UseCustomPerLevelStats = value; OnPropertyChanged(); MarkDirty(); }
    }

    // ── Cluster ───────────────────────────────────────────────────────────
    public string ClusterID { get => _settings.Cluster.ClusterID; set { _settings.Cluster.ClusterID = value; OnPropertyChanged(); MarkDirty(); } }
    public string ClusterPassword { get => _settings.Cluster.ClusterPassword; set { _settings.Cluster.ClusterPassword = value; OnPropertyChanged(); MarkDirty(); } }
    public bool PreventDownloadSurvivors { get => _settings.Cluster.PreventDownloadSurvivors; set { _settings.Cluster.PreventDownloadSurvivors = value; OnPropertyChanged(); MarkDirty(); } }
    public bool PreventDownloadItems { get => _settings.Cluster.PreventDownloadItems; set { _settings.Cluster.PreventDownloadItems = value; OnPropertyChanged(); MarkDirty(); } }
    public bool PreventDownloadDinos { get => _settings.Cluster.PreventDownloadDinos; set { _settings.Cluster.PreventDownloadDinos = value; OnPropertyChanged(); MarkDirty(); } }
    public bool PreventUploadSurvivors { get => _settings.Cluster.PreventUploadSurvivors; set { _settings.Cluster.PreventUploadSurvivors = value; OnPropertyChanged(); MarkDirty(); } }
    public bool PreventUploadItems { get => _settings.Cluster.PreventUploadItems; set { _settings.Cluster.PreventUploadItems = value; OnPropertyChanged(); MarkDirty(); } }
    public bool PreventUploadDinos { get => _settings.Cluster.PreventUploadDinos; set { _settings.Cluster.PreventUploadDinos = value; OnPropertyChanged(); MarkDirty(); } }
    public bool NoTributeDownloads { get => _settings.Cluster.NoTributeDownloads; set { _settings.Cluster.NoTributeDownloads = value; OnPropertyChanged(); MarkDirty(); } }
    public bool bAllowUnlimitedRespecs { get => _settings.Cluster.bAllowUnlimitedRespecs; set { _settings.Cluster.bAllowUnlimitedRespecs = value; OnPropertyChanged(); MarkDirty(); } }
    public int TributeItemExpirationSeconds { get => _settings.Cluster.TributeItemExpirationSeconds; set { _settings.Cluster.TributeItemExpirationSeconds = value; OnPropertyChanged(); MarkDirty(); } }
    public int TributeDinoExpirationSeconds { get => _settings.Cluster.TributeDinoExpirationSeconds; set { _settings.Cluster.TributeDinoExpirationSeconds = value; OnPropertyChanged(); MarkDirty(); } }
    public int MaxTribeLogs { get => _settings.Cluster.MaxTribeLogs; set { _settings.Cluster.MaxTribeLogs = value; OnPropertyChanged(); MarkDirty(); } }

    // ── RCON ──────────────────────────────────────────────────────────────
    public bool RCONEnabled { get => _settings.RCON.RCONEnabled; set { _settings.RCON.RCONEnabled = value; OnPropertyChanged(); MarkDirty(); } }
    public int RCONPort { get => _settings.RCON.RCONPort; set { _settings.RCON.RCONPort = value; OnPropertyChanged(); MarkDirty(); } }
    public int RCONServerGameLogBuffer { get => _settings.RCON.RCONServerGameLogBuffer; set { _settings.RCON.RCONServerGameLogBuffer = value; OnPropertyChanged(); MarkDirty(); } }

    // ── Search ────────────────────────────────────────────────────────────
    public string EngramSearch
    {
        get => _engramSearch;
        set { SetField(ref _engramSearch, value); OnPropertyChanged(nameof(FilteredEngrams)); }
    }
    public string CreatureSearch
    {
        get => _creatureSearch;
        set { SetField(ref _creatureSearch, value); OnPropertyChanged(nameof(FilteredCreatures)); }
    }
    public IEnumerable<EngramOverride> FilteredEngrams =>
        string.IsNullOrWhiteSpace(EngramSearch) ? Engrams
        : Engrams.Where(e => e.DisplayName.Contains(EngramSearch, StringComparison.OrdinalIgnoreCase)
                          || e.EngramClassName.Contains(EngramSearch, StringComparison.OrdinalIgnoreCase));
    public IEnumerable<CreatureMultiplier> FilteredCreatures =>
        string.IsNullOrWhiteSpace(CreatureSearch) ? Creatures
        : Creatures.Where(c => c.DisplayName.Contains(CreatureSearch, StringComparison.OrdinalIgnoreCase)
                            || c.DinoClassName.Contains(CreatureSearch, StringComparison.OrdinalIgnoreCase));

    // ── Map List ──────────────────────────────────────────────────────────
    public static IReadOnlyList<string> ArkMaps { get; } = new[]
    {
        "TheIsland", "ScorchedEarth_P", "Ragnarok", "Aberration_P", "Extinction",
        "Valguero_P", "Genesis", "CrystalIsles", "Gen2", "LostIsland", "Fjordur",
        "TheCenter", "TheIsland_WP", "ScorchedEarth_WP", "Aberration_WP",
        "Extinction_WP", "Genesis2_WP"
    };

    // ── Templates ─────────────────────────────────────────────────────────
    public IReadOnlyList<ServerTemplate> Templates => TemplateService.GetArkTemplates();

    // ── Library Data ─────────────────────────────────────────────────────
    public IReadOnlyList<ArkEngram> EngramLibrary => EngramLibraryService.GetEngramsForGame(Game);
    public IReadOnlyList<ArkLootContainer> LootContainerLibrary => EngramLibraryService.GetLootContainersForGame(Game);

    // ── Preview ───────────────────────────────────────────────────────────
    public string GeneratedGameIni { get => _generatedGameIni; private set => SetField(ref _generatedGameIni, value); }
    public string GeneratedGUS { get => _generatedGUS; private set => SetField(ref _generatedGUS, value); }

    public int SelectedTab { get => _selectedTab; set { SetField(ref _selectedTab, value); GeneratePreview(); } }

    // ── Commands ──────────────────────────────────────────────────────────
    public ICommand SaveCommand { get; }
    public ICommand SaveAsFileCommand { get; }
    public ICommand CopyGameIniCommand { get; }
    public ICommand CopyGUSCommand { get; }
    public ICommand GoBackCommand { get; }
    public ICommand AddLootDropCommand { get; }
    public ICommand RemoveLootDropCommand { get; }
    public ICommand AddLootSetCommand { get; }
    public ICommand RemoveLootSetCommand { get; }
    public ICommand AddLootItemCommand { get; }
    public ICommand AddEngramCommand { get; }
    public ICommand RemoveEngramCommand { get; }
    public ICommand AddCreatureCommand { get; }
    public ICommand RemoveCreatureCommand { get; }
    public ICommand AddStackSizeCommand { get; }
    public ICommand RemoveStackSizeCommand { get; }
    public ICommand AddHarvestCommand { get; }
    public ICommand RemoveHarvestCommand { get; }
    public ICommand AddSpawnCommand { get; }
    public ICommand RemoveSpawnCommand { get; }
    public ICommand LoadTemplateCommand { get; }
    public ICommand ImportConfigCommand { get; }
    public ICommand ResetPerLevelStatsCommand { get; }

    private void MarkDirty()
    {
        IsDirty = true;
        GeneratePreview();
    }

    public void GeneratePreview()
    {
        SyncCollections();
        var (gameIni, gus) = ArkConfigGeneratorService.Generate(_settings);
        GeneratedGameIni = gameIni;
        GeneratedGUS = gus;
    }

    private void SyncCollections()
    {
        _settings.LootDropOverrides = LootDrops.ToList();
        _settings.EngramOverrides = Engrams.ToList();
        _settings.CreatureMultipliers = Creatures.ToList();
        _settings.StackSizeOverrides = StackSizes.ToList();
        _settings.HarvestMultipliers = Harvests.ToList();
        _settings.SpawnOverrides = SpawnOverrides.ToList();
        SyncPerLevelStats();
    }

    private void SyncPerLevelStats()
    {
        var ps = _settings.PerLevelStats;
        foreach (var row in PerLevelRows)
        {
            ps.Player[row.Index] = row.Player;
            ps.PlayerTamed[row.Index] = row.PlayerTamed;
            ps.DinoWild[row.Index] = row.DinoWild;
            ps.DinoTamed[row.Index] = row.DinoTamed;
            ps.DinoTamedAdd[row.Index] = row.DinoTamedAdd;
            ps.DinoTamedAffinity[row.Index] = row.DinoTamedAffinity;
        }
    }

    private ObservableCollection<PerLevelStatRow> BuildPerLevelRows()
    {
        var ps = _settings.PerLevelStats;
        var rows = new ObservableCollection<PerLevelStatRow>();
        for (int i = 0; i < 12; i++)
            rows.Add(new PerLevelStatRow
            {
                Index = i,
                StatName = PerLevelStats.StatNames[i],
                Player = ps.Player[i],
                PlayerTamed = ps.PlayerTamed[i],
                DinoWild = ps.DinoWild[i],
                DinoTamed = ps.DinoTamed[i],
                DinoTamedAdd = ps.DinoTamedAdd[i],
                DinoTamedAffinity = ps.DinoTamedAffinity[i]
            });
        return rows;
    }

    private async Task SaveAsync()
    {
        SyncCollections();
        _project.Name = ProjectName;
        _project.Description = ProjectDescription;
        _project.ConfigJson = JsonSerializer.Serialize(_settings);
        await _db.SaveProjectAsync(_project);
        IsDirty = false;
        MessageBox.Show("Projekt gespeichert!", "Gespeichert", MessageBoxButton.OK, MessageBoxImage.Information);
    }

    private void SaveAsFile()
    {
        SyncCollections();
        var dialog = new SaveFileDialog
        {
            Title = "Config-Ordner wählen – Game.ini speichern",
            Filter = "INI Files (*.ini)|*.ini|All Files (*.*)|*.*",
            FileName = "Game.ini"
        };
        if (dialog.ShowDialog() != true) return;

        var dir = Path.GetDirectoryName(dialog.FileName)!;
        var (gameIni, gus) = ArkConfigGeneratorService.Generate(_settings);
        File.WriteAllText(Path.Combine(dir, "Game.ini"), gameIni);
        File.WriteAllText(Path.Combine(dir, "GameUserSettings.ini"), gus);
        MessageBox.Show($"Game.ini & GameUserSettings.ini gespeichert in:\n{dir}",
            "Export erfolgreich", MessageBoxButton.OK, MessageBoxImage.Information);
    }

    private static void CopyToClipboard(string text)
    {
        Clipboard.SetText(text);
        MessageBox.Show("In die Zwischenablage kopiert!", "Kopiert", MessageBoxButton.OK, MessageBoxImage.Information);
    }

    // ── Loot Drop Actions ─────────────────────────────────────────────────
    private void AddLootDrop()
    {
        var drop = new LootDropOverride { DropName = "Neuer Drop" };
        LootDrops.Add(drop);
        SelectedDrop = drop;
        MarkDirty();
    }

    private void RemoveLootDrop()
    {
        if (SelectedDrop == null) return;
        LootDrops.Remove(SelectedDrop);
        SelectedDrop = null;
        MarkDirty();
    }

    private void AddLootSet()
    {
        if (SelectedDrop == null) return;
        var set = new LootItemSet { SetName = "Set " + (SelectedDrop.ItemSets.Count + 1) };
        SelectedDrop.ItemSets.Add(set);
        OnPropertyChanged(nameof(CurrentSets));
        SelectedSet = set;
        MarkDirty();
    }

    private void RemoveLootSet()
    {
        if (SelectedDrop == null || SelectedSet == null) return;
        SelectedDrop.ItemSets.Remove(SelectedSet);
        OnPropertyChanged(nameof(CurrentSets));
        SelectedSet = null;
        MarkDirty();
    }

    private void AddLootItem()
    {
        if (SelectedSet == null) return;
        SelectedSet.Items.Add(new LootItem { DisplayName = "New Item", ItemClassString = "PrimalItemResource_Wood_C" });
        OnPropertyChanged(nameof(CurrentItems));
        MarkDirty();
    }

    // ── Engram Actions ────────────────────────────────────────────────────
    private void AddEngram()
    {
        var eng = new EngramOverride { EngramClassName = "EngramEntry_NewEngram_C", DisplayName = "New Engram" };
        Engrams.Add(eng);
        SelectedEngram = eng;
        MarkDirty();
    }

    private void RemoveEngram()
    {
        if (SelectedEngram == null) return;
        Engrams.Remove(SelectedEngram);
        SelectedEngram = null;
        MarkDirty();
    }

    // ── Creature Actions ──────────────────────────────────────────────────
    private void AddCreature()
    {
        var cr = new CreatureMultiplier { DisplayName = "New Creature", DinoClassName = "Dino_BP_C" };
        Creatures.Add(cr);
        SelectedCreature = cr;
        MarkDirty();
    }

    private void RemoveCreature()
    {
        if (SelectedCreature == null) return;
        Creatures.Remove(SelectedCreature);
        SelectedCreature = null;
        MarkDirty();
    }

    // ── StackSize Actions ─────────────────────────────────────────────────
    private void AddStackSize()
    {
        var s = new StackSizeOverride { DisplayName = "Neuer Stack Override", ItemClassString = "PrimalItemResource_Wood_C", MaxItemQuantity = 500 };
        StackSizes.Add(s);
        SelectedStackSize = s;
        MarkDirty();
    }

    private void RemoveStackSize()
    {
        if (SelectedStackSize == null) return;
        StackSizes.Remove(SelectedStackSize);
        SelectedStackSize = null;
        MarkDirty();
    }

    // ── Harvest Actions ───────────────────────────────────────────────────
    private void AddHarvest()
    {
        var h = new HarvestMultiplier { DisplayName = "Neuer Harvest Override", ClassName = "PrimalItemResource_Wood_C", Multiplier = 2f };
        Harvests.Add(h);
        SelectedHarvest = h;
        MarkDirty();
    }

    private void RemoveHarvest()
    {
        if (SelectedHarvest == null) return;
        Harvests.Remove(SelectedHarvest);
        SelectedHarvest = null;
        MarkDirty();
    }

    // ── Spawn Actions ─────────────────────────────────────────────────────
    private void AddSpawn()
    {
        var s = new SpawnOverride { DisplayName = "Neuer Spawn Override" };
        SpawnOverrides.Add(s);
        SelectedSpawn = s;
        MarkDirty();
    }

    private void RemoveSpawn()
    {
        if (SelectedSpawn == null) return;
        SpawnOverrides.Remove(SelectedSpawn);
        SelectedSpawn = null;
        MarkDirty();
    }

    // ── Template Loading ──────────────────────────────────────────────────
    private void LoadTemplate(ServerTemplate? template)
    {
        if (template?.Settings == null) return;
        var result = MessageBox.Show(
            $"Template \"{ template.Name}\" laden?\nAlle aktuellen Einstellungen werden überschrieben!",
            "Template laden", MessageBoxButton.YesNo, MessageBoxImage.Question);
        if (result != MessageBoxResult.Yes) return;

        var oldName = _settings.ServerName;
        _settings = template.Settings;
        _settings.ServerName = oldName;
        RefreshAllProperties();
        MarkDirty();
    }

    private void RefreshAllProperties()
    {
        LootDrops.Clear(); foreach (var d in _settings.LootDropOverrides) LootDrops.Add(d);
        Engrams.Clear(); foreach (var e in _settings.EngramOverrides) Engrams.Add(e);
        Creatures.Clear(); foreach (var c in _settings.CreatureMultipliers) Creatures.Add(c);
        StackSizes.Clear(); foreach (var s in _settings.StackSizeOverrides) StackSizes.Add(s);
        Harvests.Clear(); foreach (var h in _settings.HarvestMultipliers) Harvests.Add(h);
        SpawnOverrides.Clear(); foreach (var s in _settings.SpawnOverrides) SpawnOverrides.Add(s);
        PerLevelRows.Clear(); foreach (var r in BuildPerLevelRows()) PerLevelRows.Add(r);
        OnPropertyChanged(string.Empty);
    }

    // ── Config Import ─────────────────────────────────────────────────────
    private void ImportConfig()
    {
        var dlg = new OpenFileDialog
        {
            Title = "GameUserSettings.ini importieren",
            Filter = "INI Files (*.ini)|*.ini|All Files (*.*)|*.*",
            FileName = "GameUserSettings.ini"
        };
        if (dlg.ShowDialog() != true) return;
        var gusContent = File.ReadAllText(dlg.FileName);

        string gameIniContent = "";
        var dir = Path.GetDirectoryName(dlg.FileName)!;
        var gameIniPath = Path.Combine(dir, "Game.ini");
        if (File.Exists(gameIniPath))
            gameIniContent = File.ReadAllText(gameIniPath);
        else
        {
            var g = new OpenFileDialog { Title = "Game.ini importieren (optional)", Filter = "INI Files (*.ini)|*.ini", FileName = "Game.ini" };
            if (g.ShowDialog() == true) gameIniContent = File.ReadAllText(g.FileName);
        }

        var oldName = _settings.ServerName;
        _settings = ConfigImportService.ImportArk(gusContent, gameIniContent);
        if (string.IsNullOrEmpty(_settings.ServerName)) _settings.ServerName = oldName;
        RefreshAllProperties();
        MarkDirty();
        MessageBox.Show("Config erfolgreich importiert!", "Import", MessageBoxButton.OK, MessageBoxImage.Information);
    }

    // ── Reset PerLevelStats ───────────────────────────────────────────────
    private void ResetPerLevelStats()
    {
        _settings.PerLevelStats = new PerLevelStats();
        PerLevelRows.Clear();
        foreach (var r in BuildPerLevelRows()) PerLevelRows.Add(r);
        MarkDirty();
    }
}
24Fire Wuppertaler Tafel Romeo