app.hypergames.hypersdk 1.10.0 → 1.11.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +97 -0
- package/Editor/Scripts/Setup/HyperSDKInstaller.cs +42 -0
- package/Editor/Scripts/Tools/HyperGameModeMenu.cs +27 -7
- package/Runtime/Internal/AssemblyInfo.cs +1 -0
- package/Runtime/Internal/Bootstrap/HyperLoader.cs +186 -8
- package/Runtime/Internal/Core/HyperRuntime.cs +115 -0
- package/Runtime/Internal/Core/HyperRuntimeHost.cs +6 -0
- package/Runtime/Internal/Data/URLData.cs +18 -1
- package/Runtime/Internal/Managers/BackendManager.cs +281 -24
- package/Runtime/Internal/Managers/DataManager.cs +208 -21
- package/Runtime/Internal/Managers/GameManager.cs +404 -19
- package/Runtime/Internal/Managers/ReplayManager.cs +118 -0
- package/Runtime/Internal/Managers/ReplayManager.cs.meta +11 -0
- package/Runtime/Internal/Managers/TimeManager.cs +78 -19
- package/Runtime/Internal/Replay/ReplayActivityRecorder.cs +182 -0
- package/Runtime/Internal/Replay/ReplayActivityRecorder.cs.meta +2 -0
- package/Runtime/Internal/Replay/ReplaySessionState.cs +238 -0
- package/Runtime/Internal/Replay/ReplaySessionState.cs.meta +2 -0
- package/Runtime/Internal/Replay.meta +8 -0
- package/Runtime/Internal/ScriptableObjects/HyperSDKSettings.cs +5 -0
- package/Runtime/Internal/UI/Animation/LoadingTextAnimation.cs +15 -1
- package/Runtime/Internal/UI/Components/ControlBar.cs +25 -2
- package/Runtime/Internal/UI/Components/PauseButton.cs +4 -1
- package/Runtime/Internal/UI/Components/ReplayController.cs +531 -0
- package/Runtime/Internal/UI/Components/ReplayController.cs.meta +2 -0
- package/Runtime/Internal/UI/Components/ReplayMarkerPulse.cs +89 -0
- package/Runtime/Internal/UI/Components/ReplayMarkerPulse.cs.meta +2 -0
- package/Runtime/Internal/UI/Components/ReplayPresentationCanvas.cs +183 -0
- package/Runtime/Internal/UI/Components/ReplayPresentationCanvas.cs.meta +2 -0
- package/Runtime/Internal/UI/Modals/LoadingFailedModal.cs +12 -0
- package/Runtime/Internal/UI/Modals/ReplayOverModal.cs +61 -0
- package/Runtime/Internal/UI/Modals/ReplayOverModal.cs.meta +2 -0
- package/Runtime/Internal/UI/Modals/ReplayPausedModal.cs +145 -0
- package/Runtime/Internal/UI/Modals/ReplayPausedModal.cs.meta +2 -0
- package/Runtime/Internal/UI/Modals/StartingInstructionModal.cs +39 -29
- package/Runtime/Public/AssemblyInfo.cs +3 -0
- package/Runtime/Public/AssemblyInfo.cs.meta +2 -0
- package/Runtime/Public/Core/HyperSDK.cs +321 -109
- package/Runtime/Public/Hyper.asmdef +2 -1
- package/Runtime/Public/Replay/ReplayPlaybackEngine.cs +610 -0
- package/Runtime/Public/Replay/ReplayPlaybackEngine.cs.meta +2 -0
- package/Runtime/Public/Replay.meta +8 -0
- package/Runtime/Resources/Fonts/Nunito/Nunito-Bold SDF.asset +500 -457
- package/Runtime/Resources/Fonts/Nunito/Nunito-Light SDF.asset +528 -521
- package/Runtime/Resources/Materials/HyperReplayScanlines.preset +340 -0
- package/Runtime/Resources/Materials/HyperReplayScanlines.preset.meta +8 -0
- package/Runtime/Resources/Materials/ReplayScanlineOverlay.mat +55 -0
- package/Runtime/Resources/Materials/ReplayScanlineOverlay.mat.meta +8 -0
- package/Runtime/Resources/Shaders/ReplayScanlineOverlay.shader +150 -0
- package/Runtime/Resources/Shaders/ReplayScanlineOverlay.shader.meta +10 -0
- package/Runtime/Resources/Sprites/Buttons/Hyper Long White Button_Normal.png +0 -0
- package/Runtime/Resources/Sprites/Buttons/Hyper Long White Button_Normal.png.meta +156 -0
- package/Runtime/Resources/Sprites/Buttons/Hyper Long White Button_Pressed.png +0 -0
- package/Runtime/Resources/Sprites/Buttons/Hyper Long White Button_Pressed.png.meta +156 -0
- package/Runtime/Resources/Sprites/Replays/Fallback avatar.png +0 -0
- package/Runtime/Resources/Sprites/Replays/Fallback avatar.png.meta +156 -0
- package/Runtime/Resources/Sprites/Replays/Replay frame.png +0 -0
- package/Runtime/Resources/Sprites/Replays/Replay frame.png.meta +156 -0
- package/Runtime/Resources/Sprites/Replays/Replay modal icon.png +0 -0
- package/Runtime/Resources/Sprites/Replays/Replay modal icon.png.meta +156 -0
- package/Runtime/Resources/Sprites/Replays/Replay.png +0 -0
- package/Runtime/Resources/Sprites/Replays/Replay.png.meta +156 -0
- package/Runtime/Resources/Sprites/Replays.meta +8 -0
- package/Runtime/Resources/UIPrefabs/ControlBar.prefab +169 -2
- package/Runtime/Resources/UIPrefabs/GamePausedModal_Battle.prefab +1 -1
- package/Runtime/Resources/UIPrefabs/ReplayController.prefab +1388 -0
- package/Runtime/Resources/UIPrefabs/ReplayController.prefab.meta +7 -0
- package/Runtime/Resources/UIPrefabs/ReplayInputBlocker.prefab +90 -0
- package/Runtime/Resources/UIPrefabs/ReplayInputBlocker.prefab.meta +7 -0
- package/Runtime/Resources/UIPrefabs/ReplayOverModal.prefab +958 -0
- package/Runtime/Resources/UIPrefabs/ReplayOverModal.prefab.meta +7 -0
- package/Runtime/Resources/UIPrefabs/ReplayPausedModal.prefab +2194 -0
- package/Runtime/Resources/UIPrefabs/ReplayPausedModal.prefab.meta +7 -0
- package/Runtime/Resources/UIPrefabs/ReplaySeekOverlay.prefab +189 -0
- package/Runtime/Resources/UIPrefabs/ReplaySeekOverlay.prefab.meta +7 -0
- package/Runtime/Resources/UIPrefabs/SnackBar.prefab +2 -2
- package/Runtime/Shared/AssemblyInfo.cs +5 -0
- package/Runtime/Shared/AssemblyInfo.cs.meta +11 -0
- package/Runtime/Shared/DTOs/Enums.cs +8 -3
- package/Runtime/Shared/DTOs/Score/ScoreSnapshot.cs +13 -1
- package/Runtime/Shared/Replay/IReplayPlaybackEngine.cs +40 -0
- package/Runtime/Shared/Replay/IReplayPlaybackEngine.cs.meta +11 -0
- package/Runtime/Shared/Replay.meta +8 -0
- package/Samples~/LaneRunner/LaneRunnerSample.unity +1604 -0
- package/Samples~/LaneRunner/LaneRunnerSample.unity.meta +7 -0
- package/Samples~/LaneRunner/Materials/DoubleScore.mat +86 -0
- package/Samples~/LaneRunner/Materials/DoubleScore.mat.meta +8 -0
- package/Samples~/LaneRunner/Materials/Ground.mat +84 -0
- package/Samples~/LaneRunner/Materials/Ground.mat.meta +8 -0
- package/Samples~/LaneRunner/Materials/Lane.mat +84 -0
- package/Samples~/LaneRunner/Materials/Lane.mat.meta +8 -0
- package/Samples~/LaneRunner/Materials/LaneMarker.mat +84 -0
- package/Samples~/LaneRunner/Materials/LaneMarker.mat.meta +8 -0
- package/Samples~/LaneRunner/Materials/Obstacle.mat +84 -0
- package/Samples~/LaneRunner/Materials/Obstacle.mat.meta +8 -0
- package/Samples~/LaneRunner/Materials/Player.mat +84 -0
- package/Samples~/LaneRunner/Materials/Player.mat.meta +8 -0
- package/Samples~/LaneRunner/Materials.meta +8 -0
- package/Samples~/LaneRunner/Prefabs/DoubleScorePickup.prefab +152 -0
- package/Samples~/LaneRunner/Prefabs/DoubleScorePickup.prefab.meta +7 -0
- package/Samples~/LaneRunner/Prefabs/Obstacle.prefab +152 -0
- package/Samples~/LaneRunner/Prefabs/Obstacle.prefab.meta +7 -0
- package/Samples~/LaneRunner/Prefabs.meta +8 -0
- package/Samples~/LaneRunner/README.md +42 -0
- package/Samples~/LaneRunner/README.md.meta +7 -0
- package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerCommand.cs +9 -0
- package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerCommand.cs.meta +2 -0
- package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerGame.cs +93 -0
- package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerGame.cs.meta +2 -0
- package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerPlayer.cs +169 -0
- package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerPlayer.cs.meta +2 -0
- package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerSpawner.cs +112 -0
- package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerSpawner.cs.meta +2 -0
- package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerTrackItem.cs +41 -0
- package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerTrackItem.cs.meta +2 -0
- package/Samples~/LaneRunner/Scripts/Gameplay.meta +8 -0
- package/Samples~/LaneRunner/Scripts/Hyper/LaneRunnerReplayAdapter.cs +62 -0
- package/Samples~/LaneRunner/Scripts/Hyper/LaneRunnerReplayAdapter.cs.meta +2 -0
- package/Samples~/LaneRunner/Scripts/Hyper/LaneRunnerTournamentAdapter.cs +72 -0
- package/Samples~/LaneRunner/Scripts/Hyper/LaneRunnerTournamentAdapter.cs.meta +2 -0
- package/Samples~/LaneRunner/Scripts/Hyper.meta +8 -0
- package/Samples~/LaneRunner/Scripts.meta +8 -0
- package/Tests.meta +8 -0
- package/package.json +8 -1
|
@@ -4,6 +4,7 @@ using System.Collections.Generic;
|
|
|
4
4
|
using UnityEngine;
|
|
5
5
|
using UnityEngine.SceneManagement;
|
|
6
6
|
using Hyper.Internal.Managers;
|
|
7
|
+
using Hyper.Shared;
|
|
7
8
|
|
|
8
9
|
namespace Hyper.Internal
|
|
9
10
|
{
|
|
@@ -19,6 +20,8 @@ namespace Hyper.Internal
|
|
|
19
20
|
|
|
20
21
|
private static bool _isInitialized;
|
|
21
22
|
private static bool _isSoftRestarting;
|
|
23
|
+
private static bool _replayRandomResetPending;
|
|
24
|
+
private static bool _replayCustomRestartPending;
|
|
22
25
|
private static bool _customLoadingComplete;
|
|
23
26
|
private static bool _customLoadingActive;
|
|
24
27
|
private static float _customLoadingProgress;
|
|
@@ -30,6 +33,8 @@ namespace Hyper.Internal
|
|
|
30
33
|
private static DataManager _dataManager;
|
|
31
34
|
private static EventManager _eventManager;
|
|
32
35
|
private static TimerManager _timerManager;
|
|
36
|
+
private static ReplayManager _replayManager;
|
|
37
|
+
private static double _fixedSimulationTimeSeconds;
|
|
33
38
|
|
|
34
39
|
internal static bool IsInitialized => _isInitialized;
|
|
35
40
|
internal static bool IsSoftRestarting
|
|
@@ -37,6 +42,7 @@ namespace Hyper.Internal
|
|
|
37
42
|
get => _isSoftRestarting;
|
|
38
43
|
set => _isSoftRestarting = value;
|
|
39
44
|
}
|
|
45
|
+
internal static bool IsReplayCustomRestartPending => _replayCustomRestartPending;
|
|
40
46
|
internal static bool CustomLoadingComplete
|
|
41
47
|
{
|
|
42
48
|
get => _customLoadingComplete;
|
|
@@ -55,6 +61,8 @@ namespace Hyper.Internal
|
|
|
55
61
|
internal static DataManager DataManager => _dataManager;
|
|
56
62
|
internal static EventManager EventManager => _eventManager;
|
|
57
63
|
internal static TimerManager Timer => _timerManager;
|
|
64
|
+
internal static ReplayManager ReplayManager => _replayManager;
|
|
65
|
+
internal static double FixedSimulationTimeSeconds => _fixedSimulationTimeSeconds;
|
|
58
66
|
|
|
59
67
|
internal static event Action CustomLoadingStarted
|
|
60
68
|
{
|
|
@@ -114,6 +122,85 @@ namespace Hyper.Internal
|
|
|
114
122
|
}
|
|
115
123
|
}
|
|
116
124
|
|
|
125
|
+
/// <summary>
|
|
126
|
+
/// Restarts replay playback through HyperLoader while preserving replay session data.
|
|
127
|
+
/// </summary>
|
|
128
|
+
internal static void RestartReplaySession()
|
|
129
|
+
{
|
|
130
|
+
ReplaySessionState.ResetForWatchAgain();
|
|
131
|
+
RestartReplay();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
internal static void SeekReplaySession(float targetProgress, float resumeSpeed, float seekSpeed)
|
|
135
|
+
{
|
|
136
|
+
if (!ReplaySessionState.IsActive || ReplaySessionState.HasEnded) return;
|
|
137
|
+
|
|
138
|
+
ReplaySessionState.RequestSeek(targetProgress, resumeSpeed, seekSpeed);
|
|
139
|
+
ReplaySessionState.ResetForSeekRestart();
|
|
140
|
+
RestartReplay();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private static void RestartReplay()
|
|
144
|
+
{
|
|
145
|
+
_isSoftRestarting = true;
|
|
146
|
+
Time.timeScale = 1f;
|
|
147
|
+
|
|
148
|
+
_replayCustomRestartPending = false;
|
|
149
|
+
_replayRandomResetPending = false;
|
|
150
|
+
_replayManager?.PrepareForRestart();
|
|
151
|
+
|
|
152
|
+
if (_host != null)
|
|
153
|
+
{
|
|
154
|
+
_host.StartCoroutine(RestartSessionCoroutine());
|
|
155
|
+
}
|
|
156
|
+
else
|
|
157
|
+
{
|
|
158
|
+
ClearAllDontDestroyOnLoadObjects();
|
|
159
|
+
SceneManager.LoadScene("HyperLoader");
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
internal static void ResetReplayRandomState()
|
|
164
|
+
{
|
|
165
|
+
int originalSeed = unchecked((int)ReplaySessionState.OriginalRandomSeed);
|
|
166
|
+
if (HyperRandom.CurrentSeed == originalSeed)
|
|
167
|
+
{
|
|
168
|
+
// Preserve references held by game code while rewinding every stream.
|
|
169
|
+
HyperRandom.ResetAll();
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
HyperRandom.Init(ReplaySessionState.OriginalRandomSeed);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
internal static void ApplyPendingReplayRandomReset()
|
|
177
|
+
{
|
|
178
|
+
if (!_replayRandomResetPending) return;
|
|
179
|
+
|
|
180
|
+
_replayRandomResetPending = false;
|
|
181
|
+
ResetReplayRandomState();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
internal static void DeferReplayRandomReset()
|
|
185
|
+
{
|
|
186
|
+
_replayRandomResetPending = true;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
internal static void CompleteCustomRestart()
|
|
190
|
+
{
|
|
191
|
+
if (!_replayCustomRestartPending || _host == null) return;
|
|
192
|
+
|
|
193
|
+
_replayCustomRestartPending = false;
|
|
194
|
+
_host.StartCoroutine(CompleteReplayCustomRestartNextFrame());
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
private static IEnumerator CompleteReplayCustomRestartNextFrame()
|
|
198
|
+
{
|
|
199
|
+
yield return null;
|
|
200
|
+
_gameManager?.CompleteReplayCustomRestart();
|
|
201
|
+
_isSoftRestarting = false;
|
|
202
|
+
}
|
|
203
|
+
|
|
117
204
|
/// <summary>
|
|
118
205
|
/// Coroutine-based restart that properly unloads all scenes before reloading.
|
|
119
206
|
/// Handles async scene operations and ensures clean state before restart.
|
|
@@ -122,6 +209,7 @@ namespace Hyper.Internal
|
|
|
122
209
|
{
|
|
123
210
|
yield return UnloadAllScenesCoroutine();
|
|
124
211
|
|
|
212
|
+
Clear();
|
|
125
213
|
ClearAllDontDestroyOnLoadObjects();
|
|
126
214
|
SceneManager.LoadScene("HyperLoader");
|
|
127
215
|
}
|
|
@@ -302,6 +390,9 @@ namespace Hyper.Internal
|
|
|
302
390
|
_gameManager = _singletonObject.AddComponent<GameManager>();
|
|
303
391
|
_gameManager.Initialize();
|
|
304
392
|
|
|
393
|
+
_replayManager = new ReplayManager();
|
|
394
|
+
_replayManager.Initialize(_eventManager);
|
|
395
|
+
|
|
305
396
|
_timerManager = new TimerManager();
|
|
306
397
|
_timerManager.Initialize();
|
|
307
398
|
_timerManager.InjectEventHooks();
|
|
@@ -313,6 +404,14 @@ namespace Hyper.Internal
|
|
|
313
404
|
_timerManager?.Update();
|
|
314
405
|
}
|
|
315
406
|
|
|
407
|
+
internal static void FixedTick()
|
|
408
|
+
{
|
|
409
|
+
if (!_isInitialized) return;
|
|
410
|
+
_fixedSimulationTimeSeconds += Time.fixedDeltaTime;
|
|
411
|
+
_replayManager?.FixedTick();
|
|
412
|
+
_timerManager?.FixedTick();
|
|
413
|
+
}
|
|
414
|
+
|
|
316
415
|
internal static void Clear()
|
|
317
416
|
{
|
|
318
417
|
if (_host == null)
|
|
@@ -323,6 +422,9 @@ namespace Hyper.Internal
|
|
|
323
422
|
|
|
324
423
|
_timerManager = null;
|
|
325
424
|
|
|
425
|
+
_replayManager?.Cleanup();
|
|
426
|
+
_replayManager = null;
|
|
427
|
+
|
|
326
428
|
_dataManager?.Cleanup();
|
|
327
429
|
_dataManager = null;
|
|
328
430
|
|
|
@@ -339,6 +441,9 @@ namespace Hyper.Internal
|
|
|
339
441
|
_customLoadingComplete = false;
|
|
340
442
|
_customLoadingActive = false;
|
|
341
443
|
_customLoadingProgress = 0f;
|
|
444
|
+
_replayRandomResetPending = false;
|
|
445
|
+
_replayCustomRestartPending = false;
|
|
446
|
+
_fixedSimulationTimeSeconds = 0d;
|
|
342
447
|
|
|
343
448
|
if (_singletonObject != null)
|
|
344
449
|
{
|
|
@@ -364,6 +469,11 @@ namespace Hyper.Internal
|
|
|
364
469
|
{
|
|
365
470
|
if (obj.scene.name == "DontDestroyOnLoad" && obj.transform.parent == null)
|
|
366
471
|
{
|
|
472
|
+
if (ReplaySessionState.IsSeeking && ReplayPresentationCanvas.IsPresentationRoot(obj))
|
|
473
|
+
{
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
476
|
+
|
|
367
477
|
UnityEngine.Object.Destroy(obj);
|
|
368
478
|
}
|
|
369
479
|
}
|
|
@@ -372,6 +482,7 @@ namespace Hyper.Internal
|
|
|
372
482
|
internal static void OnHostDestroyed(HyperRuntimeHost host)
|
|
373
483
|
{
|
|
374
484
|
if (_host != host) return;
|
|
485
|
+
_replayManager?.Cleanup();
|
|
375
486
|
ResetStaticState();
|
|
376
487
|
}
|
|
377
488
|
|
|
@@ -385,11 +496,15 @@ namespace Hyper.Internal
|
|
|
385
496
|
_gameManager = null;
|
|
386
497
|
_backendManager = null;
|
|
387
498
|
_dataManager = null;
|
|
499
|
+
_replayManager = null;
|
|
388
500
|
_gameContent = null;
|
|
389
501
|
_isInitialized = false;
|
|
390
502
|
_customLoadingComplete = false;
|
|
391
503
|
_customLoadingActive = false;
|
|
392
504
|
_customLoadingProgress = 0f;
|
|
505
|
+
_replayRandomResetPending = false;
|
|
506
|
+
_replayCustomRestartPending = false;
|
|
507
|
+
_fixedSimulationTimeSeconds = 0d;
|
|
393
508
|
}
|
|
394
509
|
}
|
|
395
510
|
}
|
|
@@ -6,6 +6,7 @@ namespace Hyper.Internal
|
|
|
6
6
|
/// Lightweight MonoBehaviour that allows the internal runtime to participate in
|
|
7
7
|
/// Unity's update loop without exposing managed objects to public assemblies.
|
|
8
8
|
/// </summary>
|
|
9
|
+
[DefaultExecutionOrder(-10000)]
|
|
9
10
|
internal sealed class HyperRuntimeHost : MonoBehaviour
|
|
10
11
|
{
|
|
11
12
|
private void Awake()
|
|
@@ -18,6 +19,11 @@ namespace Hyper.Internal
|
|
|
18
19
|
HyperRuntime.Tick();
|
|
19
20
|
}
|
|
20
21
|
|
|
22
|
+
private void FixedUpdate()
|
|
23
|
+
{
|
|
24
|
+
HyperRuntime.FixedTick();
|
|
25
|
+
}
|
|
26
|
+
|
|
21
27
|
private void OnDestroy()
|
|
22
28
|
{
|
|
23
29
|
HyperRuntime.OnHostDestroyed(this);
|
|
@@ -27,6 +27,11 @@ namespace Hyper.Internal
|
|
|
27
27
|
/// </summary>
|
|
28
28
|
public bool isPVP => GameMode == GameMode.Battle;
|
|
29
29
|
|
|
30
|
+
/// <summary>
|
|
31
|
+
/// Returns true for modes that use battle gameplay, scenes, and timers.
|
|
32
|
+
/// </summary>
|
|
33
|
+
public bool IsBattleGameplay => GameMode == GameMode.Battle || GameMode == GameMode.Replay;
|
|
34
|
+
|
|
30
35
|
#endregion
|
|
31
36
|
|
|
32
37
|
#region Internal Fields
|
|
@@ -114,5 +119,17 @@ namespace Hyper.Internal
|
|
|
114
119
|
{
|
|
115
120
|
GameMode = GameMode.Practice;
|
|
116
121
|
}
|
|
122
|
+
|
|
123
|
+
public void SetModeAsReplay()
|
|
124
|
+
{
|
|
125
|
+
GameMode = GameMode.Replay;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
internal void SetModeFromLaunchFlags(bool isPVP, bool isReplay)
|
|
129
|
+
{
|
|
130
|
+
GameMode = isReplay
|
|
131
|
+
? GameMode.Replay
|
|
132
|
+
: isPVP ? GameMode.Battle : GameMode.Practice;
|
|
133
|
+
}
|
|
117
134
|
}
|
|
118
|
-
}
|
|
135
|
+
}
|
|
@@ -5,6 +5,7 @@ using System.Collections;
|
|
|
5
5
|
using System.Collections.Generic;
|
|
6
6
|
using System.Linq;
|
|
7
7
|
using System.Globalization;
|
|
8
|
+
using System.IO;
|
|
8
9
|
using System.Runtime.InteropServices;
|
|
9
10
|
using System.Text;
|
|
10
11
|
using UnityEngine;
|
|
@@ -139,6 +140,233 @@ namespace Hyper.Internal.Managers
|
|
|
139
140
|
|
|
140
141
|
#endregion
|
|
141
142
|
|
|
143
|
+
#region Replay
|
|
144
|
+
|
|
145
|
+
internal IEnumerator FetchReplay(
|
|
146
|
+
string entryId,
|
|
147
|
+
Action onSuccess,
|
|
148
|
+
Action<string> onFailure)
|
|
149
|
+
{
|
|
150
|
+
if (string.IsNullOrWhiteSpace(entryId))
|
|
151
|
+
{
|
|
152
|
+
onFailure?.Invoke("Replay entryId is missing.");
|
|
153
|
+
yield break;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
string escapedEntryId = UnityWebRequest.EscapeURL(entryId.Trim());
|
|
157
|
+
string replayAccessUrl =
|
|
158
|
+
$"https://staging-ingress.nestechops.com/hyper/tournaments/HYP/me/tournaments/entries/{escapedEntryId}/replay";
|
|
159
|
+
string lastError = "Unknown replay download error.";
|
|
160
|
+
|
|
161
|
+
for (int attempt = 1; attempt <= MAX_RETRY_COUNT; attempt++)
|
|
162
|
+
{
|
|
163
|
+
using (UnityWebRequest request = UnityWebRequest.Get(replayAccessUrl))
|
|
164
|
+
{
|
|
165
|
+
request.timeout = GENERAL_REQUEST_TIMEOUT;
|
|
166
|
+
request.SetRequestHeader(
|
|
167
|
+
"Accept",
|
|
168
|
+
"application/x-ndjson, application/ndjson, text/plain");
|
|
169
|
+
|
|
170
|
+
yield return request.SendWebRequest();
|
|
171
|
+
|
|
172
|
+
if (request.result == UnityWebRequest.Result.Success)
|
|
173
|
+
{
|
|
174
|
+
if (TryParseReplayResponse(
|
|
175
|
+
request.downloadHandler.text,
|
|
176
|
+
out ReplayResponse replay,
|
|
177
|
+
out string parseError))
|
|
178
|
+
{
|
|
179
|
+
ReplaySessionState.SetMetadata(
|
|
180
|
+
replay.PlayerName,
|
|
181
|
+
replay.AvatarUrl,
|
|
182
|
+
replay.Seed);
|
|
183
|
+
ReplaySessionState.SetReplayData(replay.ActivityNdjson);
|
|
184
|
+
HyperDebug.Log($"Replay downloaded for entry {entryId}.");
|
|
185
|
+
onSuccess?.Invoke();
|
|
186
|
+
yield break;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
lastError = parseError;
|
|
190
|
+
}
|
|
191
|
+
else
|
|
192
|
+
{
|
|
193
|
+
lastError = $"HTTP {request.responseCode}: {request.error}";
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
HyperDebug.LogWarning(
|
|
198
|
+
$"Replay download attempt {attempt}/{MAX_RETRY_COUNT} failed: {lastError}");
|
|
199
|
+
|
|
200
|
+
if (attempt < MAX_RETRY_COUNT)
|
|
201
|
+
{
|
|
202
|
+
yield return new WaitForSecondsRealtime(0.5f);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
onFailure?.Invoke(lastError);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
internal IEnumerator FetchReplayAvatar(
|
|
210
|
+
string avatarUrl,
|
|
211
|
+
Action<Texture2D> onSuccess,
|
|
212
|
+
Action<string> onFailure)
|
|
213
|
+
{
|
|
214
|
+
if (string.IsNullOrWhiteSpace(avatarUrl))
|
|
215
|
+
{
|
|
216
|
+
onSuccess?.Invoke(null);
|
|
217
|
+
yield break;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
string lastError = "Unknown replay avatar download error.";
|
|
221
|
+
for (int attempt = 1; attempt <= MAX_RETRY_COUNT; attempt++)
|
|
222
|
+
{
|
|
223
|
+
using (UnityWebRequest request = UnityWebRequestTexture.GetTexture(avatarUrl, false))
|
|
224
|
+
{
|
|
225
|
+
request.timeout = GENERAL_REQUEST_TIMEOUT;
|
|
226
|
+
yield return request.SendWebRequest();
|
|
227
|
+
|
|
228
|
+
if (request.result == UnityWebRequest.Result.Success)
|
|
229
|
+
{
|
|
230
|
+
Texture2D avatar = DownloadHandlerTexture.GetContent(request);
|
|
231
|
+
if (avatar != null)
|
|
232
|
+
{
|
|
233
|
+
HyperDebug.Log("Replay avatar downloaded.");
|
|
234
|
+
onSuccess?.Invoke(avatar);
|
|
235
|
+
yield break;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
lastError = "The replay avatar response contained no texture.";
|
|
239
|
+
}
|
|
240
|
+
else
|
|
241
|
+
{
|
|
242
|
+
lastError = $"HTTP {request.responseCode}: {request.error}";
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
HyperDebug.LogWarning(
|
|
247
|
+
$"Replay avatar attempt {attempt}/{MAX_RETRY_COUNT} failed: {lastError}");
|
|
248
|
+
|
|
249
|
+
if (attempt < MAX_RETRY_COUNT)
|
|
250
|
+
{
|
|
251
|
+
yield return new WaitForSecondsRealtime(0.5f);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
onFailure?.Invoke(lastError);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
internal IEnumerator CheckInternetConnection(Action<bool> onComplete)
|
|
259
|
+
{
|
|
260
|
+
using (UnityWebRequest request = UnityWebRequest.Head(GetServerUrl()))
|
|
261
|
+
{
|
|
262
|
+
request.timeout = 2;
|
|
263
|
+
yield return request.SendWebRequest();
|
|
264
|
+
|
|
265
|
+
// Any HTTP response proves that the backend was reachable. A 4xx/5xx
|
|
266
|
+
// response is still connectivity; replay validation belongs to bootstrap.
|
|
267
|
+
bool isConnected = request.responseCode > 0 &&
|
|
268
|
+
request.result != UnityWebRequest.Result.ConnectionError;
|
|
269
|
+
onComplete?.Invoke(isConnected);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
internal static bool TryParseReplayResponse(
|
|
274
|
+
string ndjson,
|
|
275
|
+
out ReplayResponse replay,
|
|
276
|
+
out string error)
|
|
277
|
+
{
|
|
278
|
+
replay = default;
|
|
279
|
+
error = null;
|
|
280
|
+
|
|
281
|
+
if (string.IsNullOrWhiteSpace(ndjson))
|
|
282
|
+
{
|
|
283
|
+
error = "The replay response was empty.";
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
using (var reader = new StringReader(ndjson))
|
|
288
|
+
{
|
|
289
|
+
string seedLine = reader.ReadLine();
|
|
290
|
+
string nameLine = reader.ReadLine();
|
|
291
|
+
string avatarLine = reader.ReadLine();
|
|
292
|
+
string activities = reader.ReadToEnd();
|
|
293
|
+
|
|
294
|
+
if (seedLine == null || nameLine == null || avatarLine == null)
|
|
295
|
+
{
|
|
296
|
+
error = "The replay response is missing required metadata.";
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
try
|
|
301
|
+
{
|
|
302
|
+
JArray seedData = JArray.Parse(seedLine);
|
|
303
|
+
JArray nameData = JArray.Parse(nameLine);
|
|
304
|
+
JArray avatarData = JArray.Parse(avatarLine);
|
|
305
|
+
|
|
306
|
+
if (seedData.Count != 1 ||
|
|
307
|
+
seedData[0].Type != JTokenType.Integer ||
|
|
308
|
+
!long.TryParse(
|
|
309
|
+
seedData[0].ToString(),
|
|
310
|
+
NumberStyles.Integer,
|
|
311
|
+
CultureInfo.InvariantCulture,
|
|
312
|
+
out long seed))
|
|
313
|
+
{
|
|
314
|
+
error = "Replay metadata contains an invalid seed.";
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (nameData.Count != 1 || nameData[0].Type != JTokenType.String)
|
|
319
|
+
{
|
|
320
|
+
error = "Replay metadata contains an invalid player name.";
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (avatarData.Count != 1 || avatarData[0].Type != JTokenType.String)
|
|
325
|
+
{
|
|
326
|
+
error = "Replay metadata contains an invalid avatar URL.";
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (string.IsNullOrWhiteSpace(activities))
|
|
331
|
+
{
|
|
332
|
+
error = "The replay response contains no gameplay activities.";
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
replay = new ReplayResponse(
|
|
337
|
+
seed,
|
|
338
|
+
nameData[0].Value<string>() ?? string.Empty,
|
|
339
|
+
avatarData[0].Value<string>() ?? string.Empty,
|
|
340
|
+
activities);
|
|
341
|
+
|
|
342
|
+
return true;
|
|
343
|
+
}
|
|
344
|
+
catch (JsonException)
|
|
345
|
+
{
|
|
346
|
+
error = "Replay metadata contains invalid JSON.";
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
internal readonly struct ReplayResponse
|
|
353
|
+
{
|
|
354
|
+
internal long Seed { get; }
|
|
355
|
+
internal string PlayerName { get; }
|
|
356
|
+
internal string AvatarUrl { get; }
|
|
357
|
+
internal string ActivityNdjson { get; }
|
|
358
|
+
|
|
359
|
+
internal ReplayResponse(long seed, string playerName, string avatarUrl, string activityNdjson)
|
|
360
|
+
{
|
|
361
|
+
Seed = seed;
|
|
362
|
+
PlayerName = playerName;
|
|
363
|
+
AvatarUrl = avatarUrl;
|
|
364
|
+
ActivityNdjson = activityNdjson;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
#endregion
|
|
369
|
+
|
|
142
370
|
#region Session Management
|
|
143
371
|
|
|
144
372
|
private Coroutine _createSessionCoroutine;
|
|
@@ -832,6 +1060,14 @@ namespace Hyper.Internal.Managers
|
|
|
832
1060
|
|
|
833
1061
|
public void StartReconnectionCoroutine()
|
|
834
1062
|
{
|
|
1063
|
+
if (_gameManager == null) _gameManager = HyperRuntime.GameManager;
|
|
1064
|
+
|
|
1065
|
+
if (_gameManager != null && _gameManager.IsReplayMode)
|
|
1066
|
+
{
|
|
1067
|
+
_gameManager.RetryReplayLoading();
|
|
1068
|
+
return;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
835
1071
|
if (_reconnectionCoroutine != null)
|
|
836
1072
|
{
|
|
837
1073
|
HyperDebug.LogWarning("Reconnection already in progress");
|
|
@@ -1075,23 +1311,9 @@ namespace Hyper.Internal.Managers
|
|
|
1075
1311
|
_finalScoreSubmitted = false;
|
|
1076
1312
|
_isWaitingForFinalScoreResponse = true;
|
|
1077
1313
|
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
if (currentScoreDataScoreTime > lastCapturedScoreDataScoreTime)
|
|
1083
|
-
{
|
|
1084
|
-
finalScoreData = _dataManager.CurrentScoreData;
|
|
1085
|
-
finalScoreData.isFinal = true;
|
|
1086
|
-
}
|
|
1087
|
-
else
|
|
1088
|
-
{
|
|
1089
|
-
finalScoreData = _lastSentBatch.Last();
|
|
1090
|
-
finalScoreData.isFinal = true;
|
|
1091
|
-
finalScoreData.timestampUtc = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
|
|
1092
|
-
finalScoreData.scoreSnapshot.scoreDelta = 0;
|
|
1093
|
-
finalScoreData.scoreSnapshot.gameplayModifiers = new GameplayModifiers();
|
|
1094
|
-
}
|
|
1314
|
+
ScoreData finalScoreData = CreateFinalScoreMarker(
|
|
1315
|
+
_dataManager.CurrentScoreData,
|
|
1316
|
+
_lastSentBatch.LastOrDefault());
|
|
1095
1317
|
|
|
1096
1318
|
// For consistency with per-second and backlog submissions, always send
|
|
1097
1319
|
// the final score as an array of ScoreData.
|
|
@@ -1207,8 +1429,9 @@ namespace Hyper.Internal.Managers
|
|
|
1207
1429
|
|
|
1208
1430
|
_gameManager.GameOverModalComponent.Init();
|
|
1209
1431
|
|
|
1210
|
-
var finalScoreData =
|
|
1211
|
-
|
|
1432
|
+
var finalScoreData = CreateFinalScoreMarker(
|
|
1433
|
+
_dataManager.CurrentScoreData,
|
|
1434
|
+
_lastSentBatch.LastOrDefault());
|
|
1212
1435
|
|
|
1213
1436
|
yield return new WaitForSecondsRealtime(3f);
|
|
1214
1437
|
|
|
@@ -1265,10 +1488,6 @@ namespace Hyper.Internal.Managers
|
|
|
1265
1488
|
// Scores will be flushed when the connection is restored.
|
|
1266
1489
|
if (!isSocketConnected)
|
|
1267
1490
|
{
|
|
1268
|
-
#if UNITY_WEBGL && !UNITY_EDITOR
|
|
1269
|
-
int pendingCount = _dataManager?.PendingScores?.Count ?? 0;
|
|
1270
|
-
HyperDebug.Log($"[Score] Socket disconnected - accumulating scores for offline batch. PendingCount={pendingCount}");
|
|
1271
|
-
#endif
|
|
1272
1491
|
_isWaitingForTokenLogEmitted = false;
|
|
1273
1492
|
_hasLoggedWaitingForGameStartAck = false;
|
|
1274
1493
|
yield return _scoreSendInterval;
|
|
@@ -1524,7 +1743,8 @@ namespace Hyper.Internal.Managers
|
|
|
1524
1743
|
var clonedSnapshot = new ScoreSnapshot
|
|
1525
1744
|
{
|
|
1526
1745
|
scoreTime = snapshot.scoreTime,
|
|
1527
|
-
scoreDelta = snapshot.scoreDelta
|
|
1746
|
+
scoreDelta = snapshot.scoreDelta,
|
|
1747
|
+
playActivities = ClonePlayActivities(snapshot.playActivities)
|
|
1528
1748
|
};
|
|
1529
1749
|
|
|
1530
1750
|
if (snapshot.gameplayModifiers != null)
|
|
@@ -1548,6 +1768,43 @@ namespace Hyper.Internal.Managers
|
|
|
1548
1768
|
return cloned;
|
|
1549
1769
|
}
|
|
1550
1770
|
|
|
1771
|
+
internal static ScoreData CreateFinalScoreMarker(ScoreData currentScore, ScoreData lastSentScore)
|
|
1772
|
+
{
|
|
1773
|
+
ScoreData scoreSource = currentScore ?? lastSentScore;
|
|
1774
|
+
float currentScoreTime = currentScore?.scoreSnapshot?.scoreTime ?? 0f;
|
|
1775
|
+
float lastSentScoreTime = lastSentScore?.scoreSnapshot?.scoreTime ?? 0f;
|
|
1776
|
+
|
|
1777
|
+
return new ScoreData
|
|
1778
|
+
{
|
|
1779
|
+
cumulativeScore = scoreSource?.cumulativeScore ?? 0,
|
|
1780
|
+
isFinal = true,
|
|
1781
|
+
timestampUtc = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(),
|
|
1782
|
+
scoreSnapshot = new ScoreSnapshot
|
|
1783
|
+
{
|
|
1784
|
+
// CurrentScoreData is refreshed only when the score changes, while
|
|
1785
|
+
// periodic snapshots can continue afterward. Never let the final
|
|
1786
|
+
// completion marker move the score timeline backward.
|
|
1787
|
+
scoreTime = Mathf.Max(currentScoreTime, lastSentScoreTime),
|
|
1788
|
+
scoreDelta = 0,
|
|
1789
|
+
gameplayModifiers = new GameplayModifiers(),
|
|
1790
|
+
playActivities = null
|
|
1791
|
+
}
|
|
1792
|
+
};
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
private static List<object[]> ClonePlayActivities(List<object[]> source)
|
|
1796
|
+
{
|
|
1797
|
+
if (source == null || source.Count == 0) return null;
|
|
1798
|
+
|
|
1799
|
+
var clone = new List<object[]>(source.Count);
|
|
1800
|
+
for (int i = 0; i < source.Count; i++)
|
|
1801
|
+
{
|
|
1802
|
+
object[] activity = source[i];
|
|
1803
|
+
clone.Add(activity == null ? null : (object[])activity.Clone());
|
|
1804
|
+
}
|
|
1805
|
+
return clone;
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1551
1808
|
[Serializable]
|
|
1552
1809
|
private class WebSocketScoreMessage
|
|
1553
1810
|
{
|