using System.Text; using System.Text.Json; using System.Text.Json.Serialization; using Model.BuildOrders; using Model.Entity; using Model.Entity.Data; using Model.Feedback; using Model.Types; using YamlDotNet.Serialization; namespace Services.Immortal; public class BuildOrderService : IBuildOrderService { private readonly BuildOrderModel buildOrder = new(); private int lastInterval; private readonly IToastService toastService; public BuildOrderService(IToastService toastService) { this.toastService = toastService; Reset(); } public int BuildingInputDelay { get; set; } = 2; public Dictionary> StartedOrders => buildOrder.StartedOrders; public Dictionary> CompletedOrders => buildOrder.CompletedOrders; public Dictionary UniqueCompletedTimes => buildOrder.UniqueCompletedTimes; public Dictionary SupplyCountTimes => buildOrder.SupplyCountTimes; public int GetLastRequestInterval() { return lastInterval; } public Dictionary> GetOrders() { return buildOrder.StartedOrders; } public void Subscribe(Action action) { OnChange += action; } public void Unsubscribe(Action action) { OnChange -= action; } public void Add(EntityModel entity, int atInterval) { if (!buildOrder.StartedOrders.ContainsKey(atInterval)) buildOrder.StartedOrders.Add(atInterval, new List()); var production = entity.Production(); var completedTime = atInterval; if (production != null) completedTime += production.BuildTime; if (!buildOrder.CompletedOrders.ContainsKey(completedTime)) buildOrder.CompletedOrders.Add(completedTime, new List()); var supply = entity.Supply(); buildOrder.StartedOrders[atInterval].Add(entity.Clone()); buildOrder.CompletedOrders[completedTime].Add(entity.Clone()); if (!buildOrder.UniqueCompletedTimes.ContainsKey(entity.DataType)) buildOrder.UniqueCompletedTimes.Add(entity.DataType, atInterval); if (!buildOrder.UniqueCompletedCount.ContainsKey(entity.DataType)) buildOrder.UniqueCompletedCount.Add(entity.DataType, 1); else buildOrder.UniqueCompletedCount[entity.DataType]++; //entity. if (!buildOrder.UniqueCompleted.ContainsKey(entity.DataType)) buildOrder.UniqueCompleted.Add(entity.DataType, new Dictionary>()); if (!buildOrder.UniqueCompleted[entity.DataType].ContainsKey(completedTime)) buildOrder.UniqueCompleted[entity.DataType].Add(completedTime, new List()); buildOrder.UniqueCompleted[entity.DataType][completedTime].Add(entity); if (supply != null) { if (!supply.Takes.Equals(0)) buildOrder.CurrentSupplyUsed += supply.Takes; if (!supply.Grants.Equals(0)) buildOrder.SupplyCountTimes.Add(buildOrder.SupplyCountTimes.Last().Key + supply.Grants, completedTime); } if (atInterval > lastInterval) lastInterval = atInterval; NotifyDataChanged(); } public bool AddWait(int forInterval) { if (forInterval < 0) { toastService.AddToast(new ToastModel(){SeverityType = SeverityType.Error, Title = "Wait", Message = "This should never happen."}); return false; }; lastInterval += forInterval; if (!buildOrder.StartedOrders.ContainsKey(lastInterval)) buildOrder.StartedOrders.Add(lastInterval, new List()); if (!buildOrder.CompletedOrders.ContainsKey(lastInterval)) buildOrder.CompletedOrders.Add(lastInterval, new List()); NotifyDataChanged(); return true; } public bool AddWaitTo(int interval) { if (interval <= lastInterval) { toastService.AddToast(new ToastModel(){SeverityType = SeverityType.Error, Title = "Logic Error", Message = "You cannot wait to a time that has already elapsed."}); return false; } lastInterval = interval; if (!buildOrder.StartedOrders.ContainsKey(lastInterval)) buildOrder.StartedOrders.Add(lastInterval, new List()); if (!buildOrder.CompletedOrders.ContainsKey(lastInterval)) buildOrder.CompletedOrders.Add(lastInterval, new List()); NotifyDataChanged(); return true; } public int? WillMeetRequirements(EntityModel entity) { var requirements = entity.Requirements(); if (requirements.Count == 0) return 0; var metTime = 0; foreach (var requiredEntity in requirements) if (buildOrder.UniqueCompletedTimes.TryGetValue(requiredEntity.Id, out var completedTime)) { if (completedTime > metTime) metTime = completedTime; } else { return null; } return metTime; } public int? WillMeetSupply(EntityModel entity) { var supply = entity.Supply(); if (supply == null || supply.Takes.Equals(0)) return 0; foreach (var supplyAtTime in buildOrder.SupplyCountTimes) if (supply.Takes + buildOrder.CurrentSupplyUsed < supplyAtTime.Key) return supplyAtTime.Value; return null; } public bool Add(EntityModel entity, IEconomyService withEconomy) { var atInterval = lastInterval; if (!HandleSupply(entity, ref atInterval)) return false; if (!HandleRequirements(entity, ref atInterval)) return false; if (!HandleEconomy(entity, withEconomy, ref atInterval)) return false; Add(entity, atInterval); return true; } public void RemoveLast() { if (buildOrder.StartedOrders.Keys.Count > 1) { if (buildOrder.StartedOrders.Count == 0) { buildOrder.StartedOrders.Remove(buildOrder.StartedOrders.Last().Key); buildOrder.CompletedOrders.Remove(buildOrder.CompletedOrders.Last().Key); lastInterval = buildOrder.StartedOrders.Last().Key; return; } var lastStarted = buildOrder.StartedOrders.Keys.Last(); var lastCompleted = buildOrder.CompletedOrders.Keys.Last(); EntityModel entityRemoved = default!; if (buildOrder.StartedOrders[lastStarted].Count > 0) { entityRemoved = buildOrder.StartedOrders[lastStarted].Last(); buildOrder.StartedOrders[lastStarted].Remove(buildOrder.StartedOrders[lastStarted].Last()); buildOrder.CompletedOrders[lastCompleted].Remove(buildOrder.CompletedOrders[lastCompleted].Last()); } if (buildOrder.StartedOrders[lastStarted].Count == 0) buildOrder.StartedOrders.Remove(lastStarted); if (buildOrder.CompletedOrders[lastCompleted].Count == 0) buildOrder.CompletedOrders.Remove(lastCompleted); if (buildOrder.StartedOrders.Keys.Count > 0) lastInterval = buildOrder.StartedOrders.Keys.Last(); else lastInterval = 0; if (entityRemoved.Supply()?.Grants > 0) SupplyCountTimes.Remove(SupplyCountTimes.Last().Key); if (entityRemoved.Supply()?.Takes > 0) buildOrder.CurrentSupplyUsed -= entityRemoved.Supply()!.Takes; buildOrder.UniqueCompletedCount[entityRemoved!.DataType]--; if (buildOrder.UniqueCompletedCount[entityRemoved!.DataType] == 0) UniqueCompletedTimes.Remove(entityRemoved.DataType); if (entityRemoved.Info().Descriptive == DescriptiveType.Worker) { RemoveLast(); return; } NotifyDataChanged(); } } public string AsJson() { var options = new JsonSerializerOptions { WriteIndented = true }; options.Converters.Add(new JsonStringEnumConverter()); return JsonSerializer.Serialize(buildOrder, options); } public string BuildOrderAsYaml() { var stringBuilder = new StringBuilder(); var serializer = new Serializer(); stringBuilder.AppendLine(serializer.Serialize(buildOrder)); var buildOrderText = stringBuilder.ToString(); return buildOrderText; } public List GetCompletedBefore(int interval) { return (from ordersAtTime in buildOrder.StartedOrders from orders in ordersAtTime.Value where ordersAtTime.Key + (orders.Production() == null ? 0 : orders.Production().BuildTime) <= interval select orders).ToList(); } public List GetHarvestPointsCompletedBefore(int interval) { return (from ordersAtTime in buildOrder.StartedOrders from orders in ordersAtTime.Value where ordersAtTime.Key + (orders.Production() == null ? 0 : orders.Production().BuildTime) <= interval where orders.Harvest() != null select orders).ToList(); } public void SetName(string name) { buildOrder.Name = name; NotifyDataChanged(); } public string GetName() { return buildOrder.Name; } public void SetNotes(string notes) { buildOrder.Notes = notes; NotifyDataChanged(); } public string GetNotes() { return buildOrder.Notes; } public void DeprecatedSetColor(string color) { } public string GetColor() { return ""; } public void Reset() { lastInterval = 0; buildOrder.Initialize(DataType.FACTION_Aru); NotifyDataChanged(); } public bool AddWaitTo(int interval, TimingService timingService) { if (lastInterval >= interval) return false; if (interval > timingService.GetAttackTime()) return false; if (!buildOrder.StartedOrders.ContainsKey(lastInterval)) buildOrder.StartedOrders.Add(lastInterval, new List()); if (!buildOrder.CompletedOrders.ContainsKey(lastInterval)) buildOrder.CompletedOrders.Add(lastInterval, new List()); NotifyDataChanged(); return true; } public int? WillMeetTrainingQueue(EntityModel entity) { var supply = entity.Supply(); if (supply == null || supply.Takes.Equals(0)) return 0; foreach (var supplyAtTime in buildOrder.SupplyCountTimes) if (supply.Takes + buildOrder.CurrentSupplyUsed < supplyAtTime.Key) return supplyAtTime.Value; return null; } private event Action OnChange = null!; private bool HandleEconomy(EntityModel entity, IEconomyService withEconomy, ref int atInterval) { var production = entity.Production(); if (production == null) return true; for (var interval = atInterval; interval < withEconomy.GetOverTime().Count; interval++) { var economyAtSecond = withEconomy.GetOverTime()[interval]; if (economyAtSecond.Alloy >= production.Alloy && economyAtSecond.Ether >= production.Ether && economyAtSecond.Pyre >= production.Pyre) { atInterval = interval; if (entity.EntityType != EntityType.Army) atInterval += BuildingInputDelay; return true; } } if (withEconomy.GetOverTime().Last().Ether < production.Ether) toastService.AddToast(new ToastModel { Title = "Not Enough Ether", Message = "Build more ether extractors!", SeverityType = SeverityType.Error }); if (withEconomy.GetOverTime().Last().Alloy < production.Alloy) toastService.AddToast(new ToastModel { Title = "Not Enough Alloy", Message = "Build more bases!", SeverityType = SeverityType.Error }); return false; } private bool HandleSupply(EntityModel entity, ref int atInterval) { var minSupplyInterval = WillMeetSupply(entity); if (minSupplyInterval == null) { toastService.AddToast(new ToastModel { Title = "Supply Cap Reached", Message = "Build more supply!", SeverityType = SeverityType.Error }); return false; } if (minSupplyInterval > atInterval) atInterval = (int)minSupplyInterval; return true; } private bool HandleTrainingQueue(EntityModel entity, ref int atInterval) { var minSupplyInterval = WillMeetSupply(entity); if (minSupplyInterval == null) { toastService.AddToast(new ToastModel { Title = "Supply Cap Reached", Message = "Build more supply!", SeverityType = SeverityType.Error }); return false; } if (minSupplyInterval > atInterval) atInterval = (int)minSupplyInterval; return true; } private bool HandleRequirements(EntityModel entity, ref int atInterval) { var minRequirementInterval = WillMeetRequirements(entity); if (minRequirementInterval == null) { toastService.AddToast(new ToastModel { Title = "Missing Requirements", Message = "You don't have what's needed for this unit.", SeverityType = SeverityType.Error }); return false; } if (minRequirementInterval > atInterval) atInterval = (int)minRequirementInterval; return true; } private void NotifyDataChanged() { OnChange?.Invoke(); } }