using System.Collections.ObjectModel; using System.Reactive.Disposables; using System.Reactive.Linq; using Microsoft.EntityFrameworkCore; using DynamicData; using ReactiveUI; using ReactiveUI.SourceGenerators; using StarsAssistant.Services; using Splat; using StarsAssistant.Model; namespace StarsAssistant.ViewModels; public partial class PlayerPlanetViewModel : ViewModelBase { private readonly Model.Planet Planet; private readonly Services.Game Game; private readonly IObservable> _fleetSummaryChanges; public PlayerPlanetViewModel(Model.Planet planet) { Game = Locator.Current.GetService()!; if (planet.OwnerId != Services.Game.Player.Name) throw new InvalidOperationException("PlayerPlanet ViewModels can only be created for player planets."); Planet = planet; PopulationTargetPercent = planet.PopulationTargetPercent; _populationTargetHelper = this .WhenAnyValue(vm => vm.PopulationTargetPercent) .Select(popTgt => this.MaxPopulation * popTgt / 10000 * 100) .ToProperty(this, vm => vm.PopulationTarget); _populationToShipHelper = this .WhenAnyValue(vm => vm.PopulationTarget) .Select(popToShip => this.ComputePopulationToShip()) .ToProperty(this, vm => vm.PopulationToShip); _targetFactoriesHelper = this .WhenAnyValue(vm => vm.PopulationTarget) .Select(tgtFact => GameEngine.MaxOperableFactoriesForPlayer(_populationTarget, Value)) .ToProperty(this, vm => vm.TargetFactories); _remainingFactoriesHelper = this .WhenAnyValue(vm => vm.TargetFactories) .Select(factRem => Factories < _targetFactories ? _targetFactories - Factories : 0) .ToProperty(this, vm => vm.RemainingFactories); _factoryGermaniumDeltaHelper = this .WhenAnyValue(vm => vm.RemainingFactories) .Select(gerCost => ComputeFactoryGermaniumDelta()) .ToProperty(this, vm => vm.FactoryGermaniumDelta); _remainingMinesHelper = this .WhenAnyValue(vm => vm.PopulationTarget) .Select(remMines => Math.Max(0, GameEngine.MaxOperableMinesForPlayer(_populationTarget, Value) - Mines)) .ToProperty(this, vm => vm.RemainingMines); FleetManager fm = Locator.Current.GetService()!; _fleetSummaryChanges = fm.FleetSummariesByDestination .Connect() .Watch(Name) .Log(this, "fleetSummaryChange", change => $"{Name}: {change.Reason}: {change.Previous} => {change.Current}") ; _populationEnRouteHelper = _fleetSummaryChanges .Select(change => change.Reason != ChangeReason.Remove ? change.Current.TotalColonists : 0) .Log(this, "PopulationEnRoute", pop => $"{Name}: {pop}") .ToProperty(this, vm => vm.PopulationEnRoute) ; this.WhenActivated((CompositeDisposable disposables) => { disposables.Add(_populationEnRouteHelper); // /* handle activation */ // Disposable // .Create(() => { /* handle deactivation */ }) // .DisposeWith(disposables); }); } #region Database Properties public string Name => Planet.Name; public string Owner => Planet.OwnerId; public string StarbaseType => Planet.StarbaseType; public int Population => Planet.Population ?? 0; public int Value => Planet.Value ?? 0; public int Mines => Planet.Mines ?? 0; public int Factories => Planet.Factories ?? 0; public decimal DefPercent => Planet.DefPercent ?? 0; public int SurfaceIronium => Planet.SurfaceIronium ?? 0; public int SurfaceBoranium => Planet.SurfaceBoranium ?? 0; public int SurfaceGermanium => Planet.SurfaceGermanium ?? 0; public int MRIronium => Planet.MRIronium ?? 0; public int MRBoranium => Planet.MRBoranium ?? 0; public int MRGermanium => Planet.MRGermanium ?? 0; public int MCIronium => Planet.MCIronium ?? 0; public int MCBoranium => Planet.MCBoranium ?? 0; public int MCGermanium => Planet.MCGermanium ?? 0; public int Resources => Planet.Resources ?? 0; public decimal Gravity => Planet.Gravity ?? 0; public decimal Temperature => Planet.Temperature ?? 0; public decimal Radiation => Planet.Radiation ?? 0; public decimal GravityOrig => Planet.GravityOrig ?? 0; public decimal TemperatureOrig => Planet.TemperatureOrig ?? 0; public decimal RadiationOrig => Planet.RadiationOrig ?? 0; public int MaxTerraforming => Planet.MaxTerraforming ?? 0; public int CapacityPercent => Planet.CapacityPercent ?? 0; public int ScanRange => Planet.ScanRange ?? 0; public int PenScanRange => Planet.PenScanRange ?? 0; public int Driver => Planet.Driver ?? 0; public int DriverWarp => Planet.DriverWarp ?? 0; public string RouteTarget => Planet.RouteTarget; public int GateRange => Planet.GateRange ?? 0; public int GateMass => Planet.GateMass ?? 0; public int PctDamage => Planet.PctDamage ?? 0; [Reactive] private int _populationTargetPercent; #endregion #region Derived Properties public int EffectiveValue => GameEngine.EffectivePlanetValue(Value); public int MaxPopulation => GameEngine.MaxPopOnPlanetForPlayer(Value); public int PopulationGrowth => GameEngine.PlanetPopGrowthForPlayer(Population, Value); public int PopulationT1 => Population + PopulationGrowth /* TODO + Pop En Route */; public int BuildableFactories => GameEngine.MaxBuildableFactoriesForPlayer(Value); public int BuildableMines => GameEngine.MaxBuildableMinesForPlayer(Value); public int OperableFactories => GameEngine.MaxOperableFactoriesForPlayer(Population, Value); public int OperableMines => GameEngine.MaxOperableMinesForPlayer(Population, Value); public int ResourcesFromPopulation => GameEngine.ResourcesFromPopForPlayer(Population, Value); public int ResourcesFromFactories => GameEngine.ResourcesFromFactForPlayer(Population, Factories, Value); public int AvailableIronium => MRIronium + SurfaceIronium; public int AvailableBoranium => MRBoranium + SurfaceBoranium; public int AvailableGermanium => MRGermanium + SurfaceGermanium; [ObservableAsProperty] private int _populationTarget; [ObservableAsProperty] private int _populationToShip; [ObservableAsProperty] private int _targetFactories; [ObservableAsProperty] private int _remainingFactories; [ObservableAsProperty] private int _factoryGermaniumDelta; [ObservableAsProperty] private int _remainingMines; [ObservableAsProperty] private int _populationEnRoute; #endregion #region Helper functions private int ComputePopulationToShip() { if (Population >= _populationTarget) return Population - _populationTarget; if (PopulationT1 < _populationTarget) return PopulationT1 - _populationTarget; return 0; } private int ComputeFactoryGermaniumDelta() { int gerReq = GameEngine.FactoryGermaniumCostForPlayer(_remainingFactories); if (gerReq < AvailableGermanium) return Math.Max(0, SurfaceGermanium - gerReq); return AvailableGermanium + MRGermanium - gerReq; // TODO: Extrapolate to T1, so that excess Germanium is visible. // TODO: Take shipping into account. } /* private int ComputePopulationEnRoute() { using var db = Locator.Current.GetService()!; var enRoute = from flt in db.Fleet where flt.OwnerFileId == Game.Player.PlayerFileId && flt.TrueDestination == Planet.Name select new { Colonists = flt.Sum(f => f.Colonists) }; return enRoute.Colonists ?? 0; } */ #endregion }