Initial Commit

This commit is contained in:
2026-05-29 14:17:46 -04:00
commit b7d0676d5b
498 changed files with 30308 additions and 0 deletions
+128
View File
@@ -0,0 +1,128 @@
using System.Net.Http.Json;
using Model.Notes;
namespace Services.Development;
public class NoteService : INoteService
{
private readonly HttpClient httpClient;
private bool isLoaded;
public NoteService(HttpClient httpClient)
{
this.httpClient = httpClient;
}
public List<NoteContentModel> NoteContentModelsByPageOrder { get; set; } = new();
public List<NoteContentModel> NoteContentModels { get; set; } = default!;
public List<NoteConnectionModel> NoteConnectionModels { get; set; } = null!;
public List<NoteSectionModel> NoteSectionModels { get; set; } = null!;
public void Subscribe(Action action)
{
OnChange += action;
}
public void Unsubscribe(Action action)
{
OnChange -= action;
}
public bool IsLoaded()
{
return isLoaded;
}
public async Task Load()
{
if (isLoaded) return;
NoteContentModels =
(await httpClient.GetFromJsonAsync<NoteContentModel[]>("generated/NoteContentModels.json") ??
Array.Empty<NoteContentModel>()).ToList();
NoteConnectionModels =
(await httpClient.GetFromJsonAsync<NoteConnectionModel[]>("generated/NoteConnectionModels.json") ??
Array.Empty<NoteConnectionModel>()).ToList();
NoteSectionModels =
(await httpClient.GetFromJsonAsync<NoteSectionModel[]>("generated/NoteSectionModels.json") ??
Array.Empty<NoteSectionModel>()).ToList();
isLoaded = true;
SortSQL();
NotifyDataChanged();
}
public void Update()
{
NotifyDataChanged();
}
private event Action OnChange = default!;
private void NotifyDataChanged()
{
OnChange?.Invoke();
}
private NoteContentModel? ContentById(int id)
{
foreach (var data in NoteContentModels!)
if (data.Id == id)
return data;
return null;
}
private void SortSQL()
{
foreach (var connection in NoteConnectionModels)
{
ContentById(connection.ParentId)!.NoteContentModels.Add(ContentById(connection.ChildId));
ContentById(connection.ChildId)!.Parent = ContentById(connection.ParentId);
}
foreach (var content in NoteContentModels)
if (content.NoteSectionModelId != null)
foreach (var section in NoteSectionModels)
if (section.Id == content.NoteSectionModelId)
section.NoteContentModels.Add(content);
ByPageOrder();
}
private void ByPageOrder()
{
NoteContentModelsByPageOrder = new List<NoteContentModel>();
var order = 1;
foreach (var note in NoteContentModels)
{
if (note.Parent != null) continue;
note.PageOrder = order++;
NoteContentModelsByPageOrder.Add(note);
void GetAllChildren(NoteContentModel docs)
{
foreach (var doc in docs.NoteContentModels)
{
doc.PageOrder = order++;
NoteContentModelsByPageOrder.Add(doc);
if (doc.NoteContentModels.Count > 0) GetAllChildren(doc);
}
}
GetAllChildren(note);
}
NoteContentModelsByPageOrder =
NoteContentModelsByPageOrder.OrderBy(noteContent => noteContent.PageOrder).ToList();
}
}
+300
View File
@@ -0,0 +1,300 @@
using Model.BuildOrders;
using Model.Economy;
using Model.Entity;
using Model.Feedback;
using Model.MemoryTester;
using Model.Notes;
using Model.Website;
using Model.Website.Enums;
using Services.Immortal;
using Services.Website;
namespace Services;
public interface IToastService
{
public void Subscribe(Action action);
public void Unsubscribe(Action action);
void AddToast(ToastModel toast);
void RemoveToast(ToastModel toast);
bool HasToasts();
List<ToastModel> GetToasts();
void AgeToasts();
void ClearAllToasts();
}
public interface IDataCollectionService
{
public void SendEvent<T>(string eventName, T eventData);
}
public interface IStorageService
{
public void Subscribe(Action action);
public void Unsubscribe(Action action);
T GetValue<T>(string forKey);
void SetValue<T>(string key, T value);
Task Load();
}
public interface IPermissionService
{
public void Subscribe(Action action);
public void Unsubscribe(Action action);
public bool GetIsStorageEnabled();
public bool GetIsDataCollectionEnabled();
public void SetIsStorageEnabled(bool isEnabled);
public void SetIsDataCollectionEnabled(bool isEnabled);
}
public interface ISearchService
{
public List<SearchPointModel> SearchPoints { get; set; }
public Dictionary<string, List<SearchPointModel>> Searches { get; set; }
public bool IsVisible { get; set; }
public void Subscribe(Action action);
public void Unsubscribe(Action action);
public void Search(string entityId);
public Task Load();
public bool IsLoaded();
void Show();
void Hide();
}
public interface IMyDialogService
{
public bool IsVisible { get; set; }
public void Subscribe(Action action);
public void Unsubscribe(Action action);
public void Show(DialogContents dialogContents);
public DialogContents GetDialogContents();
public void Hide();
}
public interface IEconomyComparisonService
{
public List<BuildToCompareModel> BuildsToCompare { get; set; }
public void ChangeNumberOfTownHalls(int forPlayer, int toCount);
public void ChangeTownHallTiming(int forPlayer, int forTownHall, int toTiming);
public int GetTownHallCount(int forPlayer);
public int GetTownHallBuildTime(int forPlayer, int forTownHall);
public List<int> GetTownHallBuildTimes(int forPlayer);
public void ChangeFaction(int forPlayer, string toFaction);
public string GetFaction(int forPlayer);
public void ChangeColor(int forPlayer, string toColor);
public string GetColor(int forPlayer);
public void Subscribe(Action action);
public void Unsubscribe(Action action);
}
public interface IEntityDialogService
{
public void Subscribe(Action action);
public void Unsubscribe(Action action);
public void AddDialog(string entityId);
public void CloseDialog();
public void BackDialog();
public string? GetEntityId();
public bool HasDialog();
public bool HasHistory();
}
public interface INoteService
{
public List<NoteContentModel> NoteContentModels { get; set; }
public List<NoteConnectionModel> NoteConnectionModels { get; set; }
public List<NoteSectionModel> NoteSectionModels { get; set; }
public void Subscribe(Action action);
public void Unsubscribe(Action action);
public void Update();
public Task Load();
public bool IsLoaded();
}
public interface INavigationService
{
public void Subscribe(Action action);
public void Unsubscribe(Action action);
public void ChangeNavigationSectionId(int newState);
public int GetNavigationSectionId();
public void ChangeNavigationState(string newState);
public string GetNavigationState();
public void Back();
public void SelectSection(int webSectionType);
public void SelectPage(int pageType, Type webPageType);
public NavSelectionType GetNavSelectionType();
public int GetWebPageId();
public int GetWebSectionId();
public Type GetRenderType();
}
public interface IBuildComparisonService
{
public void SetBuilds(BuildToCompareModel buildToCompareModel);
public BuildToCompareModel Get();
public string BuildOrderAsYaml();
public string AsJson();
public bool LoadJson(string data);
public void Subscribe(Action action);
public void Unsubscribe(Action action);
}
public interface ITimingService
{
public int BuildingInputDelay { get; set; }
public int WaitTime { get; set; }
public int WaitTo { get; set; }
public int GetAttackTime();
public void SetAttackTime(int timing);
public int GetTravelTime();
public void SetTravelTime(int timing);
public void Subscribe(Action? action);
public void Unsubscribe(Action? action);
}
public interface IEconomyService
{
public List<EconomyModel> GetOverTime();
public EconomyModel GetEconomy(int atInterval);
public void Calculate(IBuildOrderService buildOrder, ITimingService timing, int fromInterval);
public void Subscribe(Action action);
public void Unsubscribe(Action action);
}
public interface IEntityFilterService
{
public delegate void EntityFilterAction(EntityFilterEvent entityFilterEvent);
public string GetFactionType();
public string GetImmortalType();
public string GetEntityType();
public string GetSearchText();
public List<string> GetFactionChoices();
public List<string> GetImmortalChoices();
public List<string> GetEntityChoices();
public bool SelectFactionType(string factionType);
public bool SelectImmortalType(string immortalType);
public bool SelectEntityType(string entityType);
public bool EnterSearchText(string searchText);
public void Subscribe(EntityFilterAction action);
public void Unsubscribe(EntityFilterAction action);
}
public interface IEntityService
{
public List<EntityModel> GetEntities();
}
public interface IEntityDisplayService
{
public List<string> DefaultChoices();
public string GetDisplayType();
public void SetDisplayType(string displayType);
public void Subscribe(Action action);
public void Unsubscribe(Action action);
}
public interface IImmortalSelectionService
{
public string GetFaction();
public string GetImmortal();
public bool SelectFaction(string faction);
public bool SelectImmortal(string immortal);
public void Subscribe(Action action);
public void Unsubscribe(Action action);
}
public interface IKeyService
{
public List<string> GetAllPressedKeys();
public string? GetHotkey();
public string GetHotkeyGroup();
public bool IsHoldingSpace();
public bool AddPressedKey(string key);
public bool RemovePressedKey(string key);
public void Subscribe(Action? action);
public void Unsubscribe(Action? action);
}
public interface IMemoryTesterService
{
public delegate void MemoryAction(MemoryTesterEvent memoryEvent);
public List<MemoryEntityModel> GetEntities();
public List<MemoryQuestionModel> GetQuestions();
void GenerateQuiz();
public void Update(MemoryQuestionModel question);
public void Verify();
public void Subscribe(MemoryAction memoryAction);
public void Unsubscribe(MemoryAction memoryAction);
}
public interface IBuildOrderService
{
public Dictionary<int, List<EntityModel>> StartedOrders { get; }
public Dictionary<int, List<EntityModel>> CompletedOrders { get; }
public Dictionary<int, List<EntityModel>> DepletedOrders { get; }
public Dictionary<string, int> UniqueCompletedTimes { get; }
public Dictionary<int, int> SupplyCountTimes { get; }
public bool Add(EntityModel entity, IEconomyService withEconomy);
public void Add(EntityModel entity, int atInterval);
public bool AddWait(int forInterval);
public bool AddWaitTo(int interval);
public void SetName(string name);
public string GetName();
public void SetNotes(string notes);
public string GetNotes();
public void DeprecatedSetColor(string color);
public string GetColor();
public int? WillMeetRequirements(EntityModel entity);
public int? WillMeetSupply(EntityModel entity);
public Dictionary<int, List<EntityModel>> GetOrders();
public List<EntityModel> GetCompletedBefore(int interval);
public List<EntityModel> GetUndepletedHarvestPointsCompletedBefore(int interval);
public void RemoveLast();
public void Reset();
public int GetLastRequestInterval();
public string BuildOrderAsYaml();
public string AsJson();
public void Subscribe(Action action);
public void Unsubscribe(Action action);
}
@@ -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();
}
}
+240
View File
@@ -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();
}
}
+11
View File
@@ -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();
}
}
+107
View File
@@ -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);
}
}
+96
View File
@@ -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();
}
}
+28
View File
@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DefineConstants>TRACE;NO_SQL</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DefineConstants>TRACE;NO_SQL;</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Blazor-Analytics" Version="3.11.0"/>
<PackageReference Include="Blazored.LocalStorage" Version="4.3.0-preview.1"/>
<PackageReference Include="Microsoft.JSInterop" Version="8.0.14"/>
<PackageReference Include="YamlDotNet" Version="11.2.1"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Model\Model.csproj"/>
</ItemGroup>
</Project>
@@ -0,0 +1,45 @@
using Blazor.Analytics;
namespace Services.Website;
public class DataCollectionKeys
{
// Inputs people are using in the build calculator
public static string BuildCalcInput = "buildcalc-input";
public static string PageInitialized = "page-initialized";
public static string FirstPage = "first-page";
}
public class DataCollectionService : IDataCollectionService, IDisposable
{
private readonly IAnalytics _globalTracking;
private readonly IStorageService _storageService;
private bool _isEnabled;
public DataCollectionService(IAnalytics globalTracking,
IStorageService storageService)
{
_globalTracking = globalTracking;
_storageService = storageService;
_storageService.Subscribe(Refresh);
Refresh();
}
public void SendEvent<T>(string eventName, T eventData)
{
if (_isEnabled) _globalTracking.TrackEvent(eventName, eventData);
}
void IDisposable.Dispose()
{
_storageService.Unsubscribe(Refresh);
}
private void Refresh()
{
_isEnabled = _storageService.GetValue<bool>(StorageKeys.EnabledDataCollection);
}
}
@@ -0,0 +1,76 @@
namespace Services.Website;
//TODO Move to a database folder, with EntityService, EntityFilterService
public class EntityDialogService : IEntityDialogService
{
private readonly List<string> history = new();
private string? entityId;
public void Subscribe(Action action)
{
OnChange += action;
}
public void Unsubscribe(Action action)
{
OnChange += action;
}
public void AddDialog(string id)
{
entityId = id;
history.Add(id);
NotifyDataChanged();
}
public void CloseDialog()
{
entityId = null;
history.Clear();
NotifyDataChanged();
}
public void BackDialog()
{
if (history.Count > 1)
{
history.RemoveAt(history.Count - 1);
if (history.Count == 0)
{
entityId = null;
NotifyDataChanged();
return;
}
entityId = history.Last();
NotifyDataChanged();
}
}
public bool HasDialog()
{
return entityId != null;
}
public bool HasHistory()
{
return history.Count > 1;
}
public string? GetEntityId()
{
return entityId;
}
private event Action OnChange = null!;
private void NotifyDataChanged()
{
OnChange?.Invoke();
}
}
@@ -0,0 +1,56 @@
using Microsoft.AspNetCore.Components;
namespace Services.Website;
public class DialogContents
{
public string Title { get; set; }
public string Message { get; set; }
public string ConfirmButtonLabel { get; set; }
public EventCallback<EventArgs> OnConfirm { get; set; }
public EventCallback<EventArgs> OnCancel { get; set; }
}
public class MyDialogService : IMyDialogService
{
private DialogContents _dialogContents;
public bool IsVisible { get; set; }
public void Subscribe(Action action)
{
OnChange += action;
}
public void Unsubscribe(Action action)
{
OnChange += action;
}
public void Show(DialogContents dialogContents)
{
_dialogContents = dialogContents;
IsVisible = true;
NotifyDataChanged();
}
public DialogContents GetDialogContents()
{
return _dialogContents;
}
public void Hide()
{
IsVisible = false;
NotifyDataChanged();
}
private event Action OnChange = null!;
private void NotifyDataChanged()
{
OnChange();
}
}
@@ -0,0 +1,120 @@
using Model.Website.Enums;
namespace Services.Website;
public class NavigationService : INavigationService
{
private int navigationStateId = -1;
private string navigationStateType = NavigationStateType.Default;
private NavSelectionType navSelectionType = NavSelectionType.None;
private Type renderType = null!;
private int webPageType;
private int webSectionType;
public void Subscribe(Action action)
{
OnChange += action;
}
public void Unsubscribe(Action action)
{
OnChange += action;
}
public void ChangeNavigationSectionId(int newState)
{
navigationStateId = newState;
NotifyDataChanged();
}
public int GetNavigationSectionId()
{
return navigationStateId;
}
public void ChangeNavigationState(string newState)
{
if (newState.Equals(navigationStateType))
return;
navigationStateType = newState;
NotifyDataChanged();
}
public string GetNavigationState()
{
return navigationStateType;
}
public void SelectPage(int pageType, Type page)
{
if (renderType != page)
{
renderType = page;
webPageType = pageType;
navSelectionType = NavSelectionType.Page;
NotifyDataChanged();
}
}
public void SelectSection(int section)
{
if (section == webSectionType) return;
webSectionType = section;
navSelectionType = NavSelectionType.Section;
NotifyDataChanged();
}
public void Back()
{
if (navSelectionType == NavSelectionType.Page)
{
navSelectionType = NavSelectionType.Section;
webPageType = 0;
NotifyDataChanged();
return;
}
if (navSelectionType == NavSelectionType.Section)
{
navSelectionType = NavSelectionType.None;
webSectionType = 0;
webPageType = 0;
NotifyDataChanged();
}
}
public NavSelectionType GetNavSelectionType()
{
return navSelectionType;
}
public int GetWebPageId()
{
return webPageType;
}
public int GetWebSectionId()
{
return webSectionType;
}
public Type GetRenderType()
{
return renderType;
}
private event Action OnChange = null!;
private void NotifyDataChanged()
{
OnChange?.Invoke();
}
}
@@ -0,0 +1,63 @@
using Microsoft.JSInterop;
namespace Services.Website;
public class PermissionService : IPermissionService, IDisposable
{
private readonly IStorageService _storageService;
private IJSRuntime _jsRuntime;
private IToastService _toastService;
private bool isLoaded;
private bool isStorageEnabled = false;
public PermissionService(IJSRuntime jsRuntime, IToastService toastService, IStorageService storageService)
{
_jsRuntime = jsRuntime;
_toastService = toastService;
_storageService = storageService;
_storageService.Subscribe(NotifyDataChanged);
}
void IDisposable.Dispose()
{
_storageService.Unsubscribe(NotifyDataChanged);
}
public void Subscribe(Action action)
{
OnChange += action;
}
public void Unsubscribe(Action action)
{
OnChange += action;
}
public bool GetIsStorageEnabled()
{
return _storageService.GetValue<bool>(StorageKeys.EnabledStorage);
}
public bool GetIsDataCollectionEnabled()
{
return _storageService.GetValue<bool>(StorageKeys.EnabledDataCollection);
}
public void SetIsStorageEnabled(bool isEnabled)
{
_storageService.SetValue(StorageKeys.EnabledStorage, isEnabled);
}
public void SetIsDataCollectionEnabled(bool isEnabled)
{
_storageService.SetValue(StorageKeys.EnabledDataCollection, isEnabled);
}
private event Action OnChange = null!;
private void NotifyDataChanged()
{
OnChange();
}
}
+125
View File
@@ -0,0 +1,125 @@
using Model.Entity.Data;
using Model.Website;
using Model.Website.Data;
namespace Services.Website;
public class SearchService : ISearchService
{
private readonly INoteService noteService;
private bool isLoaded;
public SearchService(INoteService noteService)
{
this.noteService = noteService;
}
public List<SearchPointModel> SearchPoints { get; set; } = new();
public Dictionary<string, List<SearchPointModel>> Searches { get; set; } = new();
public bool IsVisible { get; set; }
public void Subscribe(Action action)
{
OnChange += action;
}
public void Unsubscribe(Action action)
{
OnChange += action;
}
public void Search(string entityId)
{
}
public async Task Load()
{
await noteService.Load();
Searches.Add("Pages", new List<SearchPointModel>());
Searches.Add("Notes", new List<SearchPointModel>());
Searches.Add("Entities", new List<SearchPointModel>());
foreach (var webPage in WebsiteData.GetPages())
{
SearchPoints.Add(new SearchPointModel
{
Title = webPage.Name,
PointType = "WebPage",
Summary = $"{webPage.Description}",
Href = webPage.Href
});
Searches["Pages"].Add(SearchPoints.Last());
}
foreach (var note in noteService.NoteContentModels)
{
SearchPoints.Add(new SearchPointModel
{
Title = note.Name,
PointType = "Note",
Href = note.GetNoteLink(),
Summary = note.Description
});
Searches["Notes"].Add(SearchPoints.Last());
}
foreach (var entity in EntityData.Get().Values)
{
var summary =
entity.Info().Description.Length > 35
? entity.Info().Description.Substring(0, 30).Trim() + "..."
: entity.Info().Description.Length > 0
? entity.Info().Description
: "";
SearchPoints.Add(new SearchPointModel
{
Title = entity.Info().Name,
Tags = $"{entity.EntityType},{entity.Descriptive}",
PointType = "Entity",
Summary = $"{entity.EntityType}, {summary}",
Href = $"database/{entity.Info().Name.ToLower()}"
});
Searches["Entities"].Add(SearchPoints.Last());
}
isLoaded = true;
NotifyDataChanged();
}
public bool IsLoaded()
{
return isLoaded;
}
public void Show()
{
IsVisible = true;
NotifyDataChanged();
}
public void Hide()
{
IsVisible = false;
NotifyDataChanged();
}
private event Action OnChange = null!;
private void NotifyDataChanged()
{
OnChange();
}
}
+101
View File
@@ -0,0 +1,101 @@
using Blazored.LocalStorage;
using Model.Feedback;
namespace Services.Website;
public class StorageKeys
{
public static string EnabledStorage = "StorageEnabled";
public static string EnabledDataCollection = "StorageDataCollection";
public static string IsPlainView { get; set; } = "IsPlainView";
public static string IsDynamicFormatting { get; set; } = "IsDynamicFormatting";
public static string AttackTime { get; set; } = "AttackTime";
public static string TravelTime { get; set; } = "TravelTime";
public static string SelectedFaction { get; set; } = "SelectedFaction";
public static string SelectedImmortal { get; set; } = "SelectedImmortal";
public static string BuildInputDelay { get; set; } = "BuildInputDelay";
public static string WaitTime { get; set; } = "WaitTime";
public static string WaitTo { get; set; } = "WaitTo";
}
public class StorageService : IStorageService
{
private readonly ISyncLocalStorageService _localStorageService;
private readonly IToastService _toastService;
private bool isLoaded;
public StorageService(IToastService toastService,
ISyncLocalStorageService localStorageService)
{
_toastService = toastService;
_localStorageService = localStorageService;
}
public void Subscribe(Action action)
{
OnChange += action;
}
public void Unsubscribe(Action action)
{
OnChange += action;
}
public T GetValue<T>(string forKey)
{
return _localStorageService.GetItem<T>(forKey);
}
public void SetValue<T>(string key, T value)
{
if (key.Equals(StorageKeys.EnabledStorage) && value.Equals(true))
{
_localStorageService.SetItem(key, value);
NotifyDataChanged();
return;
}
if (key.Equals(StorageKeys.EnabledStorage))
{
_localStorageService.Clear();
NotifyDataChanged();
return;
}
var isEnabled = GetValue<bool>(StorageKeys.EnabledStorage);
if (!isEnabled)
{
_toastService.AddToast(new ToastModel
{
Title = "Permission Error",
SeverityType = SeverityType.Error,
Message = "Storage must be enabled before Storage can be used."
});
NotifyDataChanged();
return;
}
_localStorageService.SetItem(key, value);
NotifyDataChanged();
}
public Task Load()
{
if (!isLoaded) return Task.CompletedTask;
isLoaded = true;
NotifyDataChanged();
return Task.CompletedTask;
}
private event Action OnChange = null!;
private void NotifyDataChanged()
{
OnChange();
}
}
+60
View File
@@ -0,0 +1,60 @@
using Model.Feedback;
namespace Services.Website;
public class ToastService : IToastService
{
private readonly List<ToastModel> toasts = new();
public void Subscribe(Action action)
{
OnChange += action;
}
public void Unsubscribe(Action action)
{
OnChange += action;
}
public void AddToast(ToastModel toast)
{
toasts.Insert(0, toast);
NotifyDataChanged();
}
public void RemoveToast(ToastModel toast)
{
toasts.Remove(toast);
}
public bool HasToasts()
{
return toasts.Count > 0;
}
public List<ToastModel> GetToasts()
{
return toasts;
}
public void AgeToasts()
{
foreach (var toast in toasts) toast.Age++;
NotifyDataChanged();
}
public void ClearAllToasts()
{
toasts.Clear();
NotifyDataChanged();
}
private event Action OnChange = null!;
private void NotifyDataChanged()
{
OnChange();
}
}