From 757943777afa896afc9f0e6753b67725f2f6a65c Mon Sep 17 00:00:00 2001 From: Kyle Paulsen Date: Tue, 13 Apr 2021 05:48:40 -0700 Subject: [PATCH] Adding pin feature 1.0 --- WebMap/MapDataServer.cs | 23 +++++++++++++ WebMap/WebMap.cs | 67 ++++++++++++++++++++++++++++++++++++- WebMap/web-src/index.js | 26 ++++++++++++++ WebMap/web-src/map.js | 8 +++++ WebMap/web-src/websocket.js | 20 +++++++++++ 5 files changed, 143 insertions(+), 1 deletion(-) diff --git a/WebMap/MapDataServer.cs b/WebMap/MapDataServer.cs index 5b0e7e5..3dbc3a4 100644 --- a/WebMap/MapDataServer.cs +++ b/WebMap/MapDataServer.cs @@ -1,4 +1,5 @@ using System; +using System.Text; using System.Collections.Generic; using System.IO; using System.Threading; @@ -39,6 +40,7 @@ public class MapDataServer { public byte[] mapImageData; public Texture2D fogTexture; public List players = new List(); + public List pins = new List(); static Dictionary contentTypes = new Dictionary() { { "html", "text/html" }, @@ -159,6 +161,15 @@ private bool ProcessSpecialRoutes(HttpRequestEventArgs e) { res.ContentLength64 = fogBytes.Length; res.Close(fogBytes, true); return true; + case "/pins": + res.Headers.Add(HttpResponseHeader.CacheControl, "no-cache"); + res.ContentType = "text/csv"; + res.StatusCode = 200; + var text = String.Join("\n", pins); + var textBytes = Encoding.UTF8.GetBytes(text); + res.ContentLength64 = textBytes.Length; + res.Close(textBytes, true); + return true; } return false; } @@ -176,5 +187,17 @@ public void ListenAsync() { public void BroadcastPing(long id, string name, Vector3 position) { webSocketHandler.Sessions.Broadcast($"ping\n{id}\n{name}\n{position.x},{position.z}"); } + + public void AddPin(string id, string pinId, string type, string name, Vector3 position, string pinText) { + pins.Add($"{id},{pinId},{type},{name},{position.x},{position.z},{pinText}"); + webSocketHandler.Sessions.Broadcast($"pin\n{id}\n{pinId}\n{type}\n{name}\n{position.x},{position.z}\n{pinText}"); + } + + public void RemovePin(int idx) { + var pin = pins[idx]; + var pinParts = pin.Split(','); + pins.RemoveAt(idx); + webSocketHandler.Sessions.Broadcast($"rmpin\n{pinParts[1]}"); + } } } diff --git a/WebMap/WebMap.cs b/WebMap/WebMap.cs index 063de1c..537e25a 100644 --- a/WebMap/WebMap.cs +++ b/WebMap/WebMap.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; +using System.Text.RegularExpressions; using System.IO; using System.Reflection; using BepInEx; @@ -22,6 +23,10 @@ public class WebMap : BaseUnityPlugin { static readonly float EXPLORE_RADIUS = 100f; static readonly float UPDATE_FOG_TEXTURE_INTERVAL = 1f; static readonly float SAVE_FOG_TEXTURE_INTERVAL = 30f; + static readonly int MAX_PINS_PER_USER = 50; + + static readonly DateTime unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); + static readonly HashSet ALLOWED_PINS = new HashSet { "dot", "fire", "mine", "house", "cave" }; static MapDataServer mapDataServer; static string worldDataPath; @@ -85,6 +90,14 @@ public void Awake() { InvokeRepeating("UpdateFogTexture", UPDATE_FOG_TEXTURE_INTERVAL, UPDATE_FOG_TEXTURE_INTERVAL); InvokeRepeating("SaveFogTexture", SAVE_FOG_TEXTURE_INTERVAL, SAVE_FOG_TEXTURE_INTERVAL); + + var mapPinsFile = Path.Combine(worldDataPath, "pins.csv"); + try { + var pinsLines = File.ReadAllLines(mapPinsFile); + mapDataServer.pins = new List(pinsLines); + } catch (Exception e) { + Debug.Log("Failed to read pins.csv from disk. " + e.Message); + } } public void UpdateFogTexture() { @@ -137,6 +150,15 @@ public void SaveFogTexture() { } } + public static void SavePins() { + var mapPinsFile = Path.Combine(worldDataPath, "pins.csv"); + try { + File.WriteAllLines(mapPinsFile, mapDataServer.pins); + } catch { + Debug.Log("FAILED TO WRITE PINS FILE!"); + } + } + [HarmonyPatch(typeof (ZoneSystem), "Start")] private class ZoneSystemPatch { @@ -313,15 +335,58 @@ static void Postfix(List ___m_peers) { [HarmonyPatch(typeof (ZRoutedRpc), "HandleRoutedRPC")] private class ZRoutedRpcPatch { static void Prefix(RoutedRPCData data) { + ZNetPeer peer = ZNet.instance.GetPeer(data.m_senderPeerID); + var steamid = ""; + try { + steamid = peer.m_rpc.GetSocket().GetHostName(); + } catch {} + if (data?.m_methodHash == sayMethodHash) { try { + var zdoData = ZDOMan.instance.GetZDO(peer.m_characterID); + var pos = zdoData.GetPosition(); ZPackage package = new ZPackage(data.m_parameters.GetArray()); int messageType = package.ReadInt(); string userName = package.ReadString(); string message = package.ReadString(); message = (message == null ? "" : message).Trim(); - Debug.Log("SAY!!! " + messageType + " | " + userName + " | " + message); + if (message.StartsWith("/pin")) { + var messageParts = message.Split(' '); + var pinType = "dot"; + var startIdx = 1; + if (messageParts.Length > 1 && ALLOWED_PINS.Contains(messageParts[1])) { + pinType = messageParts[1]; + startIdx = 2; + } + var pinText = ""; + if (startIdx < messageParts.Length) { + pinText = String.Join(" ", messageParts, startIdx, messageParts.Length - startIdx); + } + if (pinText.Length > 20) { + pinText = pinText.Substring(0, 20); + } + var safePinsText = Regex.Replace(pinText, @"[^a-zA-Z0-9 ]", ""); + + var timestamp = DateTime.Now - unixEpoch; + var pinId = $"{timestamp.TotalMilliseconds}-{UnityEngine.Random.Range(1000, 9999)}"; + mapDataServer.AddPin(steamid, pinId, pinType, userName, pos, safePinsText); + + var usersPins = mapDataServer.pins.FindAll(pin => pin.StartsWith(steamid)); + var numOverflowPins = usersPins.Count - MAX_PINS_PER_USER; + for (var t = numOverflowPins; t > 0; t--) { + var pinIdx = mapDataServer.pins.FindIndex(pin => pin.StartsWith(steamid)); + mapDataServer.RemovePin(pinIdx); + } + SavePins(); + } else if (message.StartsWith("/undoPin")) { + var pinIdx = mapDataServer.pins.FindLastIndex(pin => pin.StartsWith(steamid)); + if (pinIdx > -1) { + mapDataServer.RemovePin(pinIdx); + SavePins(); + } + } + //Debug.Log("SAY!!! " + messageType + " | " + userName + " | " + message); } catch (Exception e) { Debug.Log("Failed processing RoutedRPCData" + e); } diff --git a/WebMap/web-src/index.js b/WebMap/web-src/index.js index 7610784..bb3a85c 100644 --- a/WebMap/web-src/index.js +++ b/WebMap/web-src/index.js @@ -58,6 +58,32 @@ const setup = async () => { }, 8000); }); + fetch('pins').then(res => res.text()).then(text => { + const lines = text.split('\n'); + lines.forEach(line => { + const lineParts = line.split(','); + const pin = { + id: lineParts[1], + uid: lineParts[0], + type: lineParts[2], + name: lineParts[3], + x: lineParts[4], + z: lineParts[5], + text: lineParts[6] + }; + map.addIcon(pin, false); + }); + map.updateIcons(); + }); + + websocket.addActionListener('pin', (pin) => { + map.addIcon(pin); + }); + + websocket.addActionListener('rmpin', (pinid) => { + map.removeIconById(pinid); + }); + window.addEventListener('resize', () => { map.update(); }); diff --git a/WebMap/web-src/map.js b/WebMap/web-src/map.js index e3f6777..b61411c 100644 --- a/WebMap/web-src/map.js +++ b/WebMap/web-src/map.js @@ -84,6 +84,13 @@ const removeIcon = (iconObj) => { } }; +const removeIconById = (iconId) => { + const iconToRemove = mapIcons.find(icon => icon.id === iconId); + if (iconToRemove) { + removeIcon(iconToRemove); + } +}; + const explore = (mapX, mapZ) => { const radius = exploreRadius / pixelSize; const x = mapX / pixelSize + coordOffset; @@ -190,6 +197,7 @@ export default { init, addIcon, removeIcon, + removeIconById, explore, update: redrawMap, updateIcons, diff --git a/WebMap/web-src/websocket.js b/WebMap/web-src/websocket.js index 60c7ee6..5cf1899 100644 --- a/WebMap/web-src/websocket.js +++ b/WebMap/web-src/websocket.js @@ -46,6 +46,26 @@ const actions = { actionListeners.ping.forEach(func => { func(ping); }); + }, + pin: (lines) => { + const xz = lines[4].split(',').map(parseFloat); + const pin = { + id: lines[1], + uid: lines[0], + type: lines[2], + name: lines[3], + x: xz[0], + z: xz[1], + text: lines[5] + }; + actionListeners.pin.forEach(func => { + func(pin); + }); + }, + rmpin: (lines) => { + actionListeners.rmpin.forEach(func => { + func(lines[0]); + }); } };