Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
cadon committed Dec 17, 2023
2 parents ff40fb2 + 259d064 commit 4371ff1
Show file tree
Hide file tree
Showing 62 changed files with 2,155 additions and 770 deletions.
7 changes: 4 additions & 3 deletions ARKBreedingStats/ARKBreedingStats.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
<Reference Include="System.Core" />
<Reference Include="System.IO.Compression" />
<Reference Include="System.IO.Compression.FileSystem" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.Security" />
<Reference Include="System.Speech" />
Expand All @@ -82,6 +83,7 @@
<DependentUpon>AboutBox1.cs</DependentUpon>
</Compile>
<Compile Include="Ark.cs" />
<Compile Include="AsbServer\Connection.cs" />
<Compile Include="BreedingPlanning\Score.cs" />
<Compile Include="BreedingPlanning\BreedingScore.cs" />
<Compile Include="importExportGun\ExportGunCreatureFile.cs" />
Expand Down Expand Up @@ -245,7 +247,6 @@
<DependentUpon>StatsMultiplierTesting.cs</DependentUpon>
</Compile>
<Compile Include="mods\Mod.cs" />
<Compile Include="species\SpeciesListBoxEntry.cs" />
<Compile Include="testCases\ExtractionTestCase.cs" />
<Compile Include="ocr\ArkOcr.cs" />
<Compile Include="ARKOverlay.cs">
Expand Down Expand Up @@ -963,15 +964,15 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="FluentFTP">
<Version>44.0.1</Version>
<Version>48.0.3</Version>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers">
<Version>3.3.4</Version>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Newtonsoft.Json">
<Version>13.0.2</Version>
<Version>13.0.3</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>
Expand Down
11 changes: 9 additions & 2 deletions ARKBreedingStats/ARKOverlay.cs
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,9 @@ private void SetTimerAndNotesText()
{
var sb = new StringBuilder();

var timerListChanged = false;
if (timers?.Any() ?? false)
{
var timerListChanged = false;
foreach (TimerListEntry tle in timers)
{
var timeLeft = tle.time.Subtract(DateTime.Now);
Expand All @@ -216,6 +216,7 @@ private void SetTimerAndNotesText()
{
sb.AppendLine();
sb.AppendLine(Loc.S("Incubation"));
timerListChanged = false;
foreach (var it in IncubationTimers)
{
var timeLeft = it.incubationEnd.Subtract(DateTime.Now);
Expand All @@ -225,18 +226,22 @@ private void SetTimerAndNotesText()
if (!Properties.Settings.Default.KeepExpiredTimersInOverlay && secLeft < -20)
{
it.ShowInOverlay = false;
timerListChanged = true;
RemoveTimer(it);
continue;
}
sb.Append("incubated ");
}
sb.AppendLine($"{Utils.Duration(timeLeft)} : {(it.Mother?.Species ?? it.Father?.Species)?.DescriptiveName ?? "unknown species"}");
}
if (timerListChanged)
IncubationTimers = IncubationTimers.Where(it => it.ShowInOverlay).ToList();
}
if (CreatureTimers?.Any() ?? false)
{
sb.AppendLine();
sb.AppendLine(Loc.S("Maturation"));
timerListChanged = false;
foreach (var c in CreatureTimers)
{
var timeLeft = c.growingUntil?.Subtract(DateTime.Now);
Expand All @@ -246,14 +251,16 @@ private void SetTimerAndNotesText()
if (!Properties.Settings.Default.KeepExpiredTimersInOverlay && secLeft < -20)
{
c.ShowInOverlay = false;
RemoveTimer(c);
timerListChanged = true;
continue;
}

timeLeft = null;
}
sb.AppendLine($"{(timeLeft == null ? "grown" : Utils.Duration(timeLeft.Value))} : {c.name} ({c.Species.DescriptiveName})");
}
if (timerListChanged)
CreatureTimers = CreatureTimers.Where(c => c.ShowInOverlay).ToList();
}
sb.Append(_notes);
labelTimer.Text = sb.ToString();
Expand Down
3 changes: 3 additions & 0 deletions ARKBreedingStats/App.config
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,9 @@
<setting name="KeepMultipliersForNewLibrary" serializeAs="String">
<value>True</value>
</setting>
<setting name="ExportServerToken" serializeAs="String">
<value />
</setting>
</ARKBreedingStats.Properties.Settings>
</userSettings>
</configuration>
18 changes: 18 additions & 0 deletions ARKBreedingStats/Ark.cs
Original file line number Diff line number Diff line change
Expand Up @@ -188,5 +188,23 @@ public static class Stats
CraftingSpeedMultiplier,
Torpidity
};

