Initial Commit
This commit is contained in:
@@ -0,0 +1,497 @@
|
||||
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 readonly ITimingService _timingService;
|
||||
|
||||
private readonly IToastService _toastService;
|
||||
|
||||
private int _lastInterval;
|
||||
|
||||
public BuildOrderService(IToastService toastService, ITimingService timingService)
|
||||
{
|
||||
_toastService = toastService;
|
||||
_timingService = timingService;
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
public Dictionary<int, List<EntityModel>> StartedOrders => _buildOrder.StartedOrders;
|
||||
public Dictionary<int, List<EntityModel>> CompletedOrders => _buildOrder.CompletedOrders;
|
||||
public Dictionary<int, List<EntityModel>> DepletedOrders => _buildOrder.DepletedOrders;
|
||||
public Dictionary<string, int> UniqueCompletedTimes => _buildOrder.UniqueCompletedTimes;
|
||||
public Dictionary<int, int> SupplyCountTimes => _buildOrder.SupplyCountTimes;
|
||||
|
||||
public int GetLastRequestInterval()
|
||||
{
|
||||
return _lastInterval;
|
||||
}
|
||||
|
||||
public Dictionary<int, List<EntityModel>> 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, []);
|
||||
|
||||
var production = entity.Production();
|
||||
|
||||
var completedTime = atInterval;
|
||||
if (production != null) completedTime += production.BuildTime;
|
||||
|
||||
if (!_buildOrder.CompletedOrders.ContainsKey(completedTime))
|
||||
_buildOrder.CompletedOrders.Add(completedTime, []);
|
||||
|
||||
_buildOrder.StartedOrders[atInterval].Add(entity.Clone());
|
||||
_buildOrder.CompletedOrders[completedTime].Add(entity.Clone());
|
||||
|
||||
_buildOrder.UniqueCompletedTimes.TryAdd(entity.DataType, atInterval);
|
||||
|
||||
if (!_buildOrder.UniqueCompletedCount.TryAdd(entity.DataType, 1))
|
||||
_buildOrder.UniqueCompletedCount[entity.DataType]++;
|
||||
|
||||
if (!_buildOrder.UniqueCompleted.ContainsKey(entity.DataType))
|
||||
_buildOrder.UniqueCompleted.Add(entity.DataType, []);
|
||||
|
||||
if (entity.Production()?.ProducedBy != null)
|
||||
_buildOrder.TrainingCapacityUsed.Add(new TrainingCapacityUsedModel
|
||||
{
|
||||
StartingUsageTime = atInterval,
|
||||
StopUsageTime = completedTime,
|
||||
UsedSlots = entity.Supply() != null ? entity.Supply()!.Takes : 1,
|
||||
UsedBuilding = entity.Production()!.ProducedBy
|
||||
});
|
||||
|
||||
_buildOrder.UniqueCompleted[entity.DataType].Add(entity);
|
||||
|
||||
if (entity.Supply() != null && entity.Supply()!.Takes > 0)
|
||||
_buildOrder.CurrentSupplyUsed += entity.Supply()!.Takes;
|
||||
if (entity.Supply() != null && entity.Supply()!.Grants > 0)
|
||||
_buildOrder.SupplyCountTimes.Add(_buildOrder.SupplyCountTimes.Last().Key + entity.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, []);
|
||||
|
||||
if (!_buildOrder.CompletedOrders.ContainsKey(_lastInterval))
|
||||
_buildOrder.CompletedOrders.Add(_lastInterval, []);
|
||||
|
||||
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, []);
|
||||
|
||||
if (!_buildOrder.CompletedOrders.ContainsKey(_lastInterval))
|
||||
_buildOrder.CompletedOrders.Add(_lastInterval, []);
|
||||
|
||||
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
|
||||
.Where(supplyAtTime => 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;
|
||||
if (!HandleTrainingQueue(entity, ref atInterval)) return false;
|
||||
|
||||
Add(entity, atInterval);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void RemoveLast()
|
||||
{
|
||||
if (_buildOrder.StartedOrders.Keys.Count <= 1) return;
|
||||
|
||||
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);
|
||||
|
||||
_buildOrder.UniqueCompleted[entityRemoved.DataType]
|
||||
.Remove(_buildOrder.UniqueCompleted[entityRemoved.DataType].Last());
|
||||
|
||||
if (entityRemoved.Production() != null
|
||||
&& entityRemoved.Production()!.ProducedBy != null
|
||||
&& entityRemoved.Supply() != null
|
||||
&& entityRemoved.Supply()!.Takes > 0)
|
||||
_buildOrder.TrainingCapacityUsed.Remove(_buildOrder.TrainingCapacityUsed.Last());
|
||||
|
||||
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<EntityModel> 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<EntityModel> GetUndepletedHarvestPointsCompletedBefore(int interval)
|
||||
{
|
||||
return (from ordersAtTime in _buildOrder.StartedOrders
|
||||
from orders in ordersAtTime.Value
|
||||
where ordersAtTime.Key + (orders.Production() == null
|
||||
? 0
|
||||
: orders.Production().BuildTime) <= interval
|
||||
&& !orders.Harvest().IsDepleted(
|
||||
interval,
|
||||
ordersAtTime.Key + (orders.Production() == null
|
||||
? 0
|
||||
: orders.Production().BuildTime))
|
||||
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 int? WillMeetTrainingQueue(EntityModel entity)
|
||||
{
|
||||
var supply = entity.Supply();
|
||||
var production = entity.Production();
|
||||
|
||||
var checkedInterval = _lastInterval;
|
||||
|
||||
if (supply == null || production == null || supply.Takes.Equals(0)) return 1;
|
||||
|
||||
var producedBy = production.ProducedBy;
|
||||
if (producedBy == null) return 1;
|
||||
|
||||
var uniqueCompleted = _buildOrder.UniqueCompleted[producedBy];
|
||||
|
||||
var shortestIncrement = int.MaxValue;
|
||||
var didDelay = false;
|
||||
|
||||
var trainingSlots = uniqueCompleted.Sum(productionEntity => productionEntity.Supply()!.Grants);
|
||||
|
||||
|
||||
while (true)
|
||||
{
|
||||
var usedSlots = 0;
|
||||
foreach (var used in
|
||||
_buildOrder.TrainingCapacityUsed
|
||||
.Where(used =>
|
||||
checkedInterval >= used.StartingUsageTime && checkedInterval < used.StopUsageTime))
|
||||
{
|
||||
usedSlots += used.UsedSlots;
|
||||
var duration = used.StopUsageTime - used.StartingUsageTime;
|
||||
if (duration < shortestIncrement) shortestIncrement = duration;
|
||||
}
|
||||
|
||||
if (usedSlots + supply.Takes <= trainingSlots)
|
||||
{
|
||||
if (didDelay)
|
||||
_toastService.AddToast(new ToastModel
|
||||
{
|
||||
Title = "Waited", SeverityType = SeverityType.Information,
|
||||
Message = $"Had to wait {checkedInterval - _lastInterval}s for Training Queue."
|
||||
});
|
||||
|
||||
return checkedInterval;
|
||||
}
|
||||
|
||||
checkedInterval += shortestIncrement;
|
||||
didDelay = true;
|
||||
|
||||
if (shortestIncrement == int.MaxValue) 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)) continue;
|
||||
|
||||
atInterval = interval;
|
||||
|
||||
if (entity.EntityType != EntityType.Army) atInterval += _timingService.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 minTrainingQueueInterval = WillMeetTrainingQueue(entity);
|
||||
if (minTrainingQueueInterval == null)
|
||||
{
|
||||
_toastService.AddToast(new ToastModel
|
||||
{
|
||||
Title = "Invalid", Message = "Invalid Training Queue error",
|
||||
SeverityType = SeverityType.Error
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (minTrainingQueueInterval > atInterval) atInterval = (int)minTrainingQueueInterval;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Model.BuildOrders;
|
||||
using YamlDotNet.Serialization;
|
||||
|
||||
namespace Services.Immortal;
|
||||
|
||||
public class DeprecatedBuildComparisionService : IBuildComparisonService
|
||||
{
|
||||
private BuildToCompareModel buildToCompare = new();
|
||||
|
||||
public void Subscribe(Action action)
|
||||
{
|
||||
OnChange += action;
|
||||
}
|
||||
|
||||
public void Unsubscribe(Action action)
|
||||
{
|
||||
OnChange -= action;
|
||||
}
|
||||
|
||||
public void SetBuilds(BuildToCompareModel buildToCompareModel)
|
||||
{
|
||||
buildToCompare = buildToCompareModel;
|
||||
NotifyDataChanged();
|
||||
}
|
||||
|
||||
public BuildToCompareModel Get()
|
||||
{
|
||||
return buildToCompare;
|
||||
}
|
||||
|
||||
public string AsJson()
|
||||
{
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true
|
||||
};
|
||||
options.Converters.Add(new JsonStringEnumConverter());
|
||||
return JsonSerializer.Serialize(buildToCompare, options);
|
||||
}
|
||||
|
||||
public bool LoadJson(string data)
|
||||
{
|
||||
try
|
||||
{
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true
|
||||
};
|
||||
options.Converters.Add(new JsonStringEnumConverter());
|
||||
buildToCompare = JsonSerializer.Deserialize<BuildToCompareModel>(data, options)!;
|
||||
|
||||
// Must Hydrate because not loaded with Parts
|
||||
HydratedLoadedJson();
|
||||
|
||||
NotifyDataChanged();
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public string BuildOrderAsYaml()
|
||||
{
|
||||
var stringBuilder = new StringBuilder();
|
||||
var serializer = new Serializer();
|
||||
stringBuilder.AppendLine(serializer.Serialize(buildToCompare));
|
||||
var buildOrderText = stringBuilder.ToString();
|
||||
return buildOrderText;
|
||||
}
|
||||
|
||||
private event Action OnChange = default!;
|
||||
|
||||
private void NotifyDataChanged()
|
||||
{
|
||||
OnChange?.Invoke();
|
||||
}
|
||||
|
||||
|
||||
public void HydratedLoadedJson()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
using Model.BuildOrders;
|
||||
using Model.Economy;
|
||||
using Model.Entity;
|
||||
using Model.Entity.Data;
|
||||
using Model.Types;
|
||||
|
||||
namespace Services.Immortal;
|
||||
|
||||
public class EconomyComparisionService : IEconomyComparisonService
|
||||
{
|
||||
private readonly int IntervalMax = 1024;
|
||||
|
||||
public EconomyComparisionService()
|
||||
{
|
||||
BuildsToCompare = new List<BuildToCompareModel>
|
||||
{
|
||||
new() { NumberOfTownHallExpansions = 0, Faction = DataType.FACTION_Aru, ChartColor = "green" },
|
||||
new() { NumberOfTownHallExpansions = 0, Faction = DataType.FACTION_Aru, ChartColor = "red" }
|
||||
};
|
||||
|
||||
BuildsToCompare[0].EconomyOverTimeModel = CalculateEconomy(BuildsToCompare[0]);
|
||||
BuildsToCompare[1].EconomyOverTimeModel = CalculateEconomy(BuildsToCompare[1]);
|
||||
}
|
||||
|
||||
public List<BuildToCompareModel> BuildsToCompare { get; set; }
|
||||
|
||||
|
||||
public void ChangeNumberOfTownHalls(int forPlayer, int toCount)
|
||||
{
|
||||
if (BuildsToCompare[forPlayer].NumberOfTownHallExpansions == toCount) return;
|
||||
|
||||
BuildsToCompare[forPlayer].NumberOfTownHallExpansions = toCount;
|
||||
|
||||
CalculateBuildOrder(BuildsToCompare[forPlayer]);
|
||||
|
||||
NotifyDataChanged();
|
||||
}
|
||||
|
||||
public void ChangeTownHallTiming(int forPlayer, int forTownHall, int toTiming)
|
||||
{
|
||||
if (BuildsToCompare[forPlayer].TimeToBuildTownHall[forTownHall] == toTiming) return;
|
||||
|
||||
BuildsToCompare[forPlayer].TimeToBuildTownHall[forTownHall] = toTiming;
|
||||
|
||||
CalculateBuildOrder(BuildsToCompare[forPlayer]);
|
||||
|
||||
NotifyDataChanged();
|
||||
}
|
||||
|
||||
public int GetTownHallCount(int forPlayer)
|
||||
{
|
||||
return BuildsToCompare[forPlayer].NumberOfTownHallExpansions;
|
||||
}
|
||||
|
||||
public int GetTownHallBuildTime(int forPlayer, int forTownHall)
|
||||
{
|
||||
return BuildsToCompare[forPlayer].TimeToBuildTownHall[forTownHall];
|
||||
}
|
||||
|
||||
public List<int> GetTownHallBuildTimes(int forPlayer)
|
||||
{
|
||||
return BuildsToCompare[forPlayer].TimeToBuildTownHall;
|
||||
}
|
||||
|
||||
public void ChangeFaction(int forPlayer, string toFaction)
|
||||
{
|
||||
if (BuildsToCompare[forPlayer].Faction.Equals(toFaction)) return;
|
||||
|
||||
BuildsToCompare[forPlayer].Faction = toFaction;
|
||||
NotifyDataChanged();
|
||||
}
|
||||
|
||||
public string GetFaction(int forPlayer)
|
||||
{
|
||||
return BuildsToCompare[forPlayer].Faction;
|
||||
}
|
||||
|
||||
public void ChangeColor(int forPlayer, string toColor)
|
||||
{
|
||||
if (BuildsToCompare[forPlayer].ChartColor.Equals(toColor)) return;
|
||||
|
||||
BuildsToCompare[forPlayer].ChartColor = toColor;
|
||||
NotifyDataChanged();
|
||||
}
|
||||
|
||||
public string GetColor(int forPlayer)
|
||||
{
|
||||
return BuildsToCompare[forPlayer].ChartColor;
|
||||
}
|
||||
|
||||
public void Subscribe(Action action)
|
||||
{
|
||||
OnChange += action;
|
||||
}
|
||||
|
||||
public void Unsubscribe(Action action)
|
||||
{
|
||||
OnChange -= action;
|
||||
}
|
||||
|
||||
|
||||
private void CalculateBuildOrder(BuildToCompareModel buildToCompare)
|
||||
{
|
||||
buildToCompare.BuildOrderModel = new BuildOrderModel(buildToCompare.Faction);
|
||||
|
||||
foreach (var time in buildToCompare.TimeToBuildTownHall)
|
||||
{
|
||||
var townHall = buildToCompare.GetTownHallEntity;
|
||||
var townHallMining2 = buildToCompare.GetTownHallMining2Entity;
|
||||
|
||||
Add(townHall, buildToCompare, time);
|
||||
Add(townHallMining2, buildToCompare, time + townHall.Production()!.BuildTime);
|
||||
}
|
||||
|
||||
CalculateEconomy(buildToCompare);
|
||||
}
|
||||
|
||||
public void Add(EntityModel entityModel, BuildToCompareModel buildToCompare, int atInterval)
|
||||
{
|
||||
var buildOrder = buildToCompare.BuildOrderModel;
|
||||
|
||||
|
||||
if (!buildOrder.StartedOrders.ContainsKey(atInterval))
|
||||
buildOrder.StartedOrders.Add(atInterval, new List<EntityModel>());
|
||||
|
||||
var production = entityModel.Production();
|
||||
|
||||
var completedTime = atInterval;
|
||||
if (production != null) completedTime += production.BuildTime;
|
||||
|
||||
if (!buildOrder.CompletedOrders.ContainsKey(completedTime))
|
||||
buildOrder.CompletedOrders.Add(completedTime, new List<EntityModel>());
|
||||
|
||||
buildOrder.StartedOrders[atInterval].Add(entityModel.Clone());
|
||||
buildOrder.CompletedOrders[completedTime].Add(entityModel.Clone());
|
||||
|
||||
NotifyDataChanged();
|
||||
}
|
||||
|
||||
private List<EconomyModel> CalculateEconomy(BuildToCompareModel buildToCompare, int fromInterval = 0)
|
||||
{
|
||||
// We don't consider things mining at zero seconds
|
||||
if (fromInterval == 0) fromInterval = 1;
|
||||
|
||||
var buildOrder = buildToCompare.BuildOrderModel;
|
||||
|
||||
List<EconomyModel> buildEconomyOverTime = buildToCompare.EconomyOverTimeModel;
|
||||
|
||||
while (buildEconomyOverTime.Count < IntervalMax)
|
||||
buildEconomyOverTime.Add(new EconomyModel { Interval = buildEconomyOverTime.Count - 1 });
|
||||
|
||||
for (var interval = fromInterval; interval < IntervalMax; interval++)
|
||||
{
|
||||
buildEconomyOverTime[interval] = new EconomyModel();
|
||||
var economyAtSecond = buildEconomyOverTime[interval];
|
||||
if (interval > 0)
|
||||
{
|
||||
economyAtSecond.Alloy = buildEconomyOverTime[interval - 1].Alloy;
|
||||
economyAtSecond.Ether = buildEconomyOverTime[interval - 1].Ether;
|
||||
economyAtSecond.Pyre = buildEconomyOverTime[interval - 1].Pyre;
|
||||
economyAtSecond.WorkerCount = buildEconomyOverTime[interval - 1].WorkerCount;
|
||||
economyAtSecond.BusyWorkerCount = buildEconomyOverTime[interval - 1].BusyWorkerCount;
|
||||
economyAtSecond.CreatingWorkerCount = buildEconomyOverTime[interval - 1].CreatingWorkerCount;
|
||||
economyAtSecond.HarvestPoints = buildEconomyOverTime[interval - 1].HarvestPoints.ToList();
|
||||
economyAtSecond.CreatingWorkerDelays = buildEconomyOverTime[interval - 1].CreatingWorkerDelays.ToList();
|
||||
}
|
||||
|
||||
economyAtSecond.Interval = interval;
|
||||
|
||||
// Add funds
|
||||
float freeWorkers = economyAtSecond.WorkerCount - economyAtSecond.BusyWorkerCount;
|
||||
var workersNeeded = 0;
|
||||
|
||||
economyAtSecond.HarvestPoints =
|
||||
(from harvester in buildOrder.GetHarvestersCompletedBefore(interval)
|
||||
select harvester).ToList();
|
||||
|
||||
// Add funds
|
||||
economyAtSecond.Pyre += 1;
|
||||
|
||||
// Add funds
|
||||
foreach (var entity in economyAtSecond.HarvestPoints)
|
||||
{
|
||||
var harvester = entity.Harvest();
|
||||
if (harvester.RequiresWorker)
|
||||
if (harvester.Resource == ResourceType.Alloy)
|
||||
{
|
||||
var usedWorkers = Math.Min(harvester.Slots, freeWorkers);
|
||||
economyAtSecond.Alloy += harvester.HarvestedPerInterval * usedWorkers;
|
||||
freeWorkers -= usedWorkers;
|
||||
|
||||
if (usedWorkers < harvester.Slots) workersNeeded += 1;
|
||||
}
|
||||
|
||||
if (harvester.RequiresWorker == false)
|
||||
{
|
||||
if (harvester.Resource == ResourceType.Ether)
|
||||
economyAtSecond.Ether += harvester.HarvestedPerInterval * harvester.Slots;
|
||||
|
||||
if (harvester.Resource == ResourceType.Alloy)
|
||||
economyAtSecond.Alloy += harvester.HarvestedPerInterval * harvester.Slots;
|
||||
}
|
||||
}
|
||||
|
||||
// Create new worker
|
||||
if (economyAtSecond.CreatingWorkerCount > 0)
|
||||
for (var i = 0; i < economyAtSecond.CreatingWorkerDelays.Count; i++)
|
||||
if (economyAtSecond.CreatingWorkerDelays[i] > 0)
|
||||
{
|
||||
if (economyAtSecond.Alloy > 2.5f)
|
||||
{
|
||||
economyAtSecond.Alloy -= 2.5f;
|
||||
economyAtSecond.CreatingWorkerDelays[i]--;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
economyAtSecond.CreatingWorkerCount -= 1;
|
||||
economyAtSecond.WorkerCount += 1;
|
||||
economyAtSecond.CreatingWorkerDelays.Remove(i);
|
||||
i--;
|
||||
}
|
||||
|
||||
if (workersNeeded > economyAtSecond.CreatingWorkerCount)
|
||||
{
|
||||
economyAtSecond.CreatingWorkerCount += 1;
|
||||
economyAtSecond.CreatingWorkerDelays.Add(50);
|
||||
}
|
||||
|
||||
// Remove Funds from Build Order
|
||||
|
||||
if (buildOrder.StartedOrders.TryGetValue(interval, out var ordersAtTime))
|
||||
foreach (var order in ordersAtTime)
|
||||
{
|
||||
var foundEntity = EntityModel.GetDictionary()[order.DataType];
|
||||
var production = foundEntity.Production();
|
||||
|
||||
if (production != null)
|
||||
{
|
||||
economyAtSecond.Alloy -= production.Alloy;
|
||||
economyAtSecond.Ether -= production.Ether;
|
||||
economyAtSecond.Pyre -= production.Pyre;
|
||||
var finishedAt = interval + production.BuildTime;
|
||||
|
||||
if (production.RequiresWorker) economyAtSecond.BusyWorkerCount += 1;
|
||||
|
||||
if (production.ConsumesWorker) economyAtSecond.WorkerCount -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle new entities
|
||||
if (buildOrder.CompletedOrders.TryGetValue(interval, out var completedAtInterval))
|
||||
foreach (var newEntity in completedAtInterval)
|
||||
{
|
||||
var harvest = newEntity;
|
||||
if (harvest != null) economyAtSecond.HarvestPoints.Add(harvest);
|
||||
|
||||
var production = newEntity.Production();
|
||||
if (production != null && production.RequiresWorker) economyAtSecond.BusyWorkerCount -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
return buildEconomyOverTime;
|
||||
}
|
||||
|
||||
|
||||
private event Action OnChange = null!;
|
||||
|
||||
private void NotifyDataChanged()
|
||||
{
|
||||
OnChange();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
using Model.Economy;
|
||||
using Model.Entity;
|
||||
using Model.Entity.Parts;
|
||||
using Model.Types;
|
||||
|
||||
namespace Services.Immortal;
|
||||
|
||||
public class EconomyService : IEconomyService
|
||||
{
|
||||
private List<EconomyModel>? buildEconomyOverTime;
|
||||
|
||||
public List<EconomyModel> GetOverTime()
|
||||
{
|
||||
return buildEconomyOverTime;
|
||||
}
|
||||
|
||||
public void Subscribe(Action action)
|
||||
{
|
||||
OnChange += action;
|
||||
}
|
||||
|
||||
public void Unsubscribe(Action action)
|
||||
{
|
||||
OnChange -= action;
|
||||
}
|
||||
|
||||
public void Calculate(IBuildOrderService buildOrder, ITimingService timing, int fromInterval)
|
||||
{
|
||||
// We don't consider things mining at zero seconds
|
||||
if (fromInterval == 0) fromInterval = 1;
|
||||
|
||||
if (buildEconomyOverTime == null)
|
||||
{
|
||||
buildEconomyOverTime = [];
|
||||
for (var interval = 0; interval < timing.GetAttackTime(); interval++)
|
||||
buildEconomyOverTime.Add(new EconomyModel { Interval = interval });
|
||||
}
|
||||
|
||||
if (buildEconomyOverTime.Count > timing.GetAttackTime())
|
||||
buildEconomyOverTime.RemoveRange(timing.GetAttackTime(),
|
||||
buildEconomyOverTime.Count - timing.GetAttackTime());
|
||||
|
||||
while (buildEconomyOverTime.Count < timing.GetAttackTime())
|
||||
buildEconomyOverTime.Add(new EconomyModel { Interval = buildEconomyOverTime.Count - 1 });
|
||||
|
||||
for (var interval = fromInterval; interval < timing.GetAttackTime(); interval++)
|
||||
{
|
||||
buildEconomyOverTime[interval] = new EconomyModel();
|
||||
|
||||
var economyAtSecond = buildEconomyOverTime[interval];
|
||||
|
||||
CarryOverEconomyFromPreviousInterval(interval, economyAtSecond);
|
||||
|
||||
SetupCurrentInterval(buildOrder, economyAtSecond, interval);
|
||||
AddPassivePyreGain(interval, economyAtSecond);
|
||||
float freeWorkers = economyAtSecond.WorkerCount - economyAtSecond.BusyWorkerCount;
|
||||
var workersNeeded = AddFundsFromHarvestPoints(economyAtSecond, freeWorkers);
|
||||
workersNeeded -= CalculateCreatingWorkerCosts(economyAtSecond);
|
||||
MakeNeededNewWorkersRequests(workersNeeded, economyAtSecond);
|
||||
SubtractFundsOnRequestedOrders(buildOrder, interval, economyAtSecond);
|
||||
HandledAddingNewHarvestPointsAndWorkersToEconomy(buildOrder, interval, economyAtSecond);
|
||||
}
|
||||
|
||||
NotifyDataChanged();
|
||||
}
|
||||
|
||||
|
||||
public EconomyModel GetEconomy(int atInterval)
|
||||
{
|
||||
return atInterval >= buildEconomyOverTime.Count
|
||||
? buildEconomyOverTime.Last()
|
||||
: buildEconomyOverTime[atInterval];
|
||||
}
|
||||
|
||||
private static void SetupCurrentInterval(
|
||||
IBuildOrderService buildOrder,
|
||||
EconomyModel economyAtSecond,
|
||||
int interval)
|
||||
{
|
||||
economyAtSecond.Interval = interval;
|
||||
economyAtSecond.HarvestPoints =
|
||||
(from harvester
|
||||
in buildOrder
|
||||
.GetUndepletedHarvestPointsCompletedBefore(interval +
|
||||
1) // One second into the future for completed harvest points
|
||||
select harvester).ToList();
|
||||
}
|
||||
|
||||
private static void HandledAddingNewHarvestPointsAndWorkersToEconomy(IBuildOrderService buildOrder, int interval,
|
||||
EconomyModel economyAtSecond)
|
||||
{
|
||||
if (!buildOrder.CompletedOrders.TryGetValue(interval, out var completedAtInterval)) return;
|
||||
|
||||
foreach (var newEntity in completedAtInterval)
|
||||
{
|
||||
var entity = newEntity.Clone();
|
||||
//entity.Harvest().StartedAt = interval;
|
||||
economyAtSecond.HarvestPoints.Add(entity);
|
||||
|
||||
var production = newEntity.Production();
|
||||
if (production is { RequiresWorker: true }) economyAtSecond.BusyWorkerCount -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
private static void SubtractFundsOnRequestedOrders(IBuildOrderService buildOrder, int interval,
|
||||
EconomyModel economyAtSecond)
|
||||
{
|
||||
if (!buildOrder.StartedOrders.TryGetValue(interval, out var ordersAtTime)) return;
|
||||
|
||||
foreach (var production in
|
||||
ordersAtTime.Select(order => EntityModel.GetDictionary()[order.DataType])
|
||||
.Select(foundEntity => foundEntity.Production())
|
||||
.OfType<EntityProductionModel>())
|
||||
{
|
||||
economyAtSecond.Alloy -= production.Alloy;
|
||||
economyAtSecond.Ether -= production.Ether;
|
||||
economyAtSecond.Pyre -= production.Pyre;
|
||||
|
||||
if (production.RequiresWorker) economyAtSecond.BusyWorkerCount += 1;
|
||||
if (production.ConsumesWorker) economyAtSecond.WorkerCount -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
private static void MakeNeededNewWorkersRequests(int workersNeeded, EconomyModel economyAtSecond)
|
||||
{
|
||||
if (workersNeeded <= economyAtSecond.CreatingWorkerCount) return;
|
||||
|
||||
economyAtSecond.CreatingWorkerCount += 1;
|
||||
economyAtSecond.CreatingWorkerDelays.Add(20);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns number of workers created
|
||||
*/
|
||||
private static int CalculateCreatingWorkerCosts(EconomyModel economyAtSecond)
|
||||
{
|
||||
var createdWorkers = 0;
|
||||
|
||||
if (economyAtSecond.CreatingWorkerCount <= 0) return createdWorkers;
|
||||
|
||||
for (var i = 0; i < economyAtSecond.CreatingWorkerDelays.Count; i++)
|
||||
if (economyAtSecond.CreatingWorkerDelays[i] > 0)
|
||||
{
|
||||
if (!(economyAtSecond.Alloy > 2.5f)) continue;
|
||||
|
||||
economyAtSecond.Alloy -= 2.5f;
|
||||
economyAtSecond.CreatingWorkerDelays[i]--;
|
||||
}
|
||||
else
|
||||
{
|
||||
economyAtSecond.CreatingWorkerCount -= 1;
|
||||
economyAtSecond.WorkerCount += 1;
|
||||
createdWorkers++;
|
||||
economyAtSecond.CreatingWorkerDelays.Remove(i);
|
||||
i--;
|
||||
}
|
||||
|
||||
return createdWorkers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns needed workers to maximize harvest points
|
||||
*/
|
||||
private static int AddFundsFromHarvestPoints(EconomyModel economyAtSecond, float freeWorkers)
|
||||
{
|
||||
var workersNeeded = 0;
|
||||
|
||||
|
||||
foreach (var harvesterPoint in
|
||||
economyAtSecond.HarvestPoints.Select(entity => entity.Harvest()))
|
||||
//if (harvesterPoint.IsDepleted(economyAtSecond.Interval))
|
||||
// continue;
|
||||
switch (harvesterPoint.RequiresWorker)
|
||||
{
|
||||
case true:
|
||||
{
|
||||
if (harvesterPoint.Resource == ResourceType.Alloy)
|
||||
{
|
||||
var usedWorkers = Math.Min(harvesterPoint.Slots, freeWorkers);
|
||||
|
||||
economyAtSecond.Alloy += harvesterPoint.HarvestedPerInterval * usedWorkers;
|
||||
economyAtSecond.AlloyIncome += harvesterPoint.HarvestedPerInterval * usedWorkers;
|
||||
|
||||
freeWorkers -= usedWorkers;
|
||||
|
||||
if (usedWorkers < harvesterPoint.Slots) workersNeeded += 1;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case false:
|
||||
{
|
||||
switch (harvesterPoint.Resource)
|
||||
{
|
||||
case ResourceType.Ether:
|
||||
economyAtSecond.Ether += harvesterPoint.HarvestedPerInterval * harvesterPoint.Slots;
|
||||
economyAtSecond.EtherIncome += harvesterPoint.HarvestedPerInterval * harvesterPoint.Slots;
|
||||
break;
|
||||
case ResourceType.Alloy:
|
||||
economyAtSecond.Alloy += harvesterPoint.HarvestedPerInterval * harvesterPoint.Slots;
|
||||
economyAtSecond.AlloyIncome += harvesterPoint.HarvestedPerInterval * harvesterPoint.Slots;
|
||||
break;
|
||||
case ResourceType.Pyre:
|
||||
break; // Pyre Miner?
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return workersNeeded;
|
||||
}
|
||||
|
||||
private static void AddPassivePyreGain(int interval, EconomyModel economyAtSecond)
|
||||
{
|
||||
if (interval % 3 == 0) economyAtSecond.Pyre += 1;
|
||||
}
|
||||
|
||||
private void CarryOverEconomyFromPreviousInterval(int interval, EconomyModel economyAtSecond)
|
||||
{
|
||||
if (interval <= 0) return;
|
||||
|
||||
economyAtSecond.Alloy = buildEconomyOverTime[interval - 1].Alloy;
|
||||
economyAtSecond.Ether = buildEconomyOverTime[interval - 1].Ether;
|
||||
economyAtSecond.Pyre = buildEconomyOverTime[interval - 1].Pyre;
|
||||
economyAtSecond.WorkerCount = buildEconomyOverTime[interval - 1].WorkerCount;
|
||||
economyAtSecond.BusyWorkerCount = buildEconomyOverTime[interval - 1].BusyWorkerCount;
|
||||
economyAtSecond.CreatingWorkerCount = buildEconomyOverTime[interval - 1].CreatingWorkerCount;
|
||||
economyAtSecond.HarvestPoints = buildEconomyOverTime[interval - 1].HarvestPoints.ToList();
|
||||
economyAtSecond.CreatingWorkerDelays = buildEconomyOverTime[interval - 1].CreatingWorkerDelays.ToList();
|
||||
}
|
||||
|
||||
|
||||
private event Action OnChange = null!;
|
||||
|
||||
private void NotifyDataChanged()
|
||||
{
|
||||
OnChange?.Invoke();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using Services.Website;
|
||||
|
||||
namespace Services.Immortal;
|
||||
|
||||
public class EntityViewType
|
||||
{
|
||||
public static string Detailed = "Detailed";
|
||||
public static string Plain = "Plain";
|
||||
}
|
||||
|
||||
public class EntityDisplayService : IEntityDisplayService
|
||||
{
|
||||
private string _displayType;
|
||||
|
||||
public EntityDisplayService(IStorageService storageService)
|
||||
{
|
||||
_displayType = storageService.GetValue<bool>(StorageKeys.IsPlainView)
|
||||
? EntityViewType.Plain
|
||||
: EntityViewType.Detailed;
|
||||
}
|
||||
|
||||
|
||||
public List<string> DefaultChoices()
|
||||
{
|
||||
return new List<string> { EntityViewType.Detailed, EntityViewType.Plain };
|
||||
}
|
||||
|
||||
public void Subscribe(Action action)
|
||||
{
|
||||
OnChange += action;
|
||||
}
|
||||
|
||||
public void Unsubscribe(Action action)
|
||||
{
|
||||
OnChange -= action;
|
||||
}
|
||||
|
||||
public string GetDisplayType()
|
||||
{
|
||||
return _displayType;
|
||||
}
|
||||
|
||||
public void SetDisplayType(string displayType)
|
||||
{
|
||||
_displayType = displayType;
|
||||
NotifyDataChanged();
|
||||
}
|
||||
|
||||
private event Action OnChange = null!;
|
||||
|
||||
private void NotifyDataChanged()
|
||||
{
|
||||
OnChange?.Invoke();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
using Model.Entity.Data;
|
||||
using static Services.IEntityFilterService;
|
||||
|
||||
namespace Services.Immortal;
|
||||
|
||||
public enum EntityFilterEvent
|
||||
{
|
||||
OnRefreshFaction,
|
||||
OnRefreshImmortal,
|
||||
OnRefreshEntity,
|
||||
OnRefreshSearch
|
||||
}
|
||||
|
||||
public class EntityFilterService : IEntityFilterService
|
||||
{
|
||||
private readonly List<string> _entityChoices = new();
|
||||
|
||||
private readonly List<string> _factionChoices = new()
|
||||
{ DataType.Any, DataType.FACTION_QRath, DataType.FACTION_Aru };
|
||||
|
||||
private readonly List<string> _immortalChoices = new();
|
||||
private string _entityType = EntityType.Army;
|
||||
private string _searchText = "";
|
||||
private string _selectedFaction = DataType.Any;
|
||||
private string _selectedImmortal = DataType.Any;
|
||||
|
||||
public EntityFilterService()
|
||||
{
|
||||
RefreshImmortalChoices();
|
||||
RefreshEntityChoices();
|
||||
}
|
||||
|
||||
public void Subscribe(EntityFilterAction action)
|
||||
{
|
||||
OnChange += action;
|
||||
}
|
||||
|
||||
public void Unsubscribe(EntityFilterAction action)
|
||||
{
|
||||
OnChange -= action;
|
||||
}
|
||||
|
||||
public string GetEntityType()
|
||||
{
|
||||
return _entityType;
|
||||
}
|
||||
|
||||
public string GetFactionType()
|
||||
{
|
||||
return _selectedFaction;
|
||||
}
|
||||
|
||||
public string GetImmortalType()
|
||||
{
|
||||
return _selectedImmortal;
|
||||
}
|
||||
|
||||
|
||||
public bool SelectFactionType(string factionType)
|
||||
{
|
||||
if (_selectedFaction == factionType)
|
||||
{
|
||||
_selectedFaction = DataType.None;
|
||||
_selectedImmortal = DataType.None;
|
||||
|
||||
RefreshImmortalChoices();
|
||||
RefreshEntityChoices();
|
||||
|
||||
NotifyDataChanged(EntityFilterEvent.OnRefreshFaction);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_selectedFaction = factionType;
|
||||
_selectedImmortal = DataType.Any;
|
||||
|
||||
RefreshImmortalChoices();
|
||||
RefreshEntityChoices();
|
||||
|
||||
NotifyDataChanged(EntityFilterEvent.OnRefreshFaction);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool SelectImmortalType(string immortalType)
|
||||
{
|
||||
if (_selectedImmortal == immortalType)
|
||||
{
|
||||
_selectedImmortal = DataType.None;
|
||||
NotifyDataChanged(EntityFilterEvent.OnRefreshImmortal);
|
||||
return true;
|
||||
}
|
||||
|
||||
_selectedImmortal = immortalType;
|
||||
NotifyDataChanged(EntityFilterEvent.OnRefreshImmortal);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool SelectEntityType(string entityType)
|
||||
{
|
||||
if (_entityType == entityType) return false;
|
||||
_entityType = entityType;
|
||||
NotifyDataChanged(EntityFilterEvent.OnRefreshEntity);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool EnterSearchText(string searchText)
|
||||
{
|
||||
if (_searchText.Equals(searchText))
|
||||
return false;
|
||||
_searchText = searchText;
|
||||
NotifyDataChanged(EntityFilterEvent.OnRefreshSearch);
|
||||
return true;
|
||||
}
|
||||
|
||||
public List<string> GetFactionChoices()
|
||||
{
|
||||
return _factionChoices;
|
||||
}
|
||||
|
||||
public List<string> GetImmortalChoices()
|
||||
{
|
||||
return _immortalChoices;
|
||||
}
|
||||
|
||||
public List<string> GetEntityChoices()
|
||||
{
|
||||
return _entityChoices;
|
||||
}
|
||||
|
||||
public string GetSearchText()
|
||||
{
|
||||
return _searchText;
|
||||
}
|
||||
|
||||
private event EntityFilterAction OnChange = null!;
|
||||
|
||||
private void RefreshImmortalChoices()
|
||||
{
|
||||
_immortalChoices.Clear();
|
||||
|
||||
//TODO Consider getting these values from the database
|
||||
/*if (_selectedFaction == FactionType.QRath || _selectedFaction == FactionType.Any) {
|
||||
_immortalChoices.Add(ImmortalType.Orzum);
|
||||
_immortalChoices.Add(ImmortalType.Ajari);
|
||||
}
|
||||
|
||||
if (_selectedFaction == FactionType.Aru || _selectedFaction == FactionType.Any) {
|
||||
_immortalChoices.Add(ImmortalType.Mala);
|
||||
_immortalChoices.Add(ImmortalType.Xol);
|
||||
}*/
|
||||
|
||||
if (_selectedFaction == DataType.FACTION_QRath || _selectedFaction == DataType.Any)
|
||||
{
|
||||
_immortalChoices.Add(DataType.IMMORTAL_Orzum);
|
||||
_immortalChoices.Add(DataType.IMMORTAL_Ajari);
|
||||
}
|
||||
|
||||
if (_selectedFaction == DataType.FACTION_Aru || _selectedFaction == DataType.Any)
|
||||
{
|
||||
_immortalChoices.Add(DataType.IMMORTAL_Atzlan);
|
||||
_immortalChoices.Add(DataType.IMMORTAL_Mala);
|
||||
_immortalChoices.Add(DataType.IMMORTAL_Xol);
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshEntityChoices()
|
||||
{
|
||||
_entityChoices.Clear();
|
||||
|
||||
if (_selectedFaction == DataType.FACTION_QRath || _selectedFaction == DataType.FACTION_Aru ||
|
||||
_selectedFaction == DataType.Any)
|
||||
{
|
||||
_entityChoices.Add(EntityType.Army);
|
||||
_entityChoices.Add(EntityType.Immortal);
|
||||
_entityChoices.Add(EntityType.Passive);
|
||||
_entityChoices.Add(EntityType.Building);
|
||||
_entityChoices.Add(EntityType.Tech);
|
||||
_entityChoices.Add(EntityType.Ability);
|
||||
_entityChoices.Add(EntityType.Pyre_Spell);
|
||||
_entityChoices.Add(EntityType.Worker);
|
||||
}
|
||||
|
||||
if (_selectedFaction == DataType.Any) _entityChoices.Add(EntityType.Any);
|
||||
}
|
||||
|
||||
|
||||
private void NotifyDataChanged(EntityFilterEvent entityFilterEvent)
|
||||
{
|
||||
OnChange?.Invoke(entityFilterEvent);
|
||||
}
|
||||
|
||||
public void Subscribe(Action action)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Unsubscribe(Action action)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using Model.Entity;
|
||||
|
||||
namespace Services.Immortal;
|
||||
|
||||
public class EntityService : IEntityService
|
||||
{
|
||||
public List<EntityModel> GetEntities()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
using Model.Entity.Data;
|
||||
using Services.Website;
|
||||
|
||||
namespace Services.Immortal;
|
||||
|
||||
public class ImmortalSelectionService : IImmortalSelectionService, IDisposable
|
||||
{
|
||||
private readonly IStorageService _storageService;
|
||||
private string _selectedFaction = DataType.FACTION_QRath;
|
||||
private string _selectedImmortal = DataType.IMMORTAL_Orzum;
|
||||
|
||||
public ImmortalSelectionService(IStorageService storageService)
|
||||
{
|
||||
_storageService = storageService;
|
||||
|
||||
_storageService.Subscribe(RefreshDefaults);
|
||||
|
||||
RefreshDefaults();
|
||||
}
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
_storageService.Unsubscribe(RefreshDefaults);
|
||||
}
|
||||
|
||||
public void Subscribe(Action action)
|
||||
{
|
||||
OnChange += action;
|
||||
}
|
||||
|
||||
public void Unsubscribe(Action action)
|
||||
{
|
||||
OnChange -= action;
|
||||
}
|
||||
|
||||
public string GetFaction()
|
||||
{
|
||||
return _selectedFaction;
|
||||
}
|
||||
|
||||
public string GetImmortal()
|
||||
{
|
||||
return _selectedImmortal;
|
||||
}
|
||||
|
||||
public bool SelectFaction(string faction)
|
||||
{
|
||||
if (_selectedFaction == faction) return false;
|
||||
_selectedFaction = faction;
|
||||
|
||||
if (_selectedFaction == DataType.FACTION_QRath) _selectedImmortal = DataType.IMMORTAL_Orzum;
|
||||
|
||||
if (_selectedFaction == DataType.FACTION_Aru) _selectedImmortal = DataType.IMMORTAL_Mala;
|
||||
|
||||
NotifyDataChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool SelectImmortal(string immortal)
|
||||
{
|
||||
if (_selectedImmortal == immortal) return false;
|
||||
_selectedImmortal = immortal;
|
||||
NotifyDataChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void RefreshDefaults()
|
||||
{
|
||||
var foundFaction = _storageService.GetValue<string?>(StorageKeys.SelectedFaction);
|
||||
var foundImmortal = _storageService.GetValue<string?>(StorageKeys.SelectedImmortal);
|
||||
|
||||
if (foundFaction != null) _selectedFaction = foundFaction;
|
||||
|
||||
if (foundImmortal != null) _selectedImmortal = foundImmortal;
|
||||
|
||||
NotifyDataChanged();
|
||||
}
|
||||
|
||||
private event Action OnChange = null!;
|
||||
|
||||
private void NotifyDataChanged()
|
||||
{
|
||||
OnChange?.Invoke();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
using Model.Hotkeys;
|
||||
|
||||
namespace Services.Immortal;
|
||||
|
||||
public class KeyService : IKeyService
|
||||
{
|
||||
private static readonly List<string> PressedKeys = new();
|
||||
private string? _hotkey;
|
||||
private string _hotkeyGroup = "C";
|
||||
private bool _isHoldingSpace;
|
||||
|
||||
|
||||
public void Subscribe(Action? action)
|
||||
{
|
||||
OnChange += action;
|
||||
}
|
||||
|
||||
public void Unsubscribe(Action? action)
|
||||
{
|
||||
OnChange -= action;
|
||||
}
|
||||
|
||||
public bool AddPressedKey(string key)
|
||||
{
|
||||
_hotkey = null;
|
||||
|
||||
if (PressedKeys.Count > 0) return false;
|
||||
var pressedKey = key.ToUpper();
|
||||
|
||||
if (pressedKey.Equals(" ") || pressedKey.Equals("SPACE"))
|
||||
{
|
||||
if (!_isHoldingSpace)
|
||||
{
|
||||
_isHoldingSpace = true;
|
||||
NotifyDataChanged();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!PressedKeys.Contains(pressedKey))
|
||||
{
|
||||
if (HotkeyModel.KeyGroups.Contains(pressedKey)) _hotkeyGroup = pressedKey;
|
||||
|
||||
if (HotkeyModel.HotKeys.Contains(pressedKey)) _hotkey = pressedKey;
|
||||
|
||||
PressedKeys.Add(pressedKey);
|
||||
NotifyDataChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public List<string> GetAllPressedKeys()
|
||||
{
|
||||
return PressedKeys;
|
||||
}
|
||||
|
||||
public bool RemovePressedKey(string key)
|
||||
{
|
||||
_hotkey = null;
|
||||
|
||||
var pressedKey = key.ToUpper();
|
||||
if (pressedKey.Equals(" ") || pressedKey.Equals("SPACE"))
|
||||
{
|
||||
if (_isHoldingSpace || true)
|
||||
{
|
||||
_isHoldingSpace = false;
|
||||
NotifyDataChanged();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (PressedKeys.Contains(pressedKey))
|
||||
{
|
||||
PressedKeys.Remove(pressedKey);
|
||||
NotifyDataChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsHoldingSpace()
|
||||
{
|
||||
return _isHoldingSpace;
|
||||
}
|
||||
|
||||
public string? GetHotkey()
|
||||
{
|
||||
return _hotkey;
|
||||
}
|
||||
|
||||
public string GetHotkeyGroup()
|
||||
{
|
||||
return _hotkeyGroup;
|
||||
}
|
||||
|
||||
private event Action? OnChange;
|
||||
|
||||
private void NotifyDataChanged()
|
||||
{
|
||||
OnChange?.Invoke();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
using Model.Entity;
|
||||
using Model.Entity.Data;
|
||||
using Model.MemoryTester;
|
||||
using static Services.IMemoryTesterService;
|
||||
|
||||
namespace Services.Immortal;
|
||||
|
||||
public enum MemoryTesterEvent
|
||||
{
|
||||
OnVerify,
|
||||
OnRefresh
|
||||
}
|
||||
|
||||
public class MemoryTesterService : IMemoryTesterService
|
||||
{
|
||||
private readonly List<MemoryEntityModel> memoryEntities = MemoryEntityModel.TestData;
|
||||
private readonly List<MemoryQuestionModel> memoryQuestions = MemoryQuestionModel.TestData;
|
||||
|
||||
|
||||
private readonly Random random = new();
|
||||
|
||||
|
||||
public void Subscribe(MemoryAction action)
|
||||
{
|
||||
OnChange += action;
|
||||
}
|
||||
|
||||
public void Unsubscribe(MemoryAction action)
|
||||
{
|
||||
OnChange -= action;
|
||||
}
|
||||
|
||||
public void GenerateQuiz()
|
||||
{
|
||||
memoryEntities.Clear();
|
||||
memoryQuestions.Clear();
|
||||
|
||||
//TODO redo this
|
||||
var units = (from entity in EntityModel.GetListOnlyHotkey()
|
||||
where entity.EntityType == EntityType.Army
|
||||
where entity.Weapons().Count > 0
|
||||
select entity).OrderBy(entity => random.Next()).Take(4).ToList();
|
||||
|
||||
var entityIndex = 0;
|
||||
var questionIndex = 0;
|
||||
|
||||
foreach (var unit in units)
|
||||
{
|
||||
memoryEntities.Add(new MemoryEntityModel
|
||||
{
|
||||
Id = ++entityIndex,
|
||||
Name = unit.Info().Name
|
||||
});
|
||||
|
||||
var weaponIndex = 0;
|
||||
foreach (var weapon in unit.Weapons())
|
||||
{
|
||||
weaponIndex++;
|
||||
|
||||
memoryQuestions.Add(new MemoryQuestionModel
|
||||
{
|
||||
Id = ++questionIndex,
|
||||
MemoryEntityModelId = entityIndex,
|
||||
Name = $"Range (Weapon {weaponIndex})",
|
||||
Answer = weapon.Range,
|
||||
IsRevealed = entityIndex == 1 || entityIndex == 3
|
||||
});
|
||||
}
|
||||
|
||||
memoryQuestions.Add(new MemoryQuestionModel
|
||||
{
|
||||
Id = ++questionIndex,
|
||||
MemoryEntityModelId = entityIndex,
|
||||
Name = "Speed",
|
||||
Answer = (int)unit.Movement().Speed,
|
||||
IsRevealed = entityIndex == 1 || entityIndex == 2
|
||||
});
|
||||
}
|
||||
|
||||
NotifyDataChanged(MemoryTesterEvent.OnRefresh);
|
||||
}
|
||||
|
||||
public List<MemoryEntityModel> GetEntities()
|
||||
{
|
||||
return memoryEntities;
|
||||
}
|
||||
|
||||
public List<MemoryQuestionModel> GetQuestions()
|
||||
{
|
||||
return memoryQuestions;
|
||||
}
|
||||
|
||||
public void Update(MemoryQuestionModel memoryQuestion)
|
||||
{
|
||||
memoryQuestions[memoryQuestion.Id - 1].Guess = memoryQuestion.Guess;
|
||||
}
|
||||
|
||||
public void Verify()
|
||||
{
|
||||
NotifyDataChanged(MemoryTesterEvent.OnVerify);
|
||||
}
|
||||
|
||||
|
||||
private event MemoryAction OnChange = null!;
|
||||
|
||||
|
||||
//public delegate void MemoryAction(MemoryTesterActions memoryAction);
|
||||
|
||||
private void NotifyDataChanged(MemoryTesterEvent memoryAction)
|
||||
{
|
||||
OnChange?.Invoke(memoryAction);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
using Services.Website;
|
||||
|
||||
namespace Services.Immortal;
|
||||
|
||||
public class TimingService : ITimingService, IDisposable
|
||||
{
|
||||
private readonly IStorageService _storageService;
|
||||
private int attackTime = 1500;
|
||||
private int travelTime = 30;
|
||||
|
||||
public TimingService(IStorageService storageService)
|
||||
{
|
||||
_storageService = storageService;
|
||||
|
||||
_storageService.Subscribe(RefreshDefaults);
|
||||
|
||||
RefreshDefaults();
|
||||
}
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
_storageService.Unsubscribe(RefreshDefaults);
|
||||
}
|
||||
|
||||
public void Subscribe(Action? action)
|
||||
{
|
||||
OnChange += action;
|
||||
}
|
||||
|
||||
public void Unsubscribe(Action? action)
|
||||
{
|
||||
OnChange -= action;
|
||||
}
|
||||
|
||||
public int BuildingInputDelay { get; set; } = 2;
|
||||
public int WaitTime { get; set; } = 30;
|
||||
public int WaitTo { get; set; } = 60;
|
||||
|
||||
public int GetAttackTime()
|
||||
{
|
||||
return attackTime;
|
||||
}
|
||||
|
||||
public void SetAttackTime(int timing)
|
||||
{
|
||||
if (attackTime != timing)
|
||||
{
|
||||
attackTime = timing;
|
||||
NotifyDataChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public int GetTravelTime()
|
||||
{
|
||||
return travelTime;
|
||||
}
|
||||
|
||||
public void SetTravelTime(int timing)
|
||||
{
|
||||
if (travelTime != timing)
|
||||
{
|
||||
travelTime = timing;
|
||||
NotifyDataChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshDefaults()
|
||||
{
|
||||
//TODO Timing has changed in Storage
|
||||
//TODO Timing has changed in itself
|
||||
|
||||
var foundAttackTime = _storageService.GetValue<int?>(StorageKeys.AttackTime);
|
||||
var foundTravelTime = _storageService.GetValue<int?>(StorageKeys.TravelTime);
|
||||
|
||||
var foundBuildInputDefault = _storageService.GetValue<int?>(StorageKeys.BuildInputDelay);
|
||||
|
||||
var foundWaitTime = _storageService.GetValue<int?>(StorageKeys.WaitTime);
|
||||
var foundWaitTo = _storageService.GetValue<int?>(StorageKeys.WaitTo);
|
||||
|
||||
if (foundAttackTime != null) attackTime = (int)foundAttackTime;
|
||||
if (foundTravelTime != null) travelTime = (int)foundTravelTime;
|
||||
|
||||
if (foundBuildInputDefault != null) BuildingInputDelay = (int)foundBuildInputDefault;
|
||||
if (foundWaitTime != null) WaitTime = (int)foundWaitTime;
|
||||
if (foundWaitTo != null) WaitTo = (int)foundWaitTo;
|
||||
|
||||
NotifyDataChanged();
|
||||
}
|
||||
|
||||
private event Action? OnChange;
|
||||
|
||||
private void NotifyDataChanged()
|
||||
{
|
||||
OnChange?.Invoke();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user