Compare commits
4 commits
78a2479d6c
...
fc2575833b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc2575833b | ||
|
|
347cfe6423 | ||
|
|
de1b72aae5 | ||
|
|
b027a79201 |
7 changed files with 454 additions and 94 deletions
|
|
@ -10,6 +10,7 @@
|
||||||
<RootNamespace>MosswartMassacre</RootNamespace>
|
<RootNamespace>MosswartMassacre</RootNamespace>
|
||||||
<AssemblyName>MosswartMassacre</AssemblyName>
|
<AssemblyName>MosswartMassacre</AssemblyName>
|
||||||
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||||
|
<LangVersion>8.0</LangVersion>
|
||||||
<FileAlignment>512</FileAlignment>
|
<FileAlignment>512</FileAlignment>
|
||||||
<Deterministic>true</Deterministic>
|
<Deterministic>true</Deterministic>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
@ -37,6 +38,9 @@
|
||||||
<HintPath>lib\Decal.Adapter.dll</HintPath>
|
<HintPath>lib\Decal.Adapter.dll</HintPath>
|
||||||
<EmbedInteropTypes>False</EmbedInteropTypes>
|
<EmbedInteropTypes>False</EmbedInteropTypes>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
<Reference Include="uTank2">
|
||||||
|
<HintPath>lib\utank2-i.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
<Reference Include="Decal.Interop.Core, Version=2.9.8.3, Culture=neutral, PublicKeyToken=481f17d392f1fb65, processorArchitecture=MSIL">
|
<Reference Include="Decal.Interop.Core, Version=2.9.8.3, Culture=neutral, PublicKeyToken=481f17d392f1fb65, processorArchitecture=MSIL">
|
||||||
<SpecificVersion>False</SpecificVersion>
|
<SpecificVersion>False</SpecificVersion>
|
||||||
<EmbedInteropTypes>False</EmbedInteropTypes>
|
<EmbedInteropTypes>False</EmbedInteropTypes>
|
||||||
|
|
@ -69,6 +73,8 @@
|
||||||
</Reference>
|
</Reference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Compile Include="vTank.cs" />
|
||||||
|
<Compile Include="VtankControl.cs" />
|
||||||
<Compile Include="Telemetry.cs" />
|
<Compile Include="Telemetry.cs" />
|
||||||
<Compile Include="Coordinates.cs" />
|
<Compile Include="Coordinates.cs" />
|
||||||
<Compile Include="Geometry.cs" />
|
<Compile Include="Geometry.cs" />
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ namespace MosswartMassacre
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
MyHost = Host;
|
MyHost = Host;
|
||||||
|
|
||||||
WriteToChat("Mosswart Massacre has started!");
|
WriteToChat("Mosswart Massacre has started!");
|
||||||
|
|
||||||
// Subscribe to chat message event
|
// Subscribe to chat message event
|
||||||
|
|
@ -52,6 +52,8 @@ namespace MosswartMassacre
|
||||||
|
|
||||||
// Enable TLS1.2
|
// Enable TLS1.2
|
||||||
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
|
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
|
||||||
|
//Enable vTank interface
|
||||||
|
vTank.Enable();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
@ -82,6 +84,9 @@ namespace MosswartMassacre
|
||||||
|
|
||||||
// Clean up the view
|
// Clean up the view
|
||||||
MainView.ViewDestroy();
|
MainView.ViewDestroy();
|
||||||
|
//Disable vtank interface
|
||||||
|
vTank.Disable();
|
||||||
|
|
||||||
MyHost = null;
|
MyHost = null;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|
@ -409,6 +414,7 @@ namespace MosswartMassacre
|
||||||
WriteToChat("/mm meta - Toggle rare meta state");
|
WriteToChat("/mm meta - Toggle rare meta state");
|
||||||
WriteToChat("/mm http - Local http-command server enable|disable");
|
WriteToChat("/mm http - Local http-command server enable|disable");
|
||||||
WriteToChat("/mm remotecommand - Listen to allegiance !do/!dot enable|disable");
|
WriteToChat("/mm remotecommand - Listen to allegiance !do/!dot enable|disable");
|
||||||
|
WriteToChat("/mm getmetastate - Gets the current metastate");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "report":
|
case "report":
|
||||||
|
|
@ -416,6 +422,10 @@ namespace MosswartMassacre
|
||||||
string reportMessage = $"Total Kills: {totalKills}, Kills per Hour: {killsPerHour:F2}, Elapsed Time: {elapsed:dd\\.hh\\:mm\\:ss}, Rares Found: {rareCount}";
|
string reportMessage = $"Total Kills: {totalKills}, Kills per Hour: {killsPerHour:F2}, Elapsed Time: {elapsed:dd\\.hh\\:mm\\:ss}, Rares Found: {rareCount}";
|
||||||
WriteToChat(reportMessage);
|
WriteToChat(reportMessage);
|
||||||
break;
|
break;
|
||||||
|
case "getmetastate":
|
||||||
|
string metaState = VtankControl.VtGetMetaState();
|
||||||
|
WriteToChat(metaState);
|
||||||
|
break;
|
||||||
|
|
||||||
case "loc":
|
case "loc":
|
||||||
Coordinates here = Coordinates.Me;
|
Coordinates here = Coordinates.Me;
|
||||||
|
|
|
||||||
|
|
@ -10,35 +10,59 @@ namespace MosswartMassacre
|
||||||
{
|
{
|
||||||
private static PluginSettings _instance;
|
private static PluginSettings _instance;
|
||||||
private static string _filePath;
|
private static string _filePath;
|
||||||
|
private static readonly object _sync = new object();
|
||||||
|
|
||||||
|
// backing fields
|
||||||
private bool _remoteCommandsEnabled = false;
|
private bool _remoteCommandsEnabled = false;
|
||||||
private bool _rareMetaEnabled = true;
|
private bool _rareMetaEnabled = true;
|
||||||
private bool _httpServerEnabled = false;
|
private bool _httpServerEnabled = false;
|
||||||
private string _charTag = "default";
|
|
||||||
private bool _telemetryEnabled = false;
|
private bool _telemetryEnabled = false;
|
||||||
public static PluginSettings Instance => _instance;
|
private string _charTag = "default";
|
||||||
|
|
||||||
|
public static PluginSettings Instance => _instance
|
||||||
|
?? throw new InvalidOperationException("PluginSettings not initialized");
|
||||||
|
|
||||||
public static void Initialize()
|
public static void Initialize()
|
||||||
{
|
{
|
||||||
|
// determine settings file path
|
||||||
string characterName = CoreManager.Current.CharacterFilter.Name;
|
string characterName = CoreManager.Current.CharacterFilter.Name;
|
||||||
string pluginFolder = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
|
string pluginFolder = Path.GetDirectoryName(
|
||||||
|
typeof(PluginSettings).Assembly.Location);
|
||||||
_filePath = Path.Combine(pluginFolder, $"{characterName}.yaml");
|
_filePath = Path.Combine(pluginFolder, $"{characterName}.yaml");
|
||||||
|
|
||||||
|
// build serializer/deserializer once
|
||||||
|
var builder = new DeserializerBuilder()
|
||||||
|
.WithNamingConvention(UnderscoredNamingConvention.Instance);
|
||||||
|
var deserializer = builder.Build();
|
||||||
|
|
||||||
|
PluginSettings loaded = null;
|
||||||
|
|
||||||
if (File.Exists(_filePath))
|
if (File.Exists(_filePath))
|
||||||
{
|
{
|
||||||
var deserializer = new DeserializerBuilder()
|
try
|
||||||
.WithNamingConvention(UnderscoredNamingConvention.Instance)
|
{
|
||||||
.Build();
|
string yaml = File.ReadAllText(_filePath);
|
||||||
|
loaded = deserializer.Deserialize<PluginSettings>(yaml);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
PluginCore.DispatchChatToBoxWithPluginIntercept(
|
||||||
|
$"[MosswartMassacre] Error reading settings, using defaults: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
string yaml = File.ReadAllText(_filePath);
|
if (loaded == null)
|
||||||
_instance = deserializer.Deserialize<PluginSettings>(yaml);
|
{
|
||||||
|
// either file didn't exist, was empty, or deserialized as null
|
||||||
|
_instance = new PluginSettings();
|
||||||
|
Save(); // write out default skeleton
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_instance = new PluginSettings();
|
_instance = loaded;
|
||||||
Save();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply settings to runtime state
|
// apply into runtime
|
||||||
PluginCore.RareMetaEnabled = _instance.RareMetaEnabled;
|
PluginCore.RareMetaEnabled = _instance.RareMetaEnabled;
|
||||||
PluginCore.RemoteCommandsEnabled = _instance.RemoteCommandsEnabled;
|
PluginCore.RemoteCommandsEnabled = _instance.RemoteCommandsEnabled;
|
||||||
PluginCore.HttpServerEnabled = _instance.HttpServerEnabled;
|
PluginCore.HttpServerEnabled = _instance.HttpServerEnabled;
|
||||||
|
|
@ -48,14 +72,29 @@ namespace MosswartMassacre
|
||||||
|
|
||||||
public static void Save()
|
public static void Save()
|
||||||
{
|
{
|
||||||
var serializer = new SerializerBuilder()
|
lock (_sync)
|
||||||
.WithNamingConvention(UnderscoredNamingConvention.Instance)
|
{
|
||||||
.Build();
|
try
|
||||||
|
{
|
||||||
|
var serializer = new SerializerBuilder()
|
||||||
|
.WithNamingConvention(UnderscoredNamingConvention.Instance)
|
||||||
|
.Build();
|
||||||
|
var yaml = serializer.Serialize(_instance);
|
||||||
|
|
||||||
string yaml = serializer.Serialize(_instance);
|
// atomic write: write to .tmp then replace
|
||||||
File.WriteAllText(_filePath, yaml);
|
var temp = _filePath + ".tmp";
|
||||||
|
File.WriteAllText(temp, yaml);
|
||||||
|
File.Replace(temp, _filePath, null);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
PluginCore.DispatchChatToBoxWithPluginIntercept(
|
||||||
|
$"[MosswartMassacre] Error saving settings: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// public properties
|
||||||
public bool RemoteCommandsEnabled
|
public bool RemoteCommandsEnabled
|
||||||
{
|
{
|
||||||
get => _remoteCommandsEnabled;
|
get => _remoteCommandsEnabled;
|
||||||
|
|
@ -73,15 +112,17 @@ namespace MosswartMassacre
|
||||||
get => _httpServerEnabled;
|
get => _httpServerEnabled;
|
||||||
set { _httpServerEnabled = value; Save(); }
|
set { _httpServerEnabled = value; Save(); }
|
||||||
}
|
}
|
||||||
public string CharTag
|
|
||||||
{
|
|
||||||
get => _charTag;
|
|
||||||
set { _charTag = value; Save(); }
|
|
||||||
}
|
|
||||||
public bool TelemetryEnabled
|
public bool TelemetryEnabled
|
||||||
{
|
{
|
||||||
get => _telemetryEnabled;
|
get => _telemetryEnabled;
|
||||||
set { _telemetryEnabled = value; Save(); }
|
set { _telemetryEnabled = value; Save(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string CharTag
|
||||||
|
{
|
||||||
|
get => _charTag;
|
||||||
|
set { _charTag = value; Save(); }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,110 +1,109 @@
|
||||||
using System;
|
// Telemetry.cs ───────────────────────────────────────────────────────────────
|
||||||
|
using System;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Timers;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Decal.Adapter;
|
using Decal.Adapter;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace MosswartMassacre
|
namespace MosswartMassacre
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Periodically sends gameplay telemetry to your FastAPI collector.
|
|
||||||
/// Toggle with: Telemetry.Start() / Telemetry.Stop()
|
|
||||||
/// </summary>
|
|
||||||
public static class Telemetry
|
public static class Telemetry
|
||||||
{
|
{
|
||||||
/* ============ CONFIG ============ */
|
/* ───────────── configuration ───────────── */
|
||||||
|
private const string Endpoint = "https://mosswart.snakedesert.se/position/"; // <- trailing slash!
|
||||||
private const string Endpoint = "https://mosswart.snakedesert.se/position";
|
private const string SharedSecret = "your_shared_secret"; // <- keep in sync
|
||||||
private const string SharedSecret = "your_shared_secret";
|
private const int IntervalSec = 5; // seconds between posts
|
||||||
private const int IntervalSec = 5; // send every 5 s
|
|
||||||
|
|
||||||
/* ============ internals ========== */
|
|
||||||
|
|
||||||
|
/* ───────────── runtime state ───────────── */
|
||||||
private static readonly HttpClient _http = new HttpClient();
|
private static readonly HttpClient _http = new HttpClient();
|
||||||
private static Timer _timer;
|
|
||||||
private static bool _enabled;
|
|
||||||
private static string _sessionId;
|
private static string _sessionId;
|
||||||
|
private static CancellationTokenSource _cts;
|
||||||
|
private static bool _enabled;
|
||||||
|
|
||||||
/* ============ public API ========= */
|
/* ───────────── public API ───────────── */
|
||||||
|
|
||||||
public static void Start()
|
public static void Start()
|
||||||
{
|
{
|
||||||
if (_enabled) return; // already on
|
if (_enabled) return;
|
||||||
|
|
||||||
_sessionId = $"{CoreManager.Current.CharacterFilter.Name}-{DateTime.UtcNow:yyyyMMdd-HHmmss}";
|
|
||||||
_timer = new Timer(IntervalSec * 1000);
|
|
||||||
_timer.Elapsed += (_, __) => SendSnapshot();
|
|
||||||
_timer.Start();
|
|
||||||
|
|
||||||
_enabled = true;
|
_enabled = true;
|
||||||
|
_sessionId = $"{CoreManager.Current.CharacterFilter.Name}-{DateTime.UtcNow:yyyyMMdd-HHmmss}";
|
||||||
|
_cts = new CancellationTokenSource();
|
||||||
|
|
||||||
PluginCore.WriteToChat("[Telemetry] HTTP streaming ENABLED");
|
PluginCore.WriteToChat("[Telemetry] HTTP streaming ENABLED");
|
||||||
PluginCore.WriteToChat("[Tel] timer every " + IntervalSec + " s");
|
|
||||||
|
_ = Task.Run(() => LoopAsync(_cts.Token)); // fire-and-forget
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Stop()
|
public static void Stop()
|
||||||
{
|
{
|
||||||
if (!_enabled) return;
|
if (!_enabled) return;
|
||||||
|
_cts.Cancel();
|
||||||
_enabled = false;
|
_enabled = false;
|
||||||
_timer?.Stop();
|
|
||||||
_timer?.Dispose();
|
|
||||||
_timer = null;
|
|
||||||
|
|
||||||
PluginCore.WriteToChat("[Telemetry] HTTP streaming DISABLED");
|
PluginCore.WriteToChat("[Telemetry] HTTP streaming DISABLED");
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ============ snapshot builder === */
|
/* ───────────── async loop ───────────── */
|
||||||
|
private static async Task LoopAsync(CancellationToken token)
|
||||||
private static async void SendSnapshot()
|
|
||||||
{
|
{
|
||||||
try
|
while (!token.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
var coords = Coordinates.Me;
|
try
|
||||||
|
|
||||||
var payload = new
|
|
||||||
{
|
{
|
||||||
character_name = CoreManager.Current.CharacterFilter.Name,
|
await SendSnapshotAsync(token);
|
||||||
char_tag = PluginCore.CharTag,
|
|
||||||
session_id = _sessionId,
|
|
||||||
timestamp = DateTime.UtcNow.ToString("o"),
|
|
||||||
|
|
||||||
ew = coords.EW,
|
|
||||||
ns = coords.NS,
|
|
||||||
z = coords.Z,
|
|
||||||
|
|
||||||
kills = PluginCore.totalKills,
|
|
||||||
deaths = 0,
|
|
||||||
rares_found = PluginCore.rareCount,
|
|
||||||
prismatic_taper_count = 0,
|
|
||||||
vt_state = "Unknown"
|
|
||||||
};
|
|
||||||
|
|
||||||
string json = JsonConvert.SerializeObject(payload);
|
|
||||||
|
|
||||||
var req = new HttpRequestMessage(HttpMethod.Post, Endpoint)
|
|
||||||
{
|
|
||||||
Content = new StringContent(json, Encoding.UTF8, "application/json")
|
|
||||||
};
|
|
||||||
req.Headers.Add("X-Plugin-Secret", SharedSecret);
|
|
||||||
|
|
||||||
/* ---------- NEW: wait for response & print result ---------- */
|
|
||||||
var resp = await _http.SendAsync(req);
|
|
||||||
if (resp.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
PluginCore.WriteToChat($"[Tel] ✓ {resp.StatusCode}");
|
|
||||||
}
|
}
|
||||||
else
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"[Tel] ✗ {resp.StatusCode} ({await resp.Content.ReadAsStringAsync()})");
|
PluginCore.WriteToChat($"[Telemetry] send failed: {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch (Exception ex)
|
try
|
||||||
{
|
{
|
||||||
var inner = ex.InnerException?.Message ?? "no inner msg";
|
await Task.Delay(TimeSpan.FromSeconds(IntervalSec), token);
|
||||||
PluginCore.WriteToChat($"[Tel] FAILED — {ex.GetType().Name}: {ex.Message} ⇢ {inner}");
|
}
|
||||||
|
catch (TaskCanceledException) { } // expected on Stop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ───────────── single POST ───────────── */
|
||||||
|
private static async Task SendSnapshotAsync(CancellationToken token)
|
||||||
|
{
|
||||||
|
var coords = Coordinates.Me;
|
||||||
|
|
||||||
|
var payload = new
|
||||||
|
{
|
||||||
|
character_name = CoreManager.Current.CharacterFilter.Name,
|
||||||
|
char_tag = PluginCore.CharTag,
|
||||||
|
session_id = _sessionId,
|
||||||
|
timestamp = DateTime.UtcNow.ToString("o"),
|
||||||
|
|
||||||
|
ew = coords.EW,
|
||||||
|
ns = coords.NS,
|
||||||
|
z = coords.Z,
|
||||||
|
|
||||||
|
kills = PluginCore.totalKills,
|
||||||
|
onlinetime = (DateTime.Now - PluginCore.statsStartTime).ToString(@"dd\.hh\:mm\:ss"),
|
||||||
|
kills_per_hour = PluginCore.killsPerHour.ToString("F0"),
|
||||||
|
deaths = 0,
|
||||||
|
rares_found = PluginCore.rareCount,
|
||||||
|
prismatic_taper_count = 0,
|
||||||
|
vt_state = VtankControl.VtGetMetaState(),
|
||||||
|
};
|
||||||
|
|
||||||
|
string json = JsonConvert.SerializeObject(payload);
|
||||||
|
var req = new HttpRequestMessage(HttpMethod.Post, Endpoint)
|
||||||
|
{
|
||||||
|
Content = new StringContent(json, Encoding.UTF8, "application/json")
|
||||||
|
};
|
||||||
|
req.Headers.Add("X-Plugin-Secret", SharedSecret);
|
||||||
|
|
||||||
|
using var resp = await _http.SendAsync(req, token);
|
||||||
|
|
||||||
|
if (!resp.IsSuccessStatusCode) // stay quiet on success
|
||||||
|
{
|
||||||
|
PluginCore.WriteToChat($"[Telemetry] server replied {resp.StatusCode}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
108
MosswartMassacre/VtankControl.cs
Normal file
108
MosswartMassacre/VtankControl.cs
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace MosswartMassacre
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides helper methods to control VTank from within your plugin.
|
||||||
|
/// </summary>
|
||||||
|
public static class VtankControl
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a chat command to VTank to switch its current meta-state.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="state">
|
||||||
|
/// The name of the VTank meta-state to activate.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>Always returns 1 on sending the command.</returns>
|
||||||
|
public static double VtSetMetaState(string state)
|
||||||
|
{
|
||||||
|
// Dispatch a local chat command that VTank will interpret.
|
||||||
|
PluginCore.Decal_DispatchOnChatCommand($"/vt setmetastate {state}");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queries VTank for its currently active meta-state.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// The name of the current meta-state, or empty string if VTank isn’t initialized.
|
||||||
|
/// </returns>
|
||||||
|
public static string VtGetMetaState()
|
||||||
|
{
|
||||||
|
// Instance.CurrentMetaState is typed as object, so cast it:
|
||||||
|
return (vTank.Instance.CurrentMetaState as string) ?? string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to set a VTank configuration value by name.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="setting">
|
||||||
|
/// The VTank setting key (e.g. “EnableCombat”, “RingDistance”).
|
||||||
|
/// </param>
|
||||||
|
/// <param name="value">
|
||||||
|
/// The string or numeric value to assign. Numeric strings will be parsed.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// 1 if the setting was applied or possibly applied; 0 on known failure.
|
||||||
|
/// </returns>
|
||||||
|
public static double VtSetSetting(string setting, string value)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var settingType = vTank.Instance.GetSettingType(setting);
|
||||||
|
|
||||||
|
if (settingType == typeof(string))
|
||||||
|
{
|
||||||
|
vTank.Instance.SetSetting(setting, value);
|
||||||
|
}
|
||||||
|
else if (double.TryParse(value, out double number))
|
||||||
|
{
|
||||||
|
if (settingType == typeof(bool))
|
||||||
|
vTank.Instance.SetSetting(setting, number == 1);
|
||||||
|
else if (settingType == typeof(double))
|
||||||
|
vTank.Instance.SetSetting(setting, number);
|
||||||
|
else if (settingType == typeof(int))
|
||||||
|
vTank.Instance.SetSetting(setting, Convert.ToInt32(number));
|
||||||
|
else if (settingType == typeof(float))
|
||||||
|
vTank.Instance.SetSetting(setting, Convert.ToSingle(number));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Value wasn’t parseable—report failure
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Swallow any errors and signal failure
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads back a VTank configuration value as a string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="setting">The name of the setting to read.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// The raw string form of the setting, or empty string if undefined.
|
||||||
|
/// </returns>
|
||||||
|
public static string VtGetSetting(string setting)
|
||||||
|
{
|
||||||
|
var val = vTank.Instance.GetSetting(setting);
|
||||||
|
return (val as string) ?? string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks whether the VTank macro engine is currently enabled.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// <c>true</c> if macros are active; otherwise <c>false</c>.
|
||||||
|
/// </returns>
|
||||||
|
public static bool VtMacroEnabled()
|
||||||
|
{
|
||||||
|
return vTank.Instance.MacroEnabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
116
MosswartMassacre/vTank.cs
Normal file
116
MosswartMassacre/vTank.cs
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using uTank2;
|
||||||
|
using static uTank2.PluginCore;
|
||||||
|
|
||||||
|
namespace MosswartMassacre
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Helper class for working with the VTank plugin
|
||||||
|
/// </summary>
|
||||||
|
public static unsafe class vTank
|
||||||
|
{
|
||||||
|
internal static IList ChatQueue = null;
|
||||||
|
internal static Type ChatType;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The TrustedRelay interface for VTank control
|
||||||
|
/// </summary>
|
||||||
|
public static cExternalInterfaceTrustedRelay Instance { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Current VTank action locks. Key is lock type, Value is when the lock is set to expire.
|
||||||
|
/// </summary>
|
||||||
|
public static Dictionary<uTank2.ActionLockType, DateTime> locks = new Dictionary<uTank2.ActionLockType, DateTime>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enables VTank helper functionality
|
||||||
|
/// </summary>
|
||||||
|
public static void Enable()
|
||||||
|
{
|
||||||
|
foreach (uTank2.ActionLockType ty in Enum.GetValues(typeof(uTank2.ActionLockType)))
|
||||||
|
locks[ty] = DateTime.MinValue;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ConstructorInfo ctor = typeof(cExternalInterfaceTrustedRelay)
|
||||||
|
.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic)[0];
|
||||||
|
Instance = (cExternalInterfaceTrustedRelay)ctor.Invoke(new object[] { eExternalsPermissionLevel.None });
|
||||||
|
|
||||||
|
FieldInfo fieldInfo = Instance.GetType()
|
||||||
|
.GetField("a", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||||
|
fieldInfo.SetValue(Instance, 15);
|
||||||
|
|
||||||
|
Type vTankChatHandler = typeof(uTank2.PluginCore).Assembly.GetType("a7");
|
||||||
|
FieldInfo vTankChatList = vTankChatHandler
|
||||||
|
.GetField("a", BindingFlags.NonPublic | BindingFlags.Static);
|
||||||
|
ChatType = vTankChatHandler.GetNestedType("a");
|
||||||
|
ChatQueue = (IList)(vTankChatList.GetValue(null));
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Disable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disables VTank helper functionality
|
||||||
|
/// </summary>
|
||||||
|
public static void Disable()
|
||||||
|
{
|
||||||
|
ChatType = null;
|
||||||
|
ChatQueue = null;
|
||||||
|
Instance = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Lock VTank from performing actions. Use Decision_UnLock to cancel.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="actionLockType">the type of action to put a lock on</param>
|
||||||
|
/// <param name="timeSpan">time to lock vtank for</param>
|
||||||
|
public static void Decision_Lock(uTank2.ActionLockType actionLockType, TimeSpan timeSpan)
|
||||||
|
{
|
||||||
|
Instance?.Decision_Lock(actionLockType, timeSpan);
|
||||||
|
DateTime newExp = DateTime.UtcNow + timeSpan;
|
||||||
|
if (locks[actionLockType] < newExp) locks[actionLockType] = newExp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cancel a VTank lock
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="actionLockType">the type of action to unlock</param>
|
||||||
|
public static void Decision_UnLock(uTank2.ActionLockType actionLockType)
|
||||||
|
{
|
||||||
|
Instance?.Decision_UnLock(actionLockType);
|
||||||
|
locks[actionLockType] = DateTime.MinValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Tell(string message, int color = 0, int target = 0)
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a chat message to VTank so that it will be capturable by metas.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">message to send</param>
|
||||||
|
/// <param name="color">color of the chat text</param>
|
||||||
|
/// <param name="target">chat window target</param>
|
||||||
|
public static void Tell(string message, int color = 0, int target = 0)
|
||||||
|
{
|
||||||
|
if (ChatQueue != null)
|
||||||
|
{
|
||||||
|
object newA = Activator.CreateInstance(ChatType);
|
||||||
|
ChatType.GetField("a").SetValue(newA, message); // message
|
||||||
|
ChatType.GetField("b").SetValue(newA, color); // color
|
||||||
|
ChatType.GetField("c").SetValue(newA, target); // target
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ChatQueue.Add(newA);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
80
README.md
Normal file
80
README.md
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
# Mossy Plugins
|
||||||
|
|
||||||
|
A collection of DECAL plugins for Asheron's Call, providing utility overlays and automation features.
|
||||||
|
|
||||||
|
## Contents
|
||||||
|
- `mossy.sln`: Visual Studio solution containing both projects.
|
||||||
|
- `GearCycler/`: Simple plugin with a UI button to cycle gear (placeholder behavior).
|
||||||
|
- `MosswartMassacre/`: Advanced plugin tracking monster kills, rare discoveries, and offering HTTP/telemetry features.
|
||||||
|
- `packages/`: Vendored NuGet packages (Newtonsoft.Json, YamlDotNet).
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
- Windows with .NET Framework 4.8
|
||||||
|
- Visual Studio 2017+ (MSBuild Tools 15.0) or equivalent MSBuild environment
|
||||||
|
- DECAL Adapter installed for Asheron's Call
|
||||||
|
- VirindiViewService (included in each project's `lib/` folder)
|
||||||
|
|
||||||
|
## Setup & Build
|
||||||
|
1. Clone this repository.
|
||||||
|
2. Ensure the DECAL and Virindi DLLs are present under `MosswartMassacre/lib/` and referenced by each project.
|
||||||
|
3. Restore NuGet packages if needed (`nuget restore mossy.sln`).
|
||||||
|
4. Open `mossy.sln` in Visual Studio and build the solution.
|
||||||
|
5. The output DLLs will be in each project’s `bin/Debug/` or `bin/Release/` folder.
|
||||||
|
6. Deploy the plugin DLLs (and any required XML or YAML files) to your DECAL plugin directory.
|
||||||
|
|
||||||
|
## GearCycler
|
||||||
|
A minimal plugin demonstrating a VirindiViewService-based UI.
|
||||||
|
- UI layout: `GearCycler/ViewXML/mainView.xml`.
|
||||||
|
- Core logic in `GearCycler/GearCore.cs`.
|
||||||
|
- On button click, it logs a chat message; extend the `btnCycle.Hit` handler to add gear-cycling logic.
|
||||||
|
|
||||||
|
## MosswartMassacre
|
||||||
|
Tracks monster kills and rare drops, with multiple utility features.
|
||||||
|
|
||||||
|
### Features
|
||||||
|
- **Kill Tracking**: Counts total kills and computes rates (kills/5 min, kills/hour).
|
||||||
|
- **Rare Discoveries**: Increments rare count and can automatically set rare meta state.
|
||||||
|
- **UI Overlay**: Displays stats and provides buttons to reset stats or toggle rare meta.
|
||||||
|
- **Command Interface** (`/mm` commands):
|
||||||
|
- `/mm help` : Show available commands.
|
||||||
|
- `/mm report` : Display current stats in chat.
|
||||||
|
- `/mm loc` : Show current map coordinates.
|
||||||
|
- `/mm reset` : Reset kill counters and timers.
|
||||||
|
- `/mm meta` : Toggle automatic rare meta state.
|
||||||
|
- `/mm http <enable|disable>` : Start/stop local HTTP command server (port 8085).
|
||||||
|
- `/mm remotecommands <enable|disable>` : Listen for remote commands from your allegiance chat.
|
||||||
|
- `/mm telemetry <enable|disable>` : Enable/disable periodic telemetry streaming.
|
||||||
|
|
||||||
|
### HTTP Command Server
|
||||||
|
- Listens on `http://localhost:8085/`.
|
||||||
|
- Accepts POST data: `target=<player>&command=<text>`, then sends a /tell and executes the command.
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
- Per-character YAML config stored at `<PluginDir>/<CharacterName>.yaml`.
|
||||||
|
- Settings include:
|
||||||
|
- `remote_commands_enabled`
|
||||||
|
- `rare_meta_enabled`
|
||||||
|
- `http_server_enabled`
|
||||||
|
- `telemetry_enabled`
|
||||||
|
- `char_tag`
|
||||||
|
- Config is auto-generated on first run; modify it or use UI/commands to update.
|
||||||
|
|
||||||
|
### Telemetry
|
||||||
|
- Periodically posts JSON snapshots of position and stats to a configurable endpoint.
|
||||||
|
- Configure `Endpoint`, `SharedSecret`, and `IntervalSec` in `Telemetry.cs`.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
- Decal.Adapter (v2.9.8.3)
|
||||||
|
- Decal.Interop.Core & Decal.Interop.Inject
|
||||||
|
- VirindiViewService
|
||||||
|
- Newtonsoft.Json (v13.0.3)
|
||||||
|
- YamlDotNet (v16.3.0)
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
1. Fork the repository.
|
||||||
|
2. Create a feature branch.
|
||||||
|
3. Commit your changes and ensure the solution builds.
|
||||||
|
4. Submit a pull request with a description of your changes.
|
||||||
|
|
||||||
|
--
|
||||||
|
_This README provides a high-level overview to get up and running quickly._
|
||||||
Loading…
Add table
Add a link
Reference in a new issue