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>
|
||||
<AssemblyName>MosswartMassacre</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<Deterministic>true</Deterministic>
|
||||
</PropertyGroup>
|
||||
|
|
@ -37,6 +38,9 @@
|
|||
<HintPath>lib\Decal.Adapter.dll</HintPath>
|
||||
<EmbedInteropTypes>False</EmbedInteropTypes>
|
||||
</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">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<EmbedInteropTypes>False</EmbedInteropTypes>
|
||||
|
|
@ -69,6 +73,8 @@
|
|||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="vTank.cs" />
|
||||
<Compile Include="VtankControl.cs" />
|
||||
<Compile Include="Telemetry.cs" />
|
||||
<Compile Include="Coordinates.cs" />
|
||||
<Compile Include="Geometry.cs" />
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ namespace MosswartMassacre
|
|||
try
|
||||
{
|
||||
MyHost = Host;
|
||||
|
||||
|
||||
WriteToChat("Mosswart Massacre has started!");
|
||||
|
||||
// Subscribe to chat message event
|
||||
|
|
@ -52,6 +52,8 @@ namespace MosswartMassacre
|
|||
|
||||
// Enable TLS1.2
|
||||
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
|
||||
//Enable vTank interface
|
||||
vTank.Enable();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -82,6 +84,9 @@ namespace MosswartMassacre
|
|||
|
||||
// Clean up the view
|
||||
MainView.ViewDestroy();
|
||||
//Disable vtank interface
|
||||
vTank.Disable();
|
||||
|
||||
MyHost = null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -409,6 +414,7 @@ namespace MosswartMassacre
|
|||
WriteToChat("/mm meta - Toggle rare meta state");
|
||||
WriteToChat("/mm http - Local http-command server enable|disable");
|
||||
WriteToChat("/mm remotecommand - Listen to allegiance !do/!dot enable|disable");
|
||||
WriteToChat("/mm getmetastate - Gets the current metastate");
|
||||
break;
|
||||
|
||||
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}";
|
||||
WriteToChat(reportMessage);
|
||||
break;
|
||||
case "getmetastate":
|
||||
string metaState = VtankControl.VtGetMetaState();
|
||||
WriteToChat(metaState);
|
||||
break;
|
||||
|
||||
case "loc":
|
||||
Coordinates here = Coordinates.Me;
|
||||
|
|
|
|||
|
|
@ -10,35 +10,59 @@ namespace MosswartMassacre
|
|||
{
|
||||
private static PluginSettings _instance;
|
||||
private static string _filePath;
|
||||
private static readonly object _sync = new object();
|
||||
|
||||
// backing fields
|
||||
private bool _remoteCommandsEnabled = false;
|
||||
private bool _rareMetaEnabled = true;
|
||||
private bool _httpServerEnabled = false;
|
||||
private string _charTag = "default";
|
||||
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()
|
||||
{
|
||||
// determine settings file path
|
||||
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");
|
||||
|
||||
// build serializer/deserializer once
|
||||
var builder = new DeserializerBuilder()
|
||||
.WithNamingConvention(UnderscoredNamingConvention.Instance);
|
||||
var deserializer = builder.Build();
|
||||
|
||||
PluginSettings loaded = null;
|
||||
|
||||
if (File.Exists(_filePath))
|
||||
{
|
||||
var deserializer = new DeserializerBuilder()
|
||||
.WithNamingConvention(UnderscoredNamingConvention.Instance)
|
||||
.Build();
|
||||
try
|
||||
{
|
||||
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);
|
||||
_instance = deserializer.Deserialize<PluginSettings>(yaml);
|
||||
if (loaded == null)
|
||||
{
|
||||
// either file didn't exist, was empty, or deserialized as null
|
||||
_instance = new PluginSettings();
|
||||
Save(); // write out default skeleton
|
||||
}
|
||||
else
|
||||
{
|
||||
_instance = new PluginSettings();
|
||||
Save();
|
||||
_instance = loaded;
|
||||
}
|
||||
|
||||
// Apply settings to runtime state
|
||||
// apply into runtime
|
||||
PluginCore.RareMetaEnabled = _instance.RareMetaEnabled;
|
||||
PluginCore.RemoteCommandsEnabled = _instance.RemoteCommandsEnabled;
|
||||
PluginCore.HttpServerEnabled = _instance.HttpServerEnabled;
|
||||
|
|
@ -48,14 +72,29 @@ namespace MosswartMassacre
|
|||
|
||||
public static void Save()
|
||||
{
|
||||
var serializer = new SerializerBuilder()
|
||||
.WithNamingConvention(UnderscoredNamingConvention.Instance)
|
||||
.Build();
|
||||
lock (_sync)
|
||||
{
|
||||
try
|
||||
{
|
||||
var serializer = new SerializerBuilder()
|
||||
.WithNamingConvention(UnderscoredNamingConvention.Instance)
|
||||
.Build();
|
||||
var yaml = serializer.Serialize(_instance);
|
||||
|
||||
string yaml = serializer.Serialize(_instance);
|
||||
File.WriteAllText(_filePath, yaml);
|
||||
// atomic write: write to .tmp then replace
|
||||
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
|
||||
{
|
||||
get => _remoteCommandsEnabled;
|
||||
|
|
@ -73,15 +112,17 @@ namespace MosswartMassacre
|
|||
get => _httpServerEnabled;
|
||||
set { _httpServerEnabled = value; Save(); }
|
||||
}
|
||||
public string CharTag
|
||||
{
|
||||
get => _charTag;
|
||||
set { _charTag = value; Save(); }
|
||||
}
|
||||
|
||||
public bool TelemetryEnabled
|
||||
{
|
||||
get => _telemetryEnabled;
|
||||
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.Text;
|
||||
using System.Timers;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Decal.Adapter;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace MosswartMassacre
|
||||
{
|
||||
/// <summary>
|
||||
/// Periodically sends gameplay telemetry to your FastAPI collector.
|
||||
/// Toggle with: Telemetry.Start() / Telemetry.Stop()
|
||||
/// </summary>
|
||||
public static class Telemetry
|
||||
{
|
||||
/* ============ CONFIG ============ */
|
||||
|
||||
private const string Endpoint = "https://mosswart.snakedesert.se/position";
|
||||
private const string SharedSecret = "your_shared_secret";
|
||||
private const int IntervalSec = 5; // send every 5 s
|
||||
|
||||
/* ============ internals ========== */
|
||||
/* ───────────── configuration ───────────── */
|
||||
private const string Endpoint = "https://mosswart.snakedesert.se/position/"; // <- trailing slash!
|
||||
private const string SharedSecret = "your_shared_secret"; // <- keep in sync
|
||||
private const int IntervalSec = 5; // seconds between posts
|
||||
|
||||
/* ───────────── runtime state ───────────── */
|
||||
private static readonly HttpClient _http = new HttpClient();
|
||||
private static Timer _timer;
|
||||
private static bool _enabled;
|
||||
private static string _sessionId;
|
||||
private static CancellationTokenSource _cts;
|
||||
private static bool _enabled;
|
||||
|
||||
/* ============ public API ========= */
|
||||
|
||||
/* ───────────── public API ───────────── */
|
||||
public static void Start()
|
||||
{
|
||||
if (_enabled) return; // already on
|
||||
|
||||
_sessionId = $"{CoreManager.Current.CharacterFilter.Name}-{DateTime.UtcNow:yyyyMMdd-HHmmss}";
|
||||
_timer = new Timer(IntervalSec * 1000);
|
||||
_timer.Elapsed += (_, __) => SendSnapshot();
|
||||
_timer.Start();
|
||||
if (_enabled) return;
|
||||
|
||||
_enabled = true;
|
||||
_sessionId = $"{CoreManager.Current.CharacterFilter.Name}-{DateTime.UtcNow:yyyyMMdd-HHmmss}";
|
||||
_cts = new CancellationTokenSource();
|
||||
|
||||
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()
|
||||
{
|
||||
if (!_enabled) return;
|
||||
|
||||
_cts.Cancel();
|
||||
_enabled = false;
|
||||
_timer?.Stop();
|
||||
_timer?.Dispose();
|
||||
_timer = null;
|
||||
|
||||
PluginCore.WriteToChat("[Telemetry] HTTP streaming DISABLED");
|
||||
}
|
||||
|
||||
/* ============ snapshot builder === */
|
||||
|
||||
private static async void SendSnapshot()
|
||||
/* ───────────── async loop ───────────── */
|
||||
private static async Task LoopAsync(CancellationToken token)
|
||||
{
|
||||
try
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
var coords = Coordinates.Me;
|
||||
|
||||
var payload = new
|
||||
try
|
||||
{
|
||||
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,
|
||||
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}");
|
||||
await SendSnapshotAsync(token);
|
||||
}
|
||||
else
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginCore.WriteToChat($"[Tel] ✗ {resp.StatusCode} ({await resp.Content.ReadAsStringAsync()})");
|
||||
PluginCore.WriteToChat($"[Telemetry] send failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var inner = ex.InnerException?.Message ?? "no inner msg";
|
||||
PluginCore.WriteToChat($"[Tel] FAILED — {ex.GetType().Name}: {ex.Message} ⇢ {inner}");
|
||||
|
||||
try
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(IntervalSec), token);
|
||||
}
|
||||
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