Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions ElectronicObserver/Data/Quest/QuestCompletionEvaluator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
using System;
using System.Collections.Generic;
using System.Linq;
using ElectronicObserver.Window.Dialog.QuestTrackerManager.ViewModels;

namespace ElectronicObserver.Data.Quest;

public sealed class QuestCompletionEvaluator
{
private static readonly QuestCompletionEvaluator instance = new();

public static QuestCompletionEvaluator Instance => instance;

// Completion baseline keyed by quest ID from the last refresh or evaluation.
private Dictionary<int, bool> CompletionStates { get; } = new();

// Raised when an active quest transitions from incomplete to complete.
public event Action<QuestData> QuestCompleted = delegate { };

private QuestCompletionEvaluator()
{
}

/// <summary>
/// Refreshes the baseline from the current completion states without emitting completion notifications.
/// </summary>
public void RefreshBaseline()
{
KCDatabase db = KCDatabase.Instance;

// Snapshot completion states for all currently active quests.
foreach (QuestData quest in db.Quest.Quests.Values)
{
CompletionStates[quest.QuestID] = IsComplete(quest);
}

// Remove completion states for quests that are no longer active.
foreach (int questId in CompletionStates.Keys.Except(db.Quest.Quests.Keys).ToList())
{
CompletionStates.Remove(questId);
}
}

/// <summary>
/// Compares the current completion states with the baseline and emits notifications for newly completed quests.
/// </summary>
public void Evaluate()
{
KCDatabase db = KCDatabase.Instance;

// Check active quests for a transition from incomplete to complete.
foreach (QuestData quest in db.Quest.Quests.Values)
{
bool isComplete = IsComplete(quest);

if (CompletionStates.TryGetValue(quest.QuestID, out bool wasComplete) && !wasComplete && isComplete)
{
QuestCompleted(quest);
}

CompletionStates[quest.QuestID] = isComplete;
}

// Remove completion states for quests that are no longer active.
foreach (int questId in CompletionStates.Keys.Except(db.Quest.Quests.Keys).ToList())
{
CompletionStates.Remove(questId);
}
}

private static bool IsComplete(QuestData quest) => GetEffectiveProgress(quest) >= 1.0;

private static double GetEffectiveProgress(QuestData quest)
{
// Keep this priority aligned with the quest list progress display in Window/Wpf/Quest/QuestViewModel.cs.
// It uses the most precise tracker first, then falls back to legacy QuestProgress and API progress flags.
if (quest.State == 3) return 1.0;

KCDatabase db = KCDatabase.Instance;
TrackerViewModel? tracker = db.QuestTrackerManagers.GetTrackerById(quest.QuestID);
TrackerViewModel? systemTracker = db.SystemQuestTrackerManager.GetTrackerById(quest.QuestID);

if (tracker is { Tasks.Count: > 0 }) return tracker.Progress;
if (systemTracker is { Tasks.Count: > 0 }) return systemTracker.Progress;
if (db.QuestProgress.Progresses.ContainsKey(quest.QuestID)) return db.QuestProgress[quest.QuestID].ProgressPercentage;

return quest.Progress switch
{
1 => 0.5,
2 => 0.8,
_ => 0.0,
};
}
}
13 changes: 11 additions & 2 deletions ElectronicObserver/Data/Quest/QuestProgressManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1268,7 +1268,7 @@ void QuestUpdated(string apiname, dynamic data)
}

LastUpdateTime = DateTime.Now;
OnProgressChanged();
OnProgressChanged(false);

}

Expand Down Expand Up @@ -1522,8 +1522,17 @@ public void Save()
Save(DefaultFilePath);
}

