Skip to content

Commit

Permalink
Enable replaying rooms after clear (#309)
Browse files Browse the repository at this point in the history
- Set play_type = Multi on dungeon start
- Correctly set is_host on dungeon start/record using StateManager api
- Handle ClearQuestRequest in photon plugin
- Restructure plugin to have helper methods in different file
- Add Photon authentication handler

Also:
- Handle FailQuestRequest
- Simplify plugin configuration
- Set room state to closed in Photon on host leaving (should reduce
ghost rooms)

Closes #245
  • Loading branch information
SapiensAnatis authored Jul 16, 2023
1 parent 61176d8 commit e37f333
Show file tree
Hide file tree
Showing 42 changed files with 1,180 additions and 342 deletions.
4 changes: 4 additions & 0 deletions DragaliaAPI.Integration.Test/Dragalia/MatchingTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public async Task GetRoomList_ReturnsRoomList()
{
new()
{
ActorNr = 1,
ViewerId = 1,
PartyNoList = new List<int>() { 1 }
}
Expand Down Expand Up @@ -124,6 +125,7 @@ public async Task GetRoomListByQuestId_ReturnsRoomList()
{
new()
{
ActorNr = 1,
ViewerId = 1,
PartyNoList = new List<int>() { 1 }
}
Expand Down Expand Up @@ -211,6 +213,7 @@ public async Task GetRoomName_ReturnsRoom()
{
new()
{
ActorNr = 1,
ViewerId = 1,
PartyNoList = new List<int>() { 1 }
}
Expand Down Expand Up @@ -294,6 +297,7 @@ public async Task GetRoomName_UnrecognizedViewerId_UsesDefault()
{
new()
{
ActorNr = 1,
ViewerId = 40000,
PartyNoList = new List<int>() { 1 }
}
Expand Down
45 changes: 45 additions & 0 deletions DragaliaAPI.Photon.Plugin/Constants/Event.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DragaliaAPI.Photon.Plugin.Constants
{
public static class Event
{
public static class Codes
{
public const int GameEnd = 0x2;

public const int Ready = 0x3;

public const int CharacterData = 0x14;

public const int StartQuest = 0x15;

public const int RoomBroken = 0x17;

public const int GameSucceed = 0x18;

public const int WillLeave = 0x1e;

public const int Party = 0x3e;

public const int ClearQuestRequest = 0x3f;

public const int ClearQuestResponse = 0x40;

public const int FailQuestRequest = 0x43;

public const int FailQuestResponse = 0x44;

public const int SuccessiveGameTimer = 0x53;
}

public static class Constants
{
public const int EventDataKey = 245;
}
}
}
15 changes: 0 additions & 15 deletions DragaliaAPI.Photon.Plugin/Constants/EventCodes.cs

This file was deleted.

51 changes: 51 additions & 0 deletions DragaliaAPI.Photon.Plugin/GluonPlugin.Constants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DragaliaAPI.Photon.Plugin.Helpers;
using DragaliaAPI.Photon.Plugin.Models;
using MessagePack;
using Newtonsoft.Json;
using Photon.Hive.Plugin;

namespace DragaliaAPI.Photon.Plugin
{
/// <summary>
/// Hardcoded plugin configuration.
/// </summary>
public partial class GluonPlugin
{
private static readonly Uri GameCreateEndpoint = new Uri(
"Event/GameCreate",
UriKind.Relative
);

private static readonly Uri GameCloseEndpoint = new Uri(
"Event/GameClose",
UriKind.Relative
);

private static readonly Uri GameJoinEndpoint = new Uri("Event/GameJoin", UriKind.Relative);

private static readonly Uri GameLeaveEndpoint = new Uri(
"Event/GameLeave",
UriKind.Relative
);

private static readonly Uri EntryConditionsEndpoint = new Uri(
"Event/EntryConditions",
UriKind.Relative
);

private static readonly Uri MatchingTypeEndpoint = new Uri(
"Event/MatchingType",
UriKind.Relative
);

private static readonly Uri RoomIdEndpoint = new Uri("Event/RoomId", UriKind.Relative);

private static readonly Uri VisibleEndpoint = new Uri("Event/Visible", UriKind.Relative);
}
}
185 changes: 185 additions & 0 deletions DragaliaAPI.Photon.Plugin/GluonPlugin.Helper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DragaliaAPI.Photon.Plugin.Helpers;
using DragaliaAPI.Photon.Plugin.Models;
using MessagePack;
using Newtonsoft.Json;
using Photon.Hive.Plugin;

namespace DragaliaAPI.Photon.Plugin
{
/// <summary>
/// Support plugin methods.
/// </summary>
public partial class GluonPlugin
{
/// <summary>
/// Helper method to raise events.
/// </summary>
/// <param name="eventCode">The event code to raise.</param>
/// <param name="eventData">The event data.</param>
/// <param name="target">The actor to target -- if null, all actors will be targeted.</param>
public void RaiseEvent(byte eventCode, object eventData, int? target = null)
{
byte[] serializedEvent = MessagePackSerializer.Serialize(
eventData,
MessagePackSerializerOptions.Standard.WithCompression(
MessagePackCompression.Lz4Block
)
);
Dictionary<byte, object> props = new Dictionary<byte, object>()
{
{ 245, serializedEvent },
{ 254, 0 } // Server actor number
};

this.logger.DebugFormat(
"Raising event 0x{0} with data {1}",
eventCode.ToString("X"),
JsonConvert.SerializeObject(eventData)
);

if (target is null)
{
this.BroadcastEvent(eventCode, props);
}
else
{
this.logger.DebugFormat("Event will target actor {0}", target);
this.PluginHost.BroadcastEvent(
new List<int>() { target.Value },
0,
eventCode,
props,
CacheOperations.DoNotCache
);
}
}

/// <summary>
/// Helper method to POST a JSON request body to the Redis API.
/// </summary>
/// <param name="endpoint">The endpoint to send a request to.</param>
/// <param name="forwardRequest">The request object.</param>
/// <param name="info">The event info from the current event callback.</param>
/// <param name="callAsync">Whether or not to suspend execution of the room while awaiting a response.</param>
private void PostStateManagerRequest(
Uri endpoint,
object forwardRequest,
ICallInfo info,
bool callAsync = true
)
{
HttpRequestCallback callback = this.LogIfFailedCallback;

MemoryStream stream = new MemoryStream();
string json = JsonConvert.SerializeObject(
forwardRequest,
new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
TypeNameHandling = TypeNameHandling.None,
}
);
byte[] data = Encoding.UTF8.GetBytes(json);
stream.Write(data, 0, data.Length);

string url = new Uri(this.config.StateManagerUrl, endpoint).AbsoluteUri;

HttpRequest request = new HttpRequest
{
Url = url,
Method = "POST",
Accept = "application/json",
ContentType = "application/json",
Callback = callback,
CustomHeaders = new Dictionary<string, string>()
{
{ "Authorization", $"Bearer {this.config.BearerToken}" }
},
DataStream = stream,
Async = callAsync
};

this.PluginHost.LogDebug(
string.Format("PostStateManagerRequest: {0} - {1}", url, json)
);

this.PluginHost.HttpRequest(request, info);
}

/// <summary>
/// Helper method to POST a msgpack request to the main API server.
/// </summary>
/// <param name="endpoint">The endpoint to send a request to.</param>
/// <param name="requestBody">The msgpack request body.</param>
/// <param name="info">The event info from the current event callback.</param>
/// <param name="callback">The callback to trigger on a response.</param>
/// <param name="callAsync">Whether or not to suspend execution of the room while awaiting a response.</param>
private void PostApiRequest(
Uri endpoint,
byte[] requestBody,
IRaiseEventCallInfo info,
HttpRequestCallback callback,
bool callAsync = true
)
{
IActor actor = this.PluginHost.GameActors.First(x => x.ActorNr == info.ActorNr);

Uri requestUri = new Uri(this.config.ApiServerUrl, endpoint);

HttpRequest req = new HttpRequest()
{
Url = requestUri.AbsoluteUri,
ContentType = "application/octet-stream",
Callback = callback,
Async = callAsync,
Accept = "application/octet-stream",
DataStream = new MemoryStream(requestBody),
UserState = new HttpRequestUserState() { RequestActorNr = info.ActorNr },
Method = "POST",
CustomHeaders = new Dictionary<string, string>()
{
{ "Auth-ViewerId", actor.GetViewerId().ToString() },
{ "Authorization", $"Bearer {this.config.BearerToken}" }
}
};

this.PluginHost.HttpRequest(req, info);
}

/// <summary>
/// Logs an error if a HTTP response was not successful.
/// </summary>
/// <param name="httpResponse">The HTTP response.</param>
/// <param name="userState">The user state.</param>
private void LogIfFailedCallback(IHttpResponse httpResponse, object userState)
{
if (httpResponse.Status != HttpRequestQueueResult.Success)
{
this.ReportError(
$"Request to {httpResponse.Request.Url} failed with Photon status {httpResponse.Status} and HTTP status {httpResponse.HttpCode} ({httpResponse.Reason})"
);
}
}

/// <summary>
/// Report an error.
/// </summary>
/// <param name="msg">The error message.</param>
private void ReportError(string msg)
{
this.PluginHost.LogError(msg);
this.PluginHost.BroadcastErrorInfoEvent(msg);
}

private int GenerateRoomId()
{
return this.rdm.Next(100_0000, 999_9999);
}
}
}
Loading

0 comments on commit e37f333

Please sign in to comment.