Commit 44592d7e authored by Adam Leyshon's avatar Adam Leyshon
Browse files

B19 and R1 now have the same code base.

API updated to v4.
Thing list is now only sent once during the first console use along with the mod list.
Fixes #4, Combat Extended 1.0 was the culprit. ThingCategoryDef Ammo20mmFliegerfaust has no labelcap string set causing the nullref exception.

TODO:
Add translation strings for updated data screen.
Update version number in XML Description.
Update logo.
parent 7dbd6176
......@@ -9,14 +9,20 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
Debug B19|Any CPU = Debug B19|Any CPU
Debug R1|Any CPU = Debug R1|Any CPU
Release B19|Any CPU = Release B19|Any CPU
Release R1|Any CPU = Release R1|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{1976D016-8918-48C9-A54E-4D9EF7F200B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1976D016-8918-48C9-A54E-4D9EF7F200B7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1976D016-8918-48C9-A54E-4D9EF7F200B7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1976D016-8918-48C9-A54E-4D9EF7F200B7}.Release|Any CPU.Build.0 = Release|Any CPU
{1976D016-8918-48C9-A54E-4D9EF7F200B7}.Debug B19|Any CPU.ActiveCfg = Debug B19|Any CPU
{1976D016-8918-48C9-A54E-4D9EF7F200B7}.Debug B19|Any CPU.Build.0 = Debug B19|Any CPU
{1976D016-8918-48C9-A54E-4D9EF7F200B7}.Debug R1|Any CPU.ActiveCfg = Debug R1|Any CPU
{1976D016-8918-48C9-A54E-4D9EF7F200B7}.Debug R1|Any CPU.Build.0 = Debug R1|Any CPU
{1976D016-8918-48C9-A54E-4D9EF7F200B7}.Release B19|Any CPU.ActiveCfg = Release B19|Any CPU
{1976D016-8918-48C9-A54E-4D9EF7F200B7}.Release B19|Any CPU.Build.0 = Release B19|Any CPU
{1976D016-8918-48C9-A54E-4D9EF7F200B7}.Release R1|Any CPU.ActiveCfg = Release R1|Any CPU
{1976D016-8918-48C9-A54E-4D9EF7F200B7}.Release R1|Any CPU.Build.0 = Release R1|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
......
......@@ -7,6 +7,7 @@
#endregion
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
......@@ -21,13 +22,15 @@ namespace Glitterworld_Prime
{
#region Fields
private static readonly string _downloadMessage = "Downloading market data...";
private readonly Stopwatch _apiStopwatch;
private readonly GlitterWorld_MapComponent _mapComponent;
private readonly Pawn _player;
private readonly bool _shouldClose = false;
private readonly ITrader _trader;
private DownloadStatus _isDownloadComplete;
private Stopwatch _apiStopwatch;
private string _downloadMessage = "Sending Supported Thing List";
private DownloadStatus _previousStatus;
private DownloadStatus _status;
#endregion
......@@ -40,12 +43,19 @@ namespace Glitterworld_Prime
_player = playerNegotiator;
_trader = traderPrime;
_mapComponent = Utilities.GetMapComponent(_player.Map);
// Download Goods from Server
_apiStopwatch = new Stopwatch();
LogWriter.WriteMessage("Starting request to server");
_apiStopwatch.Start();
GlitterWorldApi.GetServerGoodsAsync(_mapComponent, DownloadThingsComplete_Callback);
if (_mapComponent.HasSentThingListYet)
{
_status = DownloadStatus.DoneSendingMods;
}
else
{
// Send supported things
LogWriter.WriteMessage("Sending Supported Thing List");
_apiStopwatch.Start();
GlitterWorldApi.PutItemListAsync(_mapComponent, PutThingsListComplete_Callback);
}
}
#endregion
......@@ -76,17 +86,53 @@ namespace Glitterworld_Prime
return;
}
switch (_isDownloadComplete)
Text.Font = GameFont.Small;
switch (_status)
{
case DownloadStatus.Incomplete:
Text.Font = GameFont.Small;
var textWidth = Text.CalcSize(_downloadMessage);
var label = new Rect((inRect.width - textWidth.x) / 2f, (inRect.height - textWidth.y) / 2f,
textWidth.x, textWidth.y);
Widgets.Label(label, _downloadMessage);
if (_previousStatus != DownloadStatus.Incomplete)
{
_previousStatus = DownloadStatus.Incomplete;
_downloadMessage = "Sending Supported Thing List";
}
break;
case DownloadStatus.DoneSendingSupportedThings:
if (_previousStatus != DownloadStatus.DoneSendingSupportedThings)
{
// Now send the mods
_previousStatus = DownloadStatus.DoneSendingSupportedThings;
_downloadMessage = "Sending Supported Mods";
_apiStopwatch = new Stopwatch();
LogWriter.WriteMessage("Starting request to server");
_apiStopwatch.Start();
GlitterWorldApi.PutModListAsync(_mapComponent, PutModListComplete_Callback);
}
break;
case DownloadStatus.DoneSendingMods:
if (_previousStatus != DownloadStatus.DoneSendingMods)
{
// Download Goods from Server
_previousStatus = DownloadStatus.DoneSendingMods;
_downloadMessage = "Getting Market Data";
_apiStopwatch = new Stopwatch();
LogWriter.WriteMessage("Starting request to server");
_apiStopwatch.Start();
GlitterWorldApi.GetServerGoodsAsync(_mapComponent, DownloadThingsComplete_Callback);
}
break;
case DownloadStatus.Complete:
// Mark that we've sent all the metadata, so not to do it again.
_mapComponent.HasSentThingListYet = true;
// We're done, close and go to the trade screen.
var dt = new DialogPrimeTrade(_player, _trader);
Find.WindowStack.Add(dt);
Close();
......@@ -96,32 +142,70 @@ namespace Glitterworld_Prime
Messages.Message("GWPCantGetMarketData".Translate(), MessageTypeDefOf.NegativeEvent);
Close();
break;
default:
throw new ArgumentOutOfRangeException();
}
var textWidth = Text.CalcSize(_downloadMessage);
var label = new Rect((inRect.width - textWidth.x) / 2f, (inRect.height - textWidth.y) / 2f,
textWidth.x, textWidth.y);
Widgets.Label(label, _downloadMessage);
}
public void PutThingsListComplete_Callback(IRestResponse response, RestRequestAsyncHandle handle)
{
LogWriter.WriteMessage(response.ResponseStatus.ToString());
_apiStopwatch.Stop();
LogWriter.WriteMessage($"Request completed, Request took {_apiStopwatch.Elapsed}");
GlitterWorldApi.CheckResponseForErrors(response);
if (response.StatusCode != HttpStatusCode.OK)
{
_status = DownloadStatus.Failed;
return;
}
_status = DownloadStatus.DoneSendingSupportedThings;
}
public void PutModListComplete_Callback(IRestResponse response, RestRequestAsyncHandle handle)
{
LogWriter.WriteMessage(response.ResponseStatus.ToString());
_apiStopwatch.Stop();
LogWriter.WriteMessage($"Request completed, Request took {_apiStopwatch.Elapsed}");
GlitterWorldApi.CheckResponseForErrors(response);
if (response.StatusCode != HttpStatusCode.OK)
{
_status = DownloadStatus.Failed;
return;
}
_status = DownloadStatus.DoneSendingMods;
}
public void DownloadThingsComplete_Callback(IRestResponse<List<GlitterWorldItem>> response,
RestRequestAsyncHandle handle)
{
LogWriter.WriteMessage(response.ResponseStatus.ToString());
GlitterWorldApi.CheckResponseForErrors(response);
_apiStopwatch.Stop();
LogWriter.WriteMessage($"Request completed, Request took {_apiStopwatch.Elapsed}");
GlitterWorldApi.CheckResponseForErrors(response);
// This was originally called back from a different thread, so we set the correct flag and hand control back to the UI thread.
if (response.StatusCode != HttpStatusCode.OK)
{
_isDownloadComplete = DownloadStatus.Failed;
_status = DownloadStatus.Failed;
}
else
{
if (_mapComponent != null)
{
_mapComponent.SetTraderShipGoods(response.Data);
_isDownloadComplete = DownloadStatus.Complete;
_status = DownloadStatus.Complete;
}
else
{
_isDownloadComplete = DownloadStatus.Failed;
_status = DownloadStatus.Failed;
}
}
}
......@@ -133,6 +217,8 @@ namespace Glitterworld_Prime
private enum DownloadStatus
{
Incomplete,
DoneSendingSupportedThings,
DoneSendingMods,
Complete,
Failed
}
......
......@@ -402,8 +402,16 @@ namespace Glitterworld_Prime
var allDefsListForReading = DefDatabase<ThingCategoryDef>.AllDefsListForReading;
for (var i = 0; i < allDefsListForReading.Count; i++)
{
var def = allDefsListForReading[i];
if (def.LabelCap.NullOrEmpty())
{
LogWriter.WriteMessage($"ThingCategoryDef {def.defName} has no label!, Not able to filter by this, skipping. Please report to Mod Author.");
continue;
}
// We'll never deal with corpses so don't show them.
if (!def.LabelCap.ToLower().Contains("corpse"))
list.Add(new FloatMenuOption(def.LabelCap, delegate { filterSetter(def); }));
......
......@@ -15,7 +15,7 @@ using Verse;
namespace Glitterworld_Prime
{
internal class DialogPrimeUpdateSubscription : Window
public class DialogPrimeUpdateSubscription : Window
{
#region Fields
......
......@@ -17,6 +17,7 @@ using System.Net;
using System.Text;
using Glitterworld_Prime.ApiStructs;
using RestSharp;
using RestSharp.Serializers;
using Verse;
namespace Glitterworld_Prime
......@@ -28,12 +29,18 @@ namespace Glitterworld_Prime
private static readonly float ClientVersion = 1.0f;
#if LOCALSERVER
private static readonly string BaseUrl = "http://127.0.0.1:5000/u1";
private const string BaseUrl = "http://127.0.0.1:5000";
#else
private static readonly string BaseUrl = "https://prime.thecodecache.net/u1";
private const string BaseUrl = "https://prime.thecodecache.net";
#endif
#endregion
#if B19
private const string MarketUrl = "m1";
#else
private const string MarketUrl = "m2";
#endif
#endregion
#region Methods
......@@ -42,7 +49,7 @@ namespace Glitterworld_Prime
LogWriter.WriteMessage("Getting API Version");
var client = CreateRestClient();
var request = new RestRequest("/application/version", Method.GET);
var request = new RestRequest($"/v4/{MarketUrl}/application/version", Method.GET);
var response =
ExecuteRequestExpectData<GlitterWorldApiVersionResponse>(client, request);
......@@ -76,7 +83,7 @@ namespace Glitterworld_Prime
LogWriter.WriteMessage("Getting Server Status");
var client = CreateRestClient();
var request = new RestRequest("/application/maintenance/mode", Method.GET);
var request = new RestRequest($"/v4/{MarketUrl}/application/maintenance/mode", Method.GET);
var response = ExecuteRequestNoDataExpected(client, request);
......@@ -104,7 +111,7 @@ namespace Glitterworld_Prime
{
LogWriter.WriteMessage("Getting Server Status Async");
var client = CreateRestClient();
var request = new RestRequest("/application/maintenance/mode", Method.GET);
var request = new RestRequest($"/v4/{MarketUrl}/application/maintenance/mode", Method.GET);
client.ExecuteAsyncGet(request, callback, "GET");
}
......@@ -113,7 +120,7 @@ namespace Glitterworld_Prime
LogWriter.WriteMessage("Getting Maintenance Window");
var client = CreateRestClient();
var request = new RestRequest("/application/maintenance/window", Method.GET);
var request = new RestRequest($"/v4/{MarketUrl}/application/maintenance/window", Method.GET);
var response =
ExecuteRequestExpectData<GlitterWorldApiServerMaintenanceWindow>(client, request);
......@@ -155,31 +162,10 @@ namespace Glitterworld_Prime
LogWriter.WriteMessage("Getting Market Data");
var client = CreateRestClient();
var request = new RestRequest("/market/process_item_list_request", Method.POST);
// Compress the list of things to request and send it as a file.
var jsonString = request.JsonSerializer.Serialize(mapComponent.ThingsToRequestFromMarket);
byte[] compressed;
var buffer = new byte[4096];
int read;
using (var outStream = new MemoryStream())
{
using (var tinyStream = new GZipStream(outStream, CompressionMode.Compress))
{
using (var mStream = new MemoryStream(Encoding.UTF8.GetBytes(jsonString)))
{
while ((read = mStream.Read(buffer, 0, buffer.Length)) > 0) tinyStream.Write(buffer, 0, read);
}
}
compressed = outStream.ToArray();
}
request.AddFileBytes("things", compressed, "things");
var request = new RestRequest($"/v4/{MarketUrl}/market/get_data/{mapComponent.ColonyId}", Method.GET);
client.ExecuteAsyncPost(request,
callback, "POST");
callback, "GET");
}
public static GlitterWorldSubscriptionMetadata GetSubscriptionData(Map map)
......@@ -187,21 +173,16 @@ namespace Glitterworld_Prime
LogWriter.WriteMessage("Checking Subscription.");
var client = CreateRestClient();
RestRequest request;
request = new RestRequest("/prime_subscription/check/" + Utilities.GetMapComponent(map).ColonyId,
var request = new RestRequest($"/v4/{MarketUrl}/prime_subscription/check/{Utilities.GetMapComponent(map).ColonyId}",
Method.GET);
var response =
ExecuteRequestExpectData<GlitterWorldSubscriptionMetadata>(client, request);
var response = ExecuteRequestExpectData<GlitterWorldSubscriptionMetadata>(client, request);
if (response.StatusCode != HttpStatusCode.OK)
{
LogWriter.WriteErrorMessage("Failed to get Colony Subscription Data.");
return null;
}
if (response.StatusCode == HttpStatusCode.OK) return response.Data;
return response.Data;
LogWriter.WriteErrorMessage("Failed to get Colony Subscription Data.");
return null;
}
public static bool PlaceOrder(string colonyId, int currentTick, List<GlitterWorldOrderItem> thingsSold,
......@@ -218,7 +199,7 @@ namespace Glitterworld_Prime
CurrentGameTick = currentTick
};
var request = new RestRequest($"/orders/{colonyId}", Method.PUT);
var request = new RestRequest($"/v4/{MarketUrl}/orders/{colonyId}", Method.PUT);
request.AddJsonBody(orderRequest);
......@@ -236,7 +217,7 @@ namespace Glitterworld_Prime
var client = CreateRestClient();
var request = new RestRequest($"/orders/{colonyId}?tick={currentTick}", Method.GET);
var request = new RestRequest($"/v4/{MarketUrl}/orders/{colonyId}?tick={currentTick}", Method.GET);
client.ExecuteAsyncGet(request, callback, "GET");
}
......@@ -247,7 +228,7 @@ namespace Glitterworld_Prime
var client = CreateRestClient();
var request = new RestRequest($"/orders/{colonyId}/{orderId}", Method.POST);
var request = new RestRequest($"/v4/{MarketUrl}/orders/{colonyId}/{orderId}", Method.POST);
var statusBody = new GlitterWorldOrderStatus
{
......@@ -272,7 +253,7 @@ namespace Glitterworld_Prime
var client = CreateRestClient();
var request = new RestRequest($"/orders/{colonyId}/{orderId}", Method.POST);
var request = new RestRequest($"/v4/{MarketUrl}/orders/{colonyId}/{orderId}", Method.POST);
var statusBody = new GlitterWorldOrderStatus
{
......@@ -292,7 +273,7 @@ namespace Glitterworld_Prime
var client = CreateRestClient();
var request = new RestRequest($"/orders/{colonyId}/{orderId}", Method.GET);
var request = new RestRequest($"/v4/{MarketUrl}/orders/{colonyId}/{orderId}", Method.GET);
// Make request.
try
......@@ -320,7 +301,7 @@ namespace Glitterworld_Prime
var client = CreateRestClient();
// Send colony metadata.
var request = new RestRequest("/prime_subscription/subscribe/" + Utilities.GetMapComponent(map).ColonyId,
var request = new RestRequest($"/v4/{MarketUrl}/prime_subscription/subscribe/" + Utilities.GetMapComponent(map).ColonyId,
Method.PUT);
request.AddJsonBody(gsm);
......@@ -342,7 +323,7 @@ namespace Glitterworld_Prime
#endif
var client = CreateRestClient();
var request = new RestRequest("/user/create", Method.GET);
var request = new RestRequest($"/v4/{MarketUrl}/user/create", Method.GET);
// Make request.
try
......@@ -384,14 +365,14 @@ namespace Glitterworld_Prime
if (colonyId.NullOrEmpty() || !colonyId.NullOrEmpty() && colonyId.Contains("-"))
{
var request = new RestRequest("/colonies/create", Method.PUT);
var request = new RestRequest($"/v4/{MarketUrl}/colonies/create", Method.PUT);
request.AddJsonBody(cmd);
response = ExecuteRequestExpectData<GlitterWorldIdResponse>(client, request);
}
else
{
var request = new RestRequest("/colonies/" + Utilities.GetMapComponent(map).ColonyId, Method.PUT);
var request = new RestRequest($"/v4/{MarketUrl}/colonies/" + Utilities.GetMapComponent(map).ColonyId, Method.PUT);
request.AddJsonBody(cmd);
response = ExecuteRequestExpectData<GlitterWorldIdResponse>(client, request);
......@@ -422,7 +403,7 @@ namespace Glitterworld_Prime
var client = new RestClient(BaseUrl)
{
AllowDecompression = true,
UserAgent = $"GlitterWorldPrime {Utilities.Version} {Utilities.GetUserType()}"
UserAgent = $"GlitterWorldPrime/{Utilities.Version}.{Utilities.GetUserType()}"
};
return client;
......@@ -461,6 +442,62 @@ namespace Glitterworld_Prime
request.AddHeader("Accept", "application/json");
}
public static void PutItemListAsync(GlitterWorld_MapComponent mapComponent,
Action<IRestResponse, RestRequestAsyncHandle> callback)
{
LogWriter.WriteMessage("Sending Supported Things Data");
var client = CreateRestClient();
var request = new RestRequest($"/v4/{MarketUrl}/colonies/{mapComponent.ColonyId}/thing_metadata", Method.PUT);
// Compress the list of things to request and send it as a file.
var compressed = SerializeAndCompress(request.JsonSerializer, mapComponent.GlitterWorldSupportedThingList);
request.AddFileBytes("things", compressed, "things");
client.ExecuteAsyncPost(request, callback, "PUT");
}
public static void PutModListAsync(GlitterWorld_MapComponent mapComponent,
Action<IRestResponse, RestRequestAsyncHandle> callback)
{
LogWriter.WriteMessage("Sending Mod List Data");
var client = CreateRestClient();
var request = new RestRequest($"/v4/{MarketUrl}/colonies/{mapComponent.ColonyId}/mod_metadata", Method.PUT);
var compressed =
SerializeAndCompress(request.JsonSerializer, new GlitterWorldModList(mapComponent.ModList));
request.AddFileBytes("mods", compressed, "mods");
client.ExecuteAsyncPost(request, callback, "PUT");
}
public static byte[] SerializeAndCompress(ISerializer serializer, object data)
{
// Compress the list of mods and send it as a file.
var jsonString = serializer.Serialize(data);
byte[] compressed;
var buffer = new byte[4096];
using (var outStream = new MemoryStream())
{
using (var tinyStream = new GZipStream(outStream, CompressionMode.Compress))
{
using (var mStream = new MemoryStream(Encoding.UTF8.GetBytes(jsonString)))
{
int read;
while ((read = mStream.Read(buffer, 0, buffer.Length)) > 0) tinyStream.Write(buffer, 0, read);
}
}
compressed = outStream.ToArray();
}
return compressed;
}
#endregion
}
}
......@@ -136,9 +136,9 @@ namespace Glitterworld_Prime.ApiStructs
{
}
public GlitterWorldSubscriptionMetadata(int subsciptionExpires, int cost)
public GlitterWorldSubscriptionMetadata(int subscriptionExpires, int cost)
{
TickSubscriptionExpires = subsciptionExpires;
TickSubscriptionExpires = subscriptionExpires;
SubscriptionCost = cost;
}
......@@ -230,19 +230,42 @@ namespace Glitterworld_Prime.ApiStructs
#endregion
}
[Serializable]
public class GlitterWorldSupportedThingList
{
#region ctor
public GlitterWorldSupportedThingList(string locale, IEnumerable<GlitterWorldRequestItem> supportedThings)
{
this.Locale = locale;
this.Things = new List<GlitterWorldRequestItem>(supportedThings);
}
#endregion
#region Properties
public string Locale { get; set; }
public List<GlitterWorldRequestItem> Things { get; set; }
#endregion
}
/// <summary>
/// This class is used for serialing the order from the client to the API.
/// This class is used for serializing the list of goods to get from the API.
/// </summary>
[Serializable]
public class GlitterWorldRequestItem
{
#region ctor
public GlitterWorldRequestItem(string name, string quality = "", string stuffType = "")
public GlitterWorldRequestItem(string name, string localizedName, string quality = "", string stuffType = "")
{
Name = name;
StuffType = stuffType;
Quality = quality;
LocalizedName = localizedName;
}
#endregion
......@@ -256,6 +279,32 @@ namespace Glitterworld_Prime.ApiStructs
// Add support for things made of stuff.
public string StuffType { get; set; }
public string LocalizedName { get; set; }
#endregion
}
/// <summary>
/// This class is used for serializing the list of mods that this game has.
/// </summary>
[Serializable]
public class GlitterWorldModList
{
#region ctor
public GlitterWorldModList(List<string> modList)
{
ModList = modList;
}
#endregion
#region Properties