Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#260 Add enhanced loading overlay #261

Merged
merged 1 commit into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,13 @@ public void EstablishingTwoPlayersConnection() {
// Event: the client finished loading and the game is started
clientRuntime.StartGame();

// Lists must be equal and the client player must be in loading state
AssertPlayersAreEqual(hostRuntime, clientRuntime);
Assert.AreEqual(expected: PlayerState.Loading, actual: clientPlayer.State);

// Unity transitions into the next frame
UnityTestRuntime.NextFrame();

// Lists must be equal and the client player must be ready
AssertPlayersAreEqual(hostRuntime, clientRuntime);
Assert.AreEqual(expected: PlayerState.Ready, actual: clientPlayer.State);
Expand Down Expand Up @@ -158,7 +165,13 @@ private static void AssertPlayersAreEqual(TestRuntime runtimeA, TestRuntime runt
private static Harmony SetupEnvironment() {
UnityTestRuntime.Install();
var harmony = new Harmony("MultiplayerConnectionTests");
PatchesSetup.Install(harmony, new List<Type> { typeof(WorldManagerPatch), typeof(LoadOverlayPatch) });
PatchesSetup.Install(
harmony,
new List<Type> {
typeof(WorldManagerPatch),
typeof(MultiplayerStatusOverlayPatch)
}
);
return harmony;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
using HarmonyLib;
using JetBrains.Annotations;
using MultiplayerMod.Multiplayer.UI;
using MultiplayerMod.Multiplayer.UI.Overlays;

namespace MultiplayerMod.Test.Multiplayer;

[UsedImplicitly]
[HarmonyPatch(typeof(LoadOverlay))]
public class LoadOverlayPatch {
[HarmonyPatch(typeof(MultiplayerStatusOverlay))]
public class MultiplayerStatusOverlayPatch {

[UsedImplicitly]
[HarmonyPrefix]
[HarmonyPatch(nameof(LoadOverlay.Show))]
[HarmonyPatch(nameof(MultiplayerStatusOverlay.Show))]
private static bool Show() {
return false;
}
Expand Down
4 changes: 2 additions & 2 deletions src/MultiplayerMod.Test/Multiplayer/MultiplayerTools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ public static TestRuntime CreateTestRuntime(MultiplayerMode mode, string playerN
.AddType<Recorders>()
.AddType<TestRuntime>();

var configurer = new MultiplayerCommandsConfigurer();
configurer.Configure(builder);
new MultiplayerCommandsConfigurer().Configure(builder);
new UnityTaskSchedulerConfigurer().Configure(builder);

var container = builder.Build();
var runtime = container.Get<TestRuntime>();
Expand Down
10 changes: 0 additions & 10 deletions src/MultiplayerMod/Multiplayer/Commands/Overlay/ShowLoadOverlay.cs

This file was deleted.

4 changes: 3 additions & 1 deletion src/MultiplayerMod/Multiplayer/Commands/World/LoadWorld.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ public LoadWorld(string worldName, byte[] data) {
this.data = data;
}

public override void Execute(MultiplayerCommandContext context) => WorldManager.LoadWorldSave(worldName, data);
public override void Execute(MultiplayerCommandContext context) {
context.Runtime.Dependencies.Get<WorldManager>().RequestWorldLoad(worldName, data);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
using MultiplayerMod.Core.Events;
using MultiplayerMod.Multiplayer.CoreOperations.Events;
using MultiplayerMod.Multiplayer.Players.Events;
using MultiplayerMod.Multiplayer.UI;
using MultiplayerMod.Multiplayer.UI.Overlays;
using MultiplayerMod.Network;

namespace MultiplayerMod.Multiplayer.CoreOperations;
Expand All @@ -23,7 +23,7 @@ public MultiplayerJoinRequestController(EventDispatcher events, IMultiplayerClie

private void OnMultiplayerJoinRequested(MultiplayerJoinRequestedEvent @event) {
events.Dispatch(new MultiplayerModeSelectedEvent(MultiplayerMode.Client));
LoadOverlay.Show($"Connecting to {@event.HostName}...");
MultiplayerStatusOverlay.Show($"Connecting to {@event.HostName}...");
client.Connect(@event.Endpoint);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
using MultiplayerMod.Core.Dependency;
using MultiplayerMod.Core.Events;
using MultiplayerMod.Multiplayer.CoreOperations.Events;
using MultiplayerMod.Multiplayer.UI;
using MultiplayerMod.Multiplayer.Players.Events;
using MultiplayerMod.Multiplayer.UI.Overlays;
using MultiplayerMod.Network;

namespace MultiplayerMod.Multiplayer.CoreOperations;
Expand All @@ -12,10 +13,12 @@ public class MultiplayerServerController {

private readonly IMultiplayerServer server;
private readonly IMultiplayerClient client;
private readonly EventDispatcher events;

public MultiplayerServerController(IMultiplayerServer server, IMultiplayerClient client, EventDispatcher events) {
this.server = server;
this.client = client;
this.events = events;

events.Subscribe<GameStartedEvent>(OnGameStarted);
events.Subscribe<GameQuitEvent>(OnGameQuit);
Expand All @@ -32,7 +35,14 @@ private void OnGameStarted(GameStartedEvent @event) {
if (@event.Multiplayer.Mode != MultiplayerMode.Host)
return;

LoadOverlay.Show("Starting host...");
MultiplayerStatusOverlay.Show("Starting host...");
events.Subscribe<PlayersReadyEvent>(
(_, subscription) => {
MultiplayerStatusOverlay.Close();
subscription.Cancel();
}
);

server.Start();
}

Expand Down
2 changes: 2 additions & 0 deletions src/MultiplayerMod/Multiplayer/Players/MultiplayerPlayers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ public class MultiplayerPlayers : IEnumerable<MultiplayerPlayer> {

public IEnumerator<MultiplayerPlayer> GetEnumerator() => players.Values.GetEnumerator();

public int Count => players.Values.Count;

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

public MultiplayerPlayer Current => players[currentPlayerId];
Expand Down
30 changes: 0 additions & 30 deletions src/MultiplayerMod/Multiplayer/UI/LoadOverlay.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using JetBrains.Annotations;
using MultiplayerMod.Core.Dependency;
using MultiplayerMod.Core.Events;
using MultiplayerMod.Core.Scheduling;
using MultiplayerMod.ModRuntime.StaticCompatibility;
using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;

namespace MultiplayerMod.Multiplayer.UI.Overlays;

public class MultiplayerStatusOverlay {

public static string Text {
get => overlay?.text ?? "";
set {
if (overlay == null)
return;

overlay.text = value;
overlay.textComponent.text = value;
}
}

private LocText textComponent = null!;
private string text = "";

[InjectDependency, UsedImplicitly]
private UnityTaskScheduler scheduler = null!;

[InjectDependency, UsedImplicitly]
private EventDispatcher events = null!;

private static MultiplayerStatusOverlay? overlay;

private MultiplayerStatusOverlay() {
SceneManager.sceneLoaded += OnPostLoadScene;
Dependencies.Get<IDependencyInjector>().Inject(this);
CreateOverlay();
}

private void CreateOverlay() {
LoadingOverlay.Load(() => { });
textComponent = LoadingOverlay.instance.GetComponentInChildren<LocText>();
textComponent.alignment = TextAlignmentOptions.Top;
textComponent.margin = new Vector4(0, -21.0f, 0, 0);
textComponent.text = text;

var rect = textComponent.gameObject.GetComponent<RectTransform>();
rect.sizeDelta = new Vector2(Screen.width, 0);

var scale = LoadingOverlay.instance.GetComponentInParent<KCanvasScaler>().GetCanvasScale();
ScreenResize.Instance.OnResize += () => rect.sizeDelta = new Vector2(Screen.width / scale, 0);
}

private void Dispose() {
SceneManager.sceneLoaded -= OnPostLoadScene;
LoadingOverlay.Clear();
}

private void OnPostLoadScene(Scene scene, LoadSceneMode mode) => scheduler.Run(CreateOverlay);

public static void Show(string text) {
overlay ??= new MultiplayerStatusOverlay();
Text = text;
}

public static void Close() {
overlay?.Dispose();
overlay = null;
}

}
57 changes: 50 additions & 7 deletions src/MultiplayerMod/Multiplayer/World/WorldManager.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
using System.IO;
using System.Linq;
using JetBrains.Annotations;
using MultiplayerMod.Core.Dependency;
using MultiplayerMod.Core.Events;
using MultiplayerMod.Core.Extensions;
using MultiplayerMod.Core.Scheduling;
using MultiplayerMod.ModRuntime.Context;
using MultiplayerMod.ModRuntime.StaticCompatibility;
using MultiplayerMod.Multiplayer.Commands.Overlay;
using MultiplayerMod.Multiplayer.Commands.Speed;
using MultiplayerMod.Multiplayer.Commands.World;
using MultiplayerMod.Multiplayer.CoreOperations.PlayersManagement.Commands;
using MultiplayerMod.Multiplayer.Players;
using MultiplayerMod.Multiplayer.Players.Events;
using MultiplayerMod.Multiplayer.UI.Overlays;
using MultiplayerMod.Network;

namespace MultiplayerMod.Multiplayer.World;
Expand All @@ -18,34 +22,73 @@ public class WorldManager {

private readonly IMultiplayerServer server;
private readonly MultiplayerGame multiplayer;
private readonly EventDispatcher events;
private readonly UnityTaskScheduler scheduler;

public WorldManager(IMultiplayerServer server, MultiplayerGame multiplayer) {
public WorldManager(
IMultiplayerServer server,
MultiplayerGame multiplayer,
EventDispatcher events,
UnityTaskScheduler scheduler
) {
this.server = server;
this.multiplayer = multiplayer;
this.events = events;
this.scheduler = scheduler;
}

public void Sync() {
SetupStatusOverlay();

var resume = !SpeedControlScreen.Instance.IsPaused;
server.Send(new PauseGame());
multiplayer.Objects.SynchronizeWithTracker();
multiplayer.Players.ForEach(it => server.Send(new ChangePlayerStateCommand(it.Id, PlayerState.Loading)));
server.Send(new ChangePlayerStateCommand(multiplayer.Players.Current.Id, PlayerState.Ready));
server.Send(new ShowLoadOverlay());
server.Send(new LoadWorld(WorldName, GetWorldSave()), MultiplayerCommandOptions.SkipHost);

if (resume)
server.Send(new ResumeGame());
}

public static void LoadWorldSave(string worldName, byte[] data) {
private void SetupStatusOverlay() {
MultiplayerStatusOverlay.Show("Waiting for players...");
events.Subscribe<PlayerStateChangedEvent>(
(_, subscription) => {
var players = multiplayer.Players;
if (players.Ready) {
MultiplayerStatusOverlay.Close();
subscription.Cancel();
}
var readyPlayersCount = players.Count(it => it.State == PlayerState.Ready);
var playerList = string.Join("\n", players.Select(it => $"{it.Profile.PlayerName}: {it.State}"));
var statusText = $"Waiting for players ({readyPlayersCount}/{players.Count} ready)...\n{playerList}";
MultiplayerStatusOverlay.Text = statusText;
}
);
}

public void RequestWorldLoad(string name, byte[] data) {
MultiplayerStatusOverlay.Show($"Loading {name}...");
events.Subscribe<PlayersReadyEvent>(
(_, subscription) => {
MultiplayerStatusOverlay.Close();
subscription.Cancel();
}
);
scheduler.Run(() => LoadWorldSave(name, data));
}

private void LoadWorldSave(string name, byte[] data) {
var savePath = SaveLoader.GetCloudSavesDefault()
? SaveLoader.GetCloudSavePrefix()
: SaveLoader.GetSavePrefixAndCreateFolder();

var path = Path.Combine(savePath, worldName, $"{worldName}.sav");
var path = Path.Combine(savePath, name, $"{name}.sav");
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
using (var writer = new BinaryWriter(File.OpenWrite(path))) {
using (var writer = new BinaryWriter(File.OpenWrite(path)))
writer.Write(data);
}

LoadScreen.DoLoad(path);
}

Expand Down