/// <summary>
/// Returns the stat indices for the stats usually displayed for species (e.g. no crafting speed Gacha) in game.
/// </summary>
public static readonly bool[] UsuallyVisibleStats = {
true, //Health,
true, //Stamina,
true, //Torpidity,
true, //Oxygen,
true, //Food,
false, //Water,
false, //Temperature,
true, //Weight,
true, //MeleeDamageMultiplier,
true, //SpeedMultiplier,
false, //TemperatureFortitude,
false, //CraftingSpeedMultiplier
};
}
}
237 changes: 237 additions & 0 deletions ARKBreedingStats/AsbServer/Connection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using ARKBreedingStats.importExportGun;
using ARKBreedingStats.Library;
using Newtonsoft.Json.Linq;

namespace ARKBreedingStats.AsbServer
{
/// <summary>
/// Connects to a server to receive creature data, e.g. sent by the export gun mod.
/// </summary>
internal static class Connection
{
private const string ApiUri = "https://export.arkbreeder.com/api/v1/";

private static SimpleCancellationToken _lastCancellationToken;

public static async void StartListeningAsync(
IProgress<(string jsonText, string serverHash, string message)> progressDataSent, string token = null)
{
if (string.IsNullOrEmpty(token)) return;

// stop previous listening if any
StopListening();
var cancellationToken = new SimpleCancellationToken();
_lastCancellationToken = cancellationToken;

var requestUri = ApiUri + "listen/" + token; // "https://httpstat.us/429";

try
{
using (var client = new HttpClient())
using (var response = await client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead))
{
if (!response.IsSuccessStatusCode)
{
var statusCode = (int)response.StatusCode;
string serverMessage = await response.Content.ReadAsStringAsync();

try
{
serverMessage = JObject.Parse(serverMessage).SelectToken("error.message").ToString();
}
catch
{
// server message in unknown format, use raw content string
}

serverMessage = Environment.NewLine + serverMessage;

switch (statusCode)
{
case 400: // Bad Request
WriteMessage($"Something went wrong with the server connection.{serverMessage}", response);
return;
case 429: // Too Many Requests
WriteMessage($"The server is currently at the rate limit and cannot process the request. Try again later.{serverMessage}", response);
return;
case 507: // Insufficient Storage
WriteMessage($"Too many connections to the server. Try again later.{serverMessage}", response);
return;
default:
var errorMessage = statusCode >= 500
? "Something went wrong with the server or its proxy." + Environment.NewLine
: null;
WriteMessage($"{errorMessage}{serverMessage}", response);
return;
}
}

using (var stream = await response.Content.ReadAsStreamAsync())
using (var reader = new StreamReader(stream))
{
await ReadServerSentEvents(reader, progressDataSent, token, cancellationToken);
}
}
}
catch (Exception ex)
{
WriteMessage($"ASB Server listening exception:\n{ex.Message}\n\nStack trace:\n{ex.StackTrace}");
}
finally
{
#if DEBUG
Console.WriteLine($"ASB Server listening stopped using token: {token}");
#endif
}

return;

// Displays an error message in the UI, also logs on the console if in debug mode
void WriteMessage(string message, HttpResponseMessage response = null)
{
if (response != null)
{
message = $"{(int)response.StatusCode}: {response.ReasonPhrase}{Environment.NewLine}{message}";
}
#if DEBUG
Console.WriteLine(message);
#endif
progressDataSent.Report((null, null, message));
}
}

private static Regex _eventRegex = new Regex(@"^event: (welcome|ping|replaced|export|server|closing)(?: (\-?\d+))?(?:\ndata:\s(.+))?$");

private static async Task ReadServerSentEvents(StreamReader reader, IProgress<(string jsonText, string serverHash, string message)> progressDataSent, string token, SimpleCancellationToken cancellationToken)
{
#if DEBUG
Console.WriteLine($"Now listening using token: {token}");
#endif
while (!cancellationToken.IsCancellationRequested)
{
var received = await reader.ReadLineAsync();
if (string.IsNullOrEmpty(received))
continue; // empty line marks end of event

#if DEBUG
Console.WriteLine($"{received} (token: {token})");
#endif
switch (received)
{
case "event: welcome":
continue;
case "event: ping":
continue;
case "event: replaced":
if (!cancellationToken.IsCancellationRequested)
progressDataSent.Report((null, null,
"ASB Server listening stopped. Connection used by a different user"));
return;
case "event: closing":
// only report closing if the user hasn't done this already
if (!cancellationToken.IsCancellationRequested)
progressDataSent.Report((null, null,
"ASB Server listening stopped. Connection closed by the server"));
return;
}

if (received != "event: export" && !received.StartsWith("event: server"))
{
Console.WriteLine($"unknown server event: {received}");
continue;
}

received += "\n" + await reader.ReadLineAsync();
if (cancellationToken.IsCancellationRequested)
break;
var m = _eventRegex.Match(received);
if (m.Success)
{
switch (m.Groups[1].Value)
{
case "export":
progressDataSent.Report((m.Groups[3].Value, null, null));
break;
case "server":
progressDataSent.Report((m.Groups[3].Value, m.Groups[2].Value, null));
break;
}
}
}
}

public static void StopListening()
{
if (_lastCancellationToken == null)
return; // nothing to stop

_lastCancellationToken.Cancel();
_lastCancellationToken = null;
}

/// <summary>
/// Sends creature data to the server, this is done for testing, usually other tools like the export gun mod do this.
/// </summary>
public static async void SendCreatureData(Creature creature, string token)
{
if (creature == null || string.IsNullOrEmpty(token)) return;

using (var client = new HttpClient())
{
var contentString = Newtonsoft.Json.JsonConvert.SerializeObject(ImportExportGun.ConvertCreatureToExportGunFile(creature, out _));
var msg = new HttpRequestMessage(HttpMethod.Put, ApiUri + "export/" + token);
msg.Content = new StringContent(contentString, Encoding.UTF8, "application/json");
msg.Content.Headers.Add("Content-Length", contentString.Length.ToString());
var response = await client.SendAsync(msg);
Console.WriteLine($"Sent creature data of {creature} using token: {token}\nContent:\n{contentString}");
Console.WriteLine(msg.ToString());
Console.WriteLine($"Response: Status: {(int)response.StatusCode}, ReasonPhrase: {response.ReasonPhrase}");
}
}

public static string CreateNewToken()
{
var allowedCharacters = Enumerable.Range(0x31, 9) // 1-9
.Concat(Enumerable.Range(0x61, 8)) // a-h
.Concat(new[] { 0x6b, 0x6d, 0x6e }) // k, m, n
.Concat(Enumerable.Range(0x70, 11)) // p-z
.Select(i => (char)i)
.ToArray();
var l = allowedCharacters.Length;

var guid = Guid.NewGuid().ToByteArray();
const int tokenLength = 14; // from these each 5th character is a dash for readability
var token = new char[tokenLength];
for (var i = 0; i < tokenLength; i++)
{
if ((i + 1) % 5 == 0)
{
token[i] = '-';
continue;
}
token[i] = allowedCharacters[guid[i] % l];
}

return new string(token);
}

/// <summary>
/// Simple replacement of CancellationTokenSource to avoid unnecessary complexities with disposal of CTS when the token is still in use.
/// </summary>
private class SimpleCancellationToken
{
public bool IsCancellationRequested;
public void Cancel() => IsCancellationRequested = true;
}
}
}
Loading

0 comments on commit 4371ff1

Please sign in to comment.