Skip to content
Draft
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
147 changes: 123 additions & 24 deletions ExpeditionIcons.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ public class ExpeditionIcons : BaseSettingsPlugin<ExpeditionIconsSettings>
private List<Vector2> _editedPath;
private int? _editedIndex = null;
private PathPlanner.DetailedLootScore _editedPathEval;
private readonly Dictionary<HotkeyNodeV2, Action> _hotkeyHandlers = new();
private bool _settingsHooksAttached;
private PathPlanner.DetailedLootScore EditedOrNativeScore => _editedPathEval ?? _plannerRunner?.CurrentBestPath;

private Camera Camera => GameController.Game.IngameState.Camera;
Expand All @@ -84,7 +86,7 @@ public class ExpeditionIcons : BaseSettingsPlugin<ExpeditionIconsSettings>
}

private Entity DetonatorEntity =>
GameController.EntityListWrapper.ValidEntitiesByType[EntityType.IngameIcon]
ValidEntitiesOfType(EntityType.IngameIcon)
.FirstOrDefault(x => x.Path == "Metadata/MiscellaneousObjects/Expedition/ExpeditionDetonator" ||
x.Path == "Metadata/MiscellaneousObjects/Expedition/ExpeditionDetonatorTreasureIsland");

Expand All @@ -93,7 +95,7 @@ public class ExpeditionIcons : BaseSettingsPlugin<ExpeditionIconsSettings>

private Vector2i? PlacementIndicatorPos =>
ExpeditionInfo.IsExplosivePlacementActive
? GameController.EntityListWrapper.ValidEntitiesByType[EntityType.MiscellaneousObjects]
? ValidEntitiesOfType(EntityType.MiscellaneousObjects)
.FirstOrDefault(x => x.Path == "Metadata/MiscellaneousObjects/Expedition/ExpeditionPlacementIndicator")?.GridPos.RoundToVector2I() ??
ExpeditionInfo.PlacementIndicatorGridPosition
: null;
Expand All @@ -104,22 +106,67 @@ public class ExpeditionIcons : BaseSettingsPlugin<ExpeditionIconsSettings>

public override bool Initialise()
{
GameController.SoundController.PreloadSound("expedition_attention", Path.Join(DirectoryFullName, "attention.wav"));
var soundPath = Path.Join(DirectoryFullName, "attention.wav");
if (File.Exists(soundPath))
GameController.SoundController.PreloadSound(PathPlannerRunner.SoundId, soundPath);
Graphics.InitImage(TextureName);
IconPickerDrawer.Instance._iconsImageId = Graphics.GetTextureId(TextureName);
Settings.PlannerSettings.StartSearch.OnPressed += StartSearch;
Settings.PlannerSettings.StopSearch.OnPressed += StopSearch;
Settings.PlannerSettings.ClearSearch.OnPressed += ClearSearch;
Settings.Enable.OnValueChanged += OnEnableChanged;
_settingsHooksAttached = true;
RegisterHotkey(Settings.PlannerSettings.StartSearchHotkey);
RegisterHotkey(Settings.PlannerSettings.StopSearchHotkey);
RegisterHotkey(Settings.PlannerSettings.ClearSearchHotkey);
return base.Initialise();
}

private static void RegisterHotkey(HotkeyNode hotkey)
private void RegisterHotkey(HotkeyNodeV2 hotkey)
{
Input.RegisterKey(hotkey);
hotkey.OnValueChanged += () => { Input.RegisterKey(hotkey); };
Input.RegisterKey(hotkey.Value);
Action handler = () => Input.RegisterKey(hotkey.Value);
hotkey.OnValueChanged += handler;
_hotkeyHandlers[hotkey] = handler;
}

private void OnEnableChanged(object _, bool enabled)
{
if (enabled) return;
StopSearch();
_cachedEntities.Clear();
_detonatorPos = null;
_zoneCleared = false;
}

public override void OnPluginDestroyForHotReload()
{
DetachSettingsHooks();
StopSearch();
base.OnPluginDestroyForHotReload();
}

