Untitled Diff

Created Diff never expires
1 removal
Lines
Total
Removed
Words
Total
Removed
To continue using this feature, upgrade to
Diffchecker logo
Diffchecker Pro
581 lines
3 additions
Lines
Total
Added
Words
Total
Added
To continue using this feature, upgrade to
Diffchecker logo
Diffchecker Pro
581 lines
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics;
using System.Linq;
using System.Linq;
using System.Threading;
using System.Threading;
using ExileCore;
using ExileCore;
using ExileCore.PoEMemory.Components;
using ExileCore.PoEMemory.Components;
using ExileCore.PoEMemory.Elements;
using ExileCore.PoEMemory.Elements;
using ExileCore.PoEMemory.MemoryObjects;
using ExileCore.PoEMemory.MemoryObjects;
using ExileCore.Shared.Enums;
using ExileCore.Shared.Enums;
using ExileCore.Shared.Helpers;
using ExileCore.Shared.Helpers;
using ExileCore.Shared.Nodes;
using ExileCore.Shared.Nodes;
using GameOffsets.Native;
using GameOffsets.Native;
using SharpDX;
using SharpDX;
using Matrix3x2 = System.Numerics.Matrix3x2;
using Matrix3x2 = System.Numerics.Matrix3x2;
using Vector2 = System.Numerics.Vector2;
using Vector2 = System.Numerics.Vector2;
using Vector3 = System.Numerics.Vector3;
using Vector3 = System.Numerics.Vector3;


namespace ExpeditionIcons;
namespace ExpeditionIcons;


public class ExpeditionIcons : BaseSettingsPlugin<ExpeditionIconsSettings>
public class ExpeditionIcons : BaseSettingsPlugin<ExpeditionIconsSettings>
{
{
private const string MarkerPath = "Metadata/MiscellaneousObjects/Expedition/ExpeditionMarker";
private const string MarkerPath = "Metadata/MiscellaneousObjects/Expedition/ExpeditionMarker";
private const string ExplosivePath = "Metadata/MiscellaneousObjects/Expedition/ExpeditionExplosive";
private const string ExplosivePath = "Metadata/MiscellaneousObjects/Expedition/ExpeditionExplosive";
private const string RelicPath = "Metadata/MiscellaneousObjects/Expedition/ExpeditionRelic";
private const string RelicPath = "Metadata/MiscellaneousObjects/Expedition/ExpeditionRelic";


private const string TextureName = "Icons.png";
private const string TextureName = "Icons.png";
private const double CameraAngle = 38.7 * Math.PI / 180;
private const double CameraAngle = 38.7 * Math.PI / 180;
private static readonly float CameraAngleCos = (float)Math.Cos(CameraAngle);
private static readonly float CameraAngleCos = (float)Math.Cos(CameraAngle);
private static readonly float CameraAngleSin = (float)Math.Sin(CameraAngle);
private static readonly float CameraAngleSin = (float)Math.Sin(CameraAngle);


private const float GridToWorldMultiplier = 250 / 23f;
private const float GridToWorldMultiplier = 250 / 23f;


//TODO
//TODO
private const int ExplosiveBaseRange = 87;
private const int ExplosiveBaseRange = 87;
private const int ExplosiveBaseRadius = 30;
private const int ExplosiveBaseRadius = 30;


private readonly ConcurrentDictionary<string, List<ExpeditionMarkerIconDescription>> _relicModIconMapping = new();
private readonly ConcurrentDictionary<string, List<ExpeditionMarkerIconDescription>> _relicModIconMapping = new();
private readonly ConcurrentDictionary<string, ExpeditionMarkerIconDescription> _metadataIconMapping = new();
private readonly ConcurrentDictionary<string, ExpeditionMarkerIconDescription> _metadataIconMapping = new();
private readonly Dictionary<uint, EntityCacheItem> _cachedEntities = new Dictionary<uint, EntityCacheItem>();
private readonly Dictionary<uint, EntityCacheItem> _cachedEntities = new Dictionary<uint, EntityCacheItem>();
private readonly ConcurrentDictionary<string, ExpeditionEntityType> _entityTypeCache = new();
private readonly ConcurrentDictionary<string, ExpeditionEntityType> _entityTypeCache = new();
private double _mapScale;
private double _mapScale;
private Vector2 _mapCenter;
private Vector2 _mapCenter;
private bool _largeMapOpen;
private bool _largeMapOpen;
private Vector2 _playerGridPos;
private Vector2 _playerGridPos;
private float _playerZ;
private float _playerZ;
private List<Vector2> _explosives2DPositions;
private List<Vector2> _explosives2DPositions;
private float _explosiveRadius;
private float _explosiveRadius;
private float _explosiveRange;
private float _explosiveRange;
private PathPlannerRunner _plannerRunner;
private PathPlannerRunner _plannerRunner;
private (Vector2, float)? _detonatorPos;
private (Vector2, float)? _detonatorPos;
private bool _zoneCleared;
private bool _zoneCleared;
private int[][] _pathfindingData;
private int[][] _pathfindingData;
private Vector2i _areaDimensions;
private Vector2i _areaDimensions;


private Camera Camera => GameController.Game.IngameState.Camera;
private Camera Camera => GameController.Game.IngameState.Camera;


private (Vector2 Pos, float Rotation)? DetonatorPos => _detonatorPos ??= RealDetonatorPos;
private (Vector2 Pos, float Rotation)? DetonatorPos => _detonatorPos ??= RealDetonatorPos;


private (Vector2, float)? RealDetonatorPos => DetonatorEntity is { } e
private (Vector2, float)? RealDetonatorPos => DetonatorEntity is { } e
? (e.GridPosNum, e.GetComponent<Positioned>().Rotation)
? (e.GridPosNum, e.GetComponent<Positioned>().Rotation)
: null;
: null;


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


private int PlacedExplosiveCount => ExpeditionInfo.PlacedExplosiveCount;
private int PlacedExplosiveCount => ExpeditionInfo.PlacedExplosiveCount;
private Vector2i[] PlacedExplosives => ExpeditionInfo.PlacedExplosiveGridPositions;
private Vector2i[] PlacedExplosives => ExpeditionInfo.PlacedExplosiveGridPositions;


private Vector2i? PlacementIndicatorPos =>
private Vector2i? PlacementIndicatorPos =>
ExpeditionInfo.IsExplosivePlacementActive
ExpeditionInfo.IsExplosivePlacementActive
? GameController.EntityListWrapper.ValidEntitiesByType[EntityType.MiscellaneousObjects]
? GameController.EntityListWrapper.ValidEntitiesByType[EntityType.MiscellaneousObjects]
.FirstOrDefault(x => x.Path == "Metadata/MiscellaneousObjects/Expedition/ExpeditionPlacementIndicator")?.GridPosNum.RoundToVector2I() ??
.FirstOrDefault(x => x.Path == "Metadata/MiscellaneousObjects/Expedition/ExpeditionPlacementIndicator")?.GridPosNum.RoundToVector2I() ??
ExpeditionInfo.PlacementIndicatorGridPosition
ExpeditionInfo.PlacementIndicatorGridPosition
: null;
: null;


private ExpeditionDetonatorInfo ExpeditionInfo => GameController.IngameState.IngameUi.ExpeditionDetonatorElement.Info;
private ExpeditionDetonatorInfo ExpeditionInfo => GameController.IngameState.IngameUi.ExpeditionDetonatorElement.Info;


private RectangleF LocalWindowRect => GameController.Window.GetWindowRectangleTimeCache with { Location = SharpDX.Vector2.Zero };
private RectangleF LocalWindowRect => GameController.Window.GetWindowRectangleTimeCache with { Location = SharpDX.Vector2.Zero };


public override bool Initialise()
public override bool Initialise()
{
{
Graphics.InitImage(TextureName);
Graphics.InitImage(TextureName);
Settings._iconsImageId = Graphics.GetTextureId(TextureName);
Settings._iconsImageId = Graphics.GetTextureId(TextureName);
Settings.PlannerSettings.StartSearch.OnPressed += StartSearch;
Settings.PlannerSettings.StartSearch.OnPressed += StartSearch;
Settings.PlannerSettings.StopSearch.OnPressed += StopSearch;
Settings.PlannerSettings.StopSearch.OnPressed += StopSearch;
Settings.PlannerSettings.ClearSearch.OnPressed += ClearSearch;
Settings.PlannerSettings.ClearSearch.OnPressed += ClearSearch;
RegisterHotkey(Settings.PlannerSettings.StartSearchHotkey);
RegisterHotkey(Settings.PlannerSettings.StartSearchHotkey);
RegisterHotkey(Settings.PlannerSettings.StopSearchHotkey);
RegisterHotkey(Settings.PlannerSettings.StopSearchHotkey);
RegisterHotkey(Settings.PlannerSettings.ClearSearchHotkey);
RegisterHotkey(Settings.PlannerSettings.ClearSearchHotkey);
return base.Initialise();
return base.Initialise();
}
}


private static void RegisterHotkey(HotkeyNode hotkey)
private static void RegisterHotkey(HotkeyNode hotkey)
{
{
Input.RegisterKey(hotkey);
Input.RegisterKey(hotkey);
hotkey.OnValueChanged += () => { Input.RegisterKey(hotkey); };
hotkey.OnValueChanged += () => { Input.RegisterKey(hotkey); };
}
}


private void StopSearch()
private void StopSearch()
{
{
if (_plannerRunner is { } run)
if (_plannerRunner is { } run)
{
{
run.Stop();
run.Stop();
Settings.PlannerSettings.SearchState = SearchState.Stopped;
Settings.PlannerSettings.SearchState = SearchState.Stopped;
}
}
else
else
{
{
Settings.PlannerSettings.SearchState = SearchState.Empty;
Settings.PlannerSettings.SearchState = SearchState.Empty;
}
}
}
}


private void StartSearch()
private void StartSearch()
{
{
_plannerRunner?.Stop();
_plannerRunner?.Stop();
var plannerRunner = new PathPlannerRunner();
var plannerRunner = new PathPlannerRunner();
plannerRunner.Start(Settings.PlannerSettings, PlannerEnvironment);
plannerRunner.Start(Settings.PlannerSettings, PlannerEnvironment);
_plannerRunner = plannerRunner;
_plannerRunner = plannerRunner;
Settings.PlannerSettings.SearchState = SearchState.Searching;
Settings.PlannerSettings.SearchState = SearchState.Searching;
}
}


private void ClearSearch()
private void ClearSearch()
{
{
if (_plannerRunner is { } run)
if (_plannerRunner is { } run)
{
{
run.Stop();
run.Stop();
_plannerRunner = null;
_plannerRunner = null;
}
}
}
}


public override void AreaChange(AreaInstance area)
public override void AreaChange(AreaInstance area)
{
{
_plannerRunner?.Stop();
_plannerRunner?.Stop();
_plannerRunner = null;
_plannerRunner = null;
_detonatorPos = null;
_detonatorPos = null;
_cachedEntities.Clear();
_cachedEntities.Clear();
_zoneCleared = false;
_zoneCleared = false;
_pathfindingData = GameController.IngameState.Data.RawPathfindingData;
_pathfindingData = GameController.IngameState.Data.RawPathfindingData;
_areaDimensions = GameController.IngameState.Data.AreaDimensions;
_areaDimensions = GameController.IngameState.Data.AreaDimensions;
}
}


private ExpeditionEntityType GetEntityType(string path)
private ExpeditionEntityType GetEntityType(string path)
{
{
return _entityTypeCache.GetOrAdd(path, p => p switch
return _entityTypeCache.GetOrAdd(path, p => p switch
{
{
RelicPath => ExpeditionEntityType.Relic,
RelicPath => ExpeditionEntityType.Relic,
MarkerPath => ExpeditionEntityType.Marker,
MarkerPath => ExpeditionEntityType.Marker,
_ when p.StartsWith("Metadata/Terrain/Leagues/Expedition/Tiles/ExpeditionChamber") => ExpeditionEntityType.Cave,
_ when p.StartsWith("Metadata/Terrain/Leagues/Expedition/Tiles/ExpeditionChamber") => ExpeditionEntityType.Cave,
_ when p.StartsWith("Metadata/Terrain/Leagues/Expedition/Tiles/ExpeditionBossDispenser") => ExpeditionEntityType.Boss,
_ when p.StartsWith("Metadata/Terrain/Leagues/Expedition/Tiles/ExpeditionBossDispenser") => ExpeditionEntityType.Boss,
_ => ExpeditionEntityType.None,
_ => ExpeditionEntityType.None,
});
});
}
}


private Vector3 ExpandWithTerrainHeight(Vector2 gridPosition)
private Vector3 ExpandWithTerrainHeight(Vector2 gridPosition)
{
{
return new Vector3(gridPosition.GridToWorld(), GameController.IngameState.Data.GetTerrainHeightAt(gridPosition));
return new Vector3(gridPosition.GridToWorld(), 0);
}
}


private void DrawCirclesInWorld(List<Vector3> positions, float radius, Color color)
private void DrawCirclesInWorld(List<Vector3> positions, float radius, Color color)
{
{
const int segments = 90;
const int segments = 90;
const int segmentSpan = 360 / segments;
const int segmentSpan = 360 / segments;
var playerPos = GameController.Player.GetComponent<Positioned>().WorldPosNum;
var playerPos = GameController.Player.GetComponent<Positioned>().WorldPosNum;
foreach (var position in positions
foreach (var position in positions
.Where(x => playerPos.Distance(new Vector2(x.X, x.Y)) < 80 * GridToWorldMultiplier + radius))
.Where(x => playerPos.Distance(new Vector2(x.X, x.Y)) < 80 * GridToWorldMultiplier + radius))
{
{
foreach (var segmentId in Enumerable.Range(0, segments))
foreach (var segmentId in Enumerable.Range(0, segments))
{
{
(Vector2, Vector2) GetVector(int i)
(Vector2, Vector2) GetVector(int i)
{
{
var (sin, cos) = MathF.SinCos(MathF.PI / 180 * i);
var (sin, cos) = MathF.SinCos(MathF.PI / 180 * i);
var offset = new Vector2(cos, sin) * radius;
var offset = new Vector2(cos, sin) * radius;
var xy = position.Xy() + offset;
var xy = position.Xy() + offset;
var screen = Camera.WorldToScreen(ExpandWithTerrainHeight(xy.WorldToGrid()));
var screen = Camera.WorldToScreen(ExpandWithTerrainHeight(xy.WorldToGrid()));
return (xy, screen);
return (xy, screen);
}
}


var segmentOrigin = segmentId * segmentSpan;
var segmentOrigin = segmentId * segmentSpan;
var (w1, c1) = GetVector(segmentOrigin);
var (w1, c1) = GetVector(segmentOrigin);
var (w2, c2) = GetVector(segmentOrigin + segmentSpan);
var (w2, c2) = GetVector(segmentOrigin + segmentSpan);
if (Settings.ExplosivesSettings.EnableExplosiveRadiusMerging)
if (Settings.ExplosivesSettings.EnableExplosiveRadiusMerging)
{
{
if (positions
if (positions
.Where(x => x != position)
.Where(x => x != position)
.Select(x => new Vector2(x.X, x.Y))
.Select(x => new Vector2(x.X, x.Y))
.Any(x => Vector2.Distance(w1, x) < radius &&
.Any(x => Vector2.Distance(w1, x) < radius &&
Vector2.Distance(w2, x) < radius))
Vector2.Distance(w2, x) < radius))
{
{
continue;
continue;
}
}
}
}


Graphics.DrawLine(c1, c2, 1, color);
Graphics.DrawLine(c1, c2, 1, color);
}
}
}
}
}
}


public override Job Tick()
public override Job Tick()
{
{
Settings._iconsImageId = Graphics.GetTextureId(TextureName);
Settings._iconsImageId = Graphics.GetTextureId(TextureName);
Settings.PlannerSettings.SearchState = _plannerRunner switch
Settings.PlannerSettings.SearchState = _plannerRunner switch
{
{
{ IsRunning: true } => SearchState.Searching,
{ IsRunning: true } => SearchState.Searching,
{ IsRunning: false, CurrentBestPath.Count: > 0 } => SearchState.Stopped,
{ IsRunning: false, CurrentBestPath.Count: > 0 } => SearchState.Stopped,
_ => SearchState.Empty
_ => SearchState.Empty
};
};
var detonatorPos = DetonatorPos;
var detonatorPos = DetonatorPos;
_playerGridPos = GameController.Player.GetComponent<Positioned>().WorldPosNum.WorldToGrid();
_playerGridPos = GameController.Player.GetComponent<Positioned>().WorldPosNum.WorldToGrid();
if (detonatorPos is { Pos: var dp } && _playerGridPos.Distance(dp) < 90)
if (detonatorPos is { Pos: var dp } && _playerGridPos.Distance(dp) < 90)
{
{
_zoneCleared = DetonatorEntity?.IsTargetable != true;
_zoneCleared = DetonatorEntity?.IsTargetable != true;
if (_zoneCleared)
if (_zoneCleared)
{
{
ClearSearch();
ClearSearch();
return null;
return null;
}
}
}
}


var ingameUi = GameController.Game.IngameState.IngameUi;
var ingameUi = GameController.Game.IngameState.IngameUi;
var map = ingameUi.Map;
var map = ingameUi.Map;
var largeMap = map.LargeMap.AsObject<SubMap>();
var largeMap = map.LargeMap.AsObject<SubMap>();
_largeMapOpen = largeMap.IsVisible;
_largeMapOpen = largeMap.IsVisible;
_mapScale = GameController.IngameState.Camera.Height / 677f * largeMap.Zoom;
_mapScale = GameController.IngameState.Camera.Height / 677f * largeMap.Zoom;
_mapCenter = largeMap.GetClientRect().TopLeft.ToVector2Num() + largeMap.ShiftNum + largeMap.DefaultShiftNum;
_mapCenter = largeMap.GetClientRect().TopLeft.ToVector2Num() + largeMap.ShiftNum + largeMap.DefaultShiftNum;
_playerZ = GameController.Player.GetComponent<Render>().Z;
_playerZ = GameController.Player.GetComponent<Render>().Z;


_explosiveRadius = Settings.ExplosivesSettings.CalculateRadiusAutomatically
_explosiveRadius = Settings.ExplosivesSettings.CalculateRadiusAutomatically
//ReSharper disable once PossibleLossOfFraction
//ReSharper disable once PossibleLossOfFraction
//rounding here is extremely important to get right, this is taken from the game's code
//rounding here is extremely important to get right, this is taken from the game's code
? ExplosiveBaseRadius * (100 + GameController.IngameState.Data.MapStats?.GetValueOrDefault(GameStat.MapExpeditionExplosionRadiusPct) ?? 0) / 100 * GridToWorldMultiplier
? ExplosiveBaseRadius * (100 + GameController.IngameState.Data.MapStats?.GetValueOrDefault(GameStat.MapExpeditionExplosionRadiusPct) ?? 0) / 100 * GridToWorldMultiplier
: Settings.ExplosivesSettings.ExplosiveRadius.Value;
: Settings.ExplosivesSettings.ExplosiveRadius.Value;
//ReSharper disable once PossibleLossOfFraction
//ReSharper disable once PossibleLossOfFraction
//rounding here is extremely important to get right, this is taken from the game's code
//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 *
_explosiveRange = ExplosiveBaseRange * (100 + GameController.IngameState.Data.MapStats?.GetValueOrDefault(GameStat.MapExpeditionMaximumPlacementDistancePct) ?? 0) / 100 *
GridToWorldMultiplier;
GridToWorldMultiplier;


foreach (var entity in new[] { EntityType.IngameIcon, EntityType.Terrain }
foreach (var entity in new[] { EntityType.IngameIcon, EntityType.Terrain }
.SelectMany(x => GameController.EntityListWrapper.ValidEntitiesByType[x]))
.SelectMany(x => GameController.EntityListWrapper.ValidEntitiesByType[x]))
{
{
if (GetEntityType(entity.Path) != ExpeditionEntityType.None)
if (GetEntityType(entity.Path) != ExpeditionEntityType.None)
{
{
var newValue = BuildCacheItem(entity);
var newValue = BuildCacheItem(entity);
_cachedEntities[entity.Id] = _cachedEntities.TryGetValue(entity.Id, out var oldValue)
_cachedEntities[entity.Id] = _cachedEntities.TryGetValue(entity.Id, out var oldValue)
? oldValue.Merge(newValue)
? oldValue.Merge(newValue)
: newValue;
: newValue;
}
}
}
}


return null;
return null;
}
}


private (Vector2 Min, Vector2 Max)? GetExclusionRect()
private (Vector2 Min, Vector2 Max)? GetExclusionRect()
{
{
if (DetonatorPos is not { } detonatorPos)
if (DetonatorPos is not { } detonatorPos)
{
{
return null;
return null;
}
}


var negVec = new Vector2(-11.5f, -8.5f);
var negVec = new Vector2(-11.5f, -8.5f);
var posVec = new Vector2(10.5f, 23.5f);
var posVec = new Vector2(10.5f, 23.5f);
var rotations = (int)Math.Round(detonatorPos.Rotation/(MathF.PI/2));
var rotations = (int)Math.Round(detonatorPos.Rotation/(MathF.PI/2));
for (int i = 0; i < rotations; i++)
for (int i = 0; i < rotations; i++)
{
{
(negVec.X, negVec.Y, posVec.X, posVec.Y) = (-posVec.Y, negVec.X, -negVec.Y, posVec.X);
(negVec.X, negVec.Y, posVec.X, posVec.Y) = (-posVec.Y, negVec.X, -negVec.Y, posVec.X);
}
}


return (detonatorPos.Pos + negVec, detonatorPos.Pos + posVec);
return (detonatorPos.Pos + negVec, detonatorPos.Pos + posVec);
}
}


private ExpeditionEnvironment PlannerEnvironment => BuildEnvironment();
private ExpeditionEnvironment PlannerEnvironment => BuildEnvironment();


private ExpeditionEnvironment BuildEnvironment()
private ExpeditionEnvironment BuildEnvironment()
{
{
if (DetonatorPos is not { Pos: var detonatorPos })
if (DetonatorPos is not { Pos: var detonatorPos })
{
{
throw new Exception("Unable to plan a path: detonator position is unknown");
throw new Exception("Unable to plan a path: detonator position is unknown");
}
}


var loot = new List<(Vector2, IExpeditionLoot)>();
var loot = new List<(Vector2, IExpeditionLoot)>();
var relics = new List<(Vector2, IExpeditionRelic)>();
var relics = new List<(Vector2, IExpeditionRelic)>();
foreach (var e in _cachedEntities.Values)
foreach (var e in _cachedEntities.Values)
{
{
switch (GetEntityType(e.Path))
switch (GetEntityType(e.Path))
{
{
case ExpeditionEntityType.Marker:
case ExpeditionEntityType.Marker:
{
{
var animatedMetaData = e.BaseAnimatedEntityMetadata;
var animatedMetaData = e.BaseAnimatedEntityMetadata;
if (animatedMetaData != null)
if (animatedMetaData != null)
{
{
if (animatedMetaData.Contains("elitemarker"))
if (animatedMetaData.Contains("elitemarker"))
{
{
loot.Add((e.GridPos, new RunicMonster()));
loot.Add((e.GridPos, new RunicMonster()));
}
}
else
else
{
{
var iconDescription = _metadataIconMapping.GetOrAdd(animatedMetaData,
var iconDescription = _metadataIconMapping.GetOrAdd(animatedMetaData,
a => Icons.LogbookChestIcons.FirstOrDefault(icon =>
a => Icons.LogbookChestIcons.FirstOrDefault(icon =>
icon.BaseEntityMetadataSubstrings.Any(a.Contains)));
icon.BaseEntityMetadataSubstrings.Any(a.Contains)));
if (iconDescription != null)
if (iconDescription != null)
{
{
loot.Add((e.GridPos, new Chest(iconDescription.IconPickerIndex)));
loot.Add((e.GridPos, new Chest(iconDescription.IconPickerIndex)));
}
}
}
}
}
}


continue;
continue;
}
}
case ExpeditionEntityType.Relic:
case ExpeditionEntityType.Relic:
{
{
var mods = e.Mods;
var mods = e.Mods;
if (mods == null)
if (mods == null)
{
{
continue;
continue;
}
}


if (e.MinimapIconHide != false) continue;
if (e.MinimapIconHide != false) continue;
if (!mods.Any(x => x.Contains("ExpeditionRelicModifier"))) continue;
if (!mods.Any(x => x.Contains("ExpeditionRelicModifier"))) continue;


if (ContainsWarnMods(mods))
if (ContainsWarnMods(mods))
{
{
relics.Add((e.GridPos, new WarningRelic()));
relics.Add((e.GridPos, new WarningRelic()));
continue;
continue;
}
}


var iconDescriptions = mods.SelectMany(mod =>
var iconDescriptions = mods.SelectMany(mod =>
_relicModIconMapping.GetOrAdd(mod, s =>
_relicModIconMapping.GetOrAdd(mod, s =>
Icons.ExpeditionRelicIcons.Where(icon =>
Icons.ExpeditionRelicIcons.Where(icon =>
icon.BaseEntityMetadataSubstrings.Any(s.Contains)).ToList())).Distinct();
icon.BaseEntityMetadataSubstrings.Any(s.Contains)).ToList())).Distinct();
var allSubStrings = iconDescriptions.SelectMany(d => d.BaseEntityMetadataSubstrings).ToList();
var allSubStrings = iconDescriptions.SelectMany(d => d.BaseEntityMetadataSubstrings).ToList();
var fittingMods = mods
var fittingMods = mods
.SelectMany(mod => allSubStrings.Where(mod.Contains))
.SelectMany(mod => allSubStrings.Where(mod.Contains))
.Distinct()
.Distinct()
.Select(Icons.GetRelicType);
.Select(Icons.GetRelicType);
relics.AddRange(fittingMods.Select(expeditionRelic => (e.GridPos, expeditionRelic)));
relics.AddRange(fittingMods.Select(expeditionRelic => (e.GridPos, expeditionRelic)));


break;
break;
}
}
case ExpeditionEntityType.Cave:
case ExpeditionEntityType.Cave:
{
{
//Shits given about performance: some? a few?
//Shits given about performance: some? a few?
for (int i = 0; i < Settings.PlannerSettings.LogbookCaveRunicMonsterMultiplier; i++)
for (int i = 0; i < Settings.PlannerSettings.LogbookCaveRunicMonsterMultiplier; i++)
{
{
loot.Add((e.GridPos, new RunicMonster()));
loot.Add((e.GridPos, new RunicMonster()));
}
}


for (int i = 0; i < Settings.PlannerSettings.LogbookCaveArtifactChestMultiplier; i++)
for (int i = 0; i < Settings.PlannerSettings.LogbookCaveArtifactChestMultiplier; i++)
{
{
loot.Add((e.GridPos, new Chest(IconPickerIndex.LeagueChest)));
loot.Add((e.GridPos, new Chest(IconPickerIndex.LeagueChest)));
}
}


break;
break;
}
}
case ExpeditionEntityType.Boss:
case ExpeditionEntityType.Boss:
{
{
//Shits given about performance: some? a few?
//Shits given about performance: some? a few?
for (int i = 0; i < Settings.PlannerSettings.LogbookBossRunicMonsterMultiplier; i++)
for (int i = 0; i < Settings.PlannerSettings.LogbookBossRunicMonsterMultiplier; i++)
{
{
loot.Add((e.GridPos, new RunicMonster()));
loot.Add((e.GridPos, new RunicMonster()));
}
}


break;
break;
}
}
}
}
}
}


return new ExpeditionEnvironment(
return new ExpeditionEnvironment(
relics.FindAll(x => x.Item2 != null),
relics.FindAll(x => x.Item2 != null),
loot.FindAll(x => x.Item2 != null),
loot.FindAll(x => x.Item2 != null),
_explosiveRange / GridToWorldMultiplier,
_explosiveRange / GridToWorldMultiplier,
_explosiveRadius / GridToWorldMultiplier,
_explosiveRadius / GridToWorldMultiplier,
ExpeditionInfo.TotalExplosiveCount,
ExpeditionInfo.TotalExplosiveCount,
detonatorPos,
detonatorPos,
IsValidPlacement,
IsValidPlacement,
GetExclusionRect() ?? default,
GetExclusionRect() ?? default,
(GameController.IngameState.Data.MapStats?.GetValueOrDefault(GameStat.MapMinimapMainAreaRevealed) ?? 0) != 0);
(GameController.IngameState.Data.MapStats?.GetValueOrDefault(GameStat.MapMinimapMainAreaRevealed) ?? 0) != 0);
}
}


private bool IsValidPlacement(Vector2 x)
private bool IsValidPlacement(Vector2 x)
{
{
return x.X >= 0 && x.Y >= 0 &&
return x.X >= 0 && x.Y >= 0 &&
x.X < _areaDimensions.X &&
x.X < _areaDimensions.X &&
x.Y < _areaDimensions.Y &&
x.Y < _areaDimensions.Y &&
_pathfindingData[(int)x.Y][(int)x.X] > 3;
_pathfindingData[(int)x.Y][(int)x.X] > 3;
}
}


public override void Render()
public override void Render()
{
{
if (Settings.PlannerSettings.ClearSearchHotkey.PressedOnce())
if (Settings.PlannerSettings.ClearSearchHotkey.PressedOnce())
{
{
ClearSearch();
ClearSearch();
}
}


if (Settings.PlannerSettings.StopSearchHotkey.PressedOnce())
if (Settings.PlannerSettings.StopSearchHotkey.PressedOnce())
{
{
StopSearch();
StopSearch();
}
}


if (_zoneCleared)
if (_zoneCleared)
{
{
return;
return;
}
}


if (Settings.PlannerSettings.StartSearchHotkey.PressedOnce())
if (Settings.PlannerSettings.StartSearchHotkey.PressedOnce())
{
{
StartSearch();
StartSearch();
}
}


var explosives3D = GameController.EntityListWrapper.ValidEntitiesByType[EntityType.IngameIcon]
var explosives3D = GameController.EntityListWrapper.ValidEntitiesByType[EntityType.IngameIcon]
.Where(x => x.Path == ExplosivePath)
.Where(x => x.Path == ExplosivePath)
.Select(x => x.PosNum)
.Select(x => x.PosNum)
.ToList();
.ToList();
_explosives2DPositions = explosives3D.Select(x => new Vector2(x.X, x.Y)).ToList();
_explosives2DPositions = explosives3D.Select(x => new Vector2(x.X, x.Y)).ToList();
if (Settings.ExplosivesSettings.ShowExplosives)
if (Settings.ExplosivesSettings.ShowExplosives)
{
{
DrawCirclesInWorld(
DrawCirclesInWorld(
positions: explosives3D,
positions: explosives3D,
radius: _explosiveRadius,
radius: _explosiveRadius,
color: Settings.ExplosivesSettings.ExplosiveColor.Value);
color: Settings.ExplosivesSettings.ExplosiveColor.Value);
}
}


foreach (var e in _cachedEntities.Values)
foreach (var e in _cachedEntities.Values)
{
{
switch (GetEntityType(e.Path))
switch (GetEntityType(e.Path))
{
{
case ExpeditionEntityType.Marker:
case ExpeditionEntityType.Marker:
{
{
var animatedMetaData = e.BaseAnimatedEntityMetadata;
var animatedMetaData = e.BaseAnimatedEntityMetadata;
if (animatedMetaData != null)
if (animatedMetaData != null)
{
{
if (animatedMetaData.Contains("elitemarker"))
if (animatedMetaData.Contains("elitemarker"))
{
{
var mapSettings = Settings.IconMapping.GetValueOrDefault(IconPickerIndex.EliteMonstersIndicator, new IconDisplaySettings());
var mapSettings = Settings.IconMapping.GetValueOrDefault(IconPickerIndex.EliteMonstersIndicator, new IconDisplaySettings());
if (mapSettings.ShowOnMap)
if (mapSettings.ShowOnMap)
{
{
DrawIconOnMap(e, mapSettings.Icon ?? ExpeditionIconsSettings.DefaultEliteMonsterIcon, mapSettings.Tint, Vector2.Zero);
DrawIconOnMap(e, mapSettings.Icon ?? ExpeditionIconsSettings.DefaultEliteMonsterIcon, mapSettings.Tint, Vector2.Zero);
}
}


if (mapSettings.ShowInWorld)
if (mapSettings.ShowInWorld)
{
{
DrawIconInWorld(e, mapSettings.Icon ?? ExpeditionIconsSettings.DefaultEliteMonsterIcon, mapSettings.Tint, Vector2.Zero);
DrawIconInWorld(e, mapSettings.Icon ?? ExpeditionIconsSettings.DefaultEliteMonsterIcon, mapSettings.Tint, Vector2.Zero);
}
}
}
}
else
else
{
{
var iconDescription = _metadataIconMapping.GetOrAdd(animatedMetaData,
var iconDescription = _metadataIconMapping.GetOrAdd(animatedMetaData,
a => Icons.LogbookChestIcons.FirstOrDefault(icon =>
a => Icons.LogbookChestIcons.FirstOrDefault(icon =>
icon.BaseEntityMetadataSubstrings.Any(a.Contains)));
icon.BaseEntityMetadataSubstrings.Any(a.Contains)));
if (iconDescription != null)
if (iconDescription != null)
{
{
var settings = Settings.IconMapping.GetValueOrDefault(iconDescription.IconPickerIndex, new IconDisplaySettings());
var settings = Settings.IconMapping.GetValueOrDefault(iconDescription.IconPickerIndex, new IconDisplaySettings());
var icon = settings.Icon ?? iconDescription.DefaultIcon;
var icon = settings.Icon ?? iconDescription.DefaultIcon;
if (settings.ShowOnMap)
if (settings.ShowOnMap)
{
{
DrawIconOnMap(e, icon, settings.Tint, Vector2.Zero);
DrawIconOnMap(e, icon, settings.Tint, Vector2.Zero);
}
}


if (settings.ShowInWorld)
if (settings.ShowInWorld)
{
{
DrawIconInWorld(e, icon, settings.Tint, Vector2.Zero);
DrawIconInWorld(e, icon, settings.Tint, Vector2.Zero);
}
}
}
}
}
}
}
}


continue;
continue;
}
}
case ExpeditionEntityType.Relic:
case ExpeditionEntityType.Relic:
{
{
var mods = e.Mods;
var mods = e.Mods;
if (e.Mods == null) continue;
if (e.Mods == null) continue;
if (e.MinimapIconHide != false) continue;
if (e.MinimapIconHide != false) continue;
if (!mods.Any(x => x.Contains("ExpeditionRelicModifier"))) continue;
if (!mods.Any(x => x.Contains("ExpeditionRelicModifier"))) continue;


if (ContainsWarnMods(mods))
if (ContainsWarnMods(mods))
{
{
var mapSettings = Settings.IconMapping.GetValueOrDefault(IconPickerIndex.BadModsIndicator, new IconDisplaySettings());
var mapSettings = Settings.IconMapping.GetValueOrDefault(IconPickerIndex.BadModsIndicator, new IconDisplaySettings());
if (mapSettings.ShowOnMap)
if (mapSettings.ShowOnMap)
{
{
DrawIconOnMap(e, mapSettings.Icon ?? ExpeditionIconsSettings.DefaultBadModsIcon, mapSettings.Tint, Vector2.Zero);
DrawIconOnMap(e, mapSettings.Icon ?? ExpeditionIconsSettings.DefaultBadModsIcon, mapSettings.Tint, Vector2.Zero);
}
}


if (mapSettings.ShowInWorld)
if (mapSettings.ShowInWorld)
{
{
DrawIconInWorld(e, mapSettings.Icon ?? ExpeditionIconsSettings.DefaultBadModsIcon, mapSettings.Tint, -Vector2.UnitY);
DrawIconInWorld(e, mapSettings.Icon ?? ExpeditionIconsSettings.DefaultBadModsIcon, mapSettings.Tint, -Vector2.UnitY);
}
}


continue;
continue;
}
}


if (Settings.DrawGoodModsInWorld || Settings.DrawGoodModsOnMap)
if (Settings.DrawGoodModsInWorld || Settings.DrawGoodModsOnMap)
{
{
var worldIcons = new HashSet<(MapIconsIndex, Color?)>();
var worldIcons = new HashSet<(MapIconsIndex, Color?)>();
var mapIcons = new HashSet<(MapIconsIndex, Color?)>();
var mapIcons = new HashSet<(MapIconsIndex, Color?)>();
var iconDescriptions = mods.SelectMany(mod =>
var iconDescriptions = mods.SelectMany(mod =>
_relicModIconMapping.GetOrAdd(mod, s =>
_relicModIconMapping.GetOrAdd(mod, s =>
Icons.ExpeditionRelicIcons.Where(icon =>
Icons.ExpeditionRelicIcons.Where(icon =>
icon.BaseEntityMetadataSubstrings.Any(s.Contains)).ToList())).Distinct();
icon.BaseEntityMetadataSubstrings.Any(s.Contains)).ToList())).Distinct();
foreach (var iconDescription in iconDescriptions)
foreach (var iconDescription in iconDescriptions)
{
{
var settings = Settings.IconMapping.GetValueOrDefault(iconDescription.IconPickerIndex, new IconDisplaySettings());
var settings = Settings.IconMapping.GetValueOrDefault(iconDescription.IconPickerIndex, new IconDisplaySettings());
var icon = settings.Icon ?? iconDescription.DefaultIcon;
var icon = settings.Icon ?? iconDescription.DefaultIcon;
if (settings.ShowOnMap)
if (settings.ShowOnMap)
{
{
mapIcons.Add((icon, settings.Tint));
mapIcons.Add((icon, settings.Tint));
}
}


if (settings.ShowInWorld)
if (settings.ShowInWorld)
{
{
worldIcons.Add((icon, settings.Tint));
worldIcons.Add((icon, settings.Tint));
}
}
}
}


var offset = new Vector2(-worldIcons.Count * 0.5f + 0.5f, 0);
var offset = new Vector2(-worldIcons.Count * 0.5f + 0.5f, 0);
foreach (var (icon, tint) in worldIcons)
foreach (var (icon, tint) in worldIcons)
{
{
if (Settings.DrawGoodModsInWorld)
if (Settings.DrawGoodModsInWorld)
{
{
DrawIconInWorld(e, icon, tint, offset);
DrawIconInWorld(e, icon, tint, offset);
}
}


offset += Vector2.UnitX;
offset += Vector2.UnitX;
}
}


offset = new Vector2(-mapIcons.Count * 0.5f + 0.5f, 0);
offset = new Vector2(-mapIcons.Count * 0.5f + 0.5f, 0);
foreach (var (icon, tint) in mapIcons)
foreach (var (icon, tint) in mapIcons)
{
{
if (Settings.DrawGoodModsOnMap)
if (Settings.DrawGoodModsOnMap)
{
{
DrawIconOnMap(e, icon, tint, offset);
DrawIconOnMap(e, icon, tint, offset);
}
}


offset += Vector2.UnitX;
offset += Vector2.UnitX;
}
}
}
}


break;
break;
}
}
}
}
}
}


if (_plannerRunner?.CurrentBestPath is { Count: > 0 } path)
if (_plannerRunner?.CurrentBestPath is { Count: > 0 } path)
{
{
var firstPoint = DetonatorPos?.Pos ?? _playerGridPos;
var firstPoint = DetonatorPos?.Pos ?? _playerGridPos;
var prevPoint = firstPoint;
var prevPoint = firstPoint;
for (var i = 0; i < path.Count; i++)
for (var i = 0; i < path.Count; i++)
{
{
var point = path[i];
var point = path[i];
if (_largeMapOpen)
if (_largeMapOpen)
{
{
Graphics.DrawLine(GetMapScreenPosition(prevPoint), GetMapScreenPosition(point), 1, Settings.PlannerSettings.MapLineColor);
Graphics.DrawLine(GetMapScreenPosition(prevPoint), GetMapScreenPosition(point), 1, Settings.PlannerSettings.MapLineColor);
}
}


var worldPos = GetWorldScreenPosition(point);
var worldPos = GetWorldScreenPosition(point);
Graphics.DrawLine(GetWorldScreenPosition(prevPoint), worldPos, 1, Settings.PlannerSettings.WorldLineColor);
Graphics.DrawLine(GetWorldScreenPosition(prevPoint), worldPos, 1, Settings.PlannerSettings.WorldLineColor);
var text = $"#{i}";
var text = $"#{i}";
using (Graphics.SetTextScale(Settings.PlannerSettings.TextMarkerScale))
using (Graphics.SetTextScale(Settings.PlannerSettings.TextMarkerScale))
{
{
Graphics.DrawBox(worldPos, worldPos + Graphics.MeasureText(text), Color.Black);
Graphics.DrawBox(worldPos, worldPos + Graphics.MeasureText(text), Color.Black);
Graphics.DrawText(text, worldPos, Color.White);
Graphics.DrawText(text, worldPos, Color.White);
prevPoint = point;
prevPoint = point;
}
}
}
}


DrawCirclesInWorld(
DrawCirclesInWorld(
positions: path.Select(ExpandWithTerrainHeight).ToList(),
positions: path.Select(ExpandWithTerrainHeight).ToList(),
radius: _explosiveRadius,
radius: _explosiveRadius,
color: Settings.PlannerSettings.ExplosiveColor.Value);
color: Settings.PlannerSettings.ExplosiveColor.Value);
}
}
}
}


private bool ContainsWarnMods(List<string> mods)
private bool ContainsWarnMods(List<string> mods)
{
{
return Settings.WarnPhysImmune.Value && mods.Any(x => x.Contains("ExpeditionRelicModifierImmunePhysicalDamage")) ||
return Settings.WarnPhysImmune.Value && mods.Any(x => x.Contains("ExpeditionRelicModifierImmunePhysicalDamage")) ||
Settings.WarnFireImmune.Value && mods.Any(x => x.Contains("ExpeditionRelicModifierImmuneFireDamage")) ||
Settings.WarnFireImmune.Value && mods.Any(x => x.Contains("ExpeditionRelicModifierImmuneFireDamage")) ||
Settings.WarnColdImmune.Value && mods.Any(x => x.Contains("ExpeditionRelicModifierImmuneColdDamage")) ||
Settings.WarnColdImmune.Value && mods.Any(x => x.Contains("ExpeditionRelicModifierImmuneColdDamage")) ||
Settings.WarnLightningImmune.Value && mods.Any(x => x.Contains("ExpeditionRelicModifierImmuneLightningDamage")) ||
Settings.WarnLightningImmune.Value && mods.Any(x => x.Contains("ExpeditionRelicModifierImmuneLightningDamage")) ||
Settings.WarnChaosImmune.Value && mods.Any(x => x.Contains("ExpeditionRelicModifierImmuneChaosDamage")) ||
Settings.WarnChaosImmune.Value && mods.Any(x => x.Contains("ExpeditionRelicModifierImmuneChaosDamage")) ||
Settings.WarnCritImmune.Value && mods.Any(x => x.Contains("ExpeditionRelicModifierCannotBeCrit")) ||
Settings.WarnCritImmune.Value && mods.Any(x => x.Contains("ExpeditionRelicModifierCannotBeCrit")) ||
Settings.WarnIgniteImmune.Value && mods.Any(x => x.Cont