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
235 changes: 223 additions & 12 deletions Expedition2Good.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,10 @@ public override void Render()
{
if (first && showOnMinimap)
{
var color = value >= Settings.ValuableColorThreshold ? Settings.ValuableTextColor : Settings.TextColor;
var lines = GetMapText(overridden, value, allValidRecipes, label.Data, color);
var (minimapPrice, minimapOverridden, transferRecipes) =
GetMinimapRecipeData(label.Data, value, overridden, allValidRecipes);
var color = minimapPrice >= Settings.ValuableColorThreshold ? Settings.ValuableTextColor : Settings.TextColor;
var lines = GetMapText(minimapOverridden, minimapPrice, transferRecipes, label.Data, color);
DrawMapText(lines, Graphics.GridToMap(entity.GridPos, entity.GridPos));
}

Expand Down Expand Up @@ -144,18 +146,29 @@ public override void Render()
if (entity?.HashComponents.GetValueOrDefault((ushort)0x87B2) is not 0 and { } dataAddr)
{
var data = RemoteMemoryObject.GetObjectStatic<Expedition2EncounterData>(dataAddr);
var expedition2RunesWeights = GameController.Files.Expedition2RunesWeights.EntriesList;
var allRecipes = GameController.Files.Expedition2Recipes.EntriesList.ToLookup(x => x.RuneCountRequired);
var allValidRecipes = GetRecipes(expedition2RunesWeights, areaLevel, allRecipes, data);
var recipes = allValidRecipes.FirstOrDefault();
if (recipes != default)
if (TryGetSelectedRecipe(data, out var selectedRecipe))
{
var value = recipes.value.Item1;
var color = value >= Settings.ValuableColorThreshold ? Settings.ValuableTextColor : Settings.TextColor;
var lines = GetMapText(recipes.value.Item2, recipes.value.Item1, allValidRecipes.Select(x => x.x).ToList(), data, color);
var price = GetPriceOrDefault(selectedRecipe);
var color = price.Item1 >= Settings.ValuableColorThreshold ? Settings.ValuableTextColor : Settings.TextColor;
var lines = GetMapText(price.Item2, price.Item1, [selectedRecipe], data, color);
DrawMapText(lines, Graphics.GridToMap(entity.GridPos, entity.GridPos));
found = true;
}
else
{
var expedition2RunesWeights = GameController.Files.Expedition2RunesWeights.EntriesList;
var allRecipes = GameController.Files.Expedition2Recipes.EntriesList.ToLookup(x => x.RuneCountRequired);
var allValidRecipes = GetRecipes(expedition2RunesWeights, areaLevel, allRecipes, data);
var recipes = allValidRecipes.FirstOrDefault();
if (recipes != default)
{
var value = recipes.value.Item1;
var color = value >= Settings.ValuableColorThreshold ? Settings.ValuableTextColor : Settings.TextColor;
var lines = GetMapText(recipes.value.Item2, recipes.value.Item1, allValidRecipes.Select(x => x.x).ToList(), data, color);
DrawMapText(lines, Graphics.GridToMap(entity.GridPos, entity.GridPos));
found = true;
}
}
}

if (!found)
Expand Down Expand Up @@ -204,6 +217,8 @@ public override void Render()
first = false;
}
}

DrawTransferredRunesPanel();
}

private bool IsActivated(Entity entity)
Expand Down Expand Up @@ -234,8 +249,7 @@ private bool IsActivated(Entity entity)
{
foreach (var position in positions)
{
var runeIds = recipes.Select(x => x.Runes.ElementAtOrDefault(position)).Where(x => x != null)
.Select(r => r.Id).Distinct().OrderBy(x => x).ToList();
var runeIds = GetTransferredRuneIds(recipes, position);
if (Settings.HideNonHighlightedTransferredRunes)
{
runeIds = runeIds.Where(id => GetHighlightColor(id) != null).ToList();
Expand Down Expand Up @@ -267,6 +281,159 @@ private bool IsActivated(Entity entity)
return lines;
}

private void DrawTransferredRunesPanel()
{
if (!Settings.ShowTransferredRunesPanel)
{
return;
}

var runeIds = CollectDistinctTransferredRunes(GetEncounterData());
if (runeIds.Count == 0)
{
return;
}

const float pad = 10f;
const float accentWidth = 4f;
const float rowHeight = 18f;
const float swatchSize = 8f;
const float swatchGap = 6f;
const float titleRowHeight = 22f;
const float separatorGap = 4f;

var entries = runeIds
.Select(id => (Id: id, Highlight: GetHighlightColor(id)))
.OrderByDescending(x => x.Highlight != null)
.ThenBy(x => x.Id, StringComparer.OrdinalIgnoreCase)
.ToList();

const string title = "Transferred Runes";
var countText = $"({entries.Count})";
var titleWidth = Graphics.MeasureText(title).X + 6f + Graphics.MeasureText(countText).X;
var maxRuneWidth = entries.Max(e => Graphics.MeasureText(e.Id).X);
var contentWidth = Math.Max(titleWidth, swatchSize + swatchGap + maxRuneWidth);
var innerLeft = accentWidth + pad;
var panelWidth = innerLeft + contentWidth + pad;
var panelHeight = pad + titleRowHeight + separatorGap + 1f + pad / 2f + entries.Count * rowHeight + pad;

var origin = new Vector2(Settings.TransferredRunesPanelX.Value, Settings.TransferredRunesPanelY.Value);
var panelBottomRight = origin + new Vector2(panelWidth, panelHeight);

var bgColor = Color.FromArgb(215, 12, 12, 16);
var borderColor = Color.FromArgb(200, 100, 82, 52);
var titleColor = Color.FromArgb(255, 230, 210, 170);
var countColor = Color.FromArgb(170, 170, 170, 170);
var separatorColor = Color.FromArgb(110, 100, 82, 52);

Graphics.DrawBox(origin, panelBottomRight, bgColor, 6f);
Graphics.DrawBox(origin, new Vector2(origin.X + accentWidth, panelBottomRight.Y), Settings.TopPickColor, 0f);
Graphics.DrawFrame(origin, panelBottomRight, borderColor, 6f, 1, 0);

var textX = origin.X + innerLeft + swatchSize + swatchGap;
var y = origin.Y + pad;

Graphics.DrawText(title, new Vector2(origin.X + innerLeft, y), titleColor);
Graphics.DrawText(countText, new Vector2(origin.X + innerLeft + Graphics.MeasureText(title).X + 6f, y), countColor);

y += titleRowHeight;
Graphics.DrawLine(
new Vector2(origin.X + innerLeft, y),
new Vector2(panelBottomRight.X - pad, y),
1f,
separatorColor);
y += separatorGap + 1f + pad / 2f;

foreach (var (id, highlight) in entries)
{
var textColor = highlight ?? Settings.TextColor;
var defaultColor = Settings.TextColor.Value;
var swatchColor = highlight ?? Color.FromArgb(130, defaultColor.R, defaultColor.G, defaultColor.B);

if (highlight != null)
{
var rowBg = Color.FromArgb(40, highlight.Value.R, highlight.Value.G, highlight.Value.B);
Graphics.DrawBox(
new Vector2(origin.X + accentWidth, y - 1f),
new Vector2(panelBottomRight.X, y + rowHeight - 3f),
rowBg,
3f);
}

var swatchTopLeft = new Vector2(textX - swatchSize - swatchGap, y + 3f);
var swatchBottomRight = swatchTopLeft + new Vector2(swatchSize, swatchSize);
Graphics.DrawBox(swatchTopLeft, swatchBottomRight, swatchColor, 2f);
if (highlight != null)
{
Graphics.DrawFrame(swatchTopLeft, swatchBottomRight, Color.FromArgb(180, 255, 255, 255), 2f, 1, 0);
}

Graphics.DrawText(id, new Vector2(textX, y), textColor);
y += rowHeight;
}
}

private IEnumerable<Expedition2EncounterData> GetEncounterData()
{
var seen = new HashSet<long>();
if (_labels.Value is { Count: > 0 } labels)
{
foreach (var (log, label) in labels)
{
if (IsActivated(log.ItemOnGround))
{
continue;
}

if (label?.Data is { Address: not 0 } data && seen.Add(data.Address))
{
yield return data;
}
}
}

foreach (var entity in GameController.EntityListWrapper.ValidEntitiesByType[EntityType.IngameIcon]
.Where(x => x.Metadata.StartsWith("Metadata/MiscellaneousObjects/Expedition2/Expedition2Encounter", StringComparison.Ordinal)))
{
if (IsActivated(entity))
{
continue;
}

if (entity?.HashComponents.GetValueOrDefault((ushort)0x87B2) is not 0 and { } dataAddr)
{
var data = RemoteMemoryObject.GetObjectStatic<Expedition2EncounterData>(dataAddr);
if (data is { Address: not 0 } && seen.Add(data.Address))
{
yield return data;
}
}
}
}

private List<string> CollectDistinctTransferredRunes(IEnumerable<Expedition2EncounterData> encounters)
{
var runeIds = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var data in encounters)
{
if (!TryGetSelectedRecipe(data, out var recipe) || data.PassedOnRunePositions is not { Count: > 0 } positions)
{
continue;
}

foreach (var position in positions)
{
var runeId = recipe.Runes.ElementAtOrDefault(position)?.Id;
if (!string.IsNullOrEmpty(runeId))
{
runeIds.Add(runeId);
}
}
}

return runeIds.OrderBy(x => x, StringComparer.OrdinalIgnoreCase).ToList();
}

private Color? GetHighlightColor(string runeId)
{
if (string.IsNullOrEmpty(runeId))
Expand Down Expand Up @@ -307,6 +474,50 @@ private void DrawMapText(List<List<(string text, Color color)>> lines, Vector2 a
}
}

private static bool TryGetSelectedRecipe(Expedition2EncounterData data, out Expedition2Recipe recipe)
{
recipe = null;
if (data?.SelectedRecipe is not { Address: not 0 } selected)
{
return false;
}

if (selected.Runes is not { Count: > 0 })
{
return false;
}

recipe = selected;
return true;
}

private (double price, bool overridden, IReadOnlyCollection<Expedition2Recipe> transferRecipes) GetMinimapRecipeData(
Expedition2EncounterData data,
double fallbackPrice,
bool fallbackOverridden,
IReadOnlyCollection<Expedition2Recipe> fallbackTransferRecipes)
{
if (TryGetSelectedRecipe(data, out var selectedRecipe))
{
var price = GetPriceOrDefault(selectedRecipe);
return (price.Item1, price.Item2, [selectedRecipe]);
}

return (fallbackPrice, fallbackOverridden, fallbackTransferRecipes);
}

private static List<string> GetTransferredRuneIds(IReadOnlyCollection<Expedition2Recipe> recipes, int position)
{
if (recipes.Count == 1)
{
var runeId = recipes.First().Runes.ElementAtOrDefault(position)?.Id;
return string.IsNullOrEmpty(runeId) ? [] : [runeId];
}

return recipes.Select(x => x.Runes.ElementAtOrDefault(position)).Where(x => x != null)
.Select(r => r.Id).Distinct().OrderBy(x => x).ToList();
}

private List<(Expedition2Recipe x, (double, bool) value)> GetRecipes(List<Expedition2RunesWeight> expedition2RunesWeights, int areaLevel, ILookup<int, Expedition2Recipe> allRecipes, Expedition2EncounterData data)
{
var allowedRuneCounts = expedition2RunesWeights.Where(x => x.RuneSlot - 1 == data?.FixedRunePosition)
Expand Down
9 changes: 9 additions & 0 deletions Expedition2GoodSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@ public class Expedition2GoodSettings : ISettings
[Menu(null, "When enabled, a transferred-rune line is only drawn if it contains a highlighted rune, and only the highlighted rune(s) are listed on that line.")]
public ToggleNode HideNonHighlightedTransferredRunes { get; set; } = new ToggleNode(false);

[Menu(null, "Shows a screen panel listing all distinct transferred runes from encounters with a selected recipe.")]
public ToggleNode ShowTransferredRunesPanel { get; set; } = new ToggleNode(false);

[Menu(null, "Horizontal position of the transferred-runes panel.")]
public RangeNode<int> TransferredRunesPanelX { get; set; } = new RangeNode<int>(100, 0, 4000);

[Menu(null, "Vertical position of the transferred-runes panel.")]
public RangeNode<int> TransferredRunesPanelY { get; set; } = new RangeNode<int>(100, 0, 4000);

[Menu(null, "A transferred-rune name is drawn in its configured color when it matches one of these entries. Type the rune name exactly as it appears in the 'Transfers rune' line (case-insensitive), then pick a color.")]
public ContentNode<HighlightedRune> HighlightedTransferredRunes { get; set; } = new ContentNode<HighlightedRune>
{ Content = [], EnableControls = true, EnableItemCollapsing = true, ItemFactory = () => new HighlightedRune() };
Expand Down