public override void Dispose()
{
DetachSettingsHooks();
StopSearch();
base.Dispose();
}

private void DetachSettingsHooks()
{
if (_settingsHooksAttached)
{
Settings.PlannerSettings.StartSearch.OnPressed -= StartSearch;
Settings.PlannerSettings.StopSearch.OnPressed -= StopSearch;
Settings.PlannerSettings.ClearSearch.OnPressed -= ClearSearch;
Settings.Enable.OnValueChanged -= OnEnableChanged;
_settingsHooksAttached = false;
}

foreach (var (hotkey, handler) in _hotkeyHandlers)
hotkey.OnValueChanged -= handler;
_hotkeyHandlers.Clear();
}

private void StopSearch()
Expand Down Expand Up @@ -208,13 +255,15 @@ private void DrawCirclesInWorld(List<Vector3> positions, float radius, Color col
{
const int segments = 90;
const int segmentSpan = 360 / segments;
if (!float.IsFinite(radius) || radius <= 0) return;
var playerPos = GameController.Player?.GetComponent<Positioned>()?.WorldPos;
if (playerPos == null)
if (playerPos == null || !IsFinite(playerPos.Value))
{
return;
}

foreach (var position in positions
.Where(IsFinite)
.Where(x => playerPos.Value.Distance(new Vector2(x.X, x.Y)) < 80 * GridToWorldMultiplier + radius))
{
foreach (var segmentId in Enumerable.Range(0, segments))
Expand All @@ -225,12 +274,14 @@ private void DrawCirclesInWorld(List<Vector3> positions, float radius, Color col
var offset = new Vector2(cos, sin) * radius;
var xy = position.Xy() + offset;
var screen = Camera.WorldToScreen(ExpandWithTerrainHeight(xy.WorldToGrid()));
if (!IsFinite(screen)) return (xy, new Vector2(float.NaN, float.NaN));
return (xy, screen);
}

var segmentOrigin = segmentId * segmentSpan;
var (w1, c1) = GetVector(segmentOrigin);
var (w2, c2) = GetVector(segmentOrigin + segmentSpan);
if (!IsFinite(c1) || !IsFinite(c2)) continue;
if (Settings.ExplosivesSettings.EnableExplosiveRadiusMerging)
{
if (positions
Expand All @@ -250,6 +301,9 @@ private void DrawCirclesInWorld(List<Vector3> positions, float radius, Color col

public override void Tick()
{
if (!Settings.Enable)
return;

IconPickerDrawer.Instance._iconsImageId = Graphics.GetTextureId(TextureName);
Settings.PlannerSettings.SearchState = _plannerRunner switch
{
Expand All @@ -265,6 +319,7 @@ public override void Tick()
return;
}

if (!IsFinite(playerGridPos.Value)) return;
_playerGridPos = playerGridPos.Value;
if (detonatorPos is { Pos: var dp } && _playerGridPos.Distance(dp) < 90)
{
Expand All @@ -282,7 +337,9 @@ public override void Tick()
_largeMapOpen = largeMap.IsVisible;
_mapScale = GameController.IngameState.Camera.Height / 677f * largeMap.Zoom;
_mapCenter = largeMap.GetClientRect().TopLeft + largeMap.Shift + largeMap.DefaultShift;
_playerZ = GameController.Player.GetComponent<Render>().Z;
_playerZ = GameController.Player.GetComponent<Render>()?.Z ?? float.NaN;
if (!double.IsFinite(_mapScale) || _mapScale <= 0 || !IsFinite(_mapCenter) || !float.IsFinite(_playerZ))
return;

_explosiveRadius = Settings.ExplosivesSettings.CalculateRadiusAutomatically
//ReSharper disable once PossibleLossOfFraction
Expand All @@ -293,9 +350,11 @@ public override void Tick()
//rounding here is extremely important to get right, this is taken from the game's code
_explosiveRange = ExplosiveBaseRange * (100 + (GameController.IngameState.Data.MapStats?.GetValueOrDefault(GameStat.MapExpeditionMaximumPlacementDistancePct) ?? 0)) / 100 *
GridToWorldMultiplier;
if (!float.IsFinite(_explosiveRadius) || _explosiveRadius <= 0 || !float.IsFinite(_explosiveRange) || _explosiveRange <= 0)
return;

foreach (var entity in new[] { EntityType.IngameIcon, EntityType.Terrain }
.SelectMany(x => GameController.EntityListWrapper.ValidEntitiesByType[x]))
foreach (var entity in ValidEntitiesOfType(EntityType.IngameIcon)
.Concat(ValidEntitiesOfType(EntityType.Terrain)))
{
if (GetEntityType(entity.Path) != ExpeditionEntityType.None)
{
Expand Down Expand Up @@ -461,6 +520,9 @@ private bool IsValidPlacement(Vector2 x)

public override void Render()
{
if (!Settings.Enable)
return;

if (Settings.PlannerSettings.ClearSearchHotkey.PressedOnce())
{
ClearSearch();
Expand All @@ -481,7 +543,7 @@ public override void Render()
StartSearch();
}

var explosives3D = GameController.EntityListWrapper.ValidEntitiesByType[EntityType.IngameIcon]
var explosives3D = ValidEntitiesOfType(EntityType.IngameIcon)
.Where(x => x.Path == ExplosivePath)
.Select(x => x.Pos)
.ToList();
Expand Down Expand Up @@ -624,11 +686,16 @@ public override void Render()
var point = path[i].Point;
if (_largeMapOpen)
{
Graphics.DrawLine(GetMapScreenPosition(prevPoint), GetMapScreenPosition(point), 1, Settings.PlannerSettings.MapLineColor);
var mapPrev = GetMapScreenPosition(prevPoint);
var mapPoint = GetMapScreenPosition(point);
if (IsFinite(mapPrev) && IsFinite(mapPoint))
Graphics.DrawLine(mapPrev, mapPoint, 1, Settings.PlannerSettings.MapLineColor);
}

var worldPos = GetWorldScreenPosition(point);
Graphics.DrawLine(GetWorldScreenPosition(prevPoint), worldPos, 1, Settings.PlannerSettings.WorldLineColor);
var worldPrev = GetWorldScreenPosition(prevPoint);
if (IsFinite(worldPrev) && IsFinite(worldPos))
Graphics.DrawLine(worldPrev, worldPos, 1, Settings.PlannerSettings.WorldLineColor);
var text = $"#{i}";
using (Graphics.SetTextScale(Settings.PlannerSettings.TextMarkerScale))
{
Expand All @@ -638,10 +705,8 @@ public override void Render()
}
}

if (Settings.PlannerSettings.IsSearchRunning)
{
if (Settings.PlannerSettings.IsSearchRunning && double.IsFinite(score.TotalScore))
_scoreHistory.Add((float)score.TotalScore);
}

ShowSearchWindow(score);

Expand All @@ -652,6 +717,17 @@ public override void Render()
}
}

private IEnumerable<Entity> ValidEntitiesOfType(EntityType type)
{
if (GameController.EntityListWrapper?.ValidEntitiesByType is not { } byType ||
!byType.TryGetValue(type, out var entities) || entities is null)
{
return Enumerable.Empty<Entity>();
}

return entities;
}

private void ShowSearchWindow(PathPlanner.DetailedLootScore score)
{
if (Settings.PlannerSettings.ShowScoreHistory &&
Expand All @@ -672,7 +748,8 @@ private void ShowSearchWindow(PathPlanner.DetailedLootScore score)
DrawCirclesInWorld([ExpandWithTerrainHeight(pos)], _explosiveRadius, Color.LightBlue);
Graphics.DrawLine(GetWorldScreenPosition(_editedPath[editedIndex]), GetWorldScreenPosition(pos), 1, Settings.PlannerSettings.WorldLineColor);

if (Input.IsKeyDown(Settings.PlannerSettings.ConfirmEditorPlacementHotkey))
var confirmKey = Settings.PlannerSettings.ConfirmEditorPlacementHotkey.Value;
if (confirmKey?.Mode == HotkeyNodeV2.HotkeyNodeMode.Keyboard && Input.IsKeyDown((int)confirmKey.Key))
{
_editedPath[editedIndex] = pos;
_editedPathEval = pp.GetDetailedScore(_editedPath, score.Environment);
Expand Down Expand Up @@ -799,9 +876,13 @@ private void ShowSearchWindow(PathPlanner.DetailedLootScore score)
}
}

ImGui.PlotLines("Score over time", ref CollectionsMarshal.AsSpan(_scoreHistory)[0],
_scoreHistory.Count, 0, "", 0, _scoreHistory.Max(),
new Vector2(0, ImGui.GetContentRegionAvail().Y));
if (_scoreHistory.Count > 0)
{
var maxScore = _scoreHistory.Where(float.IsFinite).DefaultIfEmpty(0f).Max();
ImGui.PlotLines("Score over time", ref CollectionsMarshal.AsSpan(_scoreHistory)[0],
_scoreHistory.Count, 0, "", 0, maxScore,
new Vector2(0, ImGui.GetContentRegionAvail().Y));
}
ImGui.End();
}
}
Expand Down Expand Up @@ -864,6 +945,7 @@ private void DrawIconOnMap(EntityCacheItem entity, MapIconsIndex icon, Color? co
var point = GetEntityPosOnMapScreen(entity) + offset * halfsize * 2;
var entityPos = entity.Pos;
var entityPos2 = new Vector2(entityPos.X, entityPos.Y);
if (!float.IsFinite(halfsize) || halfsize <= 0 || !IsFinite(point) || !IsFinite(entityPos2)) return;

DrawIcon(icon, color, point, entityPos2,
Settings.ExplosivesSettings.HideCapturedEntitiesOnMap,
Expand All @@ -881,6 +963,7 @@ private void DrawIconInWorld(EntityCacheItem entity, MapIconsIndex icon, Color?
var entityPos = entity.Pos;
var entityPos2 = new Vector2(entityPos.X, entityPos.Y);
var point = Camera.WorldToScreen(entityPos) + offset * halfsize * 2;
if (!float.IsFinite(halfsize) || halfsize <= 0 || !IsFinite(point) || !IsFinite(entityPos2)) return;
DrawIcon(icon, color, point, entityPos2,
Settings.ExplosivesSettings.HideCapturedEntitiesInWorld,
Settings.ExplosivesSettings.MarkCapturedEntitiesInWorld,
Expand All @@ -902,6 +985,7 @@ private void DrawIcon(
int frameThickness,
float iconSize)
{
if (!float.IsFinite(iconSize) || iconSize <= 0 || !IsFinite(displayPosition) || !IsFinite(worldPosition)) return;
var halfsize = iconSize / 2.0f;
var rect = new RectangleF(displayPosition.X, displayPosition.Y, 0, 0);
rect.Inflate(halfsize, halfsize);
Expand Down Expand Up @@ -936,26 +1020,41 @@ private void DrawIcon(

private Vector2 GetMapScreenPosition(Vector2 gridPos)
{
return _mapCenter + TranslateGridDeltaToMapDelta(gridPos - _playerGridPos, GameController.IngameState.Data.GetTerrainHeightAt(gridPos) - _playerZ);
if (!IsFinite(gridPos) || !IsFinite(_playerGridPos) || !double.IsFinite(_mapScale) || _mapScale <= 0) return new Vector2(float.NaN, float.NaN);
var height = GameController.IngameState.Data.GetTerrainHeightAt(gridPos);
if (!float.IsFinite(height)) return new Vector2(float.NaN, float.NaN);
return _mapCenter + TranslateGridDeltaToMapDelta(gridPos - _playerGridPos, height - _playerZ);
}

private Vector2 GetWorldScreenPosition(Vector2 gridPos)
{
return Camera.WorldToScreen(ExpandWithTerrainHeight(gridPos));
if (!IsFinite(gridPos)) return new Vector2(float.NaN, float.NaN);
var screen = Camera.WorldToScreen(ExpandWithTerrainHeight(gridPos));
return IsFinite(screen) ? screen : new Vector2(float.NaN, float.NaN);
}

private Vector2 GetEntityPosOnMapScreen(EntityCacheItem entity)
{
if (!IsFinite(entity.GridPos) || !IsFinite(_playerGridPos) || !double.IsFinite(_mapScale) || _mapScale <= 0 ||
!float.IsFinite(entity.RenderZ ?? 0f) || !float.IsFinite(_playerZ)) return new Vector2(float.NaN, float.NaN);
var point = _mapCenter + TranslateGridDeltaToMapDelta(entity.GridPos - _playerGridPos, (entity.RenderZ ?? 0) - _playerZ);
return point;
return IsFinite(point) ? point : new Vector2(float.NaN, float.NaN);
}

private Vector2 TranslateGridDeltaToMapDelta(Vector2 delta, float deltaZ)
{
if (!IsFinite(delta) || !float.IsFinite(deltaZ) || !double.IsFinite(_mapScale) || _mapScale <= 0)
return new Vector2(float.NaN, float.NaN);
deltaZ /= GridToWorldMultiplier; //z is normally "world" units, translate to grid
return (float)_mapScale * new Vector2((delta.X - delta.Y) * CameraAngleCos, (deltaZ - (delta.X + delta.Y)) * CameraAngleSin);
}

private static bool IsFinite(Vector2 value)
=> float.IsFinite(value.X) && float.IsFinite(value.Y);

private static bool IsFinite(Vector3 value)
=> float.IsFinite(value.X) && float.IsFinite(value.Y) && float.IsFinite(value.Z);

private enum ExpeditionEntityType
{
None,
Expand Down Expand Up @@ -1012,4 +1111,4 @@ private static EntityCacheItem BuildCacheItem(Entity entity)
entity.GetComponent<Render>()?.Bounds is { } b ? Math.Min(b.X, b.Y) : null,
entity.GetComponent<MinimapIcon>()?.IsHide);
}
}
}
10 changes: 5 additions & 5 deletions ExpeditionIconsSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -298,10 +298,10 @@ public PlannerSettings()
};
}

public HotkeyNode StartSearchHotkey { get; set; } = new HotkeyNode(Keys.F13);
public HotkeyNode StopSearchHotkey { get; set; } = new HotkeyNode(Keys.F13);
public HotkeyNode ClearSearchHotkey { get; set; } = new HotkeyNode(Keys.F13);
public HotkeyNode ConfirmEditorPlacementHotkey { get; set; } = new HotkeyNode(Keys.Enter);
public HotkeyNodeV2 StartSearchHotkey { get; set; } = new HotkeyNodeV2(Keys.F13);
public HotkeyNodeV2 StopSearchHotkey { get; set; } = new HotkeyNodeV2(Keys.F13);
public HotkeyNodeV2 ClearSearchHotkey { get; set; } = new HotkeyNodeV2(Keys.F13);
public HotkeyNodeV2 ConfirmEditorPlacementHotkey { get; set; } = new HotkeyNodeV2(Keys.Enter);

[JsonIgnore]
[ConditionalDisplay(nameof(IsSearchRunning), false)]
Expand Down Expand Up @@ -516,4 +516,4 @@ public enum SearchState
Empty,
Searching,
Stopped,
}
}
4 changes: 3 additions & 1 deletion Icons.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using ExileCore2.Shared.Enums;
using ExpeditionIcons.PathPlannerData;
Expand Down Expand Up @@ -246,6 +246,7 @@ public static IExpeditionRelic GetRelicType(string relicMod, PlannerSettings pla
"Metadata/Terrain/Doodads/Leagues/Expedition/ChestMarkers/ChestRitual.ao"
},
},
/*
new()
{
IconPickerIndex = IconPickerIndex.MetamorphChest,
Expand All @@ -255,6 +256,7 @@ public static IExpeditionRelic GetRelicType(string relicMod, PlannerSettings pla
"Metadata/Terrain/Doodads/Leagues/Expedition/ChestMarkers/ChestMetamorph.ao"
},
},
*/
new()
{
IconPickerIndex = IconPickerIndex.MapsChest,
Expand Down
Loading