Skip to content
Closed
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
5 changes: 5 additions & 0 deletions More World Locations_AIO/Src/Locations/BepinexConfigs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@ public class BepinexConfigs
public static ConfigEntry<PortInit.Toggle> EnableWaystones = null!;
public static ConfigEntry<PortInit.Toggle> EnableTraders = null!;
public static ConfigEntry<PortInit.Toggle> EnableTrainers = null!;
public static ConfigEntry<PortInit.Toggle> PortIconsIndividual = null!;
public static ConfigEntry<PortInit.Toggle> UseCustomTraderConfigs = null!;
public static ConfigEntry<PortInit.Toggle> UseCustomLocationYAML = null!;
public static ConfigEntry<PortInit.Toggle> UseCustomLocalization = null!;

public static bool UseIndividualPortIcons => PortIconsIndividual?.Value == PortInit.Toggle.On;

public static void BindFeatureConfigs()
{
EnableShrines = PortInit.plugin.Config.BindConfig("0 - Features", "Enable Shrines", PortInit.Toggle.On,
Expand All @@ -25,6 +28,8 @@ public static void BindFeatureConfigs()
"If Off, trader locations (taverns, blacksmiths, material vendors) will not spawn", synced: true);
EnableTrainers = PortInit.plugin.Config.BindConfig("0 - Features", "Enable Trainers", PortInit.Toggle.On,
"If Off, trainer locations (skill book vendors) will not spawn", synced: true);
PortIconsIndividual = PortInit.plugin.Config.BindConfig("0 - Features", "Port Icons Individual", PortInit.Toggle.Off,
"If On, port map icons are discovered per player when the player gets close to a port. If Off, ports use the existing vanilla shared location icon behavior.", synced: true);
UseCustomTraderConfigs = PortInit.plugin.Config.BindConfig("0 - Features", "Use Custom Trader Configs", PortInit.Toggle.Off,
"If On, uses warpalicious.More_World_Locations_TraderItems.yml from config folder. Auto-extracts default if missing.", synced: true);
UseCustomLocationYAML = PortInit.plugin.Config.BindConfig("0 - Features", "Use Custom Location YAML", PortInit.Toggle.Off,
Expand Down
10 changes: 5 additions & 5 deletions More World Locations_AIO/Src/Locations/LocationDefinitions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -581,19 +581,19 @@ public static class LocationRings
public static readonly MWLLocation[] Ports =
{
new() { Name = "MWL_Port1", AssetPath = "Assets/WarpProjects/More World Locations/Ports/MWL_Port1.prefab",
Config = new LocationConfig { Biome = Heightmap.Biome.Meadows, Priotized = true, ExteriorRadius = 20, ClearArea = true, RandomRotation = false, MinDistanceFromSimilar = 1024, MaxTerrainDelta = 3f, MinAltitude = -2f, MaxAltitude = 1, SlopeRotation = true, Group = "MWL_Ports", IconPlaced = true} },
Config = new LocationConfig { Biome = Heightmap.Biome.Meadows, Priotized = true, ExteriorRadius = 20, ClearArea = true, RandomRotation = false, MinDistanceFromSimilar = 1024, MaxTerrainDelta = 3f, MinAltitude = -2f, MaxAltitude = 1, SlopeRotation = true, Group = "MWL_Ports", IconPlaced = !BepinexConfigs.UseIndividualPortIcons } },

new() { Name = "MWL_Port2", AssetPath = "Assets/WarpProjects/More World Locations/Ports/MWL_Port2.prefab",
Config = new LocationConfig { Biome = Heightmap.Biome.Plains, Priotized = true, ExteriorRadius = 20, ClearArea = true, RandomRotation = false, MinDistanceFromSimilar = 1024, MaxTerrainDelta = 3f, MinAltitude = -2f, MaxAltitude = 1, SlopeRotation = true, Group = "MWL_Ports", BiomeArea = Heightmap.BiomeArea.Edge, IconPlaced = true } },
Config = new LocationConfig { Biome = Heightmap.Biome.Plains, Priotized = true, ExteriorRadius = 20, ClearArea = true, RandomRotation = false, MinDistanceFromSimilar = 1024, MaxTerrainDelta = 3f, MinAltitude = -2f, MaxAltitude = 1, SlopeRotation = true, Group = "MWL_Ports", BiomeArea = Heightmap.BiomeArea.Edge, IconPlaced = !BepinexConfigs.UseIndividualPortIcons } },

new() { Name = "MWL_Port3", AssetPath = "Assets/WarpProjects/More World Locations/Ports/MWL_Port3.prefab",
Config = new LocationConfig { Biome = Heightmap.Biome.Mistlands, Priotized = true, ExteriorRadius = 20, ClearArea = true, RandomRotation = false, MinDistanceFromSimilar = 1024, MaxTerrainDelta = 10f, MinAltitude = -1f, MaxAltitude = 2, SlopeRotation = true, Group = "MWL_Ports", IconPlaced = true } },
Config = new LocationConfig { Biome = Heightmap.Biome.Mistlands, Priotized = true, ExteriorRadius = 20, ClearArea = true, RandomRotation = false, MinDistanceFromSimilar = 1024, MaxTerrainDelta = 10f, MinAltitude = -1f, MaxAltitude = 2, SlopeRotation = true, Group = "MWL_Ports", IconPlaced = !BepinexConfigs.UseIndividualPortIcons } },

