diff --git a/.gitignore b/.gitignore
index 2e9693e..634fe55 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
obj
-bin
\ No newline at end of file
+bin
+/.vs
\ No newline at end of file
diff --git a/Systems/WorldMap/MapLayerData.cs b/Systems/WorldMap/MapLayerData.cs
new file mode 100644
index 0000000..4118489
--- /dev/null
+++ b/Systems/WorldMap/MapLayerData.cs
@@ -0,0 +1,18 @@
+namespace Vintagestory.GameContent;
+
+///
+/// Represents a single map layer and its associated serialised data, sent to or from the client.
+///
+[ProtoContract(ImplicitFields = ImplicitFields.AllPublic)]
+public class MapLayerData
+{
+ ///
+ /// The identifier or key corresponding to the map layer this data belongs to.
+ ///
+ public string ForMapLayer { get; set; }
+
+ ///
+ /// The raw, serialised binary data representing the layer's content or state.
+ ///
+ public byte[] Data { get; set; }
+}
\ No newline at end of file
diff --git a/Systems/WorldMap/MapLayerUpdate.cs b/Systems/WorldMap/MapLayerUpdate.cs
new file mode 100644
index 0000000..97321b7
--- /dev/null
+++ b/Systems/WorldMap/MapLayerUpdate.cs
@@ -0,0 +1,13 @@
+namespace Vintagestory.GameContent;
+
+///
+/// Represents a payload used to update the visible map layers for the current session.
+///
+[ProtoContract(ImplicitFields = ImplicitFields.AllPublic)]
+public class MapLayerUpdate
+{
+ ///
+ /// The collection of map layers that should be updated or applied.
+ ///
+ public MapLayerData[] Maplayers { get; set; }
+}
\ No newline at end of file
diff --git a/Systems/WorldMap/OnMapToggle.cs b/Systems/WorldMap/OnMapToggle.cs
new file mode 100644
index 0000000..cd0dacc
--- /dev/null
+++ b/Systems/WorldMap/OnMapToggle.cs
@@ -0,0 +1,13 @@
+namespace Vintagestory.GameContent;
+
+///
+/// Represents a network packet indicating that the map should be opened or closed.
+///
+[ProtoContract(ImplicitFields = ImplicitFields.AllPublic)]
+public class OnMapToggle
+{
+ ///
+ /// Whether to open (true) or close (false) the map.
+ ///
+ public bool OpenOrClose { get; set; }
+}
\ No newline at end of file
diff --git a/Systems/WorldMap/OnViewChangedPacket.cs b/Systems/WorldMap/OnViewChangedPacket.cs
new file mode 100644
index 0000000..d376da3
--- /dev/null
+++ b/Systems/WorldMap/OnViewChangedPacket.cs
@@ -0,0 +1,28 @@
+namespace Vintagestory.GameContent;
+
+///
+/// Represents a network packet indicating that the client's view bounds have changed.
+///
+[ProtoContract(ImplicitFields = ImplicitFields.AllPublic)]
+public class OnViewChangedPacket
+{
+ ///
+ /// The minimum X coordinate of the view bounds.
+ ///
+ public int X1 { get; set; }
+
+ ///
+ /// The minimum Z coordinate of the view bounds.
+ ///
+ public int Z1 { get; set; }
+
+ ///
+ /// The maximum X coordinate of the view bounds.
+ ///
+ public int X2 { get; set; }
+
+ ///
+ /// The maximum Z coordinate of the view bounds.
+ ///
+ public int Z2 { get; set; }
+}
\ No newline at end of file
diff --git a/Systems/WorldMap/WorldMapManager.cs b/Systems/WorldMap/WorldMapManager.cs
index 044657c..a4b5519 100644
--- a/Systems/WorldMap/WorldMapManager.cs
+++ b/Systems/WorldMap/WorldMapManager.cs
@@ -12,58 +12,30 @@
#nullable disable
-namespace Vintagestory.GameContent
-{
- [ProtoContract(ImplicitFields = ImplicitFields.AllPublic)]
- public class MapLayerUpdate
- {
- public MapLayerData[] Maplayers;
- }
-
- [ProtoContract(ImplicitFields = ImplicitFields.AllPublic)]
- public class MapLayerData
- {
- public string ForMapLayer;
- public byte[] Data;
- }
-
- [ProtoContract(ImplicitFields = ImplicitFields.AllPublic)]
- public class OnMapToggle
- {
- public bool OpenOrClose;
- }
-
- [ProtoContract(ImplicitFields = ImplicitFields.AllPublic)]
- public class OnViewChangedPacket
- {
- public int X1;
- public int Z1;
- public int X2;
- public int Z2;
- }
+namespace Vintagestory.GameContent;
public class WorldMapManager : ModSystem, IWorldMapManager
{
- public Dictionary MapLayerRegistry = new Dictionary();
- public Dictionary LayerGroupPositions = new Dictionary();
+ public Dictionary MapLayerRegistry { get; } = new Dictionary();
+ public Dictionary LayerGroupPositions { get; } = new Dictionary();
+ public bool IsShuttingDown { get; set; }
- ICoreAPI api;
+ private ICoreAPI _api;
// Client side stuff
- ICoreClientAPI capi;
- IClientNetworkChannel clientChannel;
- public GuiDialogWorldMap worldMapDlg;
+ private ICoreClientAPI _capi;
+ private IClientNetworkChannel _clientChannel;
+ public GuiDialogWorldMap worldMapDlg { get; set; }
public bool IsOpened => worldMapDlg?.IsOpened() == true;
-
// Client and Server side stuff
- public List MapLayers = new List();
- Thread mapLayerGenThread;
- public bool IsShuttingDown { get; set; }
-
+ public List MapLayers { get; set; } = new List();
+ private Thread _mapLayerGenThread;
+ private readonly object _mapLayerThreadLock = new();
+ private const float _tickInterval = 20 / 1000f;
+
// Server side stuff
- IServerNetworkChannel serverChannel;
-
+ private IServerNetworkChannel _serverChannel;
public override bool ShouldLoad(EnumAppSide side)
{
@@ -74,7 +46,7 @@ public override void Start(ICoreAPI api)
{
base.Start(api);
RegisterDefaultMapLayers();
- this.api = api;
+ _api = api;
}
public void RegisterDefaultMapLayers()
@@ -91,41 +63,69 @@ public void RegisterMapLayer(string code, double position) where T : MapLayer
LayerGroupPositions[code] = position;
}
- #region Client side
-
- public override void StartClientSide(ICoreClientAPI api)
+ private void CreateMapLayerGenerationThread()
{
- base.StartClientSide(api);
-
- capi = api;
- capi.Event.LevelFinalize += OnLvlFinalize;
-
- capi.Event.RegisterGameTickListener(OnClientTick, 20);
+ if (_mapLayerGenThread is { IsAlive: true }) return;
+ _mapLayerGenThread = new Thread(new ThreadStart(() =>
+ {
+ while (!IsShuttingDown)
+ {
+ lock (_mapLayerThreadLock)
+ {
+ var mapLayersSnapshot = MapLayers.ToList();
+ foreach (var layer in mapLayersSnapshot)
+ {
+ layer.OnOffThreadTick(_tickInterval);
+ }
+ }
- capi.Settings.AddWatcher("showMinimapHud", (on) => {
- ToggleMap(EnumDialogType.HUD);
- });
+ Thread.Sleep(20);
+ }
+ }))
+ {
+ IsBackground = true
+ };
+ _mapLayerGenThread.Start();
+ }
- capi.Event.LeaveWorld += () =>
+ private void LoadMapLayersFromRegistry()
+ {
+ lock (_mapLayerThreadLock)
{
- IsShuttingDown = true;
- int i = 0;
- while (mapLayerGenThread != null && mapLayerGenThread.IsAlive && i < 20)
+ MapLayers.Clear();
+
+ var mapLayerRegistrySnapshot = MapLayerRegistry.ToDictionary(k => k.Key, v => v.Value);
+ foreach (var val in mapLayerRegistrySnapshot)
{
- Thread.Sleep(50);
- i++;
+ if (val.Key == "entities" && !_api.World.Config.GetAsBool("entityMapLayer")) continue;
+ var instance = (MapLayer)Activator.CreateInstance(val.Value, _api, this);
+ MapLayers.Add(instance);
}
- worldMapDlg?.Dispose();
-
- foreach (var layer in MapLayers)
+ var mapLayersSnapshot = MapLayers.ToList();
+ foreach (var layer in mapLayersSnapshot)
{
- layer?.OnShutDown();
- layer?.Dispose();
+ layer.OnLoaded();
}
- };
+ }
+ }
- clientChannel =
+ #region Client side
+
+ public override void StartClientSide(ICoreClientAPI api)
+ {
+ base.StartClientSide(api);
+ _capi = api;
+ _capi.Event.LevelFinalize += OnLevelFinaliseClient;
+ _capi.Event.RegisterGameTickListener(OnClientTick, 20);
+ _capi.Settings.AddWatcher("showMinimapHud", (on) =>
+ {
+ ToggleMap(EnumDialogType.HUD);
+ });
+
+ _capi.Event.LeaveWorld += OnPlayerLeaveWorld;
+
+ _clientChannel =
api.Network.RegisterChannel("worldmap")
.RegisterMessageType(typeof(MapLayerUpdate))
.RegisterMessageType(typeof(OnViewChangedPacket))
@@ -134,8 +134,26 @@ public override void StartClientSide(ICoreClientAPI api)
;
}
+ private void OnPlayerLeaveWorld()
+ {
+ IsShuttingDown = true;
+ int i = 0;
+ while (_mapLayerGenThread != null && _mapLayerGenThread.IsAlive && i < 20)
+ {
+ Thread.Sleep(50);
+ i++;
+ }
+
+ worldMapDlg?.Dispose();
- private void onWorldMapLinkClicked(LinkTextComponent linkcomp)
+ foreach (var layer in MapLayers)
+ {
+ layer?.OnShutDown();
+ layer?.Dispose();
+ }
+ }
+
+ private void OnWorldMapLinkClicked(LinkTextComponent linkcomp)
{
string[] xyzstr = linkcomp.Href.Substring("worldmap://".Length).Split('=');
int x = xyzstr[1].ToInt();
@@ -166,7 +184,7 @@ private void onWorldMapLinkClicked(LinkTextComponent linkcomp)
if (!exists)
{
- capi.SendChatMessage(string.Format("/waypoint addati {0} ={1} ={2} ={3} {4} {5} {6}", "circle", x, y, z, false, "steelblue", text));
+ _capi.SendChatMessage(string.Format("/waypoint addati {0} ={1} ={2} ={3} {4} {5} {6}", "circle", x, y, z, false, "steelblue", text));
}
elem?.CenterMapTo(new BlockPos(x, y, z));
@@ -180,54 +198,6 @@ private void OnClientTick(float dt)
}
}
- private void OnLvlFinalize()
- {
- if (capi != null && mapAllowedClient())
- {
- capi.Input.RegisterHotKey("worldmaphud", Lang.Get("Show/Hide Minimap"), GlKeys.F6, HotkeyType.HelpAndOverlays);
- capi.Input.RegisterHotKey("minimapposition", Lang.Get("keycontrol-minimap-position"), GlKeys.F6, HotkeyType.HelpAndOverlays, false, true, false);
- capi.Input.RegisterHotKey("worldmapdialog", Lang.Get("Show World Map"), GlKeys.M, HotkeyType.HelpAndOverlays);
- capi.Input.SetHotKeyHandler("worldmaphud", OnHotKeyWorldMapHud);
- capi.Input.SetHotKeyHandler("minimapposition", OnHotKeyMinimapPosition);
- capi.Input.SetHotKeyHandler("worldmapdialog", OnHotKeyWorldMapDlg);
- capi.RegisterLinkProtocol("worldmap", onWorldMapLinkClicked);
- }
-
- foreach (var val in MapLayerRegistry)
- {
- if (val.Key == "entities" && !api.World.Config.GetAsBool("entityMapLayer")) continue;
- MapLayers.Add((MapLayer)Activator.CreateInstance(val.Value, api, this));
- }
-
-
- foreach (MapLayer layer in MapLayers)
- {
- layer.OnLoaded();
- }
-
- mapLayerGenThread = new Thread(new ThreadStart(() =>
- {
- while (!IsShuttingDown)
- {
- foreach (MapLayer layer in MapLayers)
- {
- layer.OnOffThreadTick(20 / 1000f);
- }
-
- Thread.Sleep(20);
- }
- }));
-
- mapLayerGenThread.IsBackground = true;
- mapLayerGenThread.Start();
-
- if (capi != null && (capi.Settings.Bool["showMinimapHud"] || !capi.Settings.Bool.Exists("showMinimapHud")) && (worldMapDlg == null || !worldMapDlg.IsOpened()))
- {
- ToggleMap(EnumDialogType.HUD);
- }
-
- }
-
private void OnMapLayerDataReceivedClient(MapLayerUpdate msg)
{
for (int i = 0; i < msg.Maplayers.Length; i++)
@@ -240,7 +210,7 @@ private void OnMapLayerDataReceivedClient(MapLayerUpdate msg)
public bool mapAllowedClient()
{
- return capi.World.Config.GetBool("allowMap", true) || capi.World.Player.Privileges.IndexOf("allowMap") != -1;
+ return _capi.World.Config.GetBool("allowMap", true) || _capi.World.Player.Privileges.IndexOf("allowMap") != -1;
}
private bool OnHotKeyWorldMapHud(KeyCombination comb)
@@ -251,8 +221,8 @@ private bool OnHotKeyWorldMapHud(KeyCombination comb)
private bool OnHotKeyMinimapPosition(KeyCombination comb)
{
- int prev = capi.Settings.Int["minimapHudPosition"];
- capi.Settings.Int["minimapHudPosition"] = (prev + 1) % 4;
+ int prev = _capi.Settings.Int["minimapHudPosition"];
+ _capi.Settings.Int["minimapHudPosition"] = (prev + 1) % 4;
if (worldMapDlg == null || !worldMapDlg.IsOpened()) ToggleMap(EnumDialogType.HUD);
else
@@ -286,11 +256,11 @@ public void ToggleMap(EnumDialogType asType)
{
if (!isDlgOpened)
{
- if (asType == EnumDialogType.HUD) capi.Settings.Bool.Set("showMinimapHud", true, false);
+ if (asType == EnumDialogType.HUD) _capi.Settings.Bool.Set("showMinimapHud", true, false);
worldMapDlg.Open(asType);
foreach (MapLayer layer in MapLayers) layer.OnMapOpenedClient();
- clientChannel.SendPacket(new OnMapToggle() { OpenOrClose = true });
+ _clientChannel.SendPacket(new OnMapToggle() { OpenOrClose = true });
return;
}
@@ -304,35 +274,36 @@ public void ToggleMap(EnumDialogType asType)
if (asType == EnumDialogType.HUD)
{
- capi.Settings.Bool.Set("showMinimapHud", false, false);
+ _capi.Settings.Bool.Set("showMinimapHud", false, false);
}
- else if (capi.Settings.Bool["showMinimapHud"])
+ else if (_capi.Settings.Bool["showMinimapHud"])
{
worldMapDlg.Open(EnumDialogType.HUD);
return;
}
-
+
}
worldMapDlg.TryClose();
return;
}
- worldMapDlg = new GuiDialogWorldMap(onViewChangedClient, syncViewChange, capi, getTabsOrdered());
- worldMapDlg.OnClosed += () => {
+ worldMapDlg = new GuiDialogWorldMap(OnViewChangedClient, SyncViewChange, _capi, GetTabsOrdered());
+ worldMapDlg.OnClosed += () =>
+ {
foreach (MapLayer layer in MapLayers) layer.OnMapClosedClient();
- clientChannel.SendPacket(new OnMapToggle() { OpenOrClose = false });
-
+ _clientChannel.SendPacket(new OnMapToggle() { OpenOrClose = false });
+
};
worldMapDlg.Open(asType);
foreach (MapLayer layer in MapLayers) layer.OnMapOpenedClient();
- clientChannel.SendPacket(new OnMapToggle() { OpenOrClose = true });
+ _clientChannel.SendPacket(new OnMapToggle() { OpenOrClose = true });
- if (asType == EnumDialogType.HUD) capi.Settings.Bool.Set("showMinimapHud", true, false); // Don't trigger the watcher which will call Toggle again recursively!
+ if (asType == EnumDialogType.HUD) _capi.Settings.Bool.Set("showMinimapHud", true, false); // Don't trigger the watcher which will call Toggle again recursively!
}
- private List getTabsOrdered()
+ private List GetTabsOrdered()
{
Dictionary tabs = new Dictionary();
@@ -348,7 +319,7 @@ private List getTabsOrdered()
return tabs.OrderBy(val => val.Value).Select(val => val.Key).ToList();
}
- private void onViewChangedClient(List nowVisible, List nowHidden)
+ private void OnViewChangedClient(List nowVisible, List nowHidden)
{
foreach (MapLayer layer in MapLayers)
{
@@ -356,11 +327,11 @@ private void onViewChangedClient(List nowVisible, List now
}
}
- private void syncViewChange(int x1, int z1, int x2, int z2)
+ private void SyncViewChange(int x1, int z1, int x2, int z2)
{
- clientChannel.SendPacket(new OnViewChangedPacket() { X1 = x1, Z1 = z1, X2 = x2, Z2 = z2 });
+ _clientChannel.SendPacket(new OnViewChangedPacket() { X1 = x1, Z1 = z1, X2 = x2, Z2 = z2 });
}
-
+
public void TranslateWorldPosToViewPos(Vec3d worldPos, ref Vec2f viewPos)
{
worldMapDlg.TranslateWorldPosToViewPos(worldPos, ref viewPos);
@@ -368,7 +339,7 @@ public void TranslateWorldPosToViewPos(Vec3d worldPos, ref Vec2f viewPos)
public void SendMapDataToServer(MapLayer forMapLayer, byte[] data)
{
- if (api.Side == EnumAppSide.Server) return;
+ if (_api.Side == EnumAppSide.Server) return;
List maplayerdatas = new List();
@@ -378,17 +349,50 @@ public void SendMapDataToServer(MapLayer forMapLayer, byte[] data)
ForMapLayer = MapLayerRegistry.FirstOrDefault(x => x.Value == forMapLayer.GetType()).Key
});
- clientChannel.SendPacket(new MapLayerUpdate() { Maplayers = maplayerdatas.ToArray() });
+ _clientChannel.SendPacket(new MapLayerUpdate() { Maplayers = maplayerdatas.ToArray() });
}
+
+ private void OnLevelFinaliseClient()
+ {
+ if (_capi is null) return;
+ RegisterClientHotkeys();
+ LoadMapLayersFromRegistry();
+ CreateMapLayerGenerationThread();
+ OpenMiniMap();
+ }
+
+ private void RegisterClientHotkeys()
+ {
+ if (_capi is null) return;
+ if (!mapAllowedClient()) return;
+
+ _capi.Input.RegisterHotKey("worldmaphud", Lang.Get("Show/Hide Minimap"), GlKeys.F6, HotkeyType.HelpAndOverlays);
+ _capi.Input.RegisterHotKey("minimapposition", Lang.Get("keycontrol-minimap-position"), GlKeys.F6, HotkeyType.HelpAndOverlays, false, true, false);
+ _capi.Input.RegisterHotKey("worldmapdialog", Lang.Get("Show World Map"), GlKeys.M, HotkeyType.HelpAndOverlays);
+ _capi.Input.SetHotKeyHandler("worldmaphud", OnHotKeyWorldMapHud);
+ _capi.Input.SetHotKeyHandler("minimapposition", OnHotKeyMinimapPosition);
+ _capi.Input.SetHotKeyHandler("worldmapdialog", OnHotKeyWorldMapDlg);
+
+ _capi.RegisterLinkProtocol("worldmap", OnWorldMapLinkClicked);
+ }
+
+ private void OpenMiniMap()
+ {
+ if (!(worldMapDlg is null) && worldMapDlg.IsOpened()) return;
+ if (!_capi.Settings.Bool["showMinimapHud"] && _capi.Settings.Bool.Exists("showMinimapHud")) return;
+ ToggleMap(EnumDialogType.HUD);
+ }
+
#endregion
#region Server Side
+
public override void StartServerSide(ICoreServerAPI sapi)
{
- sapi.Event.ServerRunPhase(EnumServerRunPhase.RunGame, OnLvlFinalize);;
+ sapi.Event.ServerRunPhase(EnumServerRunPhase.RunGame, OnLevelFinaliseServer);
sapi.Event.ServerRunPhase(EnumServerRunPhase.Shutdown, () => IsShuttingDown = true);
- serverChannel =
+ _serverChannel =
sapi.Network.RegisterChannel("worldmap")
.RegisterMessageType(typeof(MapLayerUpdate))
.RegisterMessageType(typeof(OnViewChangedPacket))
@@ -397,7 +401,7 @@ public override void StartServerSide(ICoreServerAPI sapi)
.SetMessageHandler(OnViewChangedServer)
.SetMessageHandler(OnMapLayerDataReceivedServer)
;
-
+
}
private void OnMapLayerDataReceivedServer(IServerPlayer fromPlayer, MapLayerUpdate msg)
@@ -438,7 +442,7 @@ private void OnViewChangedServer(IServerPlayer fromPlayer, OnViewChangedPacket n
public void SendMapDataToClient(MapLayer forMapLayer, IServerPlayer forPlayer, byte[] data)
{
- if (api.Side == EnumAppSide.Client) return;
+ if (_api.Side == EnumAppSide.Client) return;
if (forPlayer.ConnectionState != EnumClientState.Playing) return;
MapLayerData[] maplayerdatas = new MapLayerData[1] {
@@ -449,9 +453,15 @@ public void SendMapDataToClient(MapLayer forMapLayer, IServerPlayer forPlayer, b
}
};
- serverChannel.SendPacket(new MapLayerUpdate() { Maplayers = maplayerdatas }, forPlayer);
+ _serverChannel.SendPacket(new MapLayerUpdate() { Maplayers = maplayerdatas }, forPlayer);
+ }
+
+ private void OnLevelFinaliseServer()
+ {
+ LoadMapLayersFromRegistry();
+ CreateMapLayerGenerationThread();
}
#endregion
}
-}
+}
\ No newline at end of file