Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: refactor treasure hunt management #36

Merged
merged 3 commits into from
Sep 8, 2024
Merged
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
288 changes: 78 additions & 210 deletions src/TreasureSolver/Behaviours/TreasureHuntManager.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Threading.Tasks;
using BepInEx.Unity.IL2CPP.Utils.Collections;
using Com.Ankama.Dofus.Server.Game.Protocol.Common;
using Com.Ankama.Dofus.Server.Game.Protocol.Gamemap;
using Com.Ankama.Dofus.Server.Game.Protocol.Treasure.Hunt;
using Core.DataCenter;
Expand All @@ -13,6 +10,7 @@
using DofusBatteriesIncluded.Core.Maps;
using DofusBatteriesIncluded.Core.Maps.PathFinding;
using DofusBatteriesIncluded.TreasureSolver.Clues;
using DofusBatteriesIncluded.TreasureSolver.Hunts;
using Microsoft.Extensions.Logging;
using UnityEngine;
using Direction = DofusBatteriesIncluded.Core.Maps.Direction;
Expand All @@ -25,245 +23,115 @@ public class TreasureHuntManager : MonoBehaviour
static readonly ILogger Log = DBI.Logging.Create<TreasureHuntManager>();
const int CluesMaxDistance = 10;

static TreasureHuntManager _instance;
TreasureHuntEvent _lastEvent;
Coroutine _coroutine;
readonly Dictionary<int, long> _knownNpcMapsIds = [];
int? _lookingForNpcId;
int? _currentStep;
bool _useCachedMapId;
long? _nextClueMapId;
TreasureHuntEvent _lastHuntEvent;
MapComplementaryInformationEvent _lastMapEvent;
TreasureHunt _hunt;
bool _needUpdate;

public static void SetLastEvent(TreasureHuntEvent lastEvent)
void Awake()
{
if (!_instance)
{
return;
}

if (_instance._coroutine != null)
TreasureSolver.CluesServiceChanged += (_, _) =>
{
_instance.StopCoroutine(_instance._coroutine);
}

_instance._lastEvent = lastEvent;

if (lastEvent != null)
{
_instance._coroutine = _instance.StartCoroutine(_instance.HandleEvent(lastEvent).WrapToIl2Cpp());
}
else
{
_instance._knownNpcMapsIds.Clear();
}
}
if (_hunt == null)
{
return;
}

public static void Refresh(bool clearCache)
{
if (!_instance)
{
return;
}
ICluesService service = TreasureSolver.GetCluesService();
_hunt.SetCluesService(service);
_needUpdate = true;
};

if (_instance._lastEvent != null)
CorePlugin.UseScrollActionsChanged += (_, _) =>
{
if (clearCache)
if (_hunt == null)
{
_instance._useCachedMapId = false;
return;
}

SetLastEvent(_instance._lastEvent);
}
_needUpdate = true;
};

DBI.Messaging.GetListener<TreasureHuntEvent>().MessageReceived += (_, huntEvent) => HandleTreasureHuntEvent(huntEvent);
DBI.Messaging.GetListener<TreasureHuntFinishedEvent>().MessageReceived += (_, _) => _hunt = null;
DBI.Messaging.GetListener<MapComplementaryInformationEvent>().MessageReceived += (_, mapEvent) => HandleMapEvent(mapEvent);
}

public static void Finish() => SetLastEvent(null);
void Start() => StartCoroutine(UpdateCoroutine().WrapToIl2Cpp());

void Awake()
void HandleTreasureHuntEvent(TreasureHuntEvent huntEvent)
{
_instance = this;
_lastHuntEvent = huntEvent;
_needUpdate = true;
}

DBI.Player.CurrentCharacterChangeStarted += (_, state) => { state.MapChanged += (_, _) => { Refresh(false); }; };
CorePlugin.UseScrollActionsChanged += (_, _) => { Refresh(true); };
TreasureSolver.CluesServiceChanged += (_, _) => { Refresh(true); };
DBI.Messaging.GetListener<MapComplementaryInformationEvent>().MessageReceived += (_, mapCurrent) => OnMapChanged(mapCurrent);
void HandleMapEvent(MapComplementaryInformationEvent mapEvent)
{
_lastMapEvent = mapEvent;
_needUpdate = true;
}