new() { Name = "MWL_Port4", AssetPath = "Assets/WarpProjects/More World Locations/Ports/MWL_Port4.prefab",
Config = new LocationConfig { Biome = Heightmap.Biome.BlackForest, Priotized = true, ExteriorRadius = 20, ClearArea = true, RandomRotation = false, MinDistanceFromSimilar = 1024, MaxTerrainDelta = 4f, MinAltitude = -1f, MaxAltitude = 1, SlopeRotation = true, Group = "MWL_Ports", IconPlaced = true } },
Config = new LocationConfig { Biome = Heightmap.Biome.BlackForest, Priotized = true, ExteriorRadius = 20, ClearArea = true, RandomRotation = false, MinDistanceFromSimilar = 1024, MaxTerrainDelta = 4f, MinAltitude = -1f, MaxAltitude = 1, SlopeRotation = true, Group = "MWL_Ports", IconPlaced = !BepinexConfigs.UseIndividualPortIcons } },

new() { Name = "MWL_Port5", AssetPath = "Assets/WarpProjects/More World Locations/Ports/MWL_Port5.prefab",
Config = new LocationConfig { Biome = Heightmap.Biome.AshLands, Priotized = true, ExteriorRadius = 20, ClearArea = true, RandomRotation = false, MinDistanceFromSimilar = 1024, MaxTerrainDelta = 6f, MinAltitude = -0.5f, MaxAltitude = 2, SlopeRotation = true, Group = "MWL_Ports" , MaxDistance = 9100, IconPlaced = true} },
Config = new LocationConfig { Biome = Heightmap.Biome.AshLands, Priotized = true, ExteriorRadius = 20, ClearArea = true, RandomRotation = false, MinDistanceFromSimilar = 1024, MaxTerrainDelta = 6f, MinAltitude = -0.5f, MaxAltitude = 2, SlopeRotation = true, Group = "MWL_Ports" , MaxDistance = 9100, IconPlaced = !BepinexConfigs.UseIndividualPortIcons } },
};

// ── Traders ──────────────────────────────────────────────────────────
Expand Down
230 changes: 212 additions & 18 deletions More World Locations_AIO/Src/Ports/src/KnownPorts.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,38 @@
using System.Collections.Generic;
using System.Linq;
using HarmonyLib;
using JetBrains.Annotations;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using More_World_Locations_AIO.Traders;
using UnityEngine;

namespace More_World_Locations_AIO;

public static class KnownPorts
{
private const string CustomDataKey = "MWL_KnownPorts";
private const string IconCustomDataKey = "MWL_KnownPortIcons";
private static SerializedGuid? localKnownPorts; // cache player known ports
private static SerializedKnownPortIcons? localKnownPortIcons; // cache player discovered port icons
private static readonly List<Minimap.PinData> PortMapPins = new();

[HarmonyPatch(typeof(Player), nameof(Player.Load))]
private static class Player_Load_Patch
{
[UsedImplicitly]
private static void Postfix(Player __instance)
{
localKnownPorts = new SerializedGuid(__instance);
localKnownPorts = new SerializedGuid(__instance, CustomDataKey);
if (!BepinexConfigs.UseIndividualPortIcons)
{
localKnownPortIcons = null;
ClearMapPins();
return;
}

localKnownPortIcons = new SerializedKnownPortIcons(__instance, IconCustomDataKey);
RebuildMapPins();
}
}

Expand All @@ -26,39 +42,72 @@ private static class Player_Save_Patch
[UsedImplicitly]
private static void Prefix(Player __instance)
{
localKnownPorts?.Save(__instance);
GetKnownPorts(__instance).Save(__instance);
if (!BepinexConfigs.UseIndividualPortIcons) return;

GetKnownPortIcons(__instance).Save(__instance);
}
}

