2021-03-13 03:23:58 +00:00
using System ;
2026-06-21 09:37:54 +01:00
using GameCult.Aetheria.EveRuntime ;
2026-06-21 09:54:15 +01:00
using GameCult.Aetheria.State.Verse ;
2026-06-16 19:05:00 +01:00
using GameCult.Eve.Surface ;
2021-03-13 03:23:58 +00:00
using TMPro ;
2021-03-15 11:40:46 +00:00
using UniRx ;
2021-03-13 03:23:58 +00:00
using UnityEngine ;
2021-03-14 03:56:49 +00:00
using UnityEngine.SceneManagement ;
2021-03-13 03:23:58 +00:00
using UnityEngine.UI ;
2026-06-16 19:05:00 +01:00
using UnityEngine.UIElements ;
2021-03-13 03:23:58 +00:00
public class MainMenu : MonoBehaviour
{
2022-07-18 08:44:54 +01:00
public VolumeCloudRenderer CloudRenderer ;
2021-03-20 04:20:17 +00:00
public GameSettings Settings ;
2021-03-15 11:40:46 +00:00
public ConfirmationDialog Dialog ;
2021-03-14 03:56:49 +00:00
public bool InGame ;
2026-06-17 04:57:56 +01:00
2026-06-16 21:08:20 +01:00
private UIDocument _menuSurfaceDocument ;
2026-06-21 09:37:54 +01:00
private readonly AetheriaEveUnitySurfaceChrome _menuSurfaceChrome = new AetheriaEveUnitySurfaceChrome
{
RootAlignItems = Align . Center ,
RootJustifyContent = Justify . Center ,
RootPaddingLeft = 24f ,
RootPaddingRight = 24f ,
RootPaddingTop = 24f ,
RootPaddingBottom = 24f ,
RootBackgroundColor = new Color ( 0f , 0f , 0f , 0.6f ) ,
Width = 560f ,
MinWidth = 0f ,
MaxWidth = 560f ,
PaddingLeft = 20f ,
PaddingRight = 20f ,
PaddingTop = 20f ,
PaddingBottom = 20f ,
BorderRadius = 0f ,
BorderWidth = 0f ,
BackgroundColor = new Color ( 0.08f , 0.1f , 0.14f , 0.96f )
} ;
2021-03-13 03:23:58 +00:00
void Start ( )
{
ShowMain ( ) ;
2021-03-14 03:56:49 +00:00
}
2026-06-17 04:57:56 +01:00
private void ShowMain ( )
2021-03-14 03:56:49 +00:00
{
2026-06-17 10:39:28 +01:00
var stateBoot = CurrentStateBoot ( ) ;
2026-06-21 09:37:54 +01:00
var frame = LatestDaemonFrame ( stateBoot ) ;
var verseHost = LatestVerseHostSettings ( stateBoot ) ;
2026-06-21 21:52:46 +01:00
var playerSettings = LatestPlayerSettings ( stateBoot ) ;
2026-06-17 10:39:28 +01:00
RenderMenuSurface (
2026-06-21 09:37:54 +01:00
AetheriaRuntimeMainMenuSurfaceBuilder . BuildRoot (
2026-06-21 21:52:46 +01:00
AetheriaRuntimeMainMenuSurfaceBuilder . ProjectRoot (
2026-06-21 09:37:54 +01:00
stateBoot ,
frame ,
verseHost ,
2026-06-21 21:52:46 +01:00
playerSettings ,
2026-06-21 09:37:54 +01:00
CanOpenRuntimeInputScreen ( ) ,
2026-06-21 21:52:46 +01:00
InGame ,
DateTime . UtcNow . ToString ( "O" ) ) ) ,
2026-06-17 10:39:28 +01:00
HandleMainSurfaceCommand ) ;
2021-03-14 03:56:49 +00:00
}
2026-06-17 04:57:56 +01:00
private void HandleMainSurfaceCommand ( EveSurfaceCommandRequest request )
2021-03-14 03:56:49 +00:00
{
2026-06-21 09:37:54 +01:00
if ( ! AetheriaRuntimeMainMenuSurfaceCommands . TryRead ( request , out var command ) )
2026-06-17 04:57:56 +01:00
{
2026-06-21 09:37:54 +01:00
Debug . LogWarning ( "Unknown main menu command." ) ;
return ;
}
switch ( command . Kind )
{
case AetheriaRuntimeMainMenuCommandKind . ContinueRun :
if ( LatestDaemonFrame ( CurrentStateBoot ( ) ) = = null )
2026-06-17 04:57:56 +01:00
{
2026-06-21 09:37:54 +01:00
Debug . LogWarning ( "Main-menu Continue requested without an authoritative daemon frame." ) ;
2026-06-17 04:57:56 +01:00
ShowMain ( ) ;
return ;
}
2021-03-13 03:23:58 +00:00
2026-06-21 09:37:54 +01:00
ContinueGame ( ) ;
2026-06-17 04:57:56 +01:00
return ;
2026-06-21 09:37:54 +01:00
case AetheriaRuntimeMainMenuCommandKind . NewGame :
2026-06-17 04:57:56 +01:00
StartNewGame ( ) ;
return ;
2026-06-21 09:37:54 +01:00
case AetheriaRuntimeMainMenuCommandKind . ShowSettings :
2026-06-17 04:57:56 +01:00
ShowSettings ( ) ;
return ;
2026-06-21 09:37:54 +01:00
case AetheriaRuntimeMainMenuCommandKind . Quit :
2026-06-17 04:57:56 +01:00
Application . Quit ( ) ;
return ;
default :
2026-06-21 09:37:54 +01:00
Debug . LogWarning ( $"Unhandled main menu command kind: {command.Kind}" ) ;
2026-06-17 04:57:56 +01:00
return ;
}
2026-06-16 21:08:20 +01:00
}
2026-06-17 04:57:56 +01:00
private void StartNewGame ( )
2021-03-13 03:23:58 +00:00
{
2026-06-21 09:37:54 +01:00
if ( ! TryStartDaemonObservedGame ( "Starting Daemon Run" ) )
{
ShowMain ( ) ;
}
}
private bool TryStartDaemonObservedGame ( string title )
{
var stateBoot = CurrentStateBoot ( ) ;
if ( ! AetheriaRuntimeStateReader . TryReadDaemonFrame ( stateBoot . StateFilePath , out var frame ) | |
frame = = null | |
! frame . IsAuthoritative | |
frame . Run = = null | |
frame . Run . Zones = = null | |
frame . Run . Zones . Count = = 0 )
{
Debug . LogWarning ( $"Cannot start Aetheria observer scene without an authoritative daemon frame at {AetheriaRuntimeDaemonFrameStore.GetFramePath(stateBoot.StateFilePath)}." ) ;
return false ;
}
var generatorState = "Loading runtime catalog" ;
2026-06-17 04:57:56 +01:00
Action < string > setState = s = > generatorState = s ;
2026-06-16 21:08:20 +01:00
HideMenuSurface ( ) ;
2026-06-17 04:57:56 +01:00
Dialog . Clear ( ) ;
2026-06-21 09:37:54 +01:00
Dialog . Title . text = title ;
2026-06-17 04:57:56 +01:00
Dialog . AddProperty ( ( ) = > generatorState ) ;
Dialog . Show ( ) ;
2026-06-21 09:37:54 +01:00
setState ( $"Observing daemon frame {frame.FrameId}" ) ;
ActionGameManager . IsTutorial = frame . Run . IsTutorial ;
var backgroundSettings = frame . Run . IsTutorial ? Settings . TutorialBackgroundSettings : Settings . SectorBackgroundSettings ;
backgroundSettings . NoisePosition = frame . Run . GenerationSeed = = 0 ? 1 : frame . Run . GenerationSeed ;
ActionGameManager . ObservedGalaxy = Galaxy . ProjectObservedDaemonRun (
frame . Run ,
backgroundSettings ,
ActionGameManager . RuntimeCatalog ,
Debug . Log ) ;
SceneManager . LoadScene ( "ARPG" ) ;
return true ;
2026-06-14 15:12:38 +01:00
}
2026-06-17 10:39:28 +01:00
private static AetheriaRuntimeStateBootReport CurrentStateBoot ( )
{
return AetheriaRuntimeStateBoot . Inspect ( ActionGameManager . GameDataDirectory ) ;
}
2026-06-21 09:37:54 +01:00
private static AetheriaRuntimeDaemonFrameDocument LatestDaemonFrame ( AetheriaRuntimeStateBootReport stateBoot )
2026-06-16 06:09:46 +01:00
{
2026-06-21 09:37:54 +01:00
if ( ! AetheriaRuntimeStateReader . TryReadDaemonFrame ( stateBoot . StateFilePath , out var frame ) | |
frame = = null | |
! frame . IsAuthoritative | |
frame . Run = = null | |
frame . Run . Zones = = null | |
frame . Run . Zones . Count = = 0 )
2026-06-16 06:09:46 +01:00
{
return null ;
}
2026-06-21 09:37:54 +01:00
return frame ;
2026-06-16 06:09:46 +01:00
}
2026-06-21 21:52:46 +01:00
private static AetheriaRuntimePlayerSettingsSnapshot LatestPlayerSettings ( AetheriaRuntimeStateBootReport stateBoot )
{
if ( ! stateBoot . SupportsLocalStateFileRead | | ! stateBoot . StateFileExists )
return null ;
try
{
return AetheriaRuntimeStateReader . ReadPlayerSettings ( stateBoot . StateFilePath ) ;
}
catch ( Exception ex )
{
Debug . LogError ( $"Failed to read typed Aetheria player settings for the main menu: {ex}" ) ;
return null ;
}
}
2026-06-17 10:39:28 +01:00
private static AetheriaRuntimeVerseHostSettingsSnapshot LatestVerseHostSettings ( AetheriaRuntimeStateBootReport stateBoot )
2026-06-17 09:51:49 +01:00
{
2026-06-17 10:39:28 +01:00
if ( ! stateBoot . SupportsLocalStateFileRead | | ! stateBoot . StateFileExists )
return null ;
2026-06-17 09:51:49 +01:00
try
{
2026-06-17 10:39:28 +01:00
return AetheriaRuntimeStateReader . ReadVerseHostSettings ( stateBoot . StateFilePath ) ;
2026-06-17 09:51:49 +01:00
}
catch ( Exception ex )
{
Debug . LogError ( $"Failed to read typed Aetheria Verse host settings for the main menu: {ex}" ) ;
return null ;
}
}
2026-06-21 09:37:54 +01:00
private void ContinueGame ( )
2026-06-16 06:09:46 +01:00
{
2026-06-21 09:37:54 +01:00
if ( ! TryStartDaemonObservedGame ( "Continuing Daemon Run" ) )
2026-06-16 06:09:46 +01:00
{
2026-06-21 09:37:54 +01:00
ShowMain ( ) ;
}
2026-06-16 06:09:46 +01:00
}
2021-03-14 03:56:49 +00:00
private void ShowSettings ( )
{
2026-06-21 09:37:54 +01:00
RenderMenuSurface (
AetheriaRuntimeMainMenuSurfaceBuilder . BuildSettings ( DateTime . UtcNow . ToString ( "O" ) ) ,
HandleSettingsSurfaceCommand ) ;
2021-03-14 03:56:49 +00:00
}
2021-03-13 03:23:58 +00:00
2021-03-14 03:56:49 +00:00
private void ShowInputSettings ( )
2021-03-13 03:23:58 +00:00
{
2026-06-21 21:52:46 +01:00
var stateBoot = CurrentStateBoot ( ) ;
2026-06-21 09:37:54 +01:00
RenderMenuSurface (
AetheriaRuntimeMainMenuSurfaceBuilder . BuildInputSettings (
2026-06-21 21:52:46 +01:00
AetheriaRuntimeMainMenuSurfaceBuilder . ProjectRoot (
stateBoot ,
2026-06-21 09:37:54 +01:00
null ,
null ,
2026-06-21 21:52:46 +01:00
LatestPlayerSettings ( stateBoot ) ,
2026-06-21 09:37:54 +01:00
CanOpenRuntimeInputScreen ( ) ,
2026-06-21 21:52:46 +01:00
InGame ,
DateTime . UtcNow . ToString ( "O" ) ) ) ,
2026-06-21 09:37:54 +01:00
HandleInputSettingsSurfaceCommand ) ;
2021-03-13 03:23:58 +00:00
}
2026-06-16 19:05:00 +01:00
private void ShowPlayerSettingsSurface ( )
{
2026-06-16 21:08:20 +01:00
RenderMenuSurface (
2026-06-21 21:52:46 +01:00
AetheriaRuntimeMainMenuSurfaceBuilder . BuildPlayerSettingsShell (
AetheriaRuntimeMainMenuSurfaceBuilder . ProjectPlayerSettings (
LatestPlayerSettings ( CurrentStateBoot ( ) ) ,
DateTime . UtcNow . ToString ( "O" ) ) ) ,
2026-06-16 21:08:20 +01:00
HandlePlayerSettingsSurfaceCommand ) ;
2026-06-16 19:05:00 +01:00
}
2026-06-17 11:24:36 +01:00
private void ShowVerseSettingsSurface ( )
{
2026-06-21 21:52:46 +01:00
var stateBoot = CurrentStateBoot ( ) ;
2026-06-17 11:24:36 +01:00
RenderMenuSurface (
2026-06-21 21:52:46 +01:00
AetheriaRuntimeMainMenuSurfaceBuilder . BuildVerseSettingsShell (
AetheriaRuntimeMainMenuSurfaceBuilder . ProjectVerseSettings (
stateBoot ,
LatestVerseHostSettings ( stateBoot ) ,
DateTime . UtcNow . ToString ( "O" ) ) ) ,
2026-06-17 11:24:36 +01:00
HandleVerseSettingsSurfaceCommand ) ;
}
2026-06-16 21:08:20 +01:00
private void RenderMenuSurface (
2026-06-21 09:37:54 +01:00
AetheriaRuntimeSurfaceDocument document ,
2026-06-16 21:08:20 +01:00
Action < EveSurfaceCommandRequest > commandHandler )
2026-06-16 18:32:19 +01:00
{
2026-06-21 09:37:54 +01:00
_menuSurfaceDocument = AetheriaEveUnitySurfaceHost . RenderRuntime (
transform ,
_menuSurfaceDocument ,
"Aetheria Menu Surface" ,
document ,
commandHandler ,
_menuSurfaceChrome ) ;
2026-06-16 21:08:20 +01:00
}
2026-06-16 19:05:00 +01:00
2026-06-16 21:08:20 +01:00
private void HandleSettingsSurfaceCommand ( EveSurfaceCommandRequest request )
{
2026-06-21 09:37:54 +01:00
if ( ! AetheriaRuntimeMainMenuSurfaceCommands . TryRead ( request , out var command ) )
2026-06-16 21:08:20 +01:00
{
2026-06-21 09:37:54 +01:00
Debug . LogWarning ( "Unknown settings menu command." ) ;
return ;
}
switch ( command . Kind )
{
case AetheriaRuntimeMainMenuCommandKind . ShowPlayerSettings :
2026-06-16 21:08:20 +01:00
ShowPlayerSettingsSurface ( ) ;
return ;
2026-06-21 09:37:54 +01:00
case AetheriaRuntimeMainMenuCommandKind . ShowVerseSettings :
2026-06-17 11:24:36 +01:00
ShowVerseSettingsSurface ( ) ;
return ;
2026-06-21 09:37:54 +01:00
case AetheriaRuntimeMainMenuCommandKind . ShowInputSettings :
2026-06-16 21:08:20 +01:00
ShowInputSettings ( ) ;
return ;
2026-06-21 09:37:54 +01:00
case AetheriaRuntimeMainMenuCommandKind . BackToMain :
2026-06-16 21:08:20 +01:00
ShowMain ( ) ;
return ;
default :
2026-06-21 09:37:54 +01:00
Debug . LogWarning ( $"Unhandled settings menu command kind: {command.Kind}" ) ;
2026-06-16 21:08:20 +01:00
return ;
}
}
private void HandleInputSettingsSurfaceCommand ( EveSurfaceCommandRequest request )
{
2026-06-21 09:37:54 +01:00
if ( ! AetheriaRuntimeMainMenuSurfaceCommands . TryRead ( request , out var command ) )
2026-06-16 21:08:20 +01:00
{
2026-06-21 09:37:54 +01:00
Debug . LogWarning ( "Unknown input settings command." ) ;
2026-06-16 21:08:20 +01:00
return ;
}
2026-06-21 09:37:54 +01:00
switch ( command . Kind )
2026-06-17 06:10:41 +01:00
{
2026-06-21 09:37:54 +01:00
case AetheriaRuntimeMainMenuCommandKind . BackToSettings :
ShowSettings ( ) ;
return ;
case AetheriaRuntimeMainMenuCommandKind . OpenRuntimeInputScreen :
if ( ! TryOpenRuntimeInputScreen ( ) )
{
ShowInputSettings ( ) ;
}
2026-06-17 06:10:41 +01:00
2026-06-21 09:37:54 +01:00
return ;
default :
Debug . LogWarning ( $"Unhandled input settings command kind: {command.Kind}" ) ;
return ;
2026-06-17 06:10:41 +01:00
}
2026-06-16 21:08:20 +01:00
}
private void HandlePlayerSettingsSurfaceCommand ( EveSurfaceCommandRequest request )
{
2026-06-21 09:37:54 +01:00
if ( ! AetheriaRuntimeMainMenuSurfaceCommands . TryRead ( request , out var command ) )
2026-06-16 19:05:00 +01:00
{
2026-06-21 09:37:54 +01:00
Debug . LogWarning ( "Unknown player-settings command." ) ;
2026-06-16 21:08:20 +01:00
return ;
}
2026-06-21 09:37:54 +01:00
switch ( command . Kind )
2026-06-16 19:05:00 +01:00
{
2026-06-21 09:37:54 +01:00
case AetheriaRuntimeMainMenuCommandKind . BackToSettings :
ShowSettings ( ) ;
return ;
case AetheriaRuntimeMainMenuCommandKind . PlayerSettingsCommand :
2026-06-21 14:23:32 +01:00
SendKnownAetheriaEveCommand ( request , "player-settings" ) ;
2026-06-21 09:37:54 +01:00
ShowPlayerSettingsSurface ( ) ;
return ;
default :
Debug . LogWarning ( $"Unhandled player-settings command kind: {command.Kind}" ) ;
return ;
}
2026-06-16 18:32:19 +01:00
}
2026-06-17 11:24:36 +01:00
private void HandleVerseSettingsSurfaceCommand ( EveSurfaceCommandRequest request )
{
2026-06-21 09:37:54 +01:00
if ( ! AetheriaRuntimeMainMenuSurfaceCommands . TryRead ( request , out var command ) )
2026-06-17 11:24:36 +01:00
{
2026-06-21 09:37:54 +01:00
Debug . LogWarning ( "Unknown verse-settings command." ) ;
2026-06-17 11:24:36 +01:00
return ;
}
2026-06-21 09:37:54 +01:00
switch ( command . Kind )
2026-06-17 11:24:36 +01:00
{
2026-06-21 09:37:54 +01:00
case AetheriaRuntimeMainMenuCommandKind . BackToSettings :
ShowSettings ( ) ;
return ;
case AetheriaRuntimeMainMenuCommandKind . ClientTargetCommand :
2026-06-21 14:23:32 +01:00
RequestClientTargetCommand ( request ) ;
ShowVerseSettingsSurface ( ) ;
2026-06-21 09:37:54 +01:00
return ;
case AetheriaRuntimeMainMenuCommandKind . VerseHostCommand :
2026-06-21 14:23:32 +01:00
SendKnownAetheriaEveCommand ( request , "Verse-host" ) ;
ShowVerseSettingsSurface ( ) ;
2026-06-21 09:37:54 +01:00
return ;
default :
Debug . LogWarning ( $"Unhandled verse-settings command kind: {command.Kind}" ) ;
return ;
}
2026-06-17 11:24:36 +01:00
}
2026-06-16 21:08:20 +01:00
private void HideMenuSurface ( )
2026-06-15 22:47:15 +01:00
{
2026-06-16 21:08:20 +01:00
if ( _menuSurfaceDocument = = null )
2026-06-16 19:05:00 +01:00
return ;
2026-06-21 09:37:54 +01:00
AetheriaEveUnitySurfaceHost . Hide ( _menuSurfaceDocument ) ;
2026-06-16 19:05:00 +01:00
}
2026-06-17 06:10:41 +01:00
private bool CanOpenRuntimeInputScreen ( )
{
return InGame & &
ActionGameManager . Instance ! = null & &
ActionGameManager . Instance . CanShowInputScreenFromMenu ( ) & &
ActionGameManager . Instance . InputDisplayLayout ! = null ;
}
private bool TryOpenRuntimeInputScreen ( )
{
if ( ! CanOpenRuntimeInputScreen ( ) )
return false ;
HideMenuSurface ( ) ;
gameObject . SetActive ( false ) ;
ActionGameManager . Instance . ShowInputScreenFromMenu ( ) ;
return true ;
}
2026-06-21 14:23:32 +01:00
private static void RequestClientTargetCommand ( EveSurfaceCommandRequest request )
2026-06-17 11:24:36 +01:00
{
try
{
2026-06-21 09:37:54 +01:00
if ( ! AetheriaRuntimeClientTargetSurfaceCommands . TryRequest (
AetheriaState . At ( ActionGameManager . GameDataDirectory ) . ClientTarget ,
request ,
out _ ) )
2026-06-17 12:14:16 +01:00
{
2026-06-21 14:23:32 +01:00
return ;
2026-06-17 12:14:16 +01:00
}
2026-06-17 11:24:36 +01:00
}
catch ( Exception ex )
{
Debug . LogError ( $"Failed to update typed Aetheria client target: {ex}" ) ;
}
}
2026-06-21 14:23:32 +01:00
private static void SendKnownAetheriaEveCommand (
2026-06-21 09:37:54 +01:00
EveSurfaceCommandRequest request ,
2026-06-21 11:20:51 +01:00
string label )
2026-06-17 11:24:36 +01:00
{
2026-06-21 09:37:54 +01:00
if ( request = = null )
2026-06-21 14:23:32 +01:00
return ;
2026-06-17 11:24:36 +01:00
2026-06-21 09:37:54 +01:00
var stateBoot = CurrentStateBoot ( ) ;
2026-06-21 11:20:51 +01:00
if ( ! CanSendLocalEveCommand ( stateBoot , label ) )
2026-06-21 14:23:32 +01:00
return ;
2026-06-17 12:14:16 +01:00
2026-06-21 09:37:54 +01:00
try
2026-06-17 06:10:41 +01:00
{
2026-06-21 11:20:51 +01:00
if ( ! AetheriaRuntimeEveCommands . TrySendKnownSurfaceCommand (
2026-06-21 09:37:54 +01:00
stateBoot . StateFilePath ,
request ,
"unity-main-menu" ,
out var submitted ,
out var error ) )
{
2026-06-21 11:20:51 +01:00
Debug . LogError ( $"Failed to submit Aetheria {label} Eve command: {error}" ) ;
2026-06-21 14:23:32 +01:00
return ;
2026-06-21 09:37:54 +01:00
}
2026-06-17 06:10:41 +01:00
2026-06-21 11:20:51 +01:00
Debug . Log ( $"Submitted Aetheria {label} Eve command: {submitted!.CommandId}" ) ;
2026-06-17 06:10:41 +01:00
}
2026-06-21 09:37:54 +01:00
catch ( Exception ex )
2026-06-17 06:10:41 +01:00
{
2026-06-21 11:20:51 +01:00
Debug . LogError ( $"Failed to send Aetheria {label} Eve command: {ex}" ) ;
2026-06-17 06:10:41 +01:00
}
2026-06-16 21:08:20 +01:00
}
2026-06-21 09:37:54 +01:00
private static bool CanSendLocalEveCommand (
AetheriaRuntimeStateBootReport stateBoot ,
2026-06-21 11:20:51 +01:00
string label )
2026-06-16 19:05:00 +01:00
{
2026-06-21 09:37:54 +01:00
if ( ! stateBoot . SupportsLocalStateFileRead | | ! stateBoot . StateFileExists )
{
Debug . LogWarning (
2026-06-21 11:20:51 +01:00
$"Cannot send {label} command because the active target is not a readable local Verse state file." ) ;
2026-06-21 09:37:54 +01:00
return false ;
}
2026-06-16 19:05:00 +01:00
2026-06-21 09:37:54 +01:00
return true ;
2026-06-16 19:05:00 +01:00
}
private void OnDestroy ( )
{
2026-06-16 21:08:20 +01:00
if ( _menuSurfaceDocument ! = null )
2026-06-16 19:05:00 +01:00
{
2026-06-16 21:08:20 +01:00
Destroy ( _menuSurfaceDocument . gameObject ) ;
_menuSurfaceDocument = null ;
2026-06-16 19:05:00 +01:00
}
2026-06-15 22:47:15 +01:00
}
2021-03-13 03:23:58 +00:00
}