From 31996e488cf5e02a48f36b392c975f1585ede768 Mon Sep 17 00:00:00 2001 From: Trais McAllister Date: Wed, 1 Dec 2021 01:38:02 -0800 Subject: [PATCH 1/3] Implemented scheduled notifications for configuring Rally time reminders (#14) --- .../Models/NavigationManagerExtensions.cs | 41 ++++ Utilities.Games/Models/NotificationTrigger.cs | 32 --- .../Models/NotificationTriggers.cs | 40 ---- .../{ => Notifications}/INotificationData.cs | 5 +- .../INotificationTrigger.cs | 16 +- .../Models/Notifications/JSNotification.cs | 30 +++ .../Notifications/JSNotificationAction.cs | 14 ++ .../Notifications/NotificationTrigger.cs | 115 +++++++++++ .../Notifications/NotificationTriggers.cs | 50 +++++ .../AddFellowshipRallyReminderModal.razor | 100 ++++++++++ .../Components/AddUserServerModal.razor | 30 +-- .../Pages/Subsites/LOTR_RiseToWar/Index.razor | 31 ++- .../ServerFellowshipRallyReminder.cs | 49 +++++ .../LOTR_RiseToWar/UserServerView.razor | 185 ++++++++++++++++-- Utilities.Games/Program.cs | 12 +- Utilities.Games/Shared/CopyToClipboard.razor | 34 ++++ Utilities.Games/Shared/MainLayout.razor | 3 + Utilities.Games/Utilities.Games.csproj | 1 + Utilities.Games/wwwroot/clipboardCopy.js | 10 + Utilities.Games/wwwroot/index.html | 1 + .../wwwroot/notificationTrigger.js | 72 +++++-- Utilities.Games/wwwroot/service-worker.js | 4 + 22 files changed, 738 insertions(+), 137 deletions(-) create mode 100644 Utilities.Games/Models/NavigationManagerExtensions.cs delete mode 100644 Utilities.Games/Models/NotificationTrigger.cs delete mode 100644 Utilities.Games/Models/NotificationTriggers.cs rename Utilities.Games/Models/{ => Notifications}/INotificationData.cs (74%) rename Utilities.Games/Models/{ => Notifications}/INotificationTrigger.cs (80%) create mode 100644 Utilities.Games/Models/Notifications/JSNotification.cs create mode 100644 Utilities.Games/Models/Notifications/JSNotificationAction.cs create mode 100644 Utilities.Games/Models/Notifications/NotificationTrigger.cs create mode 100644 Utilities.Games/Models/Notifications/NotificationTriggers.cs create mode 100644 Utilities.Games/Pages/Subsites/LOTR_RiseToWar/Components/AddFellowshipRallyReminderModal.razor create mode 100644 Utilities.Games/Pages/Subsites/LOTR_RiseToWar/Models/Notifications/ServerFellowshipRallyReminder.cs create mode 100644 Utilities.Games/Shared/CopyToClipboard.razor create mode 100644 Utilities.Games/wwwroot/clipboardCopy.js diff --git a/Utilities.Games/Models/NavigationManagerExtensions.cs b/Utilities.Games/Models/NavigationManagerExtensions.cs new file mode 100644 index 0000000..a19ae5c --- /dev/null +++ b/Utilities.Games/Models/NavigationManagerExtensions.cs @@ -0,0 +1,41 @@ +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.WebUtilities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Utilities.Games.Models +{ + public static class NavigationManagerExtensions + { + public static bool TryGetQueryString(this NavigationManager navManager, string key, out T value) + { + var uri = navManager.ToAbsoluteUri(navManager.Uri); + + if (QueryHelpers.ParseQuery(uri.Query).TryGetValue(key, out var valueFromQueryString)) + { + if (typeof(T) == typeof(int) && int.TryParse(valueFromQueryString, out var valueAsInt)) + { + value = (T)(object)valueAsInt; + return true; + } + + if (typeof(T) == typeof(string)) + { + value = (T)(object)valueFromQueryString.ToString(); + return true; + } + + if (typeof(T) == typeof(decimal) && decimal.TryParse(valueFromQueryString, out var valueAsDecimal)) + { + value = (T)(object)valueAsDecimal; + return true; + } + } + + value = default; + return false; + } + } +} diff --git a/Utilities.Games/Models/NotificationTrigger.cs b/Utilities.Games/Models/NotificationTrigger.cs deleted file mode 100644 index 3480391..0000000 --- a/Utilities.Games/Models/NotificationTrigger.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; - -namespace Utilities.Games.Models -{ - /// - /// An abstract implementation of a Notification Trigger type. - /// - public abstract class NotificationTrigger : INotificationTrigger { - public string Tag => GetType().Name; - - public abstract string Title { get; } - - public string Body { get; set; } - - public DateTime TriggerTime { get; set; } - - public string Icon { get; set; } - - public INotificationData Data { get; set; } - - /// - /// Creates a new instance of a with a specific message and trigger time. - /// - /// Body of the message within the Notification. - /// Reference to the time the trigger should take place. - public NotificationTrigger(string message, DateTime triggerTime) - { - Body = message; - TriggerTime = triggerTime; - } - } -} diff --git a/Utilities.Games/Models/NotificationTriggers.cs b/Utilities.Games/Models/NotificationTriggers.cs deleted file mode 100644 index 4e1c7a9..0000000 --- a/Utilities.Games/Models/NotificationTriggers.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Microsoft.JSInterop; - -namespace Utilities.Games.Models -{ - /// - /// Service for creating and cancelling notifications using the experimental Notification API. - /// - public class NotificationTriggers { - private readonly IJSRuntime js; - - public NotificationTriggers(IJSRuntime js) - { - this.js = js; - } - - /// - /// Creates a new Notification Trigger from a standard type. - /// - /// Reference to the type of Notification Trigger. - /// Reference to the instance of a trigger. - public void CreateNotificationTrigger(T trigger) where T : INotificationTrigger - { - js.InvokeVoidAsync("createScheduledNotification", new object [] { trigger.Tag, trigger.Title, trigger.Body, trigger.TriggerTime }); - } - - /// - /// Cancels a Notification Trigger by its respective tag. - /// - /// Reference to the Notification Trigger tag. - public void CancelNotificationTrigger(string tag) { - js.InvokeVoidAsync("cancelScheduledNotification", new object[] { tag }); - } - - /// - /// Cancels a Notification Trigger by its respective tag, derived from the type. - /// - /// Reference to the type. - public void CancelNotificationTrigger() where T : NotificationTrigger => CancelNotificationTrigger(typeof(T).Name); - } -} diff --git a/Utilities.Games/Models/INotificationData.cs b/Utilities.Games/Models/Notifications/INotificationData.cs similarity index 74% rename from Utilities.Games/Models/INotificationData.cs rename to Utilities.Games/Models/Notifications/INotificationData.cs index da75879..ac83687 100644 --- a/Utilities.Games/Models/INotificationData.cs +++ b/Utilities.Games/Models/Notifications/INotificationData.cs @@ -1,9 +1,10 @@ -namespace Utilities.Games.Models +namespace Utilities.Games.Models.Notifications { /// /// An interface for defining types of Notification data. /// - public interface INotificationData { + public interface INotificationData + { /// /// Return URL the user should be directed to when notification is clicked. /// diff --git a/Utilities.Games/Models/INotificationTrigger.cs b/Utilities.Games/Models/Notifications/INotificationTrigger.cs similarity index 80% rename from Utilities.Games/Models/INotificationTrigger.cs rename to Utilities.Games/Models/Notifications/INotificationTrigger.cs index 2ee3733..7fe73de 100644 --- a/Utilities.Games/Models/INotificationTrigger.cs +++ b/Utilities.Games/Models/Notifications/INotificationTrigger.cs @@ -1,15 +1,12 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Threading.Tasks; -namespace Utilities.Games.Models +namespace Utilities.Games.Models.Notifications { /// /// An interface for implementing new scheduled notifications via the experimental Notification API. /// - public interface INotificationTrigger { + public interface INotificationTrigger + { /// /// An identifier for the type of notification. /// @@ -26,10 +23,15 @@ public interface INotificationTrigger { string Body { get; set; } /// - /// Specific time that the notification should be triggered. + /// Specific time that the notification should be triggered in UTC time. /// DateTime TriggerTime { get; set; } + /// + /// Mirror of Notification.timestamp + /// + long Timestamp { get; set; } + /// /// Icon to be displayed with notification. /// diff --git a/Utilities.Games/Models/Notifications/JSNotification.cs b/Utilities.Games/Models/Notifications/JSNotification.cs new file mode 100644 index 0000000..0b38718 --- /dev/null +++ b/Utilities.Games/Models/Notifications/JSNotification.cs @@ -0,0 +1,30 @@ +using System; + +namespace Utilities.Games.Models.Notifications +{ + /// + /// Mirror of JS Notification + /// + /// + public class JSNotification { + public string badge { get; set; } + + public string body { get; set; } + + public object data { get; set; } + + public string tag { get; set; } + + public string icon { get; set; } + + public string image { get; set; } + + public long timestamp { get; set; } + + public string title { get; set; } + } + public class JSNotification : JSNotification + { + public new T data { get; set; } + } +} diff --git a/Utilities.Games/Models/Notifications/JSNotificationAction.cs b/Utilities.Games/Models/Notifications/JSNotificationAction.cs new file mode 100644 index 0000000..6412bfb --- /dev/null +++ b/Utilities.Games/Models/Notifications/JSNotificationAction.cs @@ -0,0 +1,14 @@ +namespace Utilities.Games.Models.Notifications +{ + /// + /// Mirror of JS NotificationAction + /// + /// + public struct JSNotificationAction { + public string action; + + public string title; + + public string icon; + } +} diff --git a/Utilities.Games/Models/Notifications/NotificationTrigger.cs b/Utilities.Games/Models/Notifications/NotificationTrigger.cs new file mode 100644 index 0000000..a148650 --- /dev/null +++ b/Utilities.Games/Models/Notifications/NotificationTrigger.cs @@ -0,0 +1,115 @@ +using Newtonsoft.Json; +using System; +using System.ComponentModel.DataAnnotations; +using System.Text; + +namespace Utilities.Games.Models.Notifications +{ + /// + /// An abstract implementation of a Notification Trigger type. + /// + public abstract class NotificationTrigger : INotificationTrigger + { + const long epoch_time = 621_355_968_000_000_000; + const long ticks_per_millisecond = 10_000; + + public string Tag => GetType().Name; + + public abstract string Title { get; } + + [MaxLength(500)] + public virtual string Body { get; set; } = string.Empty; + + protected long _timestamp { get; set; } + public virtual long Timestamp { + get { + return _timestamp; + } + set { + _timestamp = value; + if (_triggerTime == default) + { + var ticks = epoch_time + (_timestamp * ticks_per_millisecond); // Convert JS Ticks to C# Ticks + _triggerTime = new DateTime(ticks, DateTimeKind.Utc); + } + } + } + + protected DateTime _triggerTime { get; set; } + public virtual DateTime TriggerTime { + get { + return _triggerTime; + } + set + { + _triggerTime = value.Kind == DateTimeKind.Utc ? value : value.ToUniversalTime(); + if (_timestamp == default) + { + var ticks = (_triggerTime.Ticks - epoch_time) / ticks_per_millisecond; // Convert C# Ticks to JS Ticks + _timestamp = ticks; + } + } + } + + public virtual string Icon { get; set; } + + public virtual INotificationData Data { get; set; } + + public NotificationTrigger() { } + + /// + /// Creates a new instance of a with a specific message and trigger time. + /// + /// Body of the message within the Notification. + /// Reference to the time the trigger should take place. + public NotificationTrigger(string message, DateTime triggerTime) + { + Body = message; + TriggerTime = triggerTime; + } + + public NotificationTrigger(string token) + { + var trigger = FromToken(token); + if (trigger != null) + { + Body = trigger.Body; + TriggerTime = trigger.TriggerTime; + Icon = trigger.Icon; + Data = trigger.Data; + } + } + + /// + /// Creates a sharable token of the trigger. + /// + /// + public string Tokenize() + { + string serializedObject = JsonConvert.SerializeObject(this, Formatting.None); + byte[] tokenBytes = Encoding.UTF8.GetBytes(serializedObject); + return Convert.ToBase64String(tokenBytes); + } + + /// + /// Creates a from a token. + /// + /// Sharable token of a trigger. + /// Deserialized . + public static T FromToken(string token) where T : NotificationTrigger + { + byte[] tokenBytes = Convert.FromBase64String(token); + string serializedObject = Encoding.UTF8.GetString(tokenBytes); + return JsonConvert.DeserializeObject(serializedObject); + } + } + + public abstract class NotificationTrigger : NotificationTrigger where T : new() + { + public virtual new T Data { get; set; } = new(); + + public NotificationTrigger() : base() { } + + public NotificationTrigger(string message, DateTime triggerTime) : base(message, triggerTime) { } + } +} diff --git a/Utilities.Games/Models/Notifications/NotificationTriggers.cs b/Utilities.Games/Models/Notifications/NotificationTriggers.cs new file mode 100644 index 0000000..3010430 --- /dev/null +++ b/Utilities.Games/Models/Notifications/NotificationTriggers.cs @@ -0,0 +1,50 @@ +using Microsoft.JSInterop; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Utilities.Games.Models.Notifications +{ + /// + /// Service for creating and cancelling notifications using the experimental Notification API. + /// + public class NotificationTriggers { + private readonly IJSRuntime js; + const string PREFIX = "scheduledNotifications"; + + public NotificationTriggers(IJSRuntime js) + { + this.js = js; + } + + public async ValueTask IsSupported() => await js.InvokeAsync($"{PREFIX}.isSupported", new object[] { }); + + /// + /// Creates a new Notification Trigger from a standard type. + /// + /// Reference to the type of Notification Trigger. + /// Reference to the instance of a trigger. + public async ValueTask CreateNotificationTrigger(T trigger) where T : INotificationTrigger => await js.InvokeVoidAsync($"{PREFIX}.createScheduledNotification", trigger); + + /// + /// Cancels a Notification Trigger by its respective tag. + /// + /// Reference to the Notification Trigger tag. + public async ValueTask CancelNotificationTrigger(string tag) => await js.InvokeVoidAsync($"{PREFIX}.cancelScheduledNotification", new object[] { tag }); + + /// + /// Cancels a Notification Trigger by its respective tag, derived from the type. + /// + /// Reference to the type. + public async ValueTask CancelNotificationTrigger() where T : NotificationTrigger => await CancelNotificationTrigger(typeof(T).Name); + + /// + /// Gets all notifications of the specified type. + /// + /// Reference to the type of notification. + public async Task> GetNotifications() where T : INotificationTrigger, new() + { + return await js.InvokeAsync($"{PREFIX}.getScheduledNotifications", new object[] { typeof(T).Name }); + } + } +} diff --git a/Utilities.Games/Pages/Subsites/LOTR_RiseToWar/Components/AddFellowshipRallyReminderModal.razor b/Utilities.Games/Pages/Subsites/LOTR_RiseToWar/Components/AddFellowshipRallyReminderModal.razor new file mode 100644 index 0000000..ad80d04 --- /dev/null +++ b/Utilities.Games/Pages/Subsites/LOTR_RiseToWar/Components/AddFellowshipRallyReminderModal.razor @@ -0,0 +1,100 @@ +@using LOTR_RiseToWar.Models.LocalStores +@using LOTR_RiseToWar.Models +@using LOTR_RiseToWar.Models.Notifications +@inject HttpClient Http + + + +@if (ShowBackdrop) +{ + +} + +@code { + public delegate void Callback(ServerFellowshipRallyReminder reminder); + + [Parameter] + public Callback OnSave { get; set; } + + [Parameter] + public UserServer UserServer { get; set; } + + private ServerFellowshipRallyReminder Result { get; set; } = new(); + private int ReminderOffset = 0; + + private Guid Guid = Guid.NewGuid(); + private string ModalDisplay = "none;"; + private string ModalClass = ""; + private bool ShowBackdrop = false; + + + public void Open() + { + ModalDisplay = "block;"; + ModalClass = "Show"; + ShowBackdrop = true; + StateHasChanged(); + } + + public void Close() + { + ModalDisplay = "none"; + ModalClass = ""; + ShowBackdrop = false; + StateHasChanged(); + } + + private void HandleValidSubmit() + { + if (OnSave != null) + { + Result.TriggerTime = Result.Data.EventTime.AddMinutes(ReminderOffset * -1); + if (UserServer != null) + { + Result.Data.ServerNumber = UserServer.ServerNumber; + Result.Data.FellowshipName = UserServer.FellowshipName; + } + OnSave(Result); + } + } +} diff --git a/Utilities.Games/Pages/Subsites/LOTR_RiseToWar/Components/AddUserServerModal.razor b/Utilities.Games/Pages/Subsites/LOTR_RiseToWar/Components/AddUserServerModal.razor index 3ad8882..b9b4a42 100644 --- a/Utilities.Games/Pages/Subsites/LOTR_RiseToWar/Components/AddUserServerModal.razor +++ b/Utilities.Games/Pages/Subsites/LOTR_RiseToWar/Components/AddUserServerModal.razor @@ -17,35 +17,43 @@
- - +
+ + + You can find the server # by tapping on the globe icon from the home screen. +
+ You can find your Username directly above your Keep.
+ You can find your User ID directly below your Username by tapping on the settings icon in-game.
- - - @if (Factions?.Length > 0) - { - foreach (Faction faction in Factions) +
+ + + @if (Factions?.Length > 0) { - + foreach (Faction faction in Factions) + { + + } } - } - + +
- + + This is the 4-character short name for the Fellowship/Warband you joined, found in the Fellowship/Warband screen in square brackets.
diff --git a/Utilities.Games/Pages/Subsites/LOTR_RiseToWar/Index.razor b/Utilities.Games/Pages/Subsites/LOTR_RiseToWar/Index.razor index 2292602..b3890af 100644 --- a/Utilities.Games/Pages/Subsites/LOTR_RiseToWar/Index.razor +++ b/Utilities.Games/Pages/Subsites/LOTR_RiseToWar/Index.razor @@ -6,19 +6,28 @@

The Lord of the Rings: Rise To War™

Tools for the game The Lord of the Rings: Rise to War, developed and published by NetEase Interactive Entertainment©.

-

Your servers

+
+

Gameplay Servers

+

Maintain a list of which servers you are active on. Once added, you will be able to save and collaborate rally events and manage in-game progress to share with others, compare stats, and plan your conquests.

+

Game Resources

  • @@ -55,15 +65,18 @@ private IEnumerable UserServers { get; set; } private Utilities.Games.Pages.Subsites.LOTR_RiseToWar.Components.AddUserServerModal AddUserServerModal; - protected override async Task OnInitializedAsync() { + protected override async Task OnInitializedAsync() + { UserServers = await LocalServerStore.GetAll(); } - private void ShowAddUserServerModal() { + private void ShowAddUserServerModal() + { AddUserServerModal.Open(); } - private async void SaveUserServer(UserServer userServer) { + private async void SaveUserServer(UserServer userServer) + { await LocalServerStore.SaveAsync(userServer); AddUserServerModal.Close(); UserServers = await LocalServerStore.GetAll(); diff --git a/Utilities.Games/Pages/Subsites/LOTR_RiseToWar/Models/Notifications/ServerFellowshipRallyReminder.cs b/Utilities.Games/Pages/Subsites/LOTR_RiseToWar/Models/Notifications/ServerFellowshipRallyReminder.cs new file mode 100644 index 0000000..d808ca7 --- /dev/null +++ b/Utilities.Games/Pages/Subsites/LOTR_RiseToWar/Models/Notifications/ServerFellowshipRallyReminder.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; +using Utilities.Games.Models.Notifications; +using Utilities.Games.Pages.Subsites.LOTR_RiseToWar.Models.LocalStores; + +namespace Utilities.Games.Pages.Subsites.LOTR_RiseToWar.Models.Notifications +{ + public class ServerFellowshipRallyReminder : NotificationTrigger + { + public override string Title => "LOTR: Rise to War - Fellowship Rally Reminder"; + + public override string Icon => "/Subsites/LOTR_RiseToWar/images/favicon.ico"; + + public override FellowshipRallyInformation Data { get; set; } = new(); + + public ServerFellowshipRallyReminder() : base() { } + + public ServerFellowshipRallyReminder(FellowshipRallyInformation details, string message, DateTime reminderTime) : base(message, reminderTime) { + Data = details; + } + + public class FellowshipRallyInformation : INotificationData + { + public string ReturnUrl { get; set; } = "/lotr_risetowar"; + + /// + /// Timestamp of when the event starts in local time. + /// + [Required] + public DateTime EventTime { get; set; } = DateTime.Now.ToLocalTime().AddDays(1); + + /// + /// Reference to the server the event takes place in. + /// + [Required] + public int ServerNumber { get; set; } + + /// + /// Reference to the user's Fellowship + /// + [Required] + public string FellowshipName { get; set; } + + } + } +} diff --git a/Utilities.Games/Pages/Subsites/LOTR_RiseToWar/UserServerView.razor b/Utilities.Games/Pages/Subsites/LOTR_RiseToWar/UserServerView.razor index c235c71..b16e6e8 100644 --- a/Utilities.Games/Pages/Subsites/LOTR_RiseToWar/UserServerView.razor +++ b/Utilities.Games/Pages/Subsites/LOTR_RiseToWar/UserServerView.razor @@ -1,22 +1,130 @@ @page "/lotr_risetowar/userserver/{server:int}" @inject LOTR_RiseToWar.Models.LocalStores.ServerStore LocalServerStore @inject LOTR_RiseToWar.Models.LocalStores.CommanderStore LocalCommanderStore +@inject Utilities.Games.Models.Notifications.NotificationTriggers NotificationManager +@inject NavigationManager NavigationManager +@using LOTR_RiseToWar.Models.Notifications @using LOTR_RiseToWar.Models.LocalStores +@using Utilities.Games.Models @layout LOTR_RiseToWar_Layout @if (Item != null) { -

    Server @(Item.ServerNumber) — @(Item.Username)

    - @if (!string.IsNullOrEmpty(Item.FellowshipId) || !string.IsNullOrEmpty(Item.FellowshipName)){ -

    - Joined @(Item.FellowshipName) - @if (!string.IsNullOrEmpty(Item.FellowshipId)) - { - (@(Item.FellowshipId)) - } -

    - } +

    Server @(Item.ServerNumber)

    +

    [@(Item.UserId)] @(Item.Username)

    + @if (!string.IsNullOrEmpty(Item.FellowshipId) || !string.IsNullOrEmpty(Item.FellowshipName) || !string.IsNullOrEmpty(Item.Faction)) + { +

    + Joined + @if (!string.IsNullOrEmpty(Item.FellowshipId)) + { +  [@(Item.FellowshipId)] + } + @if (!string.IsNullOrEmpty(Item.FellowshipName)) + { +  @(Item.FellowshipName) + @if (!string.IsNullOrEmpty(Item.Faction)) + { +  of the @(Item.Faction) Faction + } + } + else if (!string.IsNullOrEmpty(Item.Faction)) + { +  the @(Item.Faction) Faction + } +

    + } + +
    +

    Fellowship Rallies

    +

    View and create reminders of rally events you are either coordinating or contributing to. You will receive a notification when it's time to prepare for the rally.

    + @if (NotificationsSupported) + { +
    + @if (RallyReminders?.Any() == true) + { + foreach (var reminder in RallyReminders) + { +
    + +
    +
    @(reminder.Data.EventTime.ToString())
    +

    @(reminder.Body)

    +
    + + + + + + + + + + + +
    Server@(reminder.Data.ServerNumber)
    Fellowship@(reminder.Data.FellowshipName)
    +
    +
    + +
    + } + } + @if (NewReminderToken != null) + { +
    + +
    +
    Add Rally Reminder?
    +

    @(NewReminderToken.Body)

    +
    + + + + + + + + + + + + + + + +
    Server@(NewReminderToken.Data.ServerNumber)
    Fellowship@(NewReminderToken.Data.FellowshipName)
    Rally Time@(NewReminderToken.Data.EventTime.ToString())
    +
    +
    + +
    + } +
    + +
    +
    New Rally
    +

    Create a new Rally reminder.

    + +
    +
    +
    + } + else + { +

    Scheduled notifications are not supported in this browser.

    + } + +

    Commanders

    +

    Maintain a list of which commanders you have in the game on this server. Once added, you can use these commanders in various tools and calculators throughout this site.

    @if (UserCommanders?.Any() == true) { @@ -45,12 +153,17 @@
} +else if (NewReminderToken != null) +{ +

You have not added Server @(NewReminderToken.Data.ServerNumber) to your list of servers yet.

+} else { -

Loading...

+

Loading...

} + @code { [Parameter] @@ -59,22 +172,57 @@ else public Models.LocalStores.UserServer Item { get; set; } public List UserCommanders { get; set; } = new List(); + public IEnumerable RallyReminders { get; set; } + + private bool NotificationsSupported; + + private ServerFellowshipRallyReminder NewReminderToken; private Utilities.Games.Pages.Subsites.LOTR_RiseToWar.Components.AddUserServerCommanderModal AddUserServerCommanderModal; + private Utilities.Games.Pages.Subsites.LOTR_RiseToWar.Components.AddFellowshipRallyReminderModal AddRallyReminderModal; protected override async Task OnInitializedAsync() { + NotificationsSupported = await NotificationManager.IsSupported(); Item = await LocalServerStore.Get(Server); + if (NavigationManager.TryGetQueryString("addReminder", out string reminderToken)) + { + NewReminderToken = ServerFellowshipRallyReminder.FromToken(reminderToken); + if (NewReminderToken.TriggerTime <= DateTime.UtcNow.AddSeconds(30)) + { + NewReminderToken = null; + } + } + UserCommanders = (await LocalCommanderStore.GetAll()) .Where(o => o.ServerNumber == Server) .ToList(); + if (NotificationsSupported) + { + RallyReminders = (await NotificationManager.GetNotifications()); + Console.WriteLine("Notifications: {0}", Newtonsoft.Json.JsonConvert.SerializeObject(RallyReminders)); + } + } + + private async Task AcceptNewReminder() + { + SaveRallyReminder(NewReminderToken); + NewReminderToken = null; + } + private async Task DeclineNewReminder() + { + NewReminderToken = null; } private void ShowAddUserServerCommanderModal() { AddUserServerCommanderModal.Open(); } + private void ShowAddRallyReminderModal() + { + AddRallyReminderModal.Open(); + } private async void SaveUserServerCommander(UserServerCommander userServerCommander) { @@ -85,4 +233,19 @@ else .ToList(); StateHasChanged(); } + private async void SaveRallyReminder(ServerFellowshipRallyReminder reminder) + { + if (reminder.TriggerTime > DateTime.Now.AddSeconds(30)) + { + Console.WriteLine("Saving Reminder: {0}", Newtonsoft.Json.JsonConvert.SerializeObject(reminder)); + await NotificationManager.CreateNotificationTrigger(reminder); + AddRallyReminderModal.Close(); + RallyReminders = await NotificationManager.GetNotifications(); + StateHasChanged(); + } + else + { + Console.WriteLine("Invalid date: {0}", Newtonsoft.Json.JsonConvert.SerializeObject(reminder)); + } + } } diff --git a/Utilities.Games/Program.cs b/Utilities.Games/Program.cs index a25cfcd..e98af0d 100644 --- a/Utilities.Games/Program.cs +++ b/Utilities.Games/Program.cs @@ -1,11 +1,10 @@ using Microsoft.AspNetCore.Components.WebAssembly.Hosting; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; +using Microsoft.JSInterop; using System; -using System.Collections.Generic; using System.Net.Http; -using System.Text; +using System.Reflection; +using System.Text.Json; using System.Threading.Tasks; using LOTR_RiseToWar = Utilities.Games.Pages.Subsites.LOTR_RiseToWar; @@ -19,10 +18,13 @@ public static async Task Main(string[] args) builder.RootComponents.Add("#app"); builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); + builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); - await builder.Build().RunAsync(); + var host = builder.Build(); + + await host.RunAsync(); } } } diff --git a/Utilities.Games/Shared/CopyToClipboard.razor b/Utilities.Games/Shared/CopyToClipboard.razor new file mode 100644 index 0000000..03206c9 --- /dev/null +++ b/Utilities.Games/Shared/CopyToClipboard.razor @@ -0,0 +1,34 @@ +@inject IJSRuntime JSRuntime +@implements IDisposable + +
+ + +
+ +@code { + [Parameter] public string Text { get; set; } + + bool TextCopied = false; + System.Threading.Timer timer; + + private async Task CopyTextToClipboard() { + await JSRuntime.InvokeVoidAsync("clipboardCopy.copyText", Text); + TextCopied = true; + timer = new System.Threading.Timer(async _ => + { + TextCopied = false; + await InvokeAsync(StateHasChanged); + }, null, 2500, System.Threading.Timeout.Infinite); + } + + public void Dispose() { + timer?.Dispose(); + } +} diff --git a/Utilities.Games/Shared/MainLayout.razor b/Utilities.Games/Shared/MainLayout.razor index 1311315..444c873 100644 --- a/Utilities.Games/Shared/MainLayout.razor +++ b/Utilities.Games/Shared/MainLayout.razor @@ -52,6 +52,9 @@ var lastLink = breadcrumbLinks.Last(); var address = $"{lastLink.Address}/{link}"; string title = link; + if (title.Contains("?")) { + title = title.Remove(title.IndexOf('?')); + } var config = breadcrumbConfig?.FirstOrDefault(o => o.Address == address); if (config != null) { diff --git a/Utilities.Games/Utilities.Games.csproj b/Utilities.Games/Utilities.Games.csproj index 15c6f62..6dd536e 100644 --- a/Utilities.Games/Utilities.Games.csproj +++ b/Utilities.Games/Utilities.Games.csproj @@ -10,6 +10,7 @@ + diff --git a/Utilities.Games/wwwroot/clipboardCopy.js b/Utilities.Games/wwwroot/clipboardCopy.js new file mode 100644 index 0000000..e138f97 --- /dev/null +++ b/Utilities.Games/wwwroot/clipboardCopy.js @@ -0,0 +1,10 @@ +window.clipboardCopy = { + copyText: function (text) { + navigator.clipboard.writeText(text).then(function () { + console.log("Copied to clipboard!"); + }) + .catch(function (error) { + console.error(error); + }); + } +}; \ No newline at end of file diff --git a/Utilities.Games/wwwroot/index.html b/Utilities.Games/wwwroot/index.html index ee40a2b..362d9fc 100644 --- a/Utilities.Games/wwwroot/index.html +++ b/Utilities.Games/wwwroot/index.html @@ -29,6 +29,7 @@
+ diff --git a/Utilities.Games/wwwroot/notificationTrigger.js b/Utilities.Games/wwwroot/notificationTrigger.js index 5905402..84de4a4 100644 --- a/Utilities.Games/wwwroot/notificationTrigger.js +++ b/Utilities.Games/wwwroot/notificationTrigger.js @@ -1,32 +1,64 @@ -if ('showTrigger' in Notification.prototype && 'serviceWorker' in navigator) { - window.createScheduledNotification = async (notificationTrigger) => { - if (!window.ensureNotificationPermissions()) { +window.scheduledNotifications = { + isSupported: () => { + return 'showTrigger' in Notification.prototype && 'serviceWorker' in navigator; + } +} + +if ('showTrigger' in Notification.prototype && 'serviceWorker' in navigator) { + window.scheduledNotifications.createScheduledNotification = async (notificationTrigger) => { + if (!window.scheduledNotifications.ensureNotificationPermissions()) { throw 'You need to grant notifications permission before you can schedule a notification.'; } - const registration = await navigator.serviceWorker.getRegistration(); - var options = { - tag: notificationTrigger.tag, - body: notificationTrigger.body, - showTrigger: new TimestampTrigger(notificationTrigger.triggerTime) - }; - if (notificationTrigger.icon && notificationTrigger['icon'] !== '') { - options.icon = notificationTrigger.icon; + var typeOfTriggerTime = typeof (notificationTrigger.triggerTime); + var timestamp; + console.log("Creating notifcation from: ", notificationTrigger); + if (typeOfTriggerTime === 'object' && 'toLocaleString' in notificationTrigger.triggerTime) { + timestamp = notificationTrigger.triggerTime; // Already in Date type + } else if (typeOfTriggerTime === 'string' && notificationTrigger.timestamp) { + timestamp = new Date(notificationTrigger.timestamp); } - if (notificationTrigger.data) { - options.data = notificationTrigger.data; + + if (timestamp) { + var trigger = new TimestampTrigger(timestamp); + var options = { + tag: notificationTrigger.tag, + body: notificationTrigger.body, + showTrigger: trigger + }; + if (notificationTrigger.icon && notificationTrigger['icon'] !== '') { + options.icon = notificationTrigger.icon; + } + if (notificationTrigger.data) { + options.data = notificationTrigger.data; + } + registration.showNotification(notificationTrigger.title, options); } - registration.showNotification(notificationTrigger.title, options); }; - window.cancelScheduledNotification = async (tag) => { + window.scheduledNotifications.cancelScheduledNotification = async (tag) => { + return await navigator.serviceWorker.getRegistration() + .then(registration => registration.getNotifications({ tag: tag, includeTriggered: true })) + .then(notifications => notifications.forEach(notification => notification.close())); + }; + window.scheduledNotifications.getScheduledNotifications = async (tag) => { const registration = await navigator.serviceWorker.getRegistration(); - const notifications = await registration.getNotifications({ + var notifications = (await registration.getNotifications({ tag: tag, includeTriggered: true, - }); - notifications.forEach((notification) => notification.close()); + })); + var jsNotifications = notifications.map(notification => ({ + title: notification.title, + body: notification.body, + timestamp: notification.showTrigger.timestamp, + data: notification.data, + tag: notification.tag, + icon: notification.icon, + badge: notification.badge, + image: notification.image + })); + return jsNotifications; }; - window.ensureNotificationPermissions = async () => { + window.scheduledNotifications.ensureNotificationPermissions = async () => { let { state } = await navigator.permissions.query({ name: 'notifications' }); if (state === 'prompt') { await Notification.requestPermission(); @@ -36,7 +68,7 @@ return false; } return true; - } + }; navigator.serviceWorker.addEventListener('message', event => { if (event.data && event.data.returnUrl) { window.location.pathname = event.data.returnUrl; diff --git a/Utilities.Games/wwwroot/service-worker.js b/Utilities.Games/wwwroot/service-worker.js index abba405..739da0b 100644 --- a/Utilities.Games/wwwroot/service-worker.js +++ b/Utilities.Games/wwwroot/service-worker.js @@ -3,6 +3,10 @@ // be reflected on the first load after each change). self.addEventListener('fetch', () => { }); +self.addEventListener('install', (event) => { + self.skipWaiting(); +}); + self.addEventListener('activate', event => { event.waitUntil(self.clients.claim().then(() => { // See https://developer.mozilla.org/en-US/docs/Web/API/Clients/matchAll From e2458d038fc1f46dc4779ba52802855cd659fc6c Mon Sep 17 00:00:00 2001 From: Trais McAllister Date: Wed, 1 Dec 2021 09:59:58 -0800 Subject: [PATCH 2/3] Added help description for NotificationTriggers (#17) Added help description in the event that the user's browser does not support Notification Triggers. The description contains a link to the new help page. --- .../Pages/Subsites/LOTR_RiseToWar/UserServerView.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Utilities.Games/Pages/Subsites/LOTR_RiseToWar/UserServerView.razor b/Utilities.Games/Pages/Subsites/LOTR_RiseToWar/UserServerView.razor index b16e6e8..43d5fa4 100644 --- a/Utilities.Games/Pages/Subsites/LOTR_RiseToWar/UserServerView.razor +++ b/Utilities.Games/Pages/Subsites/LOTR_RiseToWar/UserServerView.razor @@ -119,7 +119,7 @@ } else { -

Scheduled notifications are not supported in this browser.

+

Scheduled notifications are not supported in this browser. Go to Help -> Notification Triggers for more information on how you may be able to enable this feature.

}
From 8c1cf736e8c1f6753af8f683c59ef18c69241330 Mon Sep 17 00:00:00 2001 From: Trais McAllister Date: Sat, 4 Dec 2021 00:43:54 -0800 Subject: [PATCH 3/3] Added Models based on tasks in project. (#35) --- .../Views/Subsites/LOTR_RiseToWar/MainView.cs | 10 + .../Subsites/LOTR_RiseToWar/AlignmentType.cs | 5 +- .../Subsites/LOTR_RiseToWar/AttackMethod.cs | 15 + .../Subsites/LOTR_RiseToWar/BuildingType.cs | 91 +++++ .../Subsites/LOTR_RiseToWar/Commander.cs | 5 +- .../Subsites/LOTR_RiseToWar/CommanderClass.cs | 1 + .../{ => Contracts}/BaseStat.cs | 2 +- .../LOTR_RiseToWar/Contracts/Coordinate.cs | 18 + .../LOTR_RiseToWar/{ => Contracts}/Effect.cs | 2 +- .../Subsites/LOTR_RiseToWar/EquipmentItem.cs | 37 ++ .../{SubSkill.cs => ItemType.cs} | 11 +- .../Subsites/LOTR_RiseToWar/RingPowerLevel.cs | 20 + .../Subsites/LOTR_RiseToWar/RingSkill.cs | 36 ++ .../LOTR_RiseToWar/RingSkillCategory.cs | 15 + .../LOTR_RiseToWar/SignificantStructure.cs | 58 +++ .../Subsites/LOTR_RiseToWar/Skill.cs | 33 ++ .../Subsites/LOTR_RiseToWar/SkillLevel.cs | 21 - .../Subsites/LOTR_RiseToWar/UnitType.cs | 123 ++++++ .../Utilities.Games.Models.xml | 377 +++++++++++++++++- 19 files changed, 838 insertions(+), 42 deletions(-) create mode 100644 Utilities.Games.Models/Subsites/LOTR_RiseToWar/AttackMethod.cs create mode 100644 Utilities.Games.Models/Subsites/LOTR_RiseToWar/BuildingType.cs rename Utilities.Games.Models/Subsites/LOTR_RiseToWar/{ => Contracts}/BaseStat.cs (84%) create mode 100644 Utilities.Games.Models/Subsites/LOTR_RiseToWar/Contracts/Coordinate.cs rename Utilities.Games.Models/Subsites/LOTR_RiseToWar/{ => Contracts}/Effect.cs (91%) create mode 100644 Utilities.Games.Models/Subsites/LOTR_RiseToWar/EquipmentItem.cs rename Utilities.Games.Models/Subsites/LOTR_RiseToWar/{SubSkill.cs => ItemType.cs} (52%) create mode 100644 Utilities.Games.Models/Subsites/LOTR_RiseToWar/RingPowerLevel.cs create mode 100644 Utilities.Games.Models/Subsites/LOTR_RiseToWar/RingSkill.cs create mode 100644 Utilities.Games.Models/Subsites/LOTR_RiseToWar/RingSkillCategory.cs create mode 100644 Utilities.Games.Models/Subsites/LOTR_RiseToWar/SignificantStructure.cs delete mode 100644 Utilities.Games.Models/Subsites/LOTR_RiseToWar/SkillLevel.cs create mode 100644 Utilities.Games.Models/Subsites/LOTR_RiseToWar/UnitType.cs diff --git a/Utilities.Games.ImportTool/Views/Subsites/LOTR_RiseToWar/MainView.cs b/Utilities.Games.ImportTool/Views/Subsites/LOTR_RiseToWar/MainView.cs index 84c5947..7bdb0ac 100644 --- a/Utilities.Games.ImportTool/Views/Subsites/LOTR_RiseToWar/MainView.cs +++ b/Utilities.Games.ImportTool/Views/Subsites/LOTR_RiseToWar/MainView.cs @@ -22,11 +22,21 @@ protected override IEnumerable InitializeDatasetTypes() return new List { typeof(AlignmentType), + typeof(AttackMethod), + typeof(BuildingType), typeof(Commander), typeof(CommanderClass), + typeof(CommanderSkill), + typeof(EquipmentItem), typeof(Faction), + typeof(ItemType), typeof(RaceType), + typeof(RingPowerLevel), + typeof(RingSkill), + typeof(RingSkillCategory), + typeof(SignificantStructure), typeof(Skill), + typeof(UnitType), }; } diff --git a/Utilities.Games.Models/Subsites/LOTR_RiseToWar/AlignmentType.cs b/Utilities.Games.Models/Subsites/LOTR_RiseToWar/AlignmentType.cs index 7f905a4..de9661c 100644 --- a/Utilities.Games.Models/Subsites/LOTR_RiseToWar/AlignmentType.cs +++ b/Utilities.Games.Models/Subsites/LOTR_RiseToWar/AlignmentType.cs @@ -1,4 +1,6 @@ -namespace Utilities.Games.Models.Subsites.LOTR_RiseToWar +using System.ComponentModel.DataAnnotations; + +namespace Utilities.Games.Models.Subsites.LOTR_RiseToWar { /// /// The alignment of an entity. (ie. Good, Evil, Neutral) @@ -7,6 +9,7 @@ public class AlignmentType { /// /// Name of the alignment. /// + [Key] public string Name { get; set; } } } diff --git a/Utilities.Games.Models/Subsites/LOTR_RiseToWar/AttackMethod.cs b/Utilities.Games.Models/Subsites/LOTR_RiseToWar/AttackMethod.cs new file mode 100644 index 0000000..55e773b --- /dev/null +++ b/Utilities.Games.Models/Subsites/LOTR_RiseToWar/AttackMethod.cs @@ -0,0 +1,15 @@ +using System.ComponentModel.DataAnnotations; + +namespace Utilities.Games.Models.Subsites.LOTR_RiseToWar +{ + /// + /// Reference to the type that an attack incurs. + /// + public class AttackMethod { + /// + /// Name of the attack type. + /// + [Key] + public string Name { get; set; } + } +} diff --git a/Utilities.Games.Models/Subsites/LOTR_RiseToWar/BuildingType.cs b/Utilities.Games.Models/Subsites/LOTR_RiseToWar/BuildingType.cs new file mode 100644 index 0000000..1103857 --- /dev/null +++ b/Utilities.Games.Models/Subsites/LOTR_RiseToWar/BuildingType.cs @@ -0,0 +1,91 @@ +using System; +using System.ComponentModel.DataAnnotations; +using Utilities.Games.Models.Contracts.Attributes; + +namespace Utilities.Games.Models.Subsites.LOTR_RiseToWar +{ + /// + /// Represents a building that can be constructed. + /// + public class BuildingType { + /// + /// Name of the building type. + /// + [Key] + public string Name { get; set; } + + /// + /// Reference to which levels are available for this building type and the associated costs, requirements, and effects. + /// + public object[] Levels { get; set; } + } + /// + /// Represents the costs, requirements, and effects associated with acquiring or achieving this level of a building type's construction. + /// + public class BuildingLevel + { + /// + /// Reference to the building level. + /// + [Key] + public int Level { get; set; } + + /// + /// Base resource/time costs to construct this level of a building type. + /// + public BuildingConstructionCost Costs { get; set; } + + /// + /// Collection of other building requirements before construction can begin on this leve. + /// + public MinimumConstructionRequirement[] Requirements { get; set; } + + // TODO: Effects per level + } + /// + /// Represents the construction costs associated with a specific level of a building type. + /// + public class BuildingConstructionCost + { + + /// + /// Construction cost of Wood resources. + /// + public int Wood { get; set; } + + /// + /// Construction cost of Stone resources. + /// + public int Stone { get; set; } + + /// + /// Construction cost of Iron resources. + /// + public int Iron { get; set; } + + /// + /// Construction cost of Grain resources. + /// + public int Grain { get; set; } + + /// + /// Construction cost of time. + /// + public TimeSpan Time { get; set; } + } + /// + /// Represents a single minimum building requirement for a building type. + /// + public class MinimumConstructionRequirement { + /// + /// Reference to the building type that his requirement is based on. + /// + [PseudoForeignKey(typeof(BuildingType), nameof(BuildingType.Name))] + public string Building { get; set; } + + /// + /// Represents the minimum level that the must achieve before this requirement is satisfied. + /// + public int MinimumLevel { get; set; } + } +} diff --git a/Utilities.Games.Models/Subsites/LOTR_RiseToWar/Commander.cs b/Utilities.Games.Models/Subsites/LOTR_RiseToWar/Commander.cs index 9ad858c..9e5a84b 100644 --- a/Utilities.Games.Models/Subsites/LOTR_RiseToWar/Commander.cs +++ b/Utilities.Games.Models/Subsites/LOTR_RiseToWar/Commander.cs @@ -1,5 +1,7 @@ -using System.ComponentModel.DataAnnotations; +using System; +using System.ComponentModel.DataAnnotations; using Utilities.Games.Models.Contracts.Attributes; +using Utilities.Games.Models.Subsites.LOTR_RiseToWar.Contracts; namespace Utilities.Games.Models.Subsites.LOTR_RiseToWar { @@ -29,6 +31,7 @@ public class Commander /// /// Collection of special classes for the commander. /// + [PseudoForeignKey(typeof(CommanderClass), nameof(CommanderClass.Name))] public string[] Classes { get; set; } /// diff --git a/Utilities.Games.Models/Subsites/LOTR_RiseToWar/CommanderClass.cs b/Utilities.Games.Models/Subsites/LOTR_RiseToWar/CommanderClass.cs index 61f3fc3..b2e49a6 100644 --- a/Utilities.Games.Models/Subsites/LOTR_RiseToWar/CommanderClass.cs +++ b/Utilities.Games.Models/Subsites/LOTR_RiseToWar/CommanderClass.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using Utilities.Games.Models.Subsites.LOTR_RiseToWar.Contracts; namespace Utilities.Games.Models.Subsites.LOTR_RiseToWar { diff --git a/Utilities.Games.Models/Subsites/LOTR_RiseToWar/BaseStat.cs b/Utilities.Games.Models/Subsites/LOTR_RiseToWar/Contracts/BaseStat.cs similarity index 84% rename from Utilities.Games.Models/Subsites/LOTR_RiseToWar/BaseStat.cs rename to Utilities.Games.Models/Subsites/LOTR_RiseToWar/Contracts/BaseStat.cs index 724f4f7..3c1f8fd 100644 --- a/Utilities.Games.Models/Subsites/LOTR_RiseToWar/BaseStat.cs +++ b/Utilities.Games.Models/Subsites/LOTR_RiseToWar/Contracts/BaseStat.cs @@ -1,4 +1,4 @@ -namespace Utilities.Games.Models.Subsites.LOTR_RiseToWar +namespace Utilities.Games.Models.Subsites.LOTR_RiseToWar.Contracts { /// /// A commander's base stat. diff --git a/Utilities.Games.Models/Subsites/LOTR_RiseToWar/Contracts/Coordinate.cs b/Utilities.Games.Models/Subsites/LOTR_RiseToWar/Contracts/Coordinate.cs new file mode 100644 index 0000000..0f58b56 --- /dev/null +++ b/Utilities.Games.Models/Subsites/LOTR_RiseToWar/Contracts/Coordinate.cs @@ -0,0 +1,18 @@ +namespace Utilities.Games.Models.Subsites.LOTR_RiseToWar.Contracts +{ + /// + /// Represents a 2D coordinate point. + /// + public class Coordinate + { + /// + /// Represents the -X- location on the World Map. + /// + public int X { get; set; } + + /// + /// Represents the -Y- location on the World Map. + /// + public int Y { get; set; } + } +} diff --git a/Utilities.Games.Models/Subsites/LOTR_RiseToWar/Effect.cs b/Utilities.Games.Models/Subsites/LOTR_RiseToWar/Contracts/Effect.cs similarity index 91% rename from Utilities.Games.Models/Subsites/LOTR_RiseToWar/Effect.cs rename to Utilities.Games.Models/Subsites/LOTR_RiseToWar/Contracts/Effect.cs index ebcdf85..ac691da 100644 --- a/Utilities.Games.Models/Subsites/LOTR_RiseToWar/Effect.cs +++ b/Utilities.Games.Models/Subsites/LOTR_RiseToWar/Contracts/Effect.cs @@ -1,4 +1,4 @@ -namespace Utilities.Games.Models.Subsites.LOTR_RiseToWar +namespace Utilities.Games.Models.Subsites.LOTR_RiseToWar.Contracts { /// /// An altered effect. diff --git a/Utilities.Games.Models/Subsites/LOTR_RiseToWar/EquipmentItem.cs b/Utilities.Games.Models/Subsites/LOTR_RiseToWar/EquipmentItem.cs new file mode 100644 index 0000000..0f555a3 --- /dev/null +++ b/Utilities.Games.Models/Subsites/LOTR_RiseToWar/EquipmentItem.cs @@ -0,0 +1,37 @@ +using System.ComponentModel.DataAnnotations; +using Utilities.Games.Models.Contracts.Attributes; + +namespace Utilities.Games.Models.Subsites.LOTR_RiseToWar +{ + /// + /// Represents a specific equipment item. + /// + public class EquipmentItem { + /// + /// Name of the item. + /// + [Key] + public string Name { get; set; } + + /// + /// Reference to the type of item this is. + /// + [PseudoForeignKey(typeof(ItemType), nameof(ItemType.Name))] + public string Type { get; set; } + + /// + /// Reference to the sub-type of item this is. + /// + public string SubType { get; set; } + + /// + /// List of race types this equipment can be given to. + /// + [PseudoForeignKey(typeof(RaceType), nameof(RaceType.Name))] + public string[] SupportedRaces { get; set; } + + // TODO: Effects, Growth per sharpening + + // TODO: Skills, Growth per refinement + } +} diff --git a/Utilities.Games.Models/Subsites/LOTR_RiseToWar/SubSkill.cs b/Utilities.Games.Models/Subsites/LOTR_RiseToWar/ItemType.cs similarity index 52% rename from Utilities.Games.Models/Subsites/LOTR_RiseToWar/SubSkill.cs rename to Utilities.Games.Models/Subsites/LOTR_RiseToWar/ItemType.cs index ccdb0d3..18cc053 100644 --- a/Utilities.Games.Models/Subsites/LOTR_RiseToWar/SubSkill.cs +++ b/Utilities.Games.Models/Subsites/LOTR_RiseToWar/ItemType.cs @@ -3,19 +3,18 @@ namespace Utilities.Games.Models.Subsites.LOTR_RiseToWar { /// - /// A sub-skill that a commander can learn. + /// Represents an item type. /// - public class SubSkill - { + public class ItemType { /// - /// Name of the skill. + /// Name of the item type. For example: Equipment, Respect, Boost, Special. /// [Key] public string Name { get; set; } /// - /// The round that this skill kicks in. + /// Available sub-types. For example: Head, Armour, Hand, Accessory. /// - public int Round { get; set; } + public string[] SubTypes { get; set; } } } diff --git a/Utilities.Games.Models/Subsites/LOTR_RiseToWar/RingPowerLevel.cs b/Utilities.Games.Models/Subsites/LOTR_RiseToWar/RingPowerLevel.cs new file mode 100644 index 0000000..18d30e5 --- /dev/null +++ b/Utilities.Games.Models/Subsites/LOTR_RiseToWar/RingPowerLevel.cs @@ -0,0 +1,20 @@ +using System.ComponentModel.DataAnnotations; + +namespace Utilities.Games.Models.Subsites.LOTR_RiseToWar +{ + /// + /// Represents an instance of a level applied to a Ring of Power. + /// + public class RingPowerLevel { + /// + /// Represents the level associated with this level of Ring Power. + /// + [Key] + public int Level { get; set; } + + /// + /// Reference to the minimum amount of Ring Power is required to unlock this level of Ring Power. + /// + public int MinimumPowerCost { get; set; } + } +} diff --git a/Utilities.Games.Models/Subsites/LOTR_RiseToWar/RingSkill.cs b/Utilities.Games.Models/Subsites/LOTR_RiseToWar/RingSkill.cs new file mode 100644 index 0000000..7474835 --- /dev/null +++ b/Utilities.Games.Models/Subsites/LOTR_RiseToWar/RingSkill.cs @@ -0,0 +1,36 @@ +using System.ComponentModel.DataAnnotations; +using Utilities.Games.Models.Contracts.Attributes; + +namespace Utilities.Games.Models.Subsites.LOTR_RiseToWar +{ + /// + /// Represents a skill that can be unlocked as Ring levels progress. + /// + public class RingSkill { + /// + /// Name of the Ring skill. + /// + [Key] + public string Name { get; set; } + + /// + /// Reference to the category for which this skill applies. + /// + [PseudoForeignKey(typeof(RingSkillCategory), nameof(RingSkillCategory.Name))] + public string Category { get; set; } + + /// + /// Available progressions for this skill. + /// + public RingSkillLevel[] Levels { get; set; } + } + public class RingSkillLevel { + /// + /// Reference to which level this object applies to. + /// + [Key] + public int Level { get; set; } + + // TODO: Effects per level + } +} diff --git a/Utilities.Games.Models/Subsites/LOTR_RiseToWar/RingSkillCategory.cs b/Utilities.Games.Models/Subsites/LOTR_RiseToWar/RingSkillCategory.cs new file mode 100644 index 0000000..741c47e --- /dev/null +++ b/Utilities.Games.Models/Subsites/LOTR_RiseToWar/RingSkillCategory.cs @@ -0,0 +1,15 @@ +using System.ComponentModel.DataAnnotations; + +namespace Utilities.Games.Models.Subsites.LOTR_RiseToWar +{ + /// + /// Represents the category for various s. + /// + public class RingSkillCategory { + /// + /// Name of the category. + /// + [Key] + public string Name { get; set; } + } +} diff --git a/Utilities.Games.Models/Subsites/LOTR_RiseToWar/SignificantStructure.cs b/Utilities.Games.Models/Subsites/LOTR_RiseToWar/SignificantStructure.cs new file mode 100644 index 0000000..0176c6a --- /dev/null +++ b/Utilities.Games.Models/Subsites/LOTR_RiseToWar/SignificantStructure.cs @@ -0,0 +1,58 @@ +using System.ComponentModel.DataAnnotations; +using Utilities.Games.Models.Subsites.LOTR_RiseToWar.Contracts; + +namespace Utilities.Games.Models.Subsites.LOTR_RiseToWar +{ + /// + /// Represents a significant structure on the world map. + /// + public class SignificantStructure { + /// + /// Name of the structure. + /// + [Key] + public string Name { get; set; } + + /// + /// Location on the World Map that the center of this structure exists. + /// + public Coordinate Coordinates { get; set; } + + /// + /// Represents the strength tier of the structure. + /// + public int Tier { get; set; } + + /// + /// The hourly Ring Power output rate. + /// + public int HourlyPowerRate { get; set; } + + /// + /// Base, statistical defensive characteristics for this structure. + /// + public StructureDefenses Defenses { get; set; } + + // TODO: Effects + } + + /// + /// The base statistical characteristics of a structure's defenses. + /// + public class StructureDefenses { + /// + /// The commander level of the army(ies) currently defending the structure. + /// + public int DefenderLevel { get; set; } + + /// + /// The total number of armies stationed at the structure. + /// + public int TotalArmyCount { get; set; } + + /// + /// The total amount of Siege defense is available for this structure. + /// + public int TotalSiegeDefenses { get; set; } + } +} diff --git a/Utilities.Games.Models/Subsites/LOTR_RiseToWar/Skill.cs b/Utilities.Games.Models/Subsites/LOTR_RiseToWar/Skill.cs index 0188c09..6106707 100644 --- a/Utilities.Games.Models/Subsites/LOTR_RiseToWar/Skill.cs +++ b/Utilities.Games.Models/Subsites/LOTR_RiseToWar/Skill.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using Utilities.Games.Models.Subsites.LOTR_RiseToWar.Contracts; namespace Utilities.Games.Models.Subsites.LOTR_RiseToWar { @@ -28,4 +29,36 @@ public class Skill /// public Effect MaxLevelEffect { get; set; } } + /// + /// An progression level for a Skill. + /// + public class SkillLevel + { + /// + /// An achievable level for a Skill. + /// + [Key] + public int Level { get; set; } + + /// + /// The effects gained when the level is achieved. + /// + public Effect[] Effects { get; set; } + } + /// + /// A sub-skill that a commander can learn. + /// + public class SubSkill + { + /// + /// Name of the skill. + /// + [Key] + public string Name { get; set; } + + /// + /// The round that this skill kicks in. + /// + public int Round { get; set; } + } } diff --git a/Utilities.Games.Models/Subsites/LOTR_RiseToWar/SkillLevel.cs b/Utilities.Games.Models/Subsites/LOTR_RiseToWar/SkillLevel.cs deleted file mode 100644 index 48e48d9..0000000 --- a/Utilities.Games.Models/Subsites/LOTR_RiseToWar/SkillLevel.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Utilities.Games.Models.Subsites.LOTR_RiseToWar -{ - /// - /// An progression level for a Skill. - /// - public class SkillLevel - { - /// - /// An achievable level for a Skill. - /// - [Key] - public int Level { get; set; } - - /// - /// The effects gained when the level is achieved. - /// - public Effect[] Effects { get; set; } - } -} diff --git a/Utilities.Games.Models/Subsites/LOTR_RiseToWar/UnitType.cs b/Utilities.Games.Models/Subsites/LOTR_RiseToWar/UnitType.cs new file mode 100644 index 0000000..fb24307 --- /dev/null +++ b/Utilities.Games.Models/Subsites/LOTR_RiseToWar/UnitType.cs @@ -0,0 +1,123 @@ +using System; +using System.ComponentModel.DataAnnotations; +using Utilities.Games.Models.Contracts.Attributes; + +namespace Utilities.Games.Models.Subsites.LOTR_RiseToWar +{ + /// + /// A recruitable army unit. + /// + public class UnitType { + /// + /// Name of the UnitType + /// + [Key] + public string Name { get; set; } + + /// + /// Strength tier of the UnitType. For example: I, II, III, IV + /// + public int Tier { get; set; } + + /// + /// Where the unit aligns between Good and Evil. + /// + [PseudoForeignKey(typeof(AlignmentType), nameof(AlignmentType.Name))] + public string Alignment { get; set; } + + /// + /// Reference to the primary form of attack this unit incurs. + /// + [PseudoForeignKey(typeof(AttackMethod), nameof(AttackMethod.Name))] + public string AttackType { get; set; } + + /// + /// Number of units, for this type, that make up an individual command unit. + /// + public int UnitsPerCommand { get; set; } + + /// + /// Reference to the race these units represent. + /// + [PseudoForeignKey(typeof(RaceType), nameof(RaceType.Name))] + public string Race { get; set; } + + /// + /// Base, statistical characteristics of this unit type. + /// + public UnitStats Stats { get; set; } + + /// + /// Base costs to conscript a single command unit for this unit type. + /// + public UnitCosts Costs { get; set; } + + // TODO: Skills/Perks + } + /// + /// Base, statistical characteristics of a . + /// + public class UnitStats + { + /// + /// Minimum base value for damage which affects the damage dealt by a single unit. + /// + public int MinimumBaseDamage { get; set; } + + /// + /// Maximum base value for damage which affects the damage dealt by a single unit. + /// + public int MaximumBaseDamage { get; set; } + + /// + /// Increases or modifies the amount of damage an individual unit can take before dying. + /// + public int HP { get; set; } + + /// + /// Decides unit's top marching speed and turn order during battle. + /// + public int Speed { get; set; } + + /// + /// After a battle is won, the Durability of a structure or Land is reduced by 1 for every 100 Siege points. + /// + public int Siege { get; set; } + + /// + /// Decreases the amount of Physical Damage units take. + /// + public int Defence { get; set; } + } + + /// + /// Base costs to conscript a single command unit for a unit type. + /// + public class UnitCosts + { + /// + /// Conscription cost per Command of Wood resources. + /// + public int Wood { get; set; } + + /// + /// Conscription cost per Command of Stone resources. + /// + public int Stone { get; set; } + + /// + /// Conscription cost per Command of Iron resources. + /// + public int Iron { get; set; } + + /// + /// Conscription cost per Command of Grain resources. + /// + public int Grain { get; set; } + + /// + /// Conscription cost per Command of time. + /// + public TimeSpan Time { get; set; } + } +} diff --git a/Utilities.Games.Models/Utilities.Games.Models.xml b/Utilities.Games.Models/Utilities.Games.Models.xml index 90865a0..e0749ca 100644 --- a/Utilities.Games.Models/Utilities.Games.Models.xml +++ b/Utilities.Games.Models/Utilities.Games.Models.xml @@ -29,19 +29,94 @@ Name of the alignment. - + - A commander's base stat. + Reference to the type that an attack incurs. - + - The base value for the specific stat. + Name of the attack type. - + - The rate per level that the stat increases + Represents a building that can be constructed. + + + + + Name of the building type. + + + + + Reference to which levels are available for this building type and the associated costs, requirements, and effects. + + + + + Represents the costs, requirements, and effects associated with acquiring or achieving this level of a building type's construction. + + + + + Reference to the building level. + + + + + Base resource/time costs to construct this level of a building type. + + + + + Collection of other building requirements before construction can begin on this leve. + + + + + Represents the construction costs associated with a specific level of a building type. + + + + + Construction cost of Wood resources. + + + + + Construction cost of Stone resources. + + + + + Construction cost of Iron resources. + + + + + Construction cost of Grain resources. + + + + + Construction cost of time. + + + + + Represents a single minimum building requirement for a building type. + + + + + Reference to the building type that his requirement is based on. + + + + + Represents the minimum level that the must achieve before this requirement is satisfied. @@ -129,31 +204,86 @@ Specifies where the skill aligns within the skill tree. - + + + A commander's base stat. + + + + + The base value for the specific stat. + + + + + The rate per level that the stat increases + + + + + Represents a 2D coordinate point. + + + + + Represents the -X- location on the World Map. + + + + + Represents the -Y- location on the World Map. + + + An altered effect. - + Specifies the target type. For example, Might or Speed. - + The amount that the target type is affected. - + Specifies what scope of the target type. For example, Commander or Army. - + Unit of measurement for the effect amount. + + + Represents a specific equipment item. + + + + + Name of the item. + + + + + Reference to the type of item this is. + + + + + Reference to the sub-type of item this is. + + + + + List of race types this equipment can be given to. + + A faction. @@ -169,6 +299,21 @@ Name of the first, default commander for the Faction. + + + Represents an item type. + + + + + Name of the item type. For example: Equipment, Respect, Boost, Special. + + + + + Available sub-types. For example: Head, Armour, Hand, Accessory. + + Race of a commander. @@ -179,6 +324,106 @@ Name of the race. + + + Represents an instance of a level applied to a Ring of Power. + + + + + Represents the level associated with this level of Ring Power. + + + + + Reference to the minimum amount of Ring Power is required to unlock this level of Ring Power. + + + + + Represents a skill that can be unlocked as Ring levels progress. + + + + + Name of the Ring skill. + + + + + Reference to the category for which this skill applies. + + + + + Available progressions for this skill. + + + + + Reference to which level this object applies to. + + + + + Represents the category for various s. + + + + + Name of the category. + + + + + Represents a significant structure on the world map. + + + + + Name of the structure. + + + + + Location on the World Map that the center of this structure exists. + + + + + Represents the strength tier of the structure. + + + + + The hourly Ring Power output rate. + + + + + Base, statistical defensive characteristics for this structure. + + + + + The base statistical characteristics of a structure's defenses. + + + + + The commander level of the army(ies) currently defending the structure. + + + + + The total number of armies stationed at the structure. + + + + + The total amount of Siege defense is available for this structure. + + A skill that a commander can learn. @@ -234,5 +479,115 @@ The round that this skill kicks in. + + + A recruitable army unit. + + + + + Name of the UnitType + + + + + Strength tier of the UnitType. For example: I, II, III, IV + + + + + Where the unit aligns between Good and Evil. + + + + + Reference to the primary form of attack this unit incurs. + + + + + Number of units, for this type, that make up an individual command unit. + + + + + Reference to the race these units represent. + + + + + Base, statistical characteristics of this unit type. + + + + + Base costs to conscript a single command unit for this unit type. + + + + + Base, statistical characteristics of a . + + + + + Minimum base value for damage which affects the damage dealt by a single unit. + + + + + Maximum base value for damage which affects the damage dealt by a single unit. + + + + + Increases or modifies the amount of damage an individual unit can take before dying. + + + + + Decides unit's top marching speed and turn order during battle. + + + + + After a battle is won, the Durability of a structure or Land is reduced by 1 for every 100 Siege points. + + + + + Decreases the amount of Physical Damage units take. + + + + + Base costs to conscript a single command unit for a unit type. + + + + + Conscription cost per Command of Wood resources. + + + + + Conscription cost per Command of Stone resources. + + + + + Conscription cost per Command of Iron resources. + + + + + Conscription cost per Command of Grain resources. + + + + + Conscription cost per Command of time. + +