[HarmonyPatch(typeof(Minimap), nameof(Minimap.Awake))]
private static class Minimap_Awake_Patch
{
[UsedImplicitly]
private static void Postfix()
{
RebuildMapPins();
}
}

private class SerializedGuid
{
private readonly List<string> GUIDs = new();
public SerializedGuid(Player player)
private readonly string? DataKey;

public SerializedGuid(Player player, string? dataKey)
{
if (!player.m_customData.TryGetValue(CustomDataKey, out string json)) return;
DataKey = dataKey;
if (DataKey == null) return;
if (!player.m_customData.TryGetValue(DataKey, out string json)) return;
if (string.IsNullOrEmpty(json)) return;
try
{
List<string>? data = JsonConvert.DeserializeObject<List<string>>(json);
if (data is null)
{
player.ResetKnownPorts();
}
else
{
GUIDs = data;
}
Load(json);
}
catch
{
player.ResetKnownPorts();
}
}

private void Load(string json)
{
JToken token = JToken.Parse(json);
if (token.Type != JTokenType.Array) return;

// Handles the short-lived object format used while port icons shared this field.
if (token.First?.Type == JTokenType.Object)
{
List<KnownPortData>? data = token.ToObject<List<KnownPortData>>();
if (data != null) GUIDs.AddRange(data
.Where(port => !string.IsNullOrEmpty(port.GUID))
.Select(port => port.GUID));
return;
}

List<string>? guids = token.ToObject<List<string>>();
if (guids == null) return;
GUIDs.AddRange(guids.Where(guid => !string.IsNullOrEmpty(guid)));
}

public void Save(Player player)
{
player.m_customData[CustomDataKey] = ToJson();
if (DataKey == null) return;
player.m_customData[DataKey] = ToJson();
}

public bool UsesKey(string? dataKey) => DataKey == dataKey;

private string ToJson() => JsonConvert.SerializeObject(GUIDs);

public bool IsKnownPort(ShipmentManager.PortID portID) => GUIDs.Contains(portID.GUID);
Expand All @@ -70,17 +119,162 @@ public void Add(ShipmentManager.PortID portID)
}
}

private class SerializedKnownPortIcons
{
private readonly List<KnownPortData> Ports = new();
private readonly string? DataKey;

public SerializedKnownPortIcons(Player player, string? dataKey)
{
DataKey = dataKey;
if (DataKey == null) return;
if (!player.m_customData.TryGetValue(DataKey, out string json)) return;
if (string.IsNullOrEmpty(json)) return;
try
{
List<KnownPortData>? data = JsonConvert.DeserializeObject<List<KnownPortData>>(json);
if (data != null) Ports.AddRange(data.Where(port => !string.IsNullOrEmpty(port.GUID)));
}
catch
{
player.m_customData.Remove(DataKey);
}
}

public void Save(Player player)
{
if (DataKey == null) return;
player.m_customData[DataKey] = JsonConvert.SerializeObject(Ports);
}

public bool UsesKey(string? dataKey) => DataKey == dataKey;

public void AddIcon(ShipmentManager.PortID portID, Vector3 position)
{
KnownPortData? existing = Ports.FirstOrDefault(port => port.GUID == portID.GUID);
if (existing == null)
{
Ports.Add(new KnownPortData(portID, position));
return;
}

existing.Name = portID.Name;
existing.Position = new PortManager.SerializedVector(position);
existing.HasPosition = true;
}

public List<KnownPortData> GetPortsWithPositions() => Ports
.Where(port => port.HasPosition)
.ToList();
}

private class KnownPortData
{
public string GUID = "";
public string Name = "";
public PortManager.SerializedVector Position;
public bool HasPosition;

public KnownPortData() {}

public KnownPortData(ShipmentManager.PortID portID, Vector3 position)
{
GUID = portID.GUID;
Name = portID.Name;
Position = new PortManager.SerializedVector(position);
HasPosition = true;
}
}

public static bool IsKnownPort(this Player player, ShipmentManager.PortID portID)
{
localKnownPorts ??= new SerializedGuid(player);
localKnownPorts = GetKnownPorts(player);
return localKnownPorts.IsKnownPort(portID);
}

public static void AddKnownPort(this Player player, ShipmentManager.PortID portID)
{
localKnownPorts ??= new SerializedGuid(player);
localKnownPorts = GetKnownPorts(player);
localKnownPorts.Add(portID);
}

public static void ResetKnownPorts(this Player player) => player.m_customData.Remove(CustomDataKey);
}
public static void AddDiscoveredPortIcon(this Player player, ShipmentManager.PortID portID, Vector3 position)
{
if (!BepinexConfigs.UseIndividualPortIcons) return;

localKnownPortIcons = GetKnownPortIcons(player);
localKnownPortIcons.AddIcon(portID, position);
RebuildMapPins();
}

public static void ResetKnownPorts(this Player player)
{
player.m_customData.Remove(CustomDataKey);
player.m_customData.Remove(IconCustomDataKey);
localKnownPorts = new SerializedGuid(player, CustomDataKey);
localKnownPortIcons = new SerializedKnownPortIcons(player, IconCustomDataKey);
ClearMapPins();
}

private static SerializedGuid GetKnownPorts(Player player)
{
if (localKnownPorts == null || !localKnownPorts.UsesKey(CustomDataKey))
{
localKnownPorts = new SerializedGuid(player, CustomDataKey);
}
return localKnownPorts;
}

private static SerializedKnownPortIcons GetKnownPortIcons(Player player)
{
if (!BepinexConfigs.UseIndividualPortIcons)
{
localKnownPortIcons = null;
ClearMapPins();
return new SerializedKnownPortIcons(player, null);
}

if (localKnownPortIcons == null || !localKnownPortIcons.UsesKey(IconCustomDataKey))
{
localKnownPortIcons = new SerializedKnownPortIcons(player, IconCustomDataKey);
}
return localKnownPortIcons;
}

private static void RebuildMapPins()
{
if (Minimap.instance == null || Player.m_localPlayer == null) return;
if (MinimapTraderIcons.achorSprite == null) return;
if (!BepinexConfigs.UseIndividualPortIcons)
{
ClearMapPins();
return;
}

ClearMapPins();

foreach (KnownPortData port in GetKnownPortIcons(Player.m_localPlayer).GetPortsWithPositions())
{
Minimap.PinData pin = Minimap.instance.AddPin(
port.Position.ToVector3(),
Minimap.PinType.None,
port.Name,
false,
false);
pin.m_icon = MinimapTraderIcons.achorSprite;
pin.m_doubleSize = true;
PortMapPins.Add(pin);
}
}

private static void ClearMapPins()
{
if (Minimap.instance == null) return;

foreach (Minimap.PinData pin in PortMapPins.Where(pin => pin != null).ToList())
{
Minimap.instance.RemovePin(pin);
}
PortMapPins.Clear();
}
}
22 changes: 21 additions & 1 deletion More World Locations_AIO/Src/Ports/src/Port.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,23 @@ public void Awake()
public void Start()
{
if (!m_view.IsValid()) return;
if (BepinexConfigs.UseIndividualPortIcons) StartCoroutine(DiscoverIconCoroutine());
StartCoroutine(InitCoroutine());
}

private IEnumerator DiscoverIconCoroutine()
{
for (int i = 0; i < 20; i++)
{
if (Player.m_localPlayer != null)
{
Player.m_localPlayer.AddDiscoveredPortIcon(m_portID, GetMapPinPosition());
yield break;
}
yield return i == 0 ? null : new WaitForSeconds(0.5f);
}
}

private IEnumerator InitCoroutine()
{
yield return null;
Expand Down Expand Up @@ -159,6 +173,12 @@ public bool Interact(Humanoid user, bool hold, bool alt)
return false;
}

private Vector3 GetMapPinPosition()
{
LocationProxy locationProxy = WorldUtils.GetLocationInRange(transform.position, 10);
return locationProxy != null ? locationProxy.transform.position : transform.position;
}

public bool UseItem(Humanoid user, ItemDrop.ItemData item) => false;

public string GetHoverText()
Expand Down Expand Up @@ -482,4 +502,4 @@ private static class PortVars
public static readonly int TraderName = "PortTraderName".GetStableHashCode();

}
}
}