void OnMapChanged(MapComplementaryInformationEvent evt)
IEnumerator UpdateCoroutine()
{
long mapId = evt.MapId;
bool foundLookingForNpc = false;
foreach (ActorPositionInformation actor in evt.Actors.array.Take(evt.Actors.Count))
while (true)
{
ActorPositionInformation.Types.ActorInformation.InformationOneofCase? actorCase = actor.ActorInformation?.InformationCase;
if (actorCase != ActorPositionInformation.Types.ActorInformation.InformationOneofCase.RolePlayActor || actor.ActorInformation.RolePlayActor == null)
if (!_needUpdate)
{
yield return null;
continue;
}

int npcId = actor.ActorInformation.RolePlayActor.TreasureHuntNpcId;
_knownNpcMapsIds[npcId] = mapId;

foundLookingForNpc |= npcId == _lookingForNpcId;
}

if (foundLookingForNpc)
{
Refresh(false);
}
}
if (_lastHuntEvent == null)
{
_needUpdate = false;
yield return null;
continue;
}

IEnumerator HandleEvent(TreasureHuntEvent evt)
{
int tryCount = 0;
while (tryCount < 100)
{
// ensure window exists and is accessible before handling treasure hunt events
// also clear it so that event handling only has to set fields properly
if (TreasureHuntWindowAccessor.TryClear())
if (!TreasureHuntWindowAccessor.TryClear())
{
if (evt.CurrentCheckPoint == evt.TotalCheckPoint)
{
Log.LogInformation("Reached end of current checkpoint.");
yield break;
}
yield return null;
continue;
}

if (evt.Flags.Count == evt.TotalStepCount)
{
Log.LogInformation("Reached end of hunt.");
yield break;
}
if (_hunt == null)
{
ICluesService cluesService = TreasureSolver.GetCluesService();
_hunt = new TreasureHunt(cluesService);
}

int step = evt.KnownSteps.Count - 1;
long lastMapId = evt.Flags.array.All(f => f == null) ? evt.StartMapId : evt.Flags.array.Last(f => f != null).MapId;
TreasureHuntEvent.Types.TreasureHuntStep nextStep = evt.KnownSteps.array.Last(s => s != null);
_hunt.HandleTreasureHuntEvent(_lastHuntEvent);
_hunt.HandleMapChangedEvent(_lastMapEvent);

ICluesService cluesService = TreasureSolver.GetCluesService();
if (cluesService == null)
if (_hunt.CurrentStep != null)
{
int step = _hunt.CurrentStepIndex;
Task<long?> nextPositionTask = _hunt.GetNextMap();
while (!nextPositionTask.IsCompleted)
{
Log.LogError("Could not find clue finder.");
yield break;
MarkLoading(step);
yield return null;
}

_lookingForNpcId = null;
switch (nextStep.StepCase)
long? nextPosition = nextPositionTask.Result;
bool ok;
int fuel = 100;
do
{
case TreasureHuntEvent.Types.TreasureHuntStep.StepOneofCase.FollowDirectionToPoi:
{
if (nextStep.FollowDirectionToPoi == null)
{
Log.LogWarning("Field {Field} of event is empty.", nameof(nextStep.FollowDirectionToPoi));
yield break;
}

int poiId = nextStep.FollowDirectionToPoi.PoiLabelId;

if (_currentStep != step)
{
_currentStep = step;
_useCachedMapId = false;
}

Direction? direction = GetDirection(nextStep.FollowDirectionToPoi.Direction);
if (!direction.HasValue)
{
Log.LogWarning("Found invalid direction in event {Direction}.", nextStep.FollowDirectionToPoi.Direction);
yield break;
}

if (!_useCachedMapId)
{
Task<long?> cluePositionTask = cluesService.FindMapOfNextClue(lastMapId, direction.Value, poiId, CluesMaxDistance);
while (!cluePositionTask.IsCompleted)
{
MarkLoading(step);
yield return new WaitForSecondsRealtime(0.5f);
}

_nextClueMapId = cluePositionTask.Result;
_useCachedMapId = true;
}

bool done = _nextClueMapId.HasValue ? TryMarkNextPosition(step, lastMapId, poiId, _nextClueMapId.Value) : TryMarkUnknownPosition(step, lastMapId, direction.Value);
if (done)
{
yield break;
}

break;
}
case TreasureHuntEvent.Types.TreasureHuntStep.StepOneofCase.FollowDirectionToHint:
{
if (nextStep.FollowDirectionToHint == null)
{
Log.LogWarning("Field {Field} of event is empty.", nameof(nextStep.FollowDirectionToHint));
yield break;
}

Direction? direction = GetDirection(nextStep.FollowDirectionToHint.Direction);
if (!direction.HasValue)
{
Log.LogWarning("Found invalid direction in event {Direction}.", nextStep.FollowDirectionToPoi.Direction);
yield break;
}

int npcId = nextStep.FollowDirectionToHint.NpcId;
_lookingForNpcId = npcId;

bool done = _knownNpcMapsIds.TryGetValue(npcId, out long npcMapId)
? TryMarkNextPosition(step, lastMapId, null, npcMapId)
: TryMarkUnknownPosition(step, lastMapId, direction.Value, "Keep looking...");
if (done)
{
yield break;
}

yield break;
}
case TreasureHuntEvent.Types.TreasureHuntStep.StepOneofCase.FollowDirection:
{
if (nextStep.FollowDirection == null)
{
Log.LogWarning("Field {Field} of event is empty.", nameof(nextStep.FollowDirection));
yield break;
}

Direction? direction = GetDirection(nextStep.FollowDirectionToPoi.Direction);
if (!direction.HasValue)
{
Log.LogWarning("Found invalid direction in event {Direction}.", nextStep.FollowDirectionToPoi.Direction);
yield break;
}

long targetMapId = MapUtils.MoveInDirection(lastMapId, direction.Value).Skip(nextStep.FollowDirection.MapCount - 1).FirstOrDefault();
if (targetMapId == 0)
{
if (TryMarkUnknownPosition(step, lastMapId, direction.Value))
{
yield break;
}
}
else
{
if (TryMarkNextPosition(step, lastMapId, null, targetMapId))
{
yield break;
}
}

break;
}
case TreasureHuntEvent.Types.TreasureHuntStep.StepOneofCase.None:
case TreasureHuntEvent.Types.TreasureHuntStep.StepOneofCase.Fight:
case TreasureHuntEvent.Types.TreasureHuntStep.StepOneofCase.Dig:
yield break;
default:
throw new ArgumentOutOfRangeException(nameof(nextStep.StepCase), nextStep.StepCase, null);
}

TreasureHuntWindowAccessor.TrySetStepAdditionalText(step, "Searching...");
ok = nextPosition.HasValue
? TryMarkNextPosition(step, _hunt.CurrentStep.LastMapId, _hunt.CurrentStep is TreasureHuntClueStep clueStep ? clueStep.ClueId : null, nextPosition.Value)
: TryMarkUnknownPosition(
step,
_hunt.CurrentStep.LastMapId,
_hunt.CurrentStep.Direction,
_hunt.CurrentStep is TreasureHuntHintStep ? "Keep looking..." : "Not found."
);
fuel--;
yield return null;
} while (!ok && fuel > 0);
}

tryCount++;
_needUpdate = false;
yield return null;
}
}
Expand Down
14 changes: 14 additions & 0 deletions src/TreasureSolver/Hunts/ITreasureHuntClueStep.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.Threading.Tasks;
using Com.Ankama.Dofus.Server.Game.Protocol.Gamemap;
using DofusBatteriesIncluded.Core.Maps;

namespace DofusBatteriesIncluded.TreasureSolver.Hunts;

public interface ITreasureHuntClueStep
{
long LastMapId { get; }
Direction Direction { get; }

Task<long?> FindNextMap();
void OnMapChanged(MapComplementaryInformationEvent evt);
}
Loading