private void OnProgressChanged()
private void OnProgressChanged(bool evaluateCompletion = true)
{
if (evaluateCompletion)
{
QuestCompletionEvaluator.Instance.Evaluate();
}
else
{
QuestCompletionEvaluator.Instance.RefreshBaseline();
}

KCDatabase.Instance.Quest.OnQuestUpdated();
}
}
4 changes: 4 additions & 0 deletions ElectronicObserver/Notifier/NotifierManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public sealed class NotifierManager
public NotifierBattleEnd BattleEnd { get; private set; }
public NotifierRemodelLevel RemodelLevel { get; private set; }
public NotifierBase TrainingPlan { get; private set; }
public NotifierQuestCompletion QuestCompletion { get; private set; }

private NotifierManager()
{
Expand All @@ -54,6 +55,7 @@ public void Initialize(object parent)
BattleEnd = new NotifierBattleEnd(c.NotifierBattleEnd);
RemodelLevel = new NotifierRemodelLevel(c.NotifierRemodelLevel);
TrainingPlan = new NotifierTrainingPlan(c.NotifierTrainingPlan);
QuestCompletion = new NotifierQuestCompletion(c.NotifierQuestCompletion);
}

public void ApplyToConfiguration()
Expand All @@ -72,6 +74,7 @@ public void ApplyToConfiguration()
BattleEnd.ApplyToConfiguration(c.NotifierBattleEnd);
RemodelLevel.ApplyToConfiguration(c.NotifierRemodelLevel);
TrainingPlan.ApplyToConfiguration(c.NotifierTrainingPlan);
QuestCompletion.ApplyToConfiguration(c.NotifierQuestCompletion);
}

public void ShowNotifier(ElectronicObserver.Window.Dialog.DialogNotifier form)
Expand Down Expand Up @@ -110,6 +113,7 @@ public IEnumerable<NotifierBase> GetNotifiers()
yield return BattleEnd;
yield return RemodelLevel;
yield return TrainingPlan;
yield return QuestCompletion;
}

}
23 changes: 23 additions & 0 deletions ElectronicObserver/Notifier/NotifierQuestCompletion.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using ElectronicObserver.Data;
using ElectronicObserver.Data.Quest;

namespace ElectronicObserver.Notifier;

public class NotifierQuestCompletion : NotifierBase
{
public NotifierQuestCompletion(Utility.Configuration.ConfigurationData.ConfigNotifierBase config)
: base(config)
{
DialogData.Title = NotifierRes.QuestCompletionTitle;

QuestCompletionEvaluator.Instance.QuestCompleted += Notify;
QuestCompletionEvaluator.Instance.RefreshBaseline();
}

private void Notify(QuestData quest)
{
DialogData.Message = string.Format(NotifierRes.QuestCompletionText, quest.NameWithCode);

base.Notify();
}
}
6 changes: 6 additions & 0 deletions ElectronicObserver/Notifier/NotifierRes.en.resx
Original file line number Diff line number Diff line change
Expand Up @@ -228,4 +228,10 @@
<data name="HomePortSupplyExpeditionFinished" xml:space="preserve">
<value>Home port supply fleet {0} will return from expedition in {1}.</value>
</data>
<data name="QuestCompletionTitle" xml:space="preserve">
<value>Quest completion</value>
</data>
<data name="QuestCompletionText" xml:space="preserve">
<value>Quest "{0}" is ready to complete.</value>
</data>
</root>
6 changes: 6 additions & 0 deletions ElectronicObserver/Notifier/NotifierRes.resx
Original file line number Diff line number Diff line change
Expand Up @@ -231,4 +231,10 @@
<data name="HomePortSupplyExpeditionFinished" xml:space="preserve">
<value>母港給糧艦隊「{0}」が遠征からあと{1}で帰投します。</value>
</data>
<data name="QuestCompletionTitle" xml:space="preserve">
<value>任務達成通知</value>
</data>
<data name="QuestCompletionText" xml:space="preserve">
<value>任務『{0}』の達成条件を満たしました。</value>
</data>
</root>
6 changes: 6 additions & 0 deletions ElectronicObserver/Notifier/NotifierRes.zh-TW.resx
Original file line number Diff line number Diff line change
Expand Up @@ -231,4 +231,10 @@
<data name="HomePortSupplyExpeditionFinished" xml:space="preserve">
<value>母港給糧艦隊「{0}」已經從遠征 {1} 返回。</value>
</data>
<data name="QuestCompletionTitle" xml:space="preserve">
<value>任務達成通知</value>
</data>
<data name="QuestCompletionText" xml:space="preserve">
<value>任務「{0}」已達成完成條件。</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -848,4 +848,7 @@ When enabled, the preview will be ignored and all compositions will be shown.</v
<data name="Language_TraditionalChinese" xml:space="preserve">
<value>Chinese (Traditional)</value>
</data>
<data name="Notification_QuestCompletion" xml:space="preserve">
<value>Quest completion notification</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -863,4 +863,7 @@ APIリストの書式や用法はオンラインヘルプを参照してくだ
<data name="Language_TraditionalChinese" xml:space="preserve">
<value>繁体中国語</value>
</data>
<data name="Notification_QuestCompletion" xml:space="preserve">
<value>任務達成通知</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -856,4 +856,7 @@ API列表的格式和使用方法請參見線上幫助。</value>
<data name="Language_TraditionalChinese" xml:space="preserve">
<value>繁體中文</value>
</data>
<data name="Notification_QuestCompletion" xml:space="preserve">
<value>任務達成通知</value>
</data>
</root>
4 changes: 4 additions & 0 deletions ElectronicObserver/Utility/Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1874,6 +1874,9 @@ public ConfigNotifierBattleEnd()
[DataMember]
public ConfigNotifierBase NotifierTrainingPlan { get; private set; }

[DataMember]
public ConfigNotifierBase NotifierQuestCompletion { get; private set; }

/// <summary>
/// SyncBGMPlayer の設定を扱います。
/// </summary>
Expand Down Expand Up @@ -2033,6 +2036,7 @@ public override void Initialize()
NotifierBattleEnd = new ConfigNotifierBattleEnd();
NotifierRemodelLevel = new ConfigNotifierBase();
NotifierTrainingPlan = new ConfigNotifierBase();
NotifierQuestCompletion = new ConfigNotifierBase();

BGMPlayer = new ConfigBGMPlayer();
FleetImageGenerator = new ConfigFleetImageGenerator();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using ElectronicObserver.Core.Types;
using ElectronicObserver.Core.Types.Quests;
using ElectronicObserver.Data;
using ElectronicObserver.Data.Quest;
using ElectronicObserver.Observer;
using ElectronicObserver.Utility.Mathematics;
using ElectronicObserver.Window.Dialog.QuestTrackerManager.Models;
Expand Down Expand Up @@ -108,6 +109,7 @@ private void QuestUpdated(string apiname, dynamic data)
}

LastQuestListUpdate = DateTime.Now;
QuestCompletionEvaluator.Instance.RefreshBaseline();
}

private void StartSortie(string apiname, dynamic data)
Expand All @@ -122,6 +124,8 @@ private void StartSortie(string apiname, dynamic data)
{
tracker.Increment(fleet, compass.MapAreaID, compass.MapInfoID, compass.CellId);
}

QuestCompletionEvaluator.Instance.Evaluate();
}

private void NextSortie(string apiname, dynamic data)
Expand All @@ -136,6 +140,8 @@ private void NextSortie(string apiname, dynamic data)
{
tracker.Increment(fleet, compass.MapAreaID, compass.MapInfoID, compass.CellId);
}

QuestCompletionEvaluator.Instance.Evaluate();
}

private void BattleFinished(string apiname, dynamic data)
Expand All @@ -152,6 +158,8 @@ private void BattleFinished(string apiname, dynamic data)
{
tracker.Increment(fleet, bm.Result.Rank, bm.Compass.MapAreaID, bm.Compass.MapInfoID, bm.Compass.CellId);
}

QuestCompletionEvaluator.Instance.Evaluate();
}

private void BossBattleFinished(string apiname, dynamic data)
Expand All @@ -170,6 +178,8 @@ private void BossBattleFinished(string apiname, dynamic data)
tracker.Increment(fleet, bm.Result.Rank, bm.Compass.MapAreaID, bm.Compass.MapInfoID);
}

QuestCompletionEvaluator.Instance.Evaluate();

// p.Increment(bm.Result.Rank, bm.Compass.MapAreaID * 10 + bm.Compass.MapInfoID, bm.Compass.EventID == 5);

}
Expand All @@ -190,6 +200,8 @@ private void MapClearedFirstTime(string apiname, dynamic data)
{
tracker.Increment(fleet, bm.Compass.MapAreaID, bm.Compass.MapInfoID);
}

QuestCompletionEvaluator.Instance.Evaluate();
}

private void PracticeFinished(string apiname, dynamic data)
Expand All @@ -203,6 +215,8 @@ private void PracticeFinished(string apiname, dynamic data)
{
tracker.Increment(fleet, (string)data.api_win_rank);
}

QuestCompletionEvaluator.Instance.Evaluate();
}

private void ExpeditionCompleted(string apiname, dynamic data)
Expand All @@ -221,6 +235,8 @@ private void ExpeditionCompleted(string apiname, dynamic data)
{
tracker.Increment(fleet, areaId);
}

QuestCompletionEvaluator.Instance.Evaluate();
}

public void EquipmentDiscarded(string apiname, Dictionary<string, string> data)
Expand Down Expand Up @@ -248,6 +264,8 @@ public void EquipmentDiscarded(string apiname, Dictionary<string, string> data)
{
tracker.Increment(discardedEquipment.Select(e => e.MasterEquipment.IconTypeTyped));
}

QuestCompletionEvaluator.Instance.Evaluate();
}

public abstract void Save();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public class ConfigurationNotificationTranslationViewModel : TranslationBaseView
public string Notification_Expedition => ConfigRes.ExpedReturn + ConfigRes.NotificationSetting;
public string Notification_RemodelLevel => ConfigurationResources.Notification_RemodelLevel;
public string Notification_Training => ConfigurationResources.Notification_Training;
public string Notification_QuestCompletion => ConfigurationResources.Notification_QuestCompletion;
public string Notification_BaseAirCorps => ConfigurationResources.Notification_BaseAirCorps;
public string Notification_BattleEnd => ConfigurationResources.Notification_BattleEnd;
public string Notification_HomePortSupply => NotifierRes.HomePortSupply + ConfigRes.NotificationSetting;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,5 +94,12 @@
CommandParameter="{Binding Training}"
Content="{Binding Translation.Notification_Training}"
/>

<Button
Margin="2"
Command="{Binding OpenNotificationConfigDialogCommand}"
CommandParameter="{Binding QuestCompletion}"
Content="{Binding Translation.Notification_QuestCompletion}"
/>
</WrapPanel>
</UserControl>
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public partial class ConfigurationNotificationViewModel : ConfigurationViewModel
public ConfigurationNotificationBattleEndViewModel BattleEnd { get; }
public ConfigurationNotificationBaseViewModel RemodelLevel { get; }
public ConfigurationNotificationBaseViewModel Training { get; }
public ConfigurationNotificationBaseViewModel QuestCompletion { get; }

public ConfigurationNotificationViewModel(Configuration.ConfigurationData config)
{
Expand All @@ -47,6 +48,7 @@ public ConfigurationNotificationViewModel(Configuration.ConfigurationData config
BattleEnd = new(Config.NotifierBattleEnd, NotifierManager.Instance.BattleEnd);
RemodelLevel = new(Config.NotifierRemodelLevel, NotifierManager.Instance.RemodelLevel);
Training = new(Config.NotifierTrainingPlan, NotifierManager.Instance.TrainingPlan);
QuestCompletion = new(Config.NotifierQuestCompletion, NotifierManager.Instance.QuestCompletion);
}

public override void Save()
Expand Down
Loading