Skip to content

Commit

Permalink
boneworks 100% status livesplit component
Browse files Browse the repository at this point in the history
  • Loading branch information
jakzo committed Jul 22, 2024
1 parent 34aeb29 commit 677f085
Show file tree
Hide file tree
Showing 22 changed files with 758 additions and 229 deletions.
1 change: 1 addition & 0 deletions changesets/Boneworks_SpeedrunTools_Minor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added support for sending data to the Boneworks 100% run status LiveSplit component.
1 change: 1 addition & 0 deletions changesets/LiveSplit_BoneworksHundredStatus_Major.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
First release.
46 changes: 39 additions & 7 deletions common/Boneworks/HundredPercentState.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,45 @@
using System.Collections.Generic;
using System.Linq;

namespace Sst.Common.Boneworks {
public class HundredPercentState {
public const string NAMED_PIPE = "BoneworksHundredPercent";
public const string TYPE_AMMO_LIGHT = "ammo_light";
public const string TYPE_AMMO_MEDIUM = "ammo_medium";
public const string TYPE_ITEM = "item";

public Dictionary<string, RngState> rngUnlocks =
new[] {
("Baseball", "81207815-e447-430b-9ea6-6d8c35842fef", 0.1f),
("Golf Club", "290780a8-4f88-451a-88a5-1599f8e7e89f", 0.02f),
("Baton", "53e4ff47-b0f4-426c-956d-ed391ca6f5f7", 0.1f),
}
.ToDictionary(def => def.Item2, def => new RngState() {
name = def.Item1,
attempts = 0,
prevAttemptChance = def.Item3,
probabilityNotDroppedYet = 1f,
hasDropped = false,
});
public int unlockLevelCount;
public int unlockLevelMax;
public int ammoLevelCount;
public int ammoLevelMax;
public Collectible[] justCollected;
public Collectible[] levelCollectibles;

public class Collectible {
public string Type;
public string Uuid;
public string DisplayName;
}

public int unlockRngCount;
public int unlockRngMax;
public int unlockNormalCount;
public int unlockNormalMax;
public int unlockNormalLevel;
public int levelAmmoCount;
public int levelAmmoMax;
public class RngState {
public string name;
public int attempts;
public float prevAttemptChance;
public float probabilityNotDroppedYet;
public bool hasDropped;
}
}
}
7 changes: 5 additions & 2 deletions common/LiveSplit/Logger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
namespace Sst.Common.LiveSplit {
static class Log {
public static string LOG_FILE =
Environment.GetEnvironmentVariable("SST_LIVESPLIT_LOG_PATH");
Environment.GetEnvironmentVariable("SST_LIVESPLIT_LOG_PATH") ??
$"{BuildInfo.NAME}.log";

public static void Initialize() {
if (LOG_FILE == null)
Expand All @@ -18,9 +19,11 @@ public static void Initialize() {
public static void Error(string message) { LogImpl("ERROR", message); }

private static void LogImpl(string prefix, string message) {
var text = $"{prefix} [{BuildInfo.NAME}] {message}\n";
Console.Write(text);
if (LOG_FILE == null)
return;
File.AppendAllText(LOG_FILE, $"{prefix} [{BuildInfo.NAME}] {message}\n");
File.AppendAllText(LOG_FILE, text);
}
}
}
158 changes: 127 additions & 31 deletions common/Utilities/IpcServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
using System.Collections.Generic;
using System.IO.Pipes;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace Sst.Common.Ipc {
public abstract class Logger {
Expand All @@ -14,7 +14,7 @@ public abstract class Logger {
public class Server : IDisposable {
public event Action<NamedPipeServerStream> OnClientConnected;
public event Action<NamedPipeServerStream> OnClientDisconnected;
// public event Action<string> OnMessageReceived;
public event Action<string> OnMessageReceived;

public string Name;

Expand All @@ -41,59 +41,155 @@ public void Dispose() {
}

public void Send(string message) {
foreach (var stream in _streams.ToArray())
SendToStream(stream, message);
foreach (var stream in _streams.ToArray()) {
try {
SendToStream(stream, message);
} catch (Exception ex) {
_logger.Error("Error sending IPC message:");
_logger.Error(ex.ToString());
DisposeStream(stream);
}
}
}

public static void SendToStream(NamedPipeServerStream stream,
string message) {
if (!stream.IsConnected)
return;

var bytes = Encoding.UTF8.GetBytes(message);
stream.Write(bytes, 0, bytes.Length);
}

private void StartNewPipeServerThread() {
new System.Threading.Thread(StartNewPipeServer).Start();
new Thread(StartNewPipeServer).Start();
}

private void StartNewPipeServer() {
try {
var stream = new NamedPipeServerStream(Name, PipeDirection.InOut,
MAX_NUMBER_OF_SERVER_INSTANCES,
PipeTransmissionMode.Message);
var stream = new NamedPipeServerStream(
Name, PipeDirection.InOut, MAX_NUMBER_OF_SERVER_INSTANCES,
PipeTransmissionMode.Message, PipeOptions.None, BUFFER_SIZE,
BUFFER_SIZE);
_streams.Add(stream);
stream.WaitForConnection();
_logger.Debug("Client connected");
if (_isDisposed)
return;
StartNewPipeServerThread();
SafeInvoke(() => OnClientConnected?.Invoke(stream));

// TODO: The stream.Read() call blocks writes
// var buffer = new byte[BUFFER_SIZE];
// StringBuilder sb = null;
// while (true) {
// if (sb == null)
// sb = new StringBuilder();
// var numBytes = stream.Read(buffer, 0, buffer.Length);
// if (_isDisposed)
// return;
// if (numBytes <= 0) {
// DisposeStream(stream);
// return;
// }

// sb.Append(Encoding.UTF8.GetString(buffer, 0, numBytes));

// if (stream.IsMessageComplete) {
// var message = sb.ToString().TrimEnd('\0');
// SafeInvoke(() => OnMessageReceived?.Invoke(message));
// sb = null;
// }
// }
// TODO: stream.Read() blocks writes and many named pipe features are
// missing from Mono
// Task.Run(() => ReadFromPipeConnection(stream));
// ReadFromPipeConnection2(stream);
// Task.Run(() => PollPipeConnection(stream));
} catch (Exception ex) {
_logger.Error($"Pipe server failed: {ex}");
} finally {
if (!_isDisposed)
StartNewPipeServerThread();
}
}

private void ReadFromPipeConnection(NamedPipeServerStream stream) {
var buffer = new byte[BUFFER_SIZE];
StringBuilder sb = null;
while (true) {
if (sb == null)
sb = new StringBuilder();
var numBytes = stream.Read(buffer, 0, buffer.Length);
if (_isDisposed)
return;
if (numBytes <= 0) {
DisposeStream(stream);
return;
}

sb.Append(Encoding.UTF8.GetString(buffer, 0, numBytes));

if (stream.IsMessageComplete) {
var message = sb.ToString().TrimEnd('\0');
SafeInvoke(() => OnMessageReceived?.Invoke(message));
sb = null;
}
}
}

private void ReadFromPipeConnection2(NamedPipeServerStream stream) {
var buffer = new byte[BUFFER_SIZE];
var sb = new StringBuilder();

void ReadCallback(IAsyncResult ar) {
try {
int numBytes = stream.EndRead(ar);
if (_isDisposed)
return;
if (numBytes <= 0) {
DisposeStream(stream);
return;
}

sb.Append(Encoding.UTF8.GetString(buffer, 0, numBytes));

if (stream.IsMessageComplete) {
var message = sb.ToString().TrimEnd('\0');
SafeInvoke(() => OnMessageReceived?.Invoke(message));
sb.Clear();
}

stream.BeginRead(buffer, 0, buffer.Length, ReadCallback, null);
} catch (Exception ex) {
_logger.Error("Error while reading from pipe connection:");
_logger.Error(ex.ToString());
DisposeStream(stream);
}
}

try {
stream.BeginRead(buffer, 0, buffer.Length, ReadCallback, null);
} catch (Exception ex) {
_logger.Error("Failed to begin read operation:");
_logger.Error(ex.ToString());
DisposeStream(stream);
}
}

private void PollPipeConnection(NamedPipeServerStream stream) {
var buffer = new byte[BUFFER_SIZE];
var sb = new StringBuilder();

try {
while (true) {
if (_isDisposed)
return;

if (stream.IsConnected) {
while (stream.IsMessageComplete) {
int numBytes = stream.Read(buffer, 0, buffer.Length);
if (numBytes > 0) {
sb.Append(Encoding.UTF8.GetString(buffer, 0, numBytes));
} else {
DisposeStream(stream);
return;
}

if (stream.IsMessageComplete) {
var message = sb.ToString().TrimEnd('\0');
_logger.Debug("Finally got a message! " + message);
SafeInvoke(() => OnMessageReceived?.Invoke(message));
sb.Clear();
}
}
}

_logger.Debug("IPC waiting " + stream.IsConnected + " " +
stream.IsMessageComplete);
Thread.Sleep(1000);
}
} catch (Exception ex) {
_logger.Error("Error while reading from pipe:");
_logger.Error(ex.ToString());
DisposeStream(stream);
}
}

Expand Down
25 changes: 25 additions & 0 deletions common/Utilities/Resources.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;

namespace Sst.Utilities {
public static class Resources {
public static void ExtractResource(string resourceName, string dir) {
var assembly = Assembly.GetExecutingAssembly();
string resourcePath = assembly.GetManifestResourceNames().Single(
str => str.EndsWith(resourceName));
using (var stream = assembly.GetManifestResourceStream(resourcePath)) {
using (var archive = new ZipArchive(stream, ZipArchiveMode.Read)) {
foreach (var entry in archive.Entries) {
var entryStream = entry.Open();
using (var fileStream =
File.Create(Path.Combine(dir, entry.FullName))) {
entryStream.CopyTo(fileStream);
}
}
}
}
}
}
}
4 changes: 2 additions & 2 deletions common/Utilities/Unity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ public static Color GenerateColor(int i) =>
Color.HSVToRGB(i * 0.064f % 1f, 0.9f - i / 16 * 0.3f % 0.8f, 0.9f);

public static Shader FindShader(string name) =>
Resources.FindObjectsOfTypeAll<Shader>().First(shader => shader.name ==
name);
UnityEngine.Resources.FindObjectsOfTypeAll<Shader>().First(
shader => shader.name == name);
}
}
2 changes: 1 addition & 1 deletion projects/Boneworks/Randomizer/Project.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
</Target>

<PropertyGroup>
<CopyIntoGameAfterBuild>true</CopyIntoGameAfterBuild>
<!-- <CopyIntoGameAfterBuild>true</CopyIntoGameAfterBuild> -->

<ProjectGuid>{9746DCEC-6A9C-4E84-98E2-B141A702FDD9}</ProjectGuid>
</PropertyGroup>
Expand Down
12 changes: 10 additions & 2 deletions projects/Boneworks/SpeedrunTools/SpeedrunTools.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@
<Reference Include="Il2Cppmscorlib">
<HintPath>..\..\..\references\Boneworks\Il2Cppmscorlib.dll</HintPath>
</Reference>
<Reference Include="Il2CppSystem.Core">
<HintPath>..\..\..\references\Boneworks\Il2CppSystem.Core.dll</HintPath>
</Reference>
<Reference Include="SteamVR">
<HintPath>..\..\..\references\Boneworks\SteamVR.dll</HintPath>
</Reference>
Expand All @@ -79,27 +82,32 @@
<Reference Include="Unity.TextMeshPro">
<HintPath>..\..\..\references\Boneworks\TextMeshPro-1.0.55.2017.2.0b12.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json">
<HintPath>..\..\..\references\Boneworks\Newtonsoft.Json.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="AppVersion.cs" />
<Compile Include="src/**/*.cs" />
<Compile Include="Properties/*.cs" />
<Compile Include="../../../common/FlatBuffers/**/*.cs" />
<Compile Include="../../../common/Boneworks/DebugStats.cs" />
<Compile Include="../../../common/Boneworks/HundredPercentState.cs" />
<Compile Include="../../../common/Utilities/IpcServer.cs" />
<Compile Include="../../../common/Utilities/Dbg.cs" />
<Compile Include="../../../common/Utilities/Geometry.cs" />
<Compile Include="../../../common/Utilities/Metadata.cs" />
<Compile Include="../../../common/Utilities/Unity.cs" />
<Compile Include="../../../common/Utilities/Resources.cs" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="resources/*" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup Condition=" '$(OS)' == 'Windows_NT' ">
<!-- <PostBuildEvent>
<PostBuildEvent>
COPY "$(TargetPath)" "C:\Program Files (x86)\Steam\steamapps\common\BONEWORKS\BONEWORKS\Mods"
</PostBuildEvent> -->
</PostBuildEvent>
<!-- <PostBuildEvent>
COPY "$(TargetPath)" "C:\Program Files (x86)\Steam\steamapps\common\BONEWORKS\BONEWORKS\Mods"
"C:\Program Files (x86)\Steam\steamapps\common\BONEWORKS\BONEWORKS\BONEWORKS.exe"&amp; exit 0
Expand Down
Binary file not shown.
Loading

0 comments on commit 677f085

Please sign in to comment.