diff --git a/Project/App.xojo_code b/Project/App.xojo_code index 811957ef3..211d08fca 100755 --- a/Project/App.xojo_code +++ b/Project/App.xojo_code @@ -35,6 +35,8 @@ Implements NotificationKit.Receiver,Beacon.Application Ark.DataSource.Pool.CloseAll SDTD.DataSource.Pool.CloseAll Beacon.CommonData.Pool.CloseAll + ArkSA.DataSource.Pool.CloseAll + Palworld.DataSource.Pool.CloseAll UpdatesKit.Cleanup @@ -1061,6 +1063,9 @@ Implements NotificationKit.Receiver,Beacon.Application #if ArkSA.Enabled Self.mDataSources.Add(ArkSA.DataSource.Pool.Get(False)) #endif + #if Palworld.Enabled + Self.mDataSources.Add(Palworld.DataSource.Pool.Get(False)) + #endif Catch Err As RuntimeException // Something is still wrong BeaconUI.ShowAlert("Beacon cannot start due to a problem with a local database.", "Beacon is unable to create or repair a local database. The database error was: `" + Err.Message + "`.") diff --git a/Project/Beacon.xojo_project b/Project/Beacon.xojo_project index 2af654f42..ca41283f7 100644 --- a/Project/Beacon.xojo_project +++ b/Project/Beacon.xojo_project @@ -591,6 +591,7 @@ Folder=General Dialogs;Views/ArkSA/General Dialogs;&h0000000079FD1FFF;&h00000000 Folder=Import;Views/ArkSA/Import;&h000000001BDD67FF;&h000000000DC787FF;false Folder=Templates;Views/ArkSA/Templates;&h00000000390347FF;&h000000000DC787FF;false Folder=7 Days to Die;Views/7 Days to Die;&h000000004EEF1FFF;&h0000000008A387FF;false +Folder=Palworld;Views/Palworld;&h000000002348F7FF;&h0000000008A387FF;false DesktopWindow=ArkDocumentEditorView;Views/Ark/ArkDocumentEditorView.xojo_window;&h0000000033F01FFF;&h000000007EE13FFF;false Folder=Config Editors;Views/Ark/Config Editors;&h00000000434D97FF;&h000000007EE13FFF;false Folder=General Dialogs;Views/Ark/General Dialogs;&h00000000524BBFFF;&h000000007EE13FFF;false @@ -882,15 +883,68 @@ DesktopWindow=ArkSAModEditorView;Views/ArkSA/Mods/ArkSAModEditorView.xojo_window DesktopWindow=DebugWindow;Views/Support Ticket/DebugWindow.xojo_window;&h000000006E2A3FFF;&h000000003584DFFF;false Class=PendingDataImport;Modules/DataUpdater/PendingDataImport.xojo_code;&h000000000CB60FFF;&h000000006233C7FF;false Class=SettingChange;Modules/Hosting Providers/Nitrado/SettingChange.xojo_code;&h0000000017DD3FFF;&h0000000072E03FFF;false +Module=Palworld;Modules/Game Support/Palworld.xojo_code;&h000000005F9A17FF;&h000000007CC197FF;false +Module=Configs;Modules/Game Support/Palworld/Configs.xojo_code;&h00000000142E27FF;&h000000005F9A17FF;false +Class=ConfigGroup;Modules/Game Support/Palworld/ConfigGroup.xojo_code;&h0000000065AF87FF;&h000000005F9A17FF;false +Class=Project;Modules/Game Support/Palworld/Project.xojo_code;&h000000000BAAFFFF;&h000000005F9A17FF;false +Class=ServerProfile;Modules/Game Support/Palworld/ServerProfile.xojo_code;&h000000003BDDCFFF;&h000000005F9A17FF;false +Class=ConfigOption;Modules/Game Support/Palworld/ConfigOption.xojo_code;&h0000000067108FFF;&h000000005F9A17FF;false +Class=ConfigValue;Modules/Game Support/Palworld/ConfigValue.xojo_code;&h0000000007215FFF;&h000000005F9A17FF;false +Class=DataSource;Modules/Game Support/Palworld/DataSource.xojo_code;&h000000006B4387FF;&h000000005F9A17FF;false +Class=DataSourcePool;Modules/Game Support/Palworld/DataSourcePool.xojo_code;&h000000007F107FFF;&h000000005F9A17FF;false +Class=OtherSettings;Modules/Game Support/Palworld/Configs/OtherSettings.xojo_code;&h0000000036152FFF;&h00000000142E27FF;false +Class=CustomContent;Modules/Game Support/Palworld/Configs/CustomContent.xojo_code;&h0000000025B18FFF;&h00000000142E27FF;false +Class=ConfigOrganizer;Modules/Game Support/Palworld/ConfigOrganizer.xojo_code;&h0000000063B4A7FF;&h000000005F9A17FF;false +DesktopWindow=PalworldDocumentEditorView;Views/Palworld/PalworldDocumentEditorView.xojo_window;&h0000000008707FFF;&h000000002348F7FF;false +Folder=Config Editors;Views/Palworld/Config Editors;&h000000001F7147FF;&h000000002348F7FF;false +Class=PalworldConfigEditor;Views/Palworld/Config Editors/PalworldConfigEditor.xojo_code;&h00000000453447FF;&h000000001F7147FF;false +Folder=Accounts;Views/Palworld/Config Editors/Accounts;&h00000000120FAFFF;&h000000001F7147FF;false +Folder=Custom Config;Views/Palworld/Config Editors/Custom Config;&h000000007A9F57FF;&h000000001F7147FF;false +Folder=General Settings;Views/Palworld/Config Editors/General Settings;&h000000003A9E17FF;&h000000001F7147FF;false +Folder=Project Settings;Views/Palworld/Config Editors/Project Settings;&h000000003CE537FF;&h000000001F7147FF;false +DesktopWindow=PalworldAccountsEditor;Views/Palworld/Config Editors/Accounts/PalworldAccountsEditor.xojo_window;&h000000003E81F7FF;&h00000000120FAFFF;false +DesktopWindow=PalworldCustomConfigEditor;Views/Palworld/Config Editors/Custom Config/PalworldCustomConfigEditor.xojo_window;&h000000002346BFFF;&h000000007A9F57FF;false +DesktopWindow=PalworldProjectSettingsEditor;Views/Palworld/Config Editors/Project Settings/PalworldProjectSettingsEditor.xojo_window;&h0000000032146FFF;&h000000003CE537FF;false +Folder=Servers;Views/Palworld/Config Editors/Servers;&h000000002399BFFF;&h000000001F7147FF;false +DesktopWindow=PalworldGeneralSettingsEditor;Views/Palworld/Config Editors/General Settings/PalworldGeneralSettingsEditor.xojo_window;&h000000006A997FFF;&h000000003A9E17FF;false +DesktopWindow=PalworldSettingsListContainer;Views/Palworld/Config Editors/General Settings/PalworldSettingsListContainer.xojo_window;&h000000003AF53FFF;&h000000003A9E17FF;false +Class=PalworldSettingsListElement;Views/Palworld/Config Editors/General Settings/PalworldSettingsListElement.xojo_code;&h00000000554967FF;&h000000003A9E17FF;false +DesktopWindow=PalworldSettingsListStringElement;Views/Palworld/Config Editors/General Settings/PalworldSettingsListStringElement.xojo_window;&h000000001ECF4FFF;&h000000003A9E17FF;false +DesktopWindow=PalworldSettingsListNumberElement;Views/Palworld/Config Editors/General Settings/PalworldSettingsListNumberElement.xojo_window;&h0000000057F83FFF;&h000000003A9E17FF;false +DesktopWindow=PalworldSettingsListBooleanElement;Views/Palworld/Config Editors/General Settings/PalworldSettingsListBooleanElement.xojo_window;&h000000002B72B7FF;&h000000003A9E17FF;false +DesktopWindow=PalworldSettingsListHeader;Views/Palworld/Config Editors/General Settings/PalworldSettingsListHeader.xojo_window;&h00000000533EAFFF;&h000000003A9E17FF;false +Class=ProjectTool;Modules/Game Support/Palworld/ProjectTool.xojo_code;&h000000003E4A67FF;&h000000005F9A17FF;false +Class=DiscoveredData;Modules/Game Support/Palworld/DiscoveredData.xojo_code;&h00000000317C17FF;&h000000005F9A17FF;false +Class=ImportThread;Modules/Game Support/Palworld/ImportThread.xojo_code;&h0000000007B40FFF;&h000000005F9A17FF;false +Class=ConfigParser;Modules/Game Support/Palworld/ConfigParser.xojo_code;&h0000000033A727FF;&h000000005F9A17FF;false +DesktopWindow=PalworldServersEditor;Views/Palworld/Config Editors/Servers/PalworldServersEditor.xojo_window;&h0000000041590FFF;&h000000002399BFFF;false +Class=PalworldServerViewContainer;Views/Palworld/Config Editors/Servers/PalworldServerViewContainer.xojo_code;&h0000000052D5BFFF;&h000000002399BFFF;false +DesktopWindow=PalworldCommonServerSettingsView;Views/Palworld/Config Editors/Servers/PalworldCommonServerSettingsView.xojo_window;&h000000004D4B5FFF;&h000000002399BFFF;false +DesktopWindow=PalworldMultiServerView;Views/Palworld/Config Editors/Servers/PalworldMultiServerView.xojo_window;&h00000000084817FF;&h000000002399BFFF;false +DesktopWindow=PalworldNitradoServerView;Views/Palworld/Config Editors/Servers/PalworldNitradoServerView.xojo_window;&h00000000593EA7FF;&h000000002399BFFF;false +DesktopWindow=PalworldFTPServerView;Views/Palworld/Config Editors/Servers/PalworldFTPServerView.xojo_window;&h000000004B09BFFF;&h000000002399BFFF;false +DesktopWindow=PalworldLocalServerView;Views/Palworld/Config Editors/Servers/PalworldLocalServerView.xojo_window;&h00000000695BBFFF;&h000000002399BFFF;false +DesktopWindow=PalworldGSAServerView;Views/Palworld/Config Editors/Servers/PalworldGSAServerView.xojo_window;&h00000000466E77FF;&h000000002399BFFF;false +Folder=General Dialogs;Views/Palworld/General Dialogs;&h0000000065E617FF;&h000000002348F7FF;false +Folder=Import;Views/Palworld/Import;&h0000000035E4EFFF;&h000000002348F7FF;false +DesktopWindow=PalworldImportView;Views/Palworld/Import/PalworldImportView.xojo_window;&h000000007B68C7FF;&h0000000035E4EFFF;false +Folder=Discovery Views;Views/Palworld/Import/Discovery Views;&h000000003ED167FF;&h0000000035E4EFFF;false +Class=PalworldDiscoveryView;Views/Palworld/Import/Discovery Views/PalworldDiscoveryView.xojo_code;&h0000000056C7C7FF;&h000000003ED167FF;false +DesktopWindow=PalworldClipboardDiscoveryView;Views/Palworld/Import/Discovery Views/PalworldClipboardDiscoveryView.xojo_window;&h0000000012BD97FF;&h000000003ED167FF;false +DesktopWindow=PalworldFilesDiscoveryView;Views/Palworld/Import/Discovery Views/PalworldFilesDiscoveryView.xojo_window;&h000000003392B7FF;&h000000003ED167FF;false +DesktopWindow=PalworldExportWindow;Views/Palworld/General Dialogs/PalworldExportWindow.xojo_window;&h00000000146B5FFF;&h0000000065E617FF;false +Class=DiscoverIntegration;Modules/Game Support/Palworld/DiscoverIntegration.xojo_code;&h0000000067A5BFFF;&h000000005F9A17FF;false +Class=DeployIntegration;Modules/Game Support/Palworld/DeployIntegration.xojo_code;&h0000000061E107FF;&h000000005F9A17FF;false +Class=Rewriter;Modules/Game Support/Palworld/Rewriter.xojo_code;&h0000000012B3BFFF;&h000000005F9A17FF;false AppMenuBar=MainMenuBar MajorVersion=2 -MinorVersion=0 -SubVersion=2 -NonRelease=0 -Release=3 +MinorVersion=1 +SubVersion=0 +NonRelease=1 +Release=1 InfoVersion=Beacon -LongVersion=Beacon 2.0.2 ©2016-2023 The ZAZ Studios -ShortVersion=2.0.2 +LongVersion=Beacon 2.1.0a1 ©2016-2024 The ZAZ Studios +ShortVersion=2.1.0a1 WinCompanyName=The ZAZ Studios WinInternalName= WinProductName=Beacon diff --git a/Project/Custom Controls/OmniNoticeBar.xojo_code b/Project/Custom Controls/OmniNoticeBar.xojo_code index 5a73d3bea..9ab0aa889 100644 --- a/Project/Custom Controls/OmniNoticeBar.xojo_code +++ b/Project/Custom Controls/OmniNoticeBar.xojo_code @@ -79,7 +79,7 @@ Inherits ControlCanvas #tag EndProperty - #tag Constant, Name = WarningText, Type = String, Dynamic = False, Default = \"This editor requires Beacon Omni for \?1. Click this banner to learn more.", Scope = Private + #tag Constant, Name = WarningText, Type = String, Dynamic = True, Default = \"This editor requires a \'Beacon Omni for \?1\' license. Click this banner to learn more.", Scope = Private #tag EndConstant diff --git a/Project/Modules/Beacon.xojo_code b/Project/Modules/Beacon.xojo_code index 99bd4568d..b836aff13 100644 --- a/Project/Modules/Beacon.xojo_code +++ b/Project/Modules/Beacon.xojo_code @@ -669,6 +669,9 @@ Protected Module Beacon #if ArkSA.Enabled GameList.Add(New Beacon.Game(ArkSA.Identifier, ArkSA.FullName, ArkSA.OmniFlag, Beacon.Game.FeatureTemplates Or Beacon.Game.FeatureMods)) #endif + #if Palworld.Enabled + GameList.Add(New Beacon.Game(Palworld.Identifier, Palworld.FullName, Palworld.OmniFlag, 0)) + #endif Var Names() As String Names.ResizeTo(GameList.LastIndex) diff --git a/Project/Modules/Beacon/Project.xojo_code b/Project/Modules/Beacon/Project.xojo_code index 9dec9d7bd..b71bbeea0 100644 --- a/Project/Modules/Beacon/Project.xojo_code +++ b/Project/Modules/Beacon/Project.xojo_code @@ -428,6 +428,8 @@ Implements ObservationKit.Observable Return New SDTD.Project() Case ArkSA.Identifier Return New ArkSA.Project() + Case Palworld.Identifier + Return New Palworld.Project() End Select End Function #tag EndMethod @@ -543,6 +545,8 @@ Implements ObservationKit.Observable Project = New SDTD.Project Case ArkSA.Identifier Project = New ArkSA.Project + Case Palworld.Identifier + Project = New Palworld.Project Else Var Err As New Beacon.ProjectLoadException Err.Message = "Unknown game " + GameId + "." @@ -1022,6 +1026,21 @@ Implements ObservationKit.Observable End Function #tag EndMethod + #tag Method, Flags = &h0 + Function HasConfigGroup(InternalName As String) As Boolean + Return Self.HasConfigGroup(InternalName, Self.ActiveConfigSet) + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function HasConfigGroup(InternalName As String, Set As Beacon.ConfigSet) As Boolean + Var SetDict As Dictionary = Self.ConfigSetData(Set) + If (SetDict Is Nil) = False Then + Return SetDict.HasKey(InternalName) + End If + End Function + #tag EndMethod + #tag Method, Flags = &h0 Function HasConfigSet(Set As Beacon.ConfigSet) As Boolean Return Self.IndexOf(Set) > -1 @@ -1456,6 +1475,42 @@ Implements ObservationKit.Observable End Sub #tag EndMethod + #tag Method, Flags = &h0 + Sub RemoveConfigGroup(Group As Beacon.ConfigGroup) + If Group Is Nil Then + Return + End If + + Self.RemoveConfigGroup(Group.InternalName, Self.ActiveConfigSet) + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Sub RemoveConfigGroup(Group As Beacon.ConfigGroup, Set As Beacon.ConfigSet) + If Group Is Nil Then + Return + End If + + Self.RemoveConfigGroup(Group.InternalName, Set) + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Sub RemoveConfigGroup(InternalName As String) + Self.RemoveConfigGroup(InternalName, Self.ActiveConfigSet) + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Sub RemoveConfigGroup(InternalName As String, Set As Beacon.ConfigSet) + Var SetDict As Dictionary = Self.ConfigSetData(Set) + If (SetDict Is Nil) = False And SetDict.HasKey(InternalName) Then + SetDict.Remove(InternalName) + Self.ConfigSetData(Set) = SetDict + End If + End Sub + #tag EndMethod + #tag Method, Flags = &h0 Sub RemoveConfigSet(Set As Beacon.ConfigSet) If Set Is Nil Or Set.IsBase Then diff --git a/Project/Modules/Beacon/ServerProfile.xojo_code b/Project/Modules/Beacon/ServerProfile.xojo_code index 385ba6a4a..6906cfab5 100644 --- a/Project/Modules/Beacon/ServerProfile.xojo_code +++ b/Project/Modules/Beacon/ServerProfile.xojo_code @@ -276,6 +276,8 @@ Protected Class ServerProfile Return New SDTD.ServerProfile(Dict, Project, Version) Case ArkSA.Identifier Return New ArkSA.ServerProfile(Dict, Project, Version) + Case Palworld.Identifier + Return New Palworld.ServerProfile(Dict, Project, Version) End Select End Function #tag EndMethod diff --git a/Project/Modules/FrameworkExtensions/NullableString.xojo_code b/Project/Modules/FrameworkExtensions/NullableString.xojo_code index 4cfced44d..0a047d051 100644 --- a/Project/Modules/FrameworkExtensions/NullableString.xojo_code +++ b/Project/Modules/FrameworkExtensions/NullableString.xojo_code @@ -18,6 +18,22 @@ Class NullableString End Function #tag EndMethod + #tag Method, Flags = &h0 + Shared Function Compare(FirstValue As NullableString, SecondValue As NullableString, Options As ComparisonOptions) As Integer + If FirstValue Is Nil And SecondValue Is Nil Then + Return 0 + End If + + If FirstValue Is Nil Then + Return -1 + ElseIf SecondValue Is Nil Then + Return 1 + End If + + Return FirstValue.StringValue.Compare(SecondValue.StringValue, Options) + End Function + #tag EndMethod + #tag Method, Flags = &h0 Function Compare(Other As String, Compare As ComparisonOptions = ComparisonOptions.CaseInsensitive, Locale As Locale = Nil) As Integer Return Self.mValue.Compare(Other, Compare, Locale) diff --git a/Project/Modules/Game Support/Ark/Project.xojo_code b/Project/Modules/Game Support/Ark/Project.xojo_code index 8c83a7191..03a206f2f 100644 --- a/Project/Modules/Game Support/Ark/Project.xojo_code +++ b/Project/Modules/Game Support/Ark/Project.xojo_code @@ -759,21 +759,6 @@ Inherits Beacon.Project End Function #tag EndMethod - #tag Method, Flags = &h0 - Function HasConfigGroup(InternalName As String) As Boolean - Return Self.HasConfigGroup(InternalName, Self.ActiveConfigSet) - End Function - #tag EndMethod - - #tag Method, Flags = &h0 - Function HasConfigGroup(InternalName As String, Set As Beacon.ConfigSet) As Boolean - Var SetDict As Dictionary = Self.ConfigSetData(Set) - If (SetDict Is Nil) = False Then - Return SetDict.HasKey(InternalName) - End If - End Function - #tag EndMethod - #tag Method, Flags = &h0 Function MapMask() As UInt64 Return Self.mMapMask @@ -813,42 +798,6 @@ Inherits Beacon.Project End Sub #tag EndMethod - #tag Method, Flags = &h0 - Sub RemoveConfigGroup(Group As Ark.ConfigGroup) - If Group Is Nil Then - Return - End If - - Self.RemoveConfigGroup(Group.InternalName, Self.ActiveConfigSet) - End Sub - #tag EndMethod - - #tag Method, Flags = &h0 - Sub RemoveConfigGroup(Group As Ark.ConfigGroup, Set As Beacon.ConfigSet) - If Group Is Nil Then - Return - End If - - Self.RemoveConfigGroup(Group.InternalName, Set) - End Sub - #tag EndMethod - - #tag Method, Flags = &h0 - Sub RemoveConfigGroup(InternalName As String) - Self.RemoveConfigGroup(InternalName, Self.ActiveConfigSet) - End Sub - #tag EndMethod - - #tag Method, Flags = &h0 - Sub RemoveConfigGroup(InternalName As String, Set As Beacon.ConfigSet) - Var SetDict As Dictionary = Self.ConfigSetData(Set) - If (SetDict Is Nil) = False And SetDict.HasKey(InternalName) Then - SetDict.Remove(InternalName) - Self.ConfigSetData(Set) = SetDict - End If - End Sub - #tag EndMethod - #tag Method, Flags = &h0 Function SupportsMap(Map As Ark.Map) As Boolean Return (Self.MapMask And Map.Mask) = Map.Mask diff --git a/Project/Modules/Game Support/ArkSA/Project.xojo_code b/Project/Modules/Game Support/ArkSA/Project.xojo_code index b34674bec..2933d1b5d 100644 --- a/Project/Modules/Game Support/ArkSA/Project.xojo_code +++ b/Project/Modules/Game Support/ArkSA/Project.xojo_code @@ -772,21 +772,6 @@ Inherits Beacon.Project End Function #tag EndMethod - #tag Method, Flags = &h0 - Function HasConfigGroup(InternalName As String) As Boolean - Return Self.HasConfigGroup(InternalName, Self.ActiveConfigSet) - End Function - #tag EndMethod - - #tag Method, Flags = &h0 - Function HasConfigGroup(InternalName As String, Set As Beacon.ConfigSet) As Boolean - Var SetDict As Dictionary = Self.ConfigSetData(Set) - If (SetDict Is Nil) = False Then - Return SetDict.HasKey(InternalName) - End If - End Function - #tag EndMethod - #tag Method, Flags = &h0 Function MapMask() As UInt64 Return Self.mMapMask @@ -826,42 +811,6 @@ Inherits Beacon.Project End Sub #tag EndMethod - #tag Method, Flags = &h0 - Sub RemoveConfigGroup(Group As ArkSA.ConfigGroup) - If Group Is Nil Then - Return - End If - - Self.RemoveConfigGroup(Group.InternalName, Self.ActiveConfigSet) - End Sub - #tag EndMethod - - #tag Method, Flags = &h0 - Sub RemoveConfigGroup(Group As ArkSA.ConfigGroup, Set As Beacon.ConfigSet) - If Group Is Nil Then - Return - End If - - Self.RemoveConfigGroup(Group.InternalName, Set) - End Sub - #tag EndMethod - - #tag Method, Flags = &h0 - Sub RemoveConfigGroup(InternalName As String) - Self.RemoveConfigGroup(InternalName, Self.ActiveConfigSet) - End Sub - #tag EndMethod - - #tag Method, Flags = &h0 - Sub RemoveConfigGroup(InternalName As String, Set As Beacon.ConfigSet) - Var SetDict As Dictionary = Self.ConfigSetData(Set) - If (SetDict Is Nil) = False And SetDict.HasKey(InternalName) Then - SetDict.Remove(InternalName) - Self.ConfigSetData(Set) = SetDict - End If - End Sub - #tag EndMethod - #tag Method, Flags = &h0 Function SupportsMap(Map As ArkSA.Map) As Boolean Return (Self.MapMask And Map.Mask) = Map.Mask diff --git a/Project/Modules/Game Support/Palworld.xojo_code b/Project/Modules/Game Support/Palworld.xojo_code new file mode 100644 index 000000000..9608357bd --- /dev/null +++ b/Project/Modules/Game Support/Palworld.xojo_code @@ -0,0 +1,163 @@ +#tag Module +Protected Module Palworld + #tag Method, Flags = &h1 + Protected Function OmniPurchased(Identity As Beacon.Identity) As Boolean + Return (Identity Is Nil) = False And Identity.IsOmniFlagged(Palworld.OmniFlag) + End Function + #tag EndMethod + + #tag Method, Flags = &h1, CompatibilityFlags = (TargetConsole and (Target32Bit)) or (TargetWeb and (Target32Bit)) or (TargetDesktop and (Target32Bit or Target64Bit)) + Protected Sub SetupCodeEditor(Target As CodeEditor) + Const SCE_PROPS_DEFAULT = 0 + Const SCE_PROPS_COMMENT = 1 + Const SCE_PROPS_SECTION = 2 + Const SCE_PROPS_ASSIGNMENT = 3 + Const SCE_PROPS_DEFVAL = 4 + Const SCE_PROPS_KEY = 5 + + Target.InitializeLexer("props") + + Var SectionColor, AssignmentColor, KeywordColor As Color + + If Color.IsDarkMode Then + SectionColor = &cFF7778 + AssignmentColor = &cCBCBCB + KeywordColor = &c19A9FF + Else + SectionColor = &c7D1012 + AssignmentColor = &c515151 + KeywordColor = &c0C51C3 + End If + + Target.Style(SCE_PROPS_SECTION).ForeColor = SectionColor + Target.Style(SCE_PROPS_ASSIGNMENT).ForeColor = AssignmentColor + Target.Style(SCE_PROPS_KEY).ForeColor = KeywordColor + Target.Style(SCE_PROPS_SECTION).Bold = True + + // Unknown colors, make sure they stand out so they can be discovered more readily + Target.Style(SCE_PROPS_DEFVAL).ForeColor = &cFF00FF + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Sort(Extends Values() As Palworld.ConfigValue) + If Values.Count <= 1 Then + Return + End If + + Var Sorts() As String + Sorts.ResizeTo(Values.LastIndex) + For Idx As Integer = 0 To Sorts.LastIndex + Sorts(Idx) = Values(Idx).SortKey + Next + Sorts.SortWith(Values) + End Sub + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Function ValidateIniContent(Content As String, RequiredHeaders() As Beacon.StringList) As String() + Var MissingHeaders() As String + For Each HeaderChoices As Beacon.StringList In RequiredHeaders + Var Found As Boolean = False + For Each HeaderChoice As String In HeaderChoices + If Content.IndexOf("[" + HeaderChoice + "]") > -1 Then + Found = True + Exit + End If + Next + + If Found = False Then + MissingHeaders.Add("[" + HeaderChoices(0) + "]") + End If + Next HeaderChoices + MissingHeaders.Sort + Return MissingHeaders + End Function + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Function ValidateIniContent(Content As String, Filename As String) As String() + Var RequiredHeaders() As Beacon.StringList + If Filename = Palworld.ConfigFileSettings Then + RequiredHeaders = Array(New Beacon.StringList(Palworld.HeaderPalworldSettings)) + End If + Return Palworld.ValidateIniContent(Content, RequiredHeaders) + End Function + #tag EndMethod + + + #tag Constant, Name = ConfigFileSettings, Type = String, Dynamic = False, Default = \"PalWorldSettings.ini", Scope = Protected + #tag EndConstant + + #tag Constant, Name = Enabled, Type = Boolean, Dynamic = False, Default = \"True", Scope = Protected + #tag EndConstant + + #tag Constant, Name = FullName, Type = String, Dynamic = False, Default = \"Palworld", Scope = Protected + #tag EndConstant + + #tag Constant, Name = HeaderPalworldSettings, Type = String, Dynamic = False, Default = \"/Script/Pal.PalGameWorldSettings", Scope = Protected + #tag EndConstant + + #tag Constant, Name = Identifier, Type = String, Dynamic = False, Default = \"Palworld", Scope = Protected + #tag EndConstant + + #tag Constant, Name = OmniFlag, Type = Double, Dynamic = False, Default = \"16", Scope = Protected + #tag EndConstant + + #tag Constant, Name = SteamAppId, Type = Double, Dynamic = False, Default = \"1623730", Scope = Protected + #tag EndConstant + + #tag Constant, Name = SteamServerId, Type = Double, Dynamic = False, Default = \"2394010", Scope = Protected + #tag EndConstant + + #tag Constant, Name = UserContentPackId, Type = String, Dynamic = False, Default = \"2325c10e-c30a-4dd9-883c-df2bb3afb95c", Scope = Protected + #tag EndConstant + + #tag Constant, Name = UserContentPackName, Type = String, Dynamic = False, Default = \"Palworld User Content", Scope = Protected + #tag EndConstant + + + #tag ViewBehavior + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag EndViewBehavior +End Module +#tag EndModule diff --git a/Project/Modules/Game Support/Palworld/ConfigGroup.xojo_code b/Project/Modules/Game Support/Palworld/ConfigGroup.xojo_code new file mode 100644 index 000000000..bb78610f9 --- /dev/null +++ b/Project/Modules/Game Support/Palworld/ConfigGroup.xojo_code @@ -0,0 +1,131 @@ +#tag Class +Protected Class ConfigGroup +Inherits Beacon.ConfigGroup + #tag CompatibilityFlags = ( TargetConsole and ( Target32Bit or Target64Bit ) ) or ( TargetWeb and ( Target32Bit or Target64Bit ) ) or ( TargetDesktop and ( Target32Bit or Target64Bit ) ) or ( TargetIOS and ( Target64Bit ) ) or ( TargetAndroid and ( Target64Bit ) ) + #tag Method, Flags = &h0 + Sub Constructor(Source As Palworld.ConfigGroup) + Self.Constructor() + Self.CopyFrom(Source) + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Sub CopyFrom(Other As Palworld.ConfigGroup) + If Other Is Nil Then + Var Err As New UnsupportedOperationException + Err.Message = "Cannot merge nil group into " + Self.InternalName + "." + Raise Err + ElseIf Other.InternalName <> Self.InternalName Then + Var Err As New UnsupportedOperationException + Err.Message = "Cannot merge group " + Other.InternalName + " into " + Self.InternalName + "." + Raise Err + End If + + RaiseEvent CopyFrom(Other) + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Function GenerateConfigValues(Project As Palworld.Project, Identity As Beacon.Identity, Profile As Palworld.ServerProfile) As Palworld.ConfigValue() + Var Values() As Palworld.ConfigValue + + If Palworld.Configs.ConfigUnlocked(Self, Identity) Then + Var Generated() As Palworld.ConfigValue = RaiseEvent GenerateConfigValues(Project, Profile) + If (Generated Is Nil) = False Then + Values = Generated + Else + App.Log("Warning: " + Self.InternalName + " did not generate any configs.") + End If + End If + + Return Values + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function ManagedKeys() As Palworld.ConfigOption() + // Returns all the keys that this group could provide + + If Self.mManagedKeys.Count = 0 Then + Var Keys() As Palworld.ConfigOption = RaiseEvent GetManagedKeys() + If (Keys Is Nil) = False Then + Self.mManagedKeys = Keys + End If + End If + Return Self.mManagedKeys + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Migrate(SavedWithVersion As Integer, Project As Palworld.Project) + RaiseEvent Migrate(SavedWithVersion, Project) + End Sub + #tag EndMethod + + + #tag Hook, Flags = &h0 + Event CopyFrom(Other As Palworld.ConfigGroup) + #tag EndHook + + #tag Hook, Flags = &h0 + Event GenerateConfigValues(Project As Palworld.Project, Profile As Palworld.ServerProfile) As Palworld.ConfigValue() + #tag EndHook + + #tag Hook, Flags = &h0 + Event GetManagedKeys() As Palworld.ConfigOption() + #tag EndHook + + #tag Hook, Flags = &h0 + Event Migrate(SavedWithVersion As Integer, Project As Palworld.Project) + #tag EndHook + + + #tag Property, Flags = &h21 + Private mManagedKeys() As Palworld.ConfigOption + #tag EndProperty + + + #tag ViewBehavior + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag EndViewBehavior +End Class +#tag EndClass diff --git a/Project/Modules/Game Support/Palworld/ConfigOption.xojo_code b/Project/Modules/Game Support/Palworld/ConfigOption.xojo_code new file mode 100644 index 000000000..411e88b75 --- /dev/null +++ b/Project/Modules/Game Support/Palworld/ConfigOption.xojo_code @@ -0,0 +1,432 @@ +#tag Class +Protected Class ConfigOption +Implements Beacon.GameSetting + #tag Method, Flags = &h0 + Function AutoImported() As Boolean + Var AutoImported As Variant = Self.Constraint("auto_imported") + If AutoImported.IsNull Then + Return True + End If + Return AutoImported.BooleanValue + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function ConfigOptionId() As String + Return Self.mConfigOptionId + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function Constraint(Key As String) As Variant + If Self.mConstraints Is Nil Or Self.mConstraints.HasKey(Key) = False Then + Return Nil + End If + + Return Self.mConstraints.Value(Key) + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Constructor(Source As Palworld.ConfigOption) + Self.mConfigOptionId = Source.mConfigOptionId + Self.mLabel = Source.mLabel + Self.mFile = Source.mFile + Self.mHeader = Source.mHeader + Self.mStruct = Source.mStruct + Self.mKey = Source.mKey + Self.mValueType = Source.mValueType + Self.mMaxAllowed = Source.mMaxAllowed + Self.mDescription = Source.mDescription + Self.mDefaultValue = Source.mDefaultValue + Self.mNitradoPaths = Source.NitradoPaths // Use this version to make a clone of the array + Self.mNitradoFormat = Source.mNitradoFormat + Self.mNitradoDeployStyle = Source.mNitradoDeployStyle + Self.mUIGroup = Source.mUIGroup + Self.mContentPackId = Source.mContentPackId + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Constructor(File As String, Header As String, Struct As NullableString, Key As String) + Self.mFile = File + Self.mHeader = Header + Self.mStruct = Struct + Self.mKey = Key + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Constructor(ConfigOptionId As String, Label As String, File As String, Header As String, Struct As NullableString, Key As String, ValueType As Palworld.ConfigOption.ValueTypes, MaxAllowed As NullableDouble, Description As String, DefaultValue As Variant, NitradoPath As NullableString, NitradoFormat As Palworld.ConfigOption.NitradoFormats, NitradoDeployStyle As Palworld.ConfigOption.NitradoDeployStyles, NativeEditorVersion As NullableDouble, UIGroup As NullableString, CustomSort As NullableString, Constraints As Dictionary, ContentPackId As String) + Self.Constructor(File, Header, Struct, Key) + + Self.mConfigOptionId = ConfigOptionId + Self.mLabel = Label + Self.mValueType = ValueType + Self.mMaxAllowed = MaxAllowed + Self.mDescription = Description + Self.mDefaultValue = DefaultValue + Self.mNativeEditorVersion = NativeEditorVersion + Self.mUIGroup = UIGroup + Self.mContentPackId = ContentPackId + Self.mCustomSort = CustomSort + Self.mConstraints = Constraints + + If (NitradoPath Is Nil) = False Then + Self.mNitradoPaths = NitradoPath.Split(";") + Self.mNitradoFormat = NitradoFormat + Self.mNitradoDeployStyle = NitradoDeployStyle + End If + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Function ContentPackId() As String + Return Self.mContentPackId + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function CustomSort() As NullableString + Return Self.mCustomSort + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function DefaultValue() As Variant + Return Self.mDefaultValue + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function Description() As String + Return Self.mDescription + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function File() As String + Return Self.mFile + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function HasNativeEditor() As Boolean + Return (Self.mNativeEditorVersion Is Nil) = False And Self.mNativeEditorVersion.IntegerValue >= App.BuildNumber + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function HasNitradoEquivalent() As Boolean + Return Self.mNitradoPaths.Count > 0 And Self.mNitradoFormat <> Palworld.ConfigOption.NitradoFormats.Unsupported + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function Header() As String + Return Self.mHeader + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function IsArray() As Boolean + Return Self.mValueType = Palworld.ConfigOption.ValueTypes.TypeArray + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function IsBoolean() As Boolean + Return Self.mValueType = Palworld.ConfigOption.ValueTypes.TypeBoolean + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function IsNumeric() As Boolean + Return Self.mValueType = Palworld.ConfigOption.ValueTypes.TypeNumeric + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function IsString() As Boolean + Return Self.mValueType = Palworld.ConfigOption.ValueTypes.TypeText + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function IsStruct() As Boolean + Return Self.mValueType = Palworld.ConfigOption.ValueTypes.TypeStructure + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function Key() As String + Return Self.mKey + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function Label() As String + Return Self.mLabel + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function MaxAllowed() As NullableDouble + Return Self.mMaxAllowed + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function NativeEditorVersion() As NullableDouble + Return Self.mNativeEditorVersion + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function NitradoDeployStyle() As Palworld.ConfigOption.NitradoDeployStyles + Return Self.mNitradoDeployStyle + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function NitradoFormat() As Palworld.ConfigOption.NitradoFormats + Return Self.mNitradoFormat + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function NitradoPaths() As String() + Var Clone() As String + Clone.ResizeTo(Self.mNitradoPaths.LastIndex) + For Idx As Integer = 0 To Clone.LastIndex + Clone(Idx) = Self.mNitradoPaths(Idx) + Next + Return Clone + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function Operator_Compare(Other As Palworld.ConfigOption) As Integer + If Other Is Nil Then + Return 1 + End If + + If Self.mConfigOptionId = Other.mConfigOptionId Then + Return 0 + End If + + Var StringOne As String = Self.Signature + Var StringTwo As String = Other.Signature + Return StringOne.Compare(StringTwo, ComparisonOptions.CaseInsensitive, Locale.Raw) + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function SimplifiedKey() As String + // Returns the key without its attribute + + Return Palworld.ConfigValue.SimplifyKey(Self.mKey) + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function Struct() As NullableString + Return Self.mStruct + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function UIGroup() As NullableString + Return Self.mUIGroup + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function ValuesEqual(FirstValue As Variant, SecondValue As Variant) As Boolean + Select Case Self.mValueType + Case ValueTypes.TypeNumeric + Var FirstDouble, SecondDouble As Double + Try + FirstDouble = FirstValue.DoubleValue + Catch Err As RuntimeException + Return False + End Try + Try + SecondDouble = SecondValue.DoubleValue + Catch Err As RuntimeException + Return False + End Try + Return FirstDouble = SecondDouble + Case ValueTypes.TypeBoolean + Return FirstValue.IsTruthy = SecondValue.IsTruthy + Else + Var FirstString As String = Beacon.VariantToString(FirstValue) + Var SecondString As String = Beacon.VariantToString(SecondValue) + Return FirstString.Compare(SecondString, ComparisonOptions.CaseSensitive, Locale.Raw) = 0 + End Select + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function ValueType() As Palworld.ConfigOption.ValueTypes + Return Self.mValueType + End Function + #tag EndMethod + + + #tag Property, Flags = &h21 + Private mConfigOptionId As String + #tag EndProperty + + #tag Property, Flags = &h21 + Private mConstraints As Dictionary + #tag EndProperty + + #tag Property, Flags = &h21 + Private mContentPackId As String + #tag EndProperty + + #tag Property, Flags = &h21 + Private mCustomSort As NullableString + #tag EndProperty + + #tag Property, Flags = &h21 + Private mDefaultValue As Variant + #tag EndProperty + + #tag Property, Flags = &h21 + Private mDescription As String + #tag EndProperty + + #tag Property, Flags = &h21 + Private mFile As String + #tag EndProperty + + #tag Property, Flags = &h21 + Private mHeader As String + #tag EndProperty + + #tag Property, Flags = &h21 + Private mKey As String + #tag EndProperty + + #tag Property, Flags = &h21 + Private mLabel As String + #tag EndProperty + + #tag Property, Flags = &h21 + Private mMaxAllowed As NullableDouble + #tag EndProperty + + #tag Property, Flags = &h21 + Private mNativeEditorVersion As NullableDouble + #tag EndProperty + + #tag Property, Flags = &h21 + Private mNitradoDeployStyle As Palworld.ConfigOption.NitradoDeployStyles + #tag EndProperty + + #tag Property, Flags = &h21 + Private mNitradoFormat As Palworld.ConfigOption.NitradoFormats + #tag EndProperty + + #tag Property, Flags = &h21 + Private mNitradoPaths() As String + #tag EndProperty + + #tag Property, Flags = &h21 + Private mStruct As NullableString + #tag EndProperty + + #tag Property, Flags = &h21 + Private mUIGroup As NullableString + #tag EndProperty + + #tag Property, Flags = &h21 + Private mValueType As Palworld.ConfigOption.ValueTypes + #tag EndProperty + + #tag ComputedProperty, Flags = &h0 + #tag Getter + Get + Return Self.mFile + "." + Self.mHeader + "." + If(Self.mStruct Is Nil, "", Self.mStruct.StringValue + ".") + Self.mKey + End Get + #tag EndGetter + Signature As String + #tag EndComputedProperty + + + #tag Enum, Name = NitradoDeployStyles, Type = Integer, Flags = &h0 + Unsupported + Guided + Expert + Both + #tag EndEnum + + #tag Enum, Name = NitradoFormats, Type = Integer, Flags = &h0 + Unsupported + Line + Value + #tag EndEnum + + #tag Enum, Name = ValueTypes, Type = Integer, Flags = &h0 + TypeNumeric + TypeArray + TypeStructure + TypeBoolean + TypeText + #tag EndEnum + + + #tag ViewBehavior + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Signature" + Visible=false + Group="Behavior" + InitialValue="" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag EndViewBehavior +End Class +#tag EndClass diff --git a/Project/Modules/Game Support/Palworld/ConfigOrganizer.xojo_code b/Project/Modules/Game Support/Palworld/ConfigOrganizer.xojo_code new file mode 100644 index 000000000..fb55dda77 --- /dev/null +++ b/Project/Modules/Game Support/Palworld/ConfigOrganizer.xojo_code @@ -0,0 +1,712 @@ +#tag Class +Protected Class ConfigOrganizer + #tag Method, Flags = &h0 + Sub Add(Values() As Palworld.ConfigValue, Options As Integer = 0) + // This code is a little verbose, but it's easier to follow the logic this way. + + Var UniqueOnly As Boolean = (Options And Self.OptionAddOnlyUnique) > 0 + Var IsManaged As Boolean = (Options And Self.OptionValueIsManaged) > 0 + + Self.mIndex.BeginTransaction + + Var Filtered() As Palworld.ConfigValue + If UniqueOnly Then + For Each Value As Palworld.ConfigValue In Values + If Self.mValues.HasKey(Value.Hash) = False Then + Filtered.Add(Value) + End If + Next + Else + Filtered = Values + End If + + For Each Value As Palworld.ConfigValue In Filtered + Var Siblings() As Palworld.ConfigValue + + If Self.mValues.HasKey(Value.Hash) = False Then + // Key does not exist yet + Siblings.Add(Value) + Self.mValues.Value(Value.Hash) = Siblings + Self.mIndex.ExecuteSQL("INSERT INTO keymap (hash, file, header, struct, simplekey, sortkey) VALUES (?1, ?2, ?3, ?4, ?5, ?6);", Value.Hash, Value.File, Value.Header, NullableString.ToVariant(Value.Struct), Value.SimplifiedKey, Value.SortKey) + If IsManaged Then + Self.AddManagedKeys(Value.Details) + End If + Continue + End If + + If Value.SingleInstance Then + // There should only be one of this key, so replace the older one + Siblings.Add(Value) + Self.mValues.Value(Value.Hash) = Siblings + Continue + End If + + // Add this value to the array of existing values + Siblings = Self.mValues.Value(Value.Hash) + Siblings.Add(Value) + Self.mValues.Value(Value.Hash) = Siblings + Next + Self.mIndex.CommitTransaction + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Add(Value As Palworld.ConfigValue, Options As Integer = 0) + Var Values(0) As Palworld.ConfigValue + Values(0) = Value + Self.Add(Values, Options) + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Add(File As String, Header As String, Struct As NullableString, Key As String, Value As Variant, Options As Integer = 0) + Var StringValue As String + Select Case Value.Type + Case Variant.TypeBoolean + StringValue = If(Value.BooleanValue, "True", "False") + Case Variant.TypeInt32, Variant.TypeInt64, Variant.TypeDouble, Variant.TypeSingle, Variant.TypeCurrency + StringValue = Value.DoubleValue.PrettyText + Else + StringValue = """" + Value.StringValue.ReplaceAll("""", "\""") + """" + End Select + + Self.Add(New Palworld.ConfigValue(File, Header, Struct, Key + "=" + StringValue), Options) + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Add(File As String, DefaultHeader As String, Content As String) + Var Tracker As New TimingTracker + + Content = Content.ReplaceLineEndings(EndOfLine.UNIX) + + Var DataSource As Palworld.DataSource = Palworld.DataSource.Pool.Get(False) + Var Lines() As String = Content.Split(EndOfLine.UNIX) + Var Header As String = DefaultHeader + Var Values() As Palworld.ConfigValue + Var KeyCounts As New Dictionary + Var Structs() As String = DataSource.GetConfigStructs(File, Header) + + For LineIdx As Integer = 0 To Lines.LastIndex + Var Line As String = Lines(LineIdx).Trim + If Line.IsEmpty Then + Continue + End If + + If Line.BeginsWith("[") And Line.EndsWith("]") Then + Header = Line.Middle(1, Line.Length - 2) + Structs = DataSource.GetConfigStructs(File, Header) + Continue + End If + + Var Parsed As Variant = Palworld.ImportThread.ParseLine(Line + EndOfLine.UNIX) + If Parsed.IsNull Or Parsed.Type <> Variant.TypeObject Or (Parsed.ObjectValue IsA Beacon.KeyValuePair) = False Then + Continue + End If + + Var Result As Beacon.KeyValuePair = Parsed + Var AttributedKey As String = Result.Key + Var KeyIndex As Integer = KeyCounts.Lookup(AttributedKey, 0).IntegerValue + KeyCounts.Value(AttributedKey) = KeyIndex + 1 + + If Structs.IndexOf(AttributedKey) > -1 And Result.Value.Type = Variant.TypeObject ANd Result.Value.ObjectValue IsA Dictionary Then + Var Struct As String = AttributedKey + Var StructData As Dictionary = Result.Value + For Each Entry As DictionaryEntry In StructData + Var Key As String = Entry.Key + Var Command As String + Select Case Entry.Value.Type + Case Variant.TypeBoolean + Command = Key + "=" + If(Entry.Value.BooleanValue, "True", "False") + Case Variant.TypeDouble, Variant.TypeSingle, Variant.TypeInt32, Variant.TypeInt64, Variant.TypeCurrency + Command = Key + "=" + Entry.Value.DoubleValue.PrettyText + Else + Command = Key + "=""" + Entry.Value.StringValue + """" + End Select + + Var Value As String = Entry.Value + + Values.Add(New Palworld.ConfigValue(File, Header, Struct, Command)) + Next + Continue + End If + + Values.Add(New Palworld.ConfigValue(File, Header, Nil, Line, KeyIndex)) + Next + Tracker.Log("Took %elapsed% to parse " + Content.Length.ToString + " characters of content.") + Self.Add(Values) + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Sub AddManagedKeys(Keys() As Palworld.ConfigOption) + For Idx As Integer = Keys.FirstIndex To Keys.LastIndex + If Keys(Idx) Is Nil Then + Continue + End If + + Self.mManagedKeys.Value(Keys(Idx).Signature) = Keys(Idx) + Next + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Sub AddManagedKeys(ParamArray Keys() As Palworld.ConfigOption) + Self.AddManagedKeys(Keys) + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Function BeaconKey(Key As String) As String + Return Self.mExtraBeaconKeys.Lookup(Key, "").StringValue + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub BeaconKey(Key As String, Assigns Value As String) + Self.mExtraBeaconKeys.Value(Key) = Value + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Function BeaconKeys() As String() + Var Keys() As String + For Each Entry As DictionaryEntry In Self.mExtraBeaconKeys + Keys.Add(Entry.Key) + Next Entry + Return Keys + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function Build(ForFile As String) As String + Var Headers() As String = Self.Headers(ForFile) + If ForFile = Palworld.ConfigFileSettings And Headers.IndexOf(Palworld.HeaderPalworldSettings) = -1 Then + Headers.Add(Palworld.HeaderPalworldSettings) + End If + Var Sections() As String + Var EOL As String = Encodings.UTF8.Chr(10) + + For Each Header As String In Headers + Var Lines() As String + Lines.Add("[" + Header + "]") + + Var Structs() As String = Self.Structs(ForFile, Header) + For Each Struct As String In Structs + Var Values() As Palworld.ConfigValue = Self.FilteredValues(ForFile, Header, Struct) + Var Members() As String + Values.Sort + For Each Value As Palworld.ConfigValue In Values + Members.Add(Value.Command) + Next + If Members.Count > 0 Then + Lines.Add(Struct + "=(" + String.FromArray(Members, ",") + ")") + End If + Next + + Var Values() As Palworld.ConfigValue = Self.FilteredValues(ForFile, Header, Nil) + Values.Sort + For Each Value As Palworld.ConfigValue In Values + Lines.Add(Value.Command) + Next + + Sections.Add(Lines.Join(EOL)) + Next + + Return Sections.Join(EOL + EOL) + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function Clone() As Palworld.ConfigOrganizer + Var Clone As New Palworld.ConfigOrganizer + Self.mIndex.Backup(Clone.mIndex, Nil, -1) + For Each Entry As DictionaryEntry In Self.mValues + Clone.mValues.Value(Entry.Key) = Entry.Value + Next + For Each Entry As DictionaryEntry In Self.mExtraBeaconKeys + Clone.mExtraBeaconKeys.Value(Entry.Key) = Entry.Value + Next Entry + For Each Entry As DictionaryEntry In Self.mManagedKeys + Clone.mManagedKeys.Value(Entry.Key) = Entry.Value + Next Entry + Return Clone + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Constructor() + Self.mValues = New Dictionary + Self.mExtraBeaconKeys = New Dictionary + Self.mManagedKeys = New Dictionary + Self.mIndex = New SQLiteDatabase + Self.mIndex.Connect + Self.mIndex.ExecuteSQL("CREATE TABLE keymap (hash TEXT NOT NULL PRIMARY KEY COLLATE NOCASE, file TEXT NOT NULL COLLATE NOCASE, header TEXT NOT NULL COLLATE NOCASE, struct TEXT COLLATE NOCASE, simplekey TEXT NOT NULL COLLATE NOCASE, sortkey TEXT NOT NULL COLLATE NOCASE);") + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Constructor(Values() As Palworld.ConfigValue) + Self.Constructor() + Self.Add(Values) + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Constructor(File As String, DefaultHeader As String, Content As String) + Self.Constructor() + Self.Add(File, DefaultHeader, Content) + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Function Count() As Integer + Return Self.mValues.KeyCount + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function DistinctKeys() As Palworld.ConfigOption() + Var Rows As RowSet = Self.mIndex.SelectSQL("SELECT DISTINCT file, header, struct, simplekey FROM keymap ORDER BY sortkey;") + Return Self.DistinctKeys(Rows) + End Function + #tag EndMethod + + #tag Method, Flags = &h21 + Private Function DistinctKeys(Rows As RowSet) As Palworld.ConfigOption() + Var Results() As Palworld.ConfigOption + While Rows.AfterLastRow = False + Var File As String = Rows.Column("file").StringValue + Var Header As String = Rows.Column("header").StringValue + Var Struct As NullableString = NullableString.FromVariant(Rows.Column("struct").Value) + Var SimpleKey As String = Rows.Column("simplekey").StringValue + Var Key As Palworld.ConfigOption = Palworld.DataSource.Pool.Get(False).GetConfigOption(File, Header, Struct, SimpleKey) + If Key Is Nil Then + Key = New Palworld.ConfigOption(File, Header, Struct, SimpleKey) + End If + Results.Add(Key) + Rows.MoveToNextRow + Wend + Return Results + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function DistinctKeys(ForFile As String) As Palworld.ConfigOption() + Var Rows As RowSet = Self.mIndex.SelectSQL("SELECT DISTINCT file, header, struct, simplekey FROM keymap WHERE file = ?1;", ForFile) + Return Self.DistinctKeys(Rows) + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function DistinctKeys(ForFile As String, ForHeader As String) As Palworld.ConfigOption() + Var Rows As RowSet = Self.mIndex.SelectSQL("SELECT DISTINCT file, header, struct, simplekey FROM keymap WHERE file = ?1 AND header = ?2;", ForFile, ForHeader) + Return Self.DistinctKeys(Rows) + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function DistinctKeys(ForFile As String, ForHeader As String, ForStruct As String) As Palworld.ConfigOption() + Var Rows As RowSet = Self.mIndex.SelectSQL("SELECT DISTINCT file, header, struct, simplekey FROM keymap WHERE file = ?1 AND header = ?2 AND struct = ?3;", ForFile, ForHeader, ForStruct) + Return Self.DistinctKeys(Rows) + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function FilteredValues() As Palworld.ConfigValue() + Var Rows As RowSet = Self.mIndex.SelectSQL("SELECT hash FROM keymap ORDER BY file, header, sortkey;") + Return Self.FilteredValues(Rows) + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function FilteredValues(Key As Palworld.ConfigOption) As Palworld.ConfigValue() + Return Self.FilteredValues(Key.File, Key.Header, Key.Struct, Key.SimplifiedKey) + End Function + #tag EndMethod + + #tag Method, Flags = &h21 + Private Function FilteredValues(Rows As RowSet) As Palworld.ConfigValue() + Var Results() As Palworld.ConfigValue + While Rows.AfterLastRow = False + Var Hash As String = Rows.Column("hash").StringValue + Var Values() As Palworld.ConfigValue = Self.mValues.Value(Hash) + For Each Value As Palworld.ConfigValue In Values + Results.Add(Value) + Next + Rows.MoveToNextRow + Wend + Return Results + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function FilteredValues(ForFile As String) As Palworld.ConfigValue() + Var Rows As RowSet = Self.mIndex.SelectSQL("SELECT hash FROM keymap WHERE file = ?1 ORDER BY header, sortkey;", ForFile) + Return Self.FilteredValues(Rows) + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function FilteredValues(ForFile As String, ForHeader As String) As Palworld.ConfigValue() + Var Rows As RowSet = Self.mIndex.SelectSQL("SELECT hash FROM keymap WHERE file = ?1 AND header = ?2 ORDER BY sortkey;", ForFile, ForHeader) + Return Self.FilteredValues(Rows) + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function FilteredValues(ForFile As String, ForHeader As String, ForStruct As NullableString) As Palworld.ConfigValue() + Var Rows As RowSet + If ForStruct Is Nil Then + Rows = Self.mIndex.SelectSQL("SELECT hash FROM keymap WHERE file = ?1 AND header = ?2 AND struct IS NULL ORDER BY sortkey;", ForFile, ForHeader) + Else + Rows = Self.mIndex.SelectSQL("SELECT hash FROM keymap WHERE file = ?1 AND header = ?2 AND struct = ?3 ORDER BY sortkey;", ForFile, ForHeader, ForStruct.StringValue) + End If + Return Self.FilteredValues(Rows) + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function FilteredValues(ForFile As String, ForHeader As String, ForStruct As NullableString, ForSimpleKey As String) As Palworld.ConfigValue() + Var Rows As RowSet + If ForStruct Is Nil Then + Rows = Self.mIndex.SelectSQL("SELECT hash FROM keymap WHERE file = ?1 AND header = ?2 AND struct IS NULL AND simplekey = ?3;", ForFile, ForHeader, ForSimpleKey) + Else + Rows = Self.mIndex.SelectSQL("SELECT hash FROM keymap WHERE file = ?1 AND header = ?2 AND struct = ?3 AND simplekey = ?4;", ForFile, ForHeader, ForStruct.StringValue, ForSimpleKey) + End If + Return Self.FilteredValues(Rows) + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function HasHeader(InFile As String, Header As String) As Boolean + Var Rows As RowSet = Self.mIndex.SelectSQL("SELECT COUNT(hash) AS numkeys FROM keymap WHERE file = ?1 AND header = ?2;", InFile, Header) + Return Rows.Column("numkeys").IntegerValue > 0 + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function HasStruct(InFile As String, Header As String, Struct As String) As Boolean + Var Rows As RowSet = Self.mIndex.SelectSQL("SELECT COUNT(hash) AS numkeys FROM keymap WHERE file = ?1 AND header = ?2 AND struct = ?3;", InFile, Header, Struct) + Return Rows.Column("numkeys").IntegerValue > 0 + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function Headers(ForFile As String) As String() + Var Rows As RowSet = Self.mIndex.SelectSQL("SELECT DISTINCT header FROM keymap WHERE file = ?1 ORDER BY header;", ForFile) + Var Results() As String + While Rows.AfterLastRow = False + Results.Add(Rows.Column("header").StringValue) + Rows.MoveToNextRow + Wend + Return Results + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function Keys(ForFile As String, ForHeader As String) As String() + Var Rows As RowSet = Self.mIndex.SelectSQL("SELECT DISTINCT simplekey FROM keymap WHERE file = ?1 AND header = ?2 ORDER BY sortkey;", ForFile, ForHeader) + Var Keys() As String + While Rows.AfterLastRow = False + Keys.Add(Rows.Column("simplekey").StringValue) + Rows.MoveToNextRow + Wend + Return Keys + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function ManagedKeys() As Palworld.ConfigOption() + Var Results() As Palworld.ConfigOption + For Each Entry As DictionaryEntry In Self.mManagedKeys + Results.Add(Entry.Value) + Next Entry + Return Results + End Function + #tag EndMethod + + #tag Method, Flags = &h21 + Private Shared Sub ParseLine(Line As String, ByRef Key As String, ByRef Value As Variant) + Line = Line.ReplaceAll("\""", "2488dddbde7c") + + Var EqualsPos As Integer = Line.IndexOf("=") + If EqualsPos = -1 Then + Key = "" + Value = Nil + Return + End If + + Var StartPos As Integer = EqualsPos + 1 + Var ParsedValue As Variant = ParseValue(Line, StartPos) + + Key = Line.Left(EqualsPos) + Value = ParsedValue + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Shared Function ParseStruct(File As String, Header As String, Line As String) As Palworld.ConfigValue() + Var Values() As Palworld.ConfigValue + #if false + Var EqualsPos As Integer = Line.IndexOf("=") + Var OpenPos As Integer = Line.IndexOf("(") + If EqualsPos = -1 Or OpenPos = -1 Or OpenPos <> EqualsPos + 1 Then + Return Values + End If + + Var StartPos As Integer = OpenPos + 1 + Do + Var KeyEqualsPos As Integer = Line.IndexOf(StartPos, "=") + Var ValueStartPos As Integer = KeyEqualsPos + 1 + Var ValueEndPos As Integer + If Line.Middle(ValueStartPos, 1) = """" Then + ValueStartPos = ValueStartPos + 1 + ValueEndPos = Line.IndexOf(ValueStartPos, """") + ElseIf Line.Middle(ValueStartPos, 1) = "(" Then + ValueStartPos = ValueStartPos + 1 + ValueEndPos + Else + ValueEndPos = Min(Line.IndexOf(ValueStartPos, ","), Line.IndexOf(ValueStartPos, ")")) + End If + Loop Until + #endif + Return Values + End Function + #tag EndMethod + + #tag Method, Flags = &h21 + Private Shared Function ParseValue(Line As String, ByRef StartPos As Integer) As Variant + If Line.Middle(StartPos, 1) = """" Then + // Quoted string + StartPos = StartPos + 1 + Var EndPos As Integer = Line.IndexOf(StartPos, """") + If EndPos = -1 Then + EndPos = Line.Length + End If + + Var Value As String = Line.Middle(StartPos, EndPos - StartPos).ReplaceAll("2488dddbde7c", """") + StartPos = EndPos + Return Value + End If + + If Line.Middle(StartPos, 1) = "(" Then + // Struct or array + StartPos = StartPos + 1 + + End If + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Remove(Keys() As Palworld.ConfigOption) + Self.mIndex.BeginTransaction + For Each Key As Palworld.ConfigOption In Keys + If Key Is Nil Then + Continue + End If + Var Rows As RowSet + If Key.Struct Is Nil Then + Rows = Self.mIndex.SelectSQL("SELECT hash FROM keymap WHERE file = ?1 AND header = ?2 AND struct IS NULL AND simplekey = ?3;", Key.File, Key.Header, Key.Key) + Else + Rows = Self.mIndex.SelectSQL("SELECT hash FROM keymap WHERE file = ?1 AND header = ?2 AND struct = ?3 AND simplekey = ?4;", Key.File, Key.Header, Key.Struct.StringValue, Key.Key) + End If + While Rows.AfterLastRow = False + Var Hash As String = Rows.Column("hash").StringValue + Self.mValues.Remove(Hash) + Self.mIndex.ExecuteSQL("DELETE FROM keymap WHERE hash = ?1;", Hash) + Rows.MoveToNextRow + Wend + Next + Self.mIndex.CommitTransaction + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Remove(Key As Palworld.ConfigOption) + Var Keys(0) As Palworld.ConfigOption + Keys(0) = Key + Self.Remove(Keys) + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Remove(Values() As Palworld.ConfigValue) + Self.mIndex.BeginTransaction + For Each Value As Palworld.ConfigValue In Values + If Self.mValues.HasKey(Value.Hash) Then + Self.mValues.Remove(Value.Hash) + Self.mIndex.ExecuteSQL("DELETE FROM keymap WHERE hash = ?1;", Value.Hash) + End If + Next + Self.mIndex.CommitTransaction + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Remove(Value As Palworld.ConfigValue) + Var Values(0) As Palworld.ConfigValue + Values(0) = Value + Self.Remove(Values) + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub Remove(Rows As RowSet) + Self.mIndex.BeginTransaction + While Rows.AfterLastRow = False + Var Hash As String = Rows.Column("hash").StringValue + Self.mValues.Remove(Hash) + Self.mIndex.ExecuteSQL("DELETE FROM keymap WHERE hash = ?1;", Hash) + Rows.MoveToNextRow + Wend + Self.mIndex.CommitTransaction + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Remove(ForFile As String) + Var Rows As RowSet = Self.mIndex.SelectSQL("SELECT hash FROM keymap WHERE file = ?1;", ForFile) + Self.Remove(Rows) + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Remove(ForFile As String, Header As String) + Var Rows As RowSet = Self.mIndex.SelectSQL("SELECT hash FROM keymap WHERE file = ?1 AND header = ?2;", ForFile, Header) + Self.Remove(Rows) + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Remove(ForFile As String, Header As String, Struct As NullableString) + Var Rows As RowSet + If Struct Is Nil Then + Rows = Self.mIndex.SelectSQL("SELECT hash FROM keymap WHERE file = ?1 AND header = ?2 AND struct IS NULL;", ForFile, Header) + Else + Rows = Self.mIndex.SelectSQL("SELECT hash FROM keymap WHERE file = ?1 AND header = ?2 AND struct = ?3;", ForFile, Header, Struct.StringValue) + End If + Self.Remove(Rows) + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Remove(ForFile As String, Header As String, Struct As NullableString, Key As String) + Var Rows As RowSet + If Struct Is Nil Then + Rows = Self.mIndex.SelectSQL("SELECT hash FROM keymap WHERE file = ?1 AND header = ?2 AND struct IS NULL AND simplekey = ?3;", ForFile, Header, Key) + Else + Rows = Self.mIndex.SelectSQL("SELECT hash FROM keymap WHERE file = ?1 AND header = ?2 AND struct = ?3 AND simplekey = ?4;", ForFile, Header, Struct.StringValue, Key) + End If + Self.Remove(Rows) + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Function Structs(ForFile As String, ForHeader As String) As String() + Var Rows As RowSet = Self.mIndex.SelectSQL("SELECT DISTINCT struct FROM keymap WHERE file = ?1 AND header = ?2 AND struct IS NOT NULL ORDER BY struct", ForFile, ForHeader) + Var Results() As String + While Rows.AfterLastRow = False + Results.Add(Rows.Column("struct").StringValue) + Rows.MoveToNextRow + Wend + Return Results + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Swap(TargetKey As Palworld.ConfigOption, ReplacementKey As Palworld.ConfigOption) + If TargetKey Is Nil Or ReplacementKey Is Nil Then + Return + End If + + Var Values() As Palworld.ConfigValue = Self.FilteredValues(TargetKey) + If Values.Count = 0 Then + Return + End If + + Self.Remove(Values) + Self.Remove(ReplacementKey) + + If Self.mManagedKeys.HasKey(TargetKey.Signature) Then + Self.mManagedKeys.Remove(TargetKey.Signature) + Self.mManagedKeys.Value(ReplacementKey.Signature) = ReplacementKey + End If + + Var Replacements() As Palworld.ConfigValue + For Each Value As Palworld.ConfigValue In Values + Replacements.Add(New Palworld.ConfigValue(ReplacementKey, Value.Command, Value.SortKey)) + Next Value + Self.Add(Replacements) + End Sub + #tag EndMethod + + + #tag Property, Flags = &h21 + Private mExtraBeaconKeys As Dictionary + #tag EndProperty + + #tag Property, Flags = &h21 + Private mIndex As SQLiteDatabase + #tag EndProperty + + #tag Property, Flags = &h21 + Private mManagedKeys As Dictionary + #tag EndProperty + + #tag Property, Flags = &h21 + Private mValues As Dictionary + #tag EndProperty + + + #tag Constant, Name = OptionAddOnlyUnique, Type = Double, Dynamic = False, Default = \"1", Scope = Public + #tag EndConstant + + #tag Constant, Name = OptionValueIsManaged, Type = Double, Dynamic = False, Default = \"2", Scope = Public + #tag EndConstant + + + #tag ViewBehavior + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag EndViewBehavior +End Class +#tag EndClass diff --git a/Project/Modules/Game Support/Palworld/ConfigParser.xojo_code b/Project/Modules/Game Support/Palworld/ConfigParser.xojo_code new file mode 100644 index 000000000..1bd5940c1 --- /dev/null +++ b/Project/Modules/Game Support/Palworld/ConfigParser.xojo_code @@ -0,0 +1,211 @@ +#tag Class +Private Class ConfigParser + #tag Method, Flags = &h0 + Function AddCharacter(Char As String) As Boolean + Static LineEndingChar As String = Palworld.ImportThread.LineEndingChar + + Self.ConsumedLastChar = True + + If Self.SubParser <> Nil Then + If Self.SubParser.AddCharacter(Char) Then + // This parser is done + + Select Case Self.Type + Case Self.TypePair + Self.mValue = New Beacon.KeyValuePair(Self.Key, Self.SubParser.Value) + Self.ConsumedLastChar = Self.SubParser.ConsumedLastChar + Self.SubParser = Nil + Return True + Case Self.TypeArray + Var Values() As Variant = Self.mValue + Values.Add(Self.SubParser.Value) + Self.mValue = Values + Var Consumed As Boolean = Self.SubParser.ConsumedLastChar + Self.SubParser = Nil + + If Consumed Then + Return False + End If + End Select + Else + Return False + End If + End If + + If InQuotes Then + Var LastChar As String + If Self.Buffer.LastIndex > -1 Then + LastChar = Self.Buffer(Self.Buffer.LastIndex) + End If + + If Char = """" Then + If LastChar = "\" Then + Self.Buffer(Self.Buffer.LastIndex) = Char + Else + InQuotes = False + End If + ElseIf Char = "n" And LastChar = "\" Then + Self.Buffer(Self.Buffer.LastIndex) = EndOfLine + Else + Self.Buffer.Add(Char) + End If + Return False + End If + + Select Case Self.Type + Case Self.TypeIntrinsic + Select Case Char + Case "(" + If Self.Buffer.LastIndex = -1 Then + Self.SubParser = New Palworld.ConfigParser(Self.Level + 1) + Self.Type = Self.TypeArray + + Var Values() As Variant + Self.mValue = Values + Else + Self.Buffer.Add(Char) + End If + Case "=" + If Not Self.KeyFound Then + Self.Key = Self.Buffer.Join("").Trim + Self.Buffer.ResizeTo(-1) + Self.Type = Self.TypePair + Self.SubParser = New Palworld.ConfigParser(Self.Level) // Same level + // We want the subparser to know the key was found too. The ( will start a new + // parser whose KeyFound will be false + Self.SubParser.KeyFound = True + Self.KeyFound = True + Else + Self.Buffer.Add(Char) + End If + Case ")", ",", LineEndingChar + If Self.Level = 0 And Char <> LineEndingChar Then + Self.Buffer.Add(Char) + Else + Self.ConsumedLastChar = False + Self.mValue = Self.Buffer.Join("") + Self.Buffer.ResizeTo(-1) + Self.KeyFound = False + Return True + End If + Case """" + Self.InQuotes = True + Else + Self.Buffer.Add(Char) + End Select + Case Self.TypeArray + Select Case Char + Case ")", LineEndingChar + Return True + Case "," + Self.SubParser = New Palworld.ConfigParser(Self.Level + 1) + End Select + End Select + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Constructor(Level As Integer = 0) + Self.Type = Self.TypeIntrinsic + Self.Level = Level + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Function Value() As Variant + Return Self.mValue + End Function + #tag EndMethod + + + #tag Property, Flags = &h21 + Private Buffer() As String + #tag EndProperty + + #tag Property, Flags = &h21 + Private ConsumedLastChar As Boolean + #tag EndProperty + + #tag Property, Flags = &h21 + Private InQuotes As Boolean + #tag EndProperty + + #tag Property, Flags = &h21 + Private Key As String + #tag EndProperty + + #tag Property, Flags = &h21 + Private KeyFound As Boolean + #tag EndProperty + + #tag Property, Flags = &h21 + Private Level As Integer + #tag EndProperty + + #tag Property, Flags = &h21 + Private mValue As Variant + #tag EndProperty + + #tag Property, Flags = &h21 + Private SubParser As Palworld.ConfigParser + #tag EndProperty + + #tag Property, Flags = &h21 + Private Type As Integer + #tag EndProperty + + + #tag Constant, Name = TypeArray, Type = Double, Dynamic = False, Default = \"2", Scope = Private + #tag EndConstant + + #tag Constant, Name = TypeIntrinsic, Type = Double, Dynamic = False, Default = \"0", Scope = Private + #tag EndConstant + + #tag Constant, Name = TypePair, Type = Double, Dynamic = False, Default = \"1", Scope = Private + #tag EndConstant + + + #tag ViewBehavior + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag EndViewBehavior +End Class +#tag EndClass diff --git a/Project/Modules/Game Support/Palworld/ConfigValue.xojo_code b/Project/Modules/Game Support/Palworld/ConfigValue.xojo_code new file mode 100644 index 000000000..0deb878f3 --- /dev/null +++ b/Project/Modules/Game Support/Palworld/ConfigValue.xojo_code @@ -0,0 +1,342 @@ +#tag Class +Protected Class ConfigValue + #tag Method, Flags = &h0 + Sub Constructor(Key As Palworld.ConfigOption, Command As String, Index As Integer = 0) + Self.ParseCommand(Command) + Self.mSortKey = Self.mSimplifiedKey + ":" + Index.ToString("00000") + Self.mKeyDetails = Key + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Constructor(Key As Palworld.ConfigOption, Command As String, SortKey As String) + Self.ParseCommand(Command) + Self.mSortKey = SortKey + Self.mKeyDetails = Key + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Constructor(File As String, Header As String, Struct As NullableString, Command As String, Index As Integer = 0) + Self.ParseCommand(Command) + Self.mSortKey = Self.mSimplifiedKey + ":" + Index.ToString("00000") + + Var Keys() As Palworld.ConfigOption = Palworld.DataSource.Pool.Get(False).GetConfigOptions(File, Header, Struct, Self.mSimplifiedKey, False) + Var ConfigOption As Palworld.ConfigOption + If Keys.Count >= 1 Then + ConfigOption = Keys(0) + Else + ConfigOption = New Palworld.ConfigOption(File, Header, Struct, Self.mSimplifiedKey) + End If + + Self.mKeyDetails = ConfigOption + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Constructor(File As String, Header As String, Struct As NullableString, Command As String, SortKey As String) + Self.ParseCommand(Command) + Self.mSortKey = SortKey + + Var Keys() As Palworld.ConfigOption = Palworld.DataSource.Pool.Get(False).GetConfigOptions(File, Header, Struct, Self.mSimplifiedKey, False) + Var ConfigOption As Palworld.ConfigOption + If Keys.Count >= 1 Then + ConfigOption = Keys(0) + Else + ConfigOption = New Palworld.ConfigOption(File, Header, Struct, Self.mSimplifiedKey) + End If + + Self.mKeyDetails = ConfigOption + + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub ParseCommand(Command As String) + Self.mCommand = Command + + Var Pos As Integer = Command.IndexOf("=") + If Pos > -1 Then + Self.mAttributedKey = Command.Left(Pos) + Self.mValue = Command.Middle(Pos + 1).Trim + Else + Self.mAttributedKey = Command + Self.mValue = "True" + End If + + Self.mSimplifiedKey = Self.SimplifyKey(Self.mAttributedKey) + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Shared Function SimplifyKey(Key As String) As String + Var Idx As Integer = Key.IndexOf("[") + If Idx = -1 Then + Return Key + Else + Return Key.Left(Idx) + End If + End Function + #tag EndMethod + + + #tag ComputedProperty, Flags = &h0 + #tag Getter + Get + Return Self.mAttributedKey + End Get + #tag EndGetter + AttributedKey As String + #tag EndComputedProperty + + #tag ComputedProperty, Flags = &h0 + #tag Getter + Get + Return Self.mCommand + End Get + #tag EndGetter + Command As String + #tag EndComputedProperty + + #tag ComputedProperty, Flags = &h0 + #tag Getter + Get + Return Self.mKeyDetails + End Get + #tag EndGetter + Details As Palworld.ConfigOption + #tag EndComputedProperty + + #tag ComputedProperty, Flags = &h0 + #tag Getter + Get + Return Self.mKeyDetails.File + End Get + #tag EndGetter + File As String + #tag EndComputedProperty + + #tag ComputedProperty, Flags = &h0 + #tag Getter + Get + If Self.mHash.IsEmpty Then + Var Raw As String = Self.mKeyDetails.File.Lowercase + ":" + Self.mKeyDetails.Header.Lowercase + If(Self.mKeyDetails.Struct Is Nil, "", ":" + Self.mKeyDetails.Struct.StringValue.Lowercase) + ":" + Self.mSortKey.Lowercase + #if DebugBuild + Self.mHash = Raw + #else + Self.mHash = EncodeHex(Crypto.SHA256(Raw)).Lowercase + #endif + End If + Return Self.mHash + End Get + #tag EndGetter + Hash As String + #tag EndComputedProperty + + #tag ComputedProperty, Flags = &h0 + #tag Getter + Get + Return Self.mKeyDetails.Header + End Get + #tag EndGetter + Header As String + #tag EndComputedProperty + + #tag Property, Flags = &h21 + Private mAttributedKey As String + #tag EndProperty + + #tag Property, Flags = &h21 + Private mCommand As String + #tag EndProperty + + #tag Property, Flags = &h21 + Private mHash As String + #tag EndProperty + + #tag Property, Flags = &h21 + Private mKeyDetails As Palworld.ConfigOption + #tag EndProperty + + #tag Property, Flags = &h21 + Private mSimplifiedKey As String + #tag EndProperty + + #tag Property, Flags = &h21 + Private mSortKey As String + #tag EndProperty + + #tag Property, Flags = &h21 + Private mStruct As NullableString + #tag EndProperty + + #tag Property, Flags = &h21 + Private mValue As String + #tag EndProperty + + #tag ComputedProperty, Flags = &h0 + #tag Getter + Get + Return Self.mSimplifiedKey + End Get + #tag EndGetter + SimplifiedKey As String + #tag EndComputedProperty + + #tag ComputedProperty, Flags = &h0 + #tag Getter + Get + If Self.mKeyDetails Is Nil Then + Return False + Else + Return (Self.mKeyDetails.MaxAllowed Is Nil) = False And Self.mKeyDetails.MaxAllowed.DoubleValue = 1 + End If + End Get + #tag EndGetter + SingleInstance As Boolean + #tag EndComputedProperty + + #tag ComputedProperty, Flags = &h0 + #tag Getter + Get + Return Self.mSortKey + End Get + #tag EndGetter + SortKey As String + #tag EndComputedProperty + + #tag ComputedProperty, Flags = &h0 + #tag Getter + Get + Return Self.mKeyDetails.Struct + End Get + #tag EndGetter + Struct As NullableString + #tag EndComputedProperty + + #tag ComputedProperty, Flags = &h0 + #tag Getter + Get + Return Self.mValue + End Get + #tag EndGetter + Value As String + #tag EndComputedProperty + + + #tag ViewBehavior + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Value" + Visible=false + Group="Behavior" + InitialValue="" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="Header" + Visible=false + Group="Behavior" + InitialValue="" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="SimplifiedKey" + Visible=false + Group="Behavior" + InitialValue="" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="Command" + Visible=false + Group="Behavior" + InitialValue="" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="File" + Visible=false + Group="Behavior" + InitialValue="" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="Hash" + Visible=false + Group="Behavior" + InitialValue="" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="AttributedKey" + Visible=false + Group="Behavior" + InitialValue="" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="SortKey" + Visible=false + Group="Behavior" + InitialValue="" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="SingleInstance" + Visible=false + Group="Behavior" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag EndViewBehavior +End Class +#tag EndClass diff --git a/Project/Modules/Game Support/Palworld/Configs.xojo_code b/Project/Modules/Game Support/Palworld/Configs.xojo_code new file mode 100644 index 000000000..d23d18dbe --- /dev/null +++ b/Project/Modules/Game Support/Palworld/Configs.xojo_code @@ -0,0 +1,348 @@ +#tag Module +Protected Module Configs + #tag Method, Flags = &h1 + Protected Function AllNames(Human As Boolean = False) As String() + Static Names() As String + If Names.LastIndex = -1 Then + Names.Add(NameCustomConfig) + Names.Add(NameGeneralSettings) + End If + If Human = True Then + Static HumanNames() As String + If HumanNames.LastIndex = -1 Then + HumanNames.ResizeTo(Names.LastIndex) + For I As Integer = 0 To Names.LastIndex + HumanNames(I) = Language.LabelForConfig(Names(I)) + Next + HumanNames.Sort + End If + Return HumanNames + Else + Return Names + End If + End Function + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Function AllTools() As Palworld.ProjectTool() + Static Tools() As Palworld.ProjectTool + If Tools.LastIndex = -1 Then + Tools.ResizeTo(-1) + Tools.Add(New Palworld.ProjectTool("Setup Guided Editors", "4add8d2b-fca9-4f37-9eb3-aae75b2c21fc", NameCustomConfig)) + + Var Names() As String + Names.ResizeTo(Tools.LastIndex) + For Idx As Integer = Tools.FirstIndex To Tools.LastIndex + Names(Idx) = Language.LabelForConfig(Tools(Idx).FirstGroup) + " - " + Tools(Idx).Caption + Next Idx + Names.SortWith(Tools) + End If + Return Tools + End Function + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Function CloneInstance(Group As Palworld.ConfigGroup) As Palworld.ConfigGroup + If Group Is Nil Then + Return Nil + End If + + Var NewInstance As Palworld.ConfigGroup = CreateInstance(Group.InternalName) + NewInstance.CopyFrom(Group) + Return NewInstance + End Function + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Function ConfigUnlocked(Config As Palworld.ConfigGroup, Identity As Beacon.Identity) As Boolean + Return ConfigUnlocked(Config.InternalName, Identity) + End Function + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Function ConfigUnlocked(InternalName As String, Identity As Beacon.Identity) As Boolean + #if DebugBuild + Return True + #endif + + If mConfigOmniCache Is Nil Then + mConfigOmniCache = New Dictionary + End If + + Var RequiresOmni As Boolean + If mConfigOmniCache.HasKey(InternalName) = False Then + Select Case InternalName + Case NameServers, NameProjectSettings, NameAccounts + RequiresOmni = False + Else + Var Instance As Palworld.ConfigGroup = CreateInstance(InternalName) + If (Instance Is Nil) = False Then + RequiresOmni = Instance.RequiresOmni + End If + End Select + mConfigOmniCache.Value(InternalName) = RequiresOmni + Else + RequiresOmni = mConfigOmniCache.Value(InternalName) + End If + + If RequiresOmni Then + Return Palworld.OmniPurchased(Identity) + Else + Return True + End If + End Function + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Function CreateInstance(InternalName As String) As Palworld.ConfigGroup + Select Case InternalName + Case NameCustomConfig + Return New Palworld.Configs.CustomContent() + Case NameGeneralSettings + Return New Palworld.Configs.OtherSettings() + Else + Var Err As New FunctionNotFoundException + Err.Message = "Config group """ + InternalName + """ is not known." + Raise Err + End Select + End Function + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Function CreateInstance(InternalName As String, SaveData As Dictionary, EncryptedData As Dictionary) As Palworld.ConfigGroup + If EncryptedData Is Nil Then + If SaveData.HasAllKeys("Plain", "Encrypted") Then + EncryptedData = SaveData.Value("Encrypted") + SaveData = SaveData.Value("Plain") + Else + EncryptedData = New Dictionary + End If + End If + + Select Case InternalName + Case NameCustomConfig + Return New Palworld.Configs.CustomContent(SaveData, EncryptedData) + Case NameGeneralSettings + Return New Palworld.Configs.OtherSettings(SaveData, EncryptedData) + Else + Var Err As New FunctionNotFoundException + Err.Message = "Config group """ + InternalName + """ is not known." + Raise Err + End Select + End Function + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Function CreateInstance(InternalName As String, ParsedData As Dictionary, Project As Palworld.Project) As Palworld.ConfigGroup + // Why not just pass the project itself to these methods? Because we don't want it to be possible + // for the creation of an instance to modify the project. MapMask and ContentPacks are immutable, + // but the difficulty object is not, so we'll pass the raw value instead. + + Select Case InternalName + Case Palworld.Configs.NameCustomConfig + Return Nil + Case Palworld.Configs.NameGeneralSettings + Return Palworld.Configs.OtherSettings.FromImport(ParsedData, Project.ContentPacks) + Else + Var Err As New FunctionNotFoundException + Err.Message = "Config group """ + InternalName + """ is not known." + Raise Err + End Select + End Function + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Function Merge(ZeroHasPriority As Boolean, ParamArray Groups() As Palworld.ConfigGroup) As Palworld.ConfigGroup + Return Merge(Groups, ZeroHasPriority) + End Function + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Function Merge(Groups() As Palworld.ConfigGroup, ZeroHasPriority As Boolean) As Palworld.ConfigGroup + // First, make sure all groups are the same type + + If Groups.Count = 0 Then + Return Nil + ElseIf Groups.Count = 1 Then + Return CloneInstance(Groups(0)) + End If + + Var MergeSupported As Boolean = Groups(0).SupportsMerging + Var GroupName As String = Groups(0).InternalName + For Idx As Integer = 1 To Groups.LastIndex + If Groups(Idx) Is Nil Or Groups(Idx).InternalName <> GroupName Then + Return Nil + End If + Next Idx + + Var NewGroup As Palworld.ConfigGroup + If ZeroHasPriority Then + If MergeSupported Then + For Idx As Integer = Groups.LastIndex DownTo 0 + If NewGroup Is Nil Then + NewGroup = CreateInstance(GroupName) + End If + NewGroup.CopyFrom(Groups(Idx)) + Next Idx + Else + NewGroup = CloneInstance(Groups(0)) + End If + Else + If MergeSupported Then + For Idx As Integer = 0 To Groups.LastIndex + If NewGroup Is Nil Then + NewGroup = CreateInstance(GroupName) + End If + NewGroup.CopyFrom(Groups(Idx)) + Next Idx + Else + NewGroup = CloneInstance(Groups(Groups.LastIndex)) + End If + End If + + Return NewGroup + End Function + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Function ShouldImport(InternalName As String, Identity As Beacon.Identity) As Boolean + If ConfigUnlocked(InternalName, Identity) Then + Return True + End If + + // Import these, even if locked. + Select Case InternalName + Case Palworld.Configs.NameGeneralSettings + Return True + End Select + End Function + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Function SupportsConfigSets(InternalName As String) As Boolean + If mConfigSetSupportCache Is Nil Then + mConfigSetSupportCache = New Dictionary + End If + + If mConfigSetSupportCache.HasKey(InternalName) = False Then + Var Instance As Palworld.ConfigGroup + + #Pragma BreakOnExceptions False + Try + Instance = CreateInstance(InternalName) + Catch Err As RuntimeException + End Try + #Pragma BreakOnExceptions Default + + If Instance Is Nil Then + mConfigSetSupportCache.Value(InternalName) = False + Else + mConfigSetSupportCache.Value(InternalName) = Instance.SupportsConfigSets + End If + End If + + Return mConfigSetSupportCache.Value(InternalName) + End Function + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Function SupportsMerging(InternalName As String) As Boolean + If mMergingSupportCache Is Nil Then + mMergingSupportCache = New Dictionary + End If + + If mMergingSupportCache.HasKey(InternalName) = False Then + Var Instance As Palworld.ConfigGroup + + #Pragma BreakOnExceptions False + Try + Instance = CreateInstance(InternalName) + Catch Err As RuntimeException + End Try + #Pragma BreakOnExceptions Default + + If Instance Is Nil Then + mMergingSupportCache.Value(InternalName) = False + Else + mMergingSupportCache.Value(InternalName) = Instance.SupportsMerging + End If + End If + + Return mMergingSupportCache.Value(InternalName) + End Function + #tag EndMethod + + + #tag Property, Flags = &h21 + Private mConfigOmniCache As Dictionary + #tag EndProperty + + #tag Property, Flags = &h21 + Private mConfigSetSupportCache As Dictionary + #tag EndProperty + + #tag Property, Flags = &h21 + Private mMergingSupportCache As Dictionary + #tag EndProperty + + + #tag Constant, Name = NameAccounts, Type = String, Dynamic = False, Default = \"Palworld.Accounts", Scope = Protected + #tag EndConstant + + #tag Constant, Name = NameCustomConfig, Type = String, Dynamic = False, Default = \"Palworld.CustomConfig", Scope = Protected + #tag EndConstant + + #tag Constant, Name = NameGeneralSettings, Type = String, Dynamic = False, Default = \"Palworld.GeneralSettings", Scope = Protected + #tag EndConstant + + #tag Constant, Name = NameProjectSettings, Type = String, Dynamic = False, Default = \"Palworld.ProjectSettings", Scope = Protected + #tag EndConstant + + #tag Constant, Name = NameServers, Type = String, Dynamic = False, Default = \"Palworld.Servers", Scope = Protected + #tag EndConstant + + + #tag ViewBehavior + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag EndViewBehavior +End Module +#tag EndModule diff --git a/Project/Modules/Game Support/Palworld/Configs/CustomContent.xojo_code b/Project/Modules/Game Support/Palworld/Configs/CustomContent.xojo_code new file mode 100644 index 000000000..e57322dab --- /dev/null +++ b/Project/Modules/Game Support/Palworld/Configs/CustomContent.xojo_code @@ -0,0 +1,345 @@ +#tag Class +Protected Class CustomContent +Inherits Palworld.ConfigGroup + #tag CompatibilityFlags = ( TargetConsole and ( Target32Bit or Target64Bit ) ) or ( TargetWeb and ( Target32Bit or Target64Bit ) ) or ( TargetDesktop and ( Target32Bit or Target64Bit ) ) or ( TargetIOS and ( Target64Bit ) ) or ( TargetAndroid and ( Target64Bit ) ) + #tag Event + Sub CopyFrom(Other As Palworld.ConfigGroup) + Var Source As Palworld.Configs.CustomContent = Palworld.Configs.CustomContent(Other) + + Var SelfOrganizer As New Palworld.ConfigOrganizer + SelfOrganizer.Add(Palworld.ConfigFileSettings, Palworld.HeaderPalworldSettings, Self.mSettingsIniContent) + + Var SourceOrganizer As New Palworld.ConfigOrganizer + SourceOrganizer.Add(Palworld.ConfigFileSettings, Palworld.HeaderPalworldSettings, Source.mSettingsIniContent) + + SelfOrganizer.Remove(SourceOrganizer.DistinctKeys) + SelfOrganizer.Add(SourceOrganizer.FilteredValues) + + Var MergedSettingsIni As String = SelfOrganizer.Build(Palworld.ConfigFileSettings) + + If Self.mSettingsIniContent <> MergedSettingsIni Then + Self.Modified = True + Self.mSettingsIniContent = MergedSettingsIni + End If + End Sub + #tag EndEvent + + #tag Event + Function GenerateConfigValues(Project As Palworld.Project, Profile As Palworld.ServerProfile) As Palworld.ConfigValue() + #Pragma Unused Project + + Var Organizer As New Palworld.ConfigOrganizer(Palworld.ConfigFileSettings, Palworld.HeaderPalworldSettings, Self.CleanupContent(Self.mSettingsIniContent, Palworld.HeaderPalworldSettings, Profile)) + Return Organizer.FilteredValues() + End Function + #tag EndEvent + + #tag Event + Function HasContent() As Boolean + Return Self.mSettingsIniContent.IsEmpty = False + End Function + #tag EndEvent + + #tag Event + Sub ReadSaveData(SaveData As Dictionary, EncryptedData As Dictionary) + Var Rainbow As Dictionary + If EncryptedData.HasKey("Rainbow") Then + Try + Rainbow = EncryptedData.Value("Rainbow") + Catch Err As RuntimeException + End Try + End If + + If EncryptedData.HasKey("Salt") Then + Self.mSalt = DecodeHex(EncryptedData.Value("Salt").StringValue) + End If + + If SaveData.HasKey(Palworld.ConfigFileSettings) Then + Self.mSettingsIniContent = Self.ReadContent(SaveData.Value(Palworld.ConfigFileSettings), Rainbow) + End If + End Sub + #tag EndEvent + + #tag Event + Sub WriteSaveData(SaveData As Dictionary, EncryptedData As Dictionary) + Var Rainbow As New Dictionary + SaveData.Value(Palworld.ConfigFileSettings) = Self.WriteContent(Self.mSettingsIniContent, Rainbow) + + If Rainbow.KeyCount > 0 Then + EncryptedData.Value("Salt") = EncodeHex(Self.mSalt) + EncryptedData.Value("Rainbow") = Rainbow + End If + End Sub + #tag EndEvent + + + #tag Method, Flags = &h21 + Private Function CleanupContent(Content As String, DefaultHeader As String, Profile As Palworld.ServerProfile) As String + // First, remove the encryption tags + Content = Content.ReplaceAll(Self.EncryptedTag, "") + + // Now we need to remove all comment lines + Var Lines() As String = Content.ReplaceLineEndings(EndOfLine).Split(EndOfLine) + For Idx As Integer = Lines.LastIndex DownTo 0 + Lines(Idx) = Lines(Idx).Trim + If Lines(Idx).IsEmpty Or Lines(Idx).BeginsWith("//") Then + Lines.RemoveAt(Idx) + End If + Next + + Try + // Next, server-specific blocks are important + Var HeaderStack() As String = Array(DefaultHeader) + Var ServersStack() As String = Array("") + Var Map As New Dictionary + Var MapKey As String = "" + For Idx As Integer = 0 To Lines.LastIndex + Var Line As String = Lines(Idx) + + If Line.BeginsWith("#Server") Then + Var IDList As String = Line.Middle(Line.IndexOf(" ") + 1).Trim + HeaderStack.Add(HeaderStack(HeaderStack.LastIndex)) + ServersStack.Add(IDList) + MapKey = ServersStack.Join(".") + Continue + ElseIf Line.BeginsWith("#End") Then + If ServersStack.Count > 1 Then + ServersStack.RemoveAt(ServersStack.LastIndex) + HeaderStack.RemoveAt(HeaderStack.LastIndex) + MapKey = ServersStack.Join(".") + + // Switch the context back + Line = "[" + HeaderStack(HeaderStack.LastIndex) + "]" + End If + ElseIf Line.BeginsWith("[") And Line.EndsWith("]") Then + Var Header As String = Line.Middle(1, Line.Length - 2) + HeaderStack.RemoveAt(HeaderStack.LastIndex) + HeaderStack.Add(Header) + ElseIf Line.IsEmpty Then + Continue + End If + + Var ContextLines() As String + If Map.HasKey(MapKey) Then + ContextLines = Map.Value(MapKey) + End If + ContextLines.Add(Line) + Map.Value(MapKey) = ContextLines + Next + + Var FinishedLines() As String + Var ProfileShort As String + If (Profile Is Nil) = False Then + ProfileShort = Profile.ProfileID.Left(8) + End If + For Each Entry As DictionaryEntry In Map + Var Key As String = Entry.Key + If Key.IsEmpty = False Then + If (Profile Is Nil) Then + Continue + End If + + Var Components() As String = Key.Split(".") + For Each Component As String In Components + If Component.IsEmpty = False And Component.IndexOf(ProfileShort) = -1 Then + Continue For Entry + End If + Next + End If + + Var ContextLines() As String = Entry.Value + For Each Line As String In ContextLines + FinishedLines.Add(Line) + Next + Next + + // And put it all back together + Return FinishedLines.Join(EndOfLine) + Catch Err As RuntimeException + App.Log(Err, CurrentMethodName) + Return Lines.Join(EndOfLine) + End Try + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Constructor() + Self.mSalt = Crypto.GenerateRandomBytes(32) + + Super.Constructor() + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Function DefaultImported() As Boolean + Return False + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function InternalName() As String + Return Palworld.Configs.NameCustomConfig + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function IsDefaultImported() As Boolean + Return False + End Function + #tag EndMethod + + #tag Method, Flags = &h21 + Private Function ReadContent(Input As String, Rainbow As Dictionary) As String + Input = Input.GuessEncoding("/Script/") + + Var Pos As Integer + + Do + Pos = Input.IndexOf(Pos, Self.EncryptedTag) + If Pos = -1 Then + Return Input + End If + + Var StartPos As Integer = Pos + Self.EncryptedTag.Length + Var EndPos As Integer = Input.IndexOf(StartPos, Self.EncryptedTag) + If EndPos = -1 Then + EndPos = Input.Length + End If + + Var Prefix As String = Input.Left(StartPos) + Var Suffix As String = Input.Right(Input.Length - EndPos) + Var EncryptedContent As String = Input.Middle(StartPos, EndPos - StartPos) + Var DecryptedContent As String + If (Rainbow Is Nil) = False And Rainbow.HasKey(EncryptedContent) Then + DecryptedContent = Rainbow.Value(EncryptedContent) + End If + + If DecryptedContent.IsEmpty Then + Prefix = Prefix.Left(Prefix.Length - Self.EncryptedTag.Length) + Suffix = Suffix.Right(Suffix.Length - Self.EncryptedTag.Length) + Input = Prefix + Suffix + Pos = Prefix.Length + Else + Input = Prefix + DecryptedContent + Suffix + Pos = Prefix.Length + DecryptedContent.Length + Self.EncryptedTag.Length + End If + Loop + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function SettingsIniContent() As String + Return Self.mSettingsIniContent + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub SettingsIniContent(Assigns Organizer As Palworld.ConfigOrganizer) + Self.SettingsIniContent = Organizer.Build(Palworld.ConfigFileSettings) + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Sub SettingsIniContent(Assigns Content As String) + If Content.BeginsWith("[" + Palworld.HeaderPalworldSettings + "]") Then + Content = Content.Middle(Content.IndexOf("]") + 1).TrimLeft + End If + + If Self.mSettingsIniContent.Compare(Content, ComparisonOptions.CaseSensitive) <> 0 Then + Self.mSettingsIniContent = Content + Self.Modified = True + End If + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Function SupportsMerging() As Boolean + Return True + End Function + #tag EndMethod + + #tag Method, Flags = &h21 + Private Function WriteContent(Input As String, Rainbow As Dictionary) As String + Var Pos As Integer + + Do + Pos = Input.IndexOf(Pos, Self.EncryptedTag) + If Pos = -1 Then + Return Input + End If + + Var StartPos As Integer = Pos + Self.EncryptedTag.Length + Var EndPos As Integer = Input.IndexOf(StartPos, Self.EncryptedTag) + If EndPos = -1 Then + EndPos = Input.Length + End If + + Var Prefix As String = Input.Left(StartPos) + Var Suffix As String = Input.Right(Input.Length - EndPos) + Var DecryptedContent As String = Input.Middle(StartPos, EndPos - StartPos) + Var Hash As String = EncodeHex(Crypto.HMAC(Self.mSalt, DecryptedContent, Crypto.HashAlgorithms.SHA512)).Lowercase + Rainbow.Value(Hash) = DecryptedContent + + Input = Prefix + Hash + Suffix + Pos = Prefix.Length + Hash.Length + Self.EncryptedTag.Length + Loop + End Function + #tag EndMethod + + + #tag Property, Flags = &h21 + Private mSalt As String + #tag EndProperty + + #tag Property, Flags = &h21 + Private mSettingsIniContent As String + #tag EndProperty + + + #tag Constant, Name = EncryptedTag, Type = String, Dynamic = False, Default = \"$$BeaconEncrypted$$", Scope = Public + #tag EndConstant + + + #tag ViewBehavior + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag EndViewBehavior +End Class +#tag EndClass diff --git a/Project/Modules/Game Support/Palworld/Configs/OtherSettings.xojo_code b/Project/Modules/Game Support/Palworld/Configs/OtherSettings.xojo_code new file mode 100644 index 000000000..95556924c --- /dev/null +++ b/Project/Modules/Game Support/Palworld/Configs/OtherSettings.xojo_code @@ -0,0 +1,359 @@ +#tag Class +Protected Class OtherSettings +Inherits Palworld.ConfigGroup + #tag CompatibilityFlags = ( TargetConsole and ( Target32Bit or Target64Bit ) ) or ( TargetWeb and ( Target32Bit or Target64Bit ) ) or ( TargetDesktop and ( Target32Bit or Target64Bit ) ) or ( TargetIOS and ( Target64Bit ) ) or ( TargetAndroid and ( Target64Bit ) ) + #tag Event + Sub CopyFrom(Other As Palworld.ConfigGroup) + Var Source As Palworld.Configs.OtherSettings = Palworld.Configs.OtherSettings(Other) + For Each Entry As DictionaryEntry In Source.mSettings + Self.mSettings.Value(Entry.Key) = Entry.Value + Next Entry + End Sub + #tag EndEvent + + #tag Event + Function GenerateConfigValues(Project As Palworld.Project, Profile As Palworld.ServerProfile) As Palworld.ConfigValue() + Var ConsoleSafe As Boolean = Project.ConsoleSafe + Var Configs() As Palworld.ConfigValue + Var DataSource As Palworld.DataSource = Palworld.DataSource.Pool.Get(False) + Var IsGameServerApp As Boolean = (Profile.ProviderId = GameServerApp.Identifier) + For Each Entry As DictionaryEntry In Self.mSettings + Try + Var KeyUUID As String = Entry.Key + Var Key As Palworld.ConfigOption = DataSource.GetConfigOption(KeyUUID) + Var Value As Variant = Entry.Value + Var StringValue As String + + If Key Is Nil Or Project.ContentPackEnabled(Key.ContentPackId) = False Then + Continue + End If + + Var Requirements As Variant = Key.Constraint("other") + If IsNull(Requirements) = False Then + Var Dict As Dictionary = Requirements + Var OtherKeyUUID As String = Dict.Value("key") + Var RequiredValue As Boolean = Dict.Value("value") + Var CurrentValue As Variant + If Self.mSettings.HasKey(OtherKeyUUID) Then + CurrentValue = Self.mSettings.Value(OtherKeyUUID) + Else + Var OtherKey As Palworld.ConfigOption = DataSource.GetConfigOption(OtherKeyUUID) + If (OtherKey Is Nil) = False Then + CurrentValue = OtherKey.DefaultValue + End If + End If + If CurrentValue <> RequiredValue Then + Continue + End If + End If + + If Key.ValueType = Palworld.ConfigOption.ValueTypes.TypeText Then + Var AllowedChars As Variant = Key.Constraint("allowed_chars") + Var DisallowedChars As Variant = Key.Constraint("disallowed_chars") + If IsNull(AllowedChars) = False Then + Var Filter As New Regex + Filter.SearchPattern = "[^" + AllowedChars.StringValue + "]+" + Filter.ReplacementPattern = "" + Filter.Options.ReplaceAllMatches = True + Value = Filter.Replace(Value.StringValue) + ElseIf IsNull(DisallowedChars) = False Then + Var Filter As New Regex + Filter.SearchPattern = "[" + DisallowedChars.StringValue + "]+" + Filter.ReplacementPattern = "" + Filter.Options.ReplaceAllMatches = True + Value = Filter.Replace(Value.StringValue) + End If + + Var TrimChars As Variant = Key.Constraint("trim_chars") + If IsNull(TrimChars) = False Then + Var Chars() As Variant = TrimChars + For Each Char As String In Chars + Value = Value.StringValue.Trim(Char) + Next Char + End If + End If + + If ConsoleSafe Then + Var RequiredPlatform As Variant = Key.Constraint("platform") + Var SupportedOnPlatform As Boolean = True + If IsNull(RequiredPlatform) = False Then + Select Case RequiredPlatform.StringValue + Case "pc", "steam", "epic" + SupportedOnPlatform = False + End Select + End If + If SupportedOnPlatform = False Then + Continue + End If + ElseIf Profile.Platform <> Beacon.PlatformUnknown Then + Var RequiredPlatform As Variant = Key.Constraint("platform") + Var SupportedOnPlatform As Boolean = True + If IsNull(RequiredPlatform) = False Then + If Profile.Platform = Beacon.PlatformUniversal Then + SupportedOnPlatform = True + Else + Select Case RequiredPlatform.StringValue + Case "pc", "steam", "epic" + SupportedOnPlatform = (Profile.Platform = Beacon.PlatformPC) + Case "xbox" + SupportedOnPlatform = (Profile.Platform = Beacon.PlatformXbox) + Case "ps" + SupportedOnPlatform = (Profile.Platform = Beacon.PlatformPlayStation) + Case "switch" + SupportedOnPlatform = (Profile.Platform = Beacon.PlatformSwitch) + Case "console" + SupportedOnPlatform = (Profile.Platform = Beacon.PlatformXbox Or Profile.Platform = Beacon.PlatformPlayStation) + End Select + End If + If SupportedOnPlatform = False Then + Continue + End If + End If + End If + + Select Case Key.ValueType + Case Palworld.ConfigOption.ValueTypes.TypeBoolean + StringValue = If(Value.BooleanValue, "True", "False") + Case Palworld.ConfigOption.ValueTypes.TypeNumeric + StringValue = Value.DoubleValue.PrettyText + Case Palworld.ConfigOption.ValueTypes.TypeText + StringValue = """" + Value.StringValue + """" + Else + Continue + End Select + + Configs.Add(New Palworld.ConfigValue(Key, Key.Key + "=" + StringValue, Key.SimplifiedKey)) + Catch Err As RuntimeException + App.ReportException(Err) + End Try + Next + Return Configs + End Function + #tag EndEvent + + #tag Event + Function GetManagedKeys() As Palworld.ConfigOption() + Var Keys() As Palworld.ConfigOption + Var DataSource As Palworld.DataSource = Palworld.DataSource.Pool.Get(False) + For Each Entry As DictionaryEntry In Self.mSettings + Var KeyUUID As String = Entry.Key + Var Key As Palworld.ConfigOption = DataSource.GetConfigOption(KeyUUID) + If (Key Is Nil) = False Then + Keys.Add(Key) + End If + Next + Return Keys + End Function + #tag EndEvent + + #tag Event + Function HasContent() As Boolean + Return Self.mSettings.KeyCount > 0 + End Function + #tag EndEvent + + #tag Event + Sub ReadSaveData(SaveData As Dictionary, EncryptedData As Dictionary) + #Pragma Unused EncryptedData + + Try + Self.mSettings = SaveData.Value("Settings") + Catch Err As RuntimeException + Self.mSettings = New Dictionary + End Try + End Sub + #tag EndEvent + + #tag Event + Sub WriteSaveData(SaveData As Dictionary, EncryptedData As Dictionary) + #Pragma Unused EncryptedData + + SaveData.Value("Settings") = Self.mSettings + End Sub + #tag EndEvent + + + #tag Method, Flags = &h0 + Sub Constructor() + Self.mSettings = New Dictionary + Super.Constructor + + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Shared Function FromImport(ParsedData As Dictionary, ContentPacks As Beacon.StringList) As Palworld.Configs.OtherSettings + Var DataSource As Palworld.DataSource = Palworld.DataSource.Pool.Get(False) + Var AllContentPacks() As Beacon.ContentPack = DataSource.GetContentPacks + Var ContentPackLookup As New Dictionary + For Each Pack As Beacon.ContentPack In AllContentPacks + ContentPackLookup.Value(Pack.ContentPackId) = Pack + Next Pack + + Var Config As New Palworld.Configs.OtherSettings + Var AllKeys() As Palworld.ConfigOption = DataSource.GetConfigOptions("", "", "", "", False) + For Each Key As Palworld.ConfigOption In AllKeys + If KeySupported(Key, ContentPacks) = False Or Key.AutoImported = False Then + Continue + End If + + Var LookupKey As String = Key.Key + Var Pack As Beacon.ContentPack = ContentPackLookup.Lookup(Key.ContentPackId, Nil) + If (Pack Is Nil) = False And Pack.IsConsoleSafe = False Then + LookupKey = Key.Header + "." + Key.Key + End If + + Var TargetDict As Dictionary + If (Key.Struct Is Nil) = False And ParsedData.HasKey(Key.Struct.StringValue) Then + TargetDict = ParsedData.Value(Key.Struct.StringValue) + ElseIf ParsedData.HasKey(LookupKey) Then + TargetDict = ParsedData + Else + Continue + End If + + Var Value As Variant + Select Case Key.ValueType + Case Palworld.ConfigOption.ValueTypes.TypeNumeric + Value = TargetDict.DoubleValue(LookupKey, Key.DefaultValue.DoubleValue, True) + Case Palworld.ConfigOption.ValueTypes.TypeBoolean + Value = TargetDict.BooleanValue(LookupKey, Key.DefaultValue.BooleanValue, True) + Case Palworld.ConfigOption.ValueTypes.TypeText + Value = TargetDict.StringValue(LookupKey, Key.DefaultValue.StringValue, True) + Else + Continue + End Select + + Config.Value(Key) = Value + Next Key + + If Config.mSettings.KeyCount > 0 Then + Return Config + End If + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function InternalName() As String + Return Palworld.Configs.NameGeneralSettings + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Shared Function KeySupported(Key As Palworld.ConfigOption, ContentPacks As Beacon.StringList) As Boolean + If (Key.NativeEditorVersion Is Nil) = False And Key.NativeEditorVersion.IntegerValue <= App.BuildNumber Then + // We have a native editor for this key + Return False + End If + If Key.ValueType = Palworld.ConfigOption.ValueTypes.TypeArray Or Key.ValueType = Palworld.ConfigOption.ValueTypes.TypeStructure Then + // Can't support these types + Return False + End If + If Key.MaxAllowed Is Nil Or Key.MaxAllowed.IntegerValue <> 1 Then + // Only support single value keys + Return False + End If + If ContentPacks.IndexOf(Key.ContentPackId) = -1 Then + // Is this not obvious? + Return False + End If + + Return True + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function RequiresOmni() As Boolean + Return True + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function SupportsMerging() As Boolean + Return True + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function Value(Key As Palworld.ConfigOption) As Variant + If Self.mSettings.HasKey(Key.ConfigOptionId) = False Then + Return Nil + End If + + Return Self.mSettings.Value(Key.ConfigOptionId) + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Value(Key As Palworld.ConfigOption, Assigns NewValue As Variant) + Var ConfigOptionId As String = Key.ConfigOptionId + If NewValue.IsNull Then + If Self.mSettings.HasKey(ConfigOptionId) Then + Self.mSettings.Remove(ConfigOptionId) + Self.Modified = True + Return + End If + End If + + If Self.mSettings.HasKey(ConfigOptionId) Then + Var OldValue As Variant = Self.mSettings.Value(ConfigOptionId) + If Key.ValuesEqual(OldValue, NewValue) = True Then + Return + End If + End If + + Self.mSettings.Value(ConfigOptionId) = NewValue + Self.Modified = True + End Sub + #tag EndMethod + + + #tag Property, Flags = &h21 + Private mSettings As Dictionary + #tag EndProperty + + + #tag ViewBehavior + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag EndViewBehavior +End Class +#tag EndClass diff --git a/Project/Modules/Game Support/Palworld/DataSource.xojo_code b/Project/Modules/Game Support/Palworld/DataSource.xojo_code new file mode 100644 index 000000000..0661d9fea --- /dev/null +++ b/Project/Modules/Game Support/Palworld/DataSource.xojo_code @@ -0,0 +1,662 @@ +#tag Class +Protected Class DataSource +Inherits Beacon.DataSource + #tag Event + Sub BuildSchema() + Self.SQLExecute("CREATE TABLE content_packs (content_pack_id TEXT COLLATE NOCASE NOT NULL PRIMARY KEY, game_id TEXT COLLATE NOCASE NOT NULL, marketplace TEXT COLLATE NOCASE NOT NULL, marketplace_id TEXT NOT NULL, name TEXT COLLATE NOCASE NOT NULL, console_safe INTEGER NOT NULL, default_enabled INTEGER NOT NULL, is_local BOOLEAN NOT NULL, last_update INTEGER NOT NULL);") + Self.SQLExecute("CREATE TABLE game_variables (key TEXT COLLATE NOCASE NOT NULL PRIMARY KEY, value TEXT NOT NULL);") + Self.SQLExecute("CREATE TABLE ini_options (object_id TEXT COLLATE NOCASE NOT NULL PRIMARY KEY, content_pack_id TEXT COLLATE NOCASE NOT NULL REFERENCES content_packs(content_pack_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, label TEXT COLLATE NOCASE NOT NULL, alternate_label TEXT COLLATE NOCASE, tags TEXT COLLATE NOCASE NOT NULL DEFAULT '', native_editor_version INTEGER, file TEXT COLLATE NOCASE NOT NULL, header TEXT COLLATE NOCASE NOT NULL, struct TEXT COLLATE NOCASE, key TEXT COLLATE NOCASE NOT NULL, value_type TEXT COLLATE NOCASE NOT NULL, max_allowed INTEGER, description TEXT NOT NULL, default_value TEXT, nitrado_path TEXT COLLATE NOCASE, nitrado_format TEXT COLLATE NOCASE, nitrado_deploy_style TEXT COLLATE NOCASE, ui_group TEXT COLLATE NOCASE, custom_sort TEXT COLLATE NOCASE, constraints TEXT);") + End Sub + #tag EndEvent + + #tag Event + Function DefineIndexes() As Beacon.DataIndex() + Var Indexes() As Beacon.DataIndex + Indexes.Add(New Beacon.DataIndex("content_packs", True, "marketplace", "marketplace_id", "WHERE is_local = 0")) + Indexes.Add(New Beacon.DataIndex("content_packs", True, "marketplace", "marketplace_id", "WHERE is_local = 1 AND (marketplace != '' OR marketplace_id != '')")) + Indexes.Add(New Beacon.DataIndex("ini_options", True, "file", "header", "key")) + Return Indexes + End Function + #tag EndEvent + + #tag Event + Function DeleteContentPack(ContentPackId As String) As Boolean + If ContentPackId = Palworld.UserContentPackId Then + Self.DeleteDataForContentPack(ContentPackId) + Return True + End If + + Self.BeginTransaction() + + Var Rows As RowSet = Self.SQLSelect("SELECT content_pack_id FROM content_packs WHERE content_pack_id = ?1 AND is_local = 1;", ContentPackId) + If Rows.RowCount = 0 Then + Self.RollbackTransaction() + Return False + End If + + Self.DeleteDataForContentPack(ContentPackId) + Self.SQLExecute("DELETE FROM content_packs WHERE content_pack_id = ?1;", ContentPackId) + Self.CommitTransaction() + Return True + End Function + #tag EndEvent + + #tag Event + Function GetSchemaVersion() As Integer + Return 100 + End Function + #tag EndEvent + + #tag Event + Function Import(ChangeDict As Dictionary, StatusData As Dictionary, IsUserData As Boolean) As Boolean + Var BuildNumber As Integer = App.BuildNumber + + Var InitialChanges As Integer + Try + InitialChanges = Self.TotalChanges() + Catch Err As RuntimeException + InitialChanges = -1 + End Try + + If ChangeDict.HasKey("deletions") Then + Var Deletions() As Variant = ChangeDict.Value("deletions") + For Each Deletion As Dictionary In Deletions + Var ObjectId As String = Deletion.Value("objectId").StringValue + Select Case Deletion.Value("group") + Case "ini_options" + Self.SQLExecute("DELETE FROM ini_options WHERE object_id = ?1;", ObjectId) + End Select + Next + End If + + If ChangeDict.HasKey("contentPacks") Then + Var ContentPacks() As Variant = ChangeDict.Value("contentPacks") + For Each Dict As Dictionary In ContentPacks + Var MinVersion As Double = Dict.Value("minVersion") + If MinVersion > BuildNumber Then + Continue + End If + + Var Pack As New Beacon.MutableContentPack(Palworld.Identifier, Dict.Value("name").StringValue, Dict.Value("contentPackId").StringValue) + Pack.IsConsoleSafe = Dict.Value("isConsoleSafe").BooleanValue + Pack.IsDefaultEnabled = Dict.Value("isDefaultEnabled").BooleanValue + Pack.Marketplace = Dict.Lookup("marketplace", "").StringValue + Pack.MarketplaceId = Dict.Lookup("marketplaceId", "").StringValue + Pack.IsLocal = Pack.MarketplaceId.IsEmpty Or Dict.Lookup("isConfirmed", False).BooleanValue = False + Pack.LastUpdate = Dict.Value("lastUpdate").DoubleValue + Call Self.SaveContentPack(Pack, False) + Next + End If + + If ChangeDict.HasKey("configOptions") Then + Self.mConfigOptionCache = New Dictionary + + Var Options() As Variant = ChangeDict.Value("configOptions") + For Each Dict As Dictionary In Options + If Dict.Value("minVersion") > BuildNumber Then + Continue + End If + + Var ConfigOptionId As String = Dict.Value("configOptionId") + Var ContentPackId As String = Dict.Value("contentPackId") + Var File As String = Dict.Value("file") + Var Header As String = Dict.Value("header") + Var Struct As Variant = Dict.Value("struct") + Var Key As String = Dict.Value("key") + Var TagString, TagStringForSearching As String + Try + Var Tags() As String + Var Temp() As Variant = Dict.Value("tags") + For Each Tag As String In Temp + Tags.Add(Tag) + Next + TagString = Tags.Join(",") + Tags.AddAt(0, "object") + TagStringForSearching = Tags.Join(",") + Catch Err As RuntimeException + + End Try + + Var Values(19) As Variant + Values(0) = ConfigOptionId + Values(1) = Dict.Value("label") + Values(2) = ContentPackId + Values(3) = Dict.Value("nativeEditorVersion") + Values(4) = File + Values(5) = Header + Values(6) = Key + Values(7) = Dict.Value("valueType") + Values(8) = Dict.Value("maxAllowed") + Values(9) = Dict.Value("description") + Values(10) = Dict.Value("defaultValue") + Values(11) = Dict.Value("alternateLabel") + If Dict.HasKey("nitradoEquivalent") And IsNull(Dict.Value("nitradoEquivalent")) = False Then + Var NitradoEq As Dictionary = Dict.Value("nitradoEquivalent") + Values(12) = NitradoEq.Value("path") + Values(13) = NitradoEq.Value("format") + Values(14) = NitradoEq.Value("deployStyle") + Else + Values(12) = Nil + Values(13) = Nil + Values(14) = Nil + End If + Values(15) = TagString + If Dict.HasKey("uiGroup") Then + Values(16) = Dict.Value("uiGroup") + End If + If Dict.HasKey("customSort") Then + Values(17) = Dict.Value("customSort") + End If + If Dict.HasKey("constraints") And IsNull(Dict.Value("constraints")) = False Then + Try + Values(18) = Beacon.GenerateJSON(Dict.Value("constraints"), False) + Catch JSONErr As RuntimeException + End Try + End If + Values(19) = Struct + + Var Results As RowSet = Self.SQLSelect("SELECT object_id FROM ini_options WHERE object_id = ?1 OR (file = ?2 AND header = ?3 AND struct = ?4 AND key = ?5);", ConfigOptionId, File, Header, Struct, Key) + If Results.RowCount > 1 Then + Self.SQLExecute("DELETE FROM ini_options WHERE object_id = ?1 OR (file = ?2 AND header = ?3 AND struct = ?4 AND key = ?5);", ConfigOptionId, File, Header, Struct, Key) + End If + If Results.RowCount = 1 Then + // Update + Var OriginalConfigOptionId As String = Results.Column("object_id").StringValue + Values.Add(OriginalConfigOptionId) + Self.SQLExecute("UPDATE ini_options SET object_id = ?1, label = ?2, content_pack_id = ?3, native_editor_version = ?4, file = ?5, header = ?6, key = ?7, value_type = ?8, max_allowed = ?9, description = ?10, default_value = ?11, alternate_label = ?12, nitrado_path = ?13, nitrado_format = ?14, nitrado_deploy_style = ?15, tags = ?16, ui_group = ?17, custom_sort = ?18, constraints = ?19, struct = ?20 WHERE object_id = ?21;", Values) + Else + // Insert + Self.SQLExecute("INSERT INTO ini_options (object_id, label, content_pack_id, native_editor_version, file, header, key, value_type, max_allowed, description, default_value, alternate_label, nitrado_path, nitrado_format, nitrado_deploy_style, tags, ui_group, custom_sort, constraints, struct) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19, ?20);", Values) + End If + Next Dict + End If + + If ChangeDict.HasKey("gameVariables") Then + Var GameVariables() As Variant = ChangeDict.Value("gameVariables") + For Each Dict As Dictionary In GameVariables + Var Key As String = Dict.Value("key") + Var Value As String = Dict.Value("value") + + Var Results As RowSet = Self.SQLSelect("SELECT key FROM game_variables WHERE key = ?1;", Key) + If Results.RowCount = 1 Then + Self.SQLExecute("UPDATE game_variables SET value = ?2 WHERE key = ?1;", Key, Value) + Else + Self.SQLExecute("INSERT INTO game_variables (key, value) VALUES (?1, ?2);", Key, Value) + End If + Next + End If + + Var TotalChanges As Integer + Try + TotalChanges = Self.TotalChanges() + Catch Err As RuntimeException + TotalChanges = -2 + End Try + + If IsUserData And TotalChanges <> InitialChanges Then + Self.ExportCloudFiles() + End If + + Return True + End Function + #tag EndEvent + + #tag Event + Sub ObtainLock() + mLock.Enter + End Sub + #tag EndEvent + + #tag Event + Sub ReleaseLock() + mLock.Leave + End Sub + #tag EndEvent + + #tag Event + Function SaveContentPack(Pack As Beacon.ContentPack) As Boolean + Var Rows As RowSet + If Pack.MarketplaceId.IsEmpty Then + Rows = Self.SQLSelect("SELECT content_pack_id, last_update, is_local FROM content_packs WHERE content_pack_id = ?1;", Pack.ContentPackId) + Else + Rows = Self.SQLSelect("SELECT content_pack_id, last_update, is_local FROM content_packs WHERE content_pack_id = ?1 OR (marketplace = ?2 AND marketplace_id = ?3);", Pack.ContentPackId, Pack.Marketplace, Pack.MarketplaceId) + End If + + Var DidSave as Boolean + Var NewContentPackId As String = Pack.ContentPackId + Var ShouldInsert As Boolean = True + If Rows.RowCount > 0 Then + // The new content pack could be an official replacing a custom, or even just changing the id. + While Not Rows.AfterLastRow + Var OldContentPackId As String = Rows.Column("content_pack_id").StringValue + Var OldIsLocal As Boolean = Rows.Column("is_local").BooleanValue + If OldContentPackId = NewContentPackId Then + // We can just update the row + If Pack.LastUpdate > Rows.Column("last_update").DoubleValue Then + Self.SQLExecute("UPDATE content_packs SET name = ?2, console_safe = ?3, default_enabled = ?4, marketplace = ?5, marketplace_id = ?6, is_local = ?7, last_update = ?8, game_id = ?9 WHERE content_pack_id = ?1;", Pack.ContentPackId, Pack.Name, Pack.IsConsoleSafe, Pack.IsDefaultEnabled, Pack.Marketplace, Pack.MarketplaceId, Pack.IsLocal, Pack.LastUpdate, Pack.GameId) + DidSave = True + End If + ShouldInsert = False + Else + Var ShouldDelete As Boolean = True + If Pack.IsLocal And OldIsLocal Then + Self.ScheduleContentPackMigration(OldContentPackId, NewContentPackId) + ElseIf OldIsLocal Then // New is official, old is local + Self.ScheduleContentPackMigration(OldContentPackId, Palworld.UserContentPackId) + ElseIf Pack.IsLocal Then // Old is official, new is local + Self.ScheduleContentPackMigration(NewContentPackId, Palworld.UserContentPackId) + ShouldInsert = False + ShouldDelete = False + Else // Both the old and new pack are official + Self.DeleteDataForContentPack(OldContentPackId) + End If + + If ShouldDelete Then + Self.SQLExecute("DELETE FROM content_packs WHERE content_pack_id = ?1;", OldContentPackId) + End If + End If + Rows.MoveToNextRow + Wend + End If + + If ShouldInsert Then + Self.SQLExecute("INSERT INTO content_packs (content_pack_id, name, console_safe, default_enabled, marketplace, marketplace_id, is_local, last_update, game_id) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9);", Pack.ContentPackId, Pack.Name, Pack.IsConsoleSafe, Pack.IsDefaultEnabled, Pack.Marketplace, Pack.MarketplaceId, Pack.IsLocal, Pack.LastUpdate, Pack.GameId) + DidSave = True + End If + + Return DidSave + End Function + #tag EndEvent + + + #tag Method, Flags = &h0 + Sub Constructor(AllowWriting As Boolean) + If Self.mConfigOptionCache Is Nil Then + Self.mConfigOptionCache = New Dictionary + End If + + If mLock Is Nil Then + mLock = New CriticalSection + End If + + Super.Constructor(AllowWriting) + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub DeleteDataForContentPack(ContentPackId As String) + #Pragma Unused ContentPackId + + // Nothing needs to happen + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Function GetBooleanVariable(Key As String, Default As Boolean = False) As Boolean + Var Value As Variant = Self.GetVariable(Key, Default) + Return Value.BooleanValue + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function GetConfigOption(KeyUUID As String) As Palworld.ConfigOption + If Self.mConfigOptionCache.HasKey(KeyUUID) Then + Return Self.mConfigOptionCache.Value(KeyUUID) + End If + + Var Rows As RowSet = Self.SQLSelect(Self.ConfigOptionSelectSQL + " WHERE object_id = ?1;", KeyUUID) + If Rows.RowCount <> 1 Then + Return Nil + End If + + Var Results() As Palworld.ConfigOption = Self.RowSetToConfigOptions(Rows) + If Results.Count = 1 Then + Return Results(0) + End If + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function GetConfigOption(File As String, Header As String, Struct As NullableString, Key As String) As Palworld.ConfigOption + Var Results() As Palworld.ConfigOption = Self.GetConfigOptions(File, Header, Key, Struct, False) + If Results.Count = 1 Then + Return Results(0) + End If + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function GetConfigOptions(File As String, Header As String, Struct As NullableString, Key As String, SortHuman As Boolean) As Palworld.ConfigOption() + Var Clauses() As String + Var Values As New Dictionary + Var Idx As Integer = 1 + + If File.IsEmpty = False Then + Values.Value(Idx) = File + Clauses.Add("file = ?" + Idx.ToString) + Idx = Idx + 1 + End If + If Header.IsEmpty = False Then + Values.Value(Idx) = Header + Clauses.Add("header = ?" + Idx.ToString) + Idx = Idx + 1 + End If + If Struct Is Nil Then + Clauses.Add("struct IS NULL") + ElseIf Struct.IsEmpty = False Then + Values.Value(Idx) = Struct.StringValue + Clauses.Add("struct = ?" + Idx.ToString) + Idx = Idx + 1 + End If + If Key.IsEmpty = False Then + If Key.IndexOf("*") > -1 Then + Values.Value(Idx) = Self.EscapeLikeValue(Key).ReplaceAll("*", "%") + Clauses.Add("key LIKE ?" + Idx.ToString + " ESCAPE '\'") + Else + Values.Value(Idx) = Key + Clauses.Add("key = ?" + Idx.ToString) + End If + Idx = Idx + 1 + End If + + Var SQL As String = Self.ConfigOptionSelectSQL + If Clauses.Count > 0 Then + SQL = SQL + " WHERE " + Clauses.Join(" AND ") + End If + If SortHuman Then + SQL = SQL + " ORDER BY COALESCE(custom_sort, label)" + Else + SQL = SQL + " ORDER BY key" + End If + + Var Results() As Palworld.ConfigOption + Try + Results = Self.RowSetToConfigOptions(Self.SQLSelect(SQL, Values)) + Catch Err As RuntimeException + App.ReportException(Err) + End Try + Return Results + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function GetConfigStructs(ForFile As String, ForHeader As String) As String() + Var Clauses() As String = Array("struct IS NOT NULL") + Var Values As New Dictionary + Var Idx As Integer = 1 + + If ForFile.IsEmpty = False Then + Values.Value(Idx) = ForFile + Clauses.Add("file = ?" + Idx.ToString) + Idx = Idx + 1 + End If + If ForHeader.IsEmpty = False Then + Values.Value(Idx) = ForHeader + Clauses.Add("header = ?" + Idx.ToString) + Idx = Idx + 1 + End If + + Var SQL As String = "SELECT DISTINCT struct FROM ini_options" + If Clauses.Count > 0 Then + SQL = SQL + " WHERE " + Clauses.Join(" AND ") + End If + SQL = SQL + " ORDER BY struct;" + + Var Structs() As String + Var Rows As RowSet = Self.SQLSelect(SQL, Values) + While Not Rows.AfterLastRow + Structs.Add(Rows.Column("struct").StringValue) + Rows.MoveToNextRow + Wend + Return Structs + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function GetContentPack(Marketplace As String, MarketplaceId As String) As Beacon.ContentPack + Var Results As RowSet = Self.SQLSelect("SELECT content_pack_id, game_id, name, console_safe, default_enabled, marketplace, marketplace_id, is_local, last_update FROM content_packs WHERE marketplace = ?1 AND marketplace_id = ?2 ORDER BY is_local DESC LIMIT 1;", Marketplace, MarketplaceId) + Var Packs() As Beacon.ContentPack = Beacon.ContentPack.FromDatabase(Results) + If Packs.Count = 1 Then + Return Packs(0) + End If + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function GetContentPack(Marketplace As String, MarketplaceId As String, Type As Beacon.ContentPack.Types) As Beacon.ContentPack + Var Results As RowSet = Self.SQLSelect("SELECT content_pack_id, game_id, name, console_safe, default_enabled, marketplace, marketplace_id, is_local, last_update FROM content_packs WHERE marketplace = ?1 AND marketplace_id = ?2 AND is_local = ?3 ORDER BY is_local DESC LIMIT 1;", Marketplace, MarketplaceId, Type = Beacon.ContentPack.Types.Custom) + Var Packs() As Beacon.ContentPack = Beacon.ContentPack.FromDatabase(Results) + If Packs.Count = 1 Then + Return Packs(0) + End If + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function GetDoubleVariable(Key As String, Default As Double = 0.0) As Double + Var Value As Variant = Self.GetVariable(Key, Default) + Return Value.DoubleValue + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function GetIntegerVariable(Key As String, Default As Integer = 0) As Integer + Var Value As Variant = Self.GetVariable(Key, Default) + Return Value.IntegerValue + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function GetStringVariable(Key As String, Default As String = "") As String + Var Value As Variant = Self.GetVariable(Key, Default) + Var StringValue As String = Value.StringValue + If StringValue.Encoding = Nil Then + If Encodings.UTF8.IsValidData(StringValue) Then + StringValue = StringValue.DefineEncoding(Encodings.UTF8) + Else + Return Default + End If + End If + Return StringValue + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function GetVariable(Key As String, Default As Variant = Nil) As Variant + Var Results As RowSet = Self.SQLSelect("SELECT value FROM game_variables WHERE key = ?1;", Key) + If Results.RowCount = 1 Then + Return Results.Column("value").Value + Else + Return Default + End If + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function Identifier() As String + Return Palworld.Identifier + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Shared Function Pool() As Palworld.DataSourcePool + If mPool Is Nil Then + mPool = New Palworld.DataSourcePool + End If + Return mPool + End Function + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub ReplaceUserBlueprints() + If Self.Writeable = False Then + Return + End If + + Self.BeginTransaction() + Self.SQLExecute("INSERT OR REPLACE INTO content_packs (content_pack_id, name, console_safe, default_enabled, is_local, last_update, marketplace, marketplace_id, game_id) VALUES (?1, ?2, ?3, ?4, ?5, ?6, '', '', ?7);", Palworld.UserContentPackId, Palworld.UserContentPackName, True, True, True, Beacon.FixedTimestamp, Palworld.Identifier) + Self.CommitTransaction() + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Function RowSetToConfigOptions(Results As RowSet) As Palworld.ConfigOption() + Var Keys() As Palworld.ConfigOption + Try + While Results.AfterLastRow = False + Var ConfigOptionId As String = Results.Column("object_id").StringValue + If Self.mConfigOptionCache.HasKey(ConfigOptionId) Then + Results.MoveToNextRow + Keys.Add(Self.mConfigOptionCache.Value(ConfigOptionId)) + Continue + End If + + Var Label As String = Results.Column("label").StringValue + Var ConfigFile As String = Results.Column("file").StringValue + Var ConfigHeader As String = Results.Column("header").StringValue + Var ConfigStruct As NullableString = NullableString.FromVariant(Results.Column("struct").Value) + Var ConfigKey As String = Results.Column("key").StringValue + Var ValueType As Palworld.ConfigOption.ValueTypes + Select Case Results.Column("value_type").StringValue + Case "Numeric" + ValueType = Palworld.ConfigOption.ValueTypes.TypeNumeric + Case "Array" + ValueType = Palworld.ConfigOption.ValueTypes.TypeArray + Case "Structure" + ValueType = Palworld.ConfigOption.ValueTypes.TypeStructure + Case "Boolean" + ValueType = Palworld.ConfigOption.ValueTypes.TypeBoolean + Case "Text" + ValueType = Palworld.ConfigOption.ValueTypes.TypeText + End Select + Var MaxAllowed As NullableDouble + If IsNull(Results.Column("max_allowed").Value) = False Then + MaxAllowed = Results.Column("max_allowed").IntegerValue + End If + Var Description As String = Results.Column("description").StringValue + Var DefaultValue As Variant = Results.Column("default_value").Value + Var NitradoPath As NullableString + Var NitradoFormat As Palworld.ConfigOption.NitradoFormats = Palworld.ConfigOption.NitradoFormats.Unsupported + Var NitradoDeployStyle As Palworld.ConfigOption.NitradoDeployStyles = Palworld.ConfigOption.NitradoDeployStyles.Unsupported + If IsNull(Results.Column("nitrado_format").Value) = False Then + NitradoPath = Results.Column("nitrado_path").StringValue + Select Case Results.Column("nitrado_format").StringValue + Case "Line" + NitradoFormat = Palworld.ConfigOption.NitradoFormats.Line + Case "Value" + NitradoFormat = Palworld.ConfigOption.NitradoFormats.Value + End Select + Select Case Results.Column("nitrado_deploy_style").StringValue + Case "Guided" + NitradoDeployStyle = Palworld.ConfigOption.NitradoDeployStyles.Guided + Case "Expert" + NitradoDeployStyle = Palworld.ConfigOption.NitradoDeployStyles.Expert + Case "Both" + NitradoDeployStyle = Palworld.ConfigOption.NitradoDeployStyles.Both + End Select + End If + Var NativeEditorVersion As NullableDouble = NullableDouble.FromVariant(Results.Column("native_editor_version").Value) + Var UIGroup As NullableString = NullableString.FromVariant(Results.Column("ui_group").Value) + Var CustomSort As NullableString = NullableString.FromVariant(Results.Column("custom_sort").Value) + Var ContentPackId As String = Results.Column("content_pack_id").StringValue + + Var Constraints As Dictionary + If IsNull(Results.Column("constraints").Value) = False Then + Try + Var Parsed As Variant = Beacon.ParseJSON(Results.Column("constraints").StringValue) + If IsNull(Parsed) = False And Parsed IsA Dictionary Then + Constraints = Parsed + End If + Catch JSONErr As RuntimeException + End Try + End If + + Var Key As New Palworld.ConfigOption(ConfigOptionId, Label, ConfigFile, ConfigHeader, ConfigStruct, ConfigKey, ValueType, MaxAllowed, Description, DefaultValue, NitradoPath, NitradoFormat, NitradoDeployStyle, NativeEditorVersion, UIGroup, CustomSort, Constraints, ContentPackId) + Self.mConfigOptionCache.Value(ConfigOptionId) = Key + Keys.Add(Key) + Results.MoveToNextRow + Wend + Catch Err As RuntimeException + App.ReportException(Err) + Keys.ResizeTo(-1) + End Try + Return Keys + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function WriteableInstance() As Palworld.DataSource + Return Self.Pool.Get(True) + End Function + #tag EndMethod + + + #tag Property, Flags = &h21 + Private Shared mConfigOptionCache As Dictionary + #tag EndProperty + + #tag Property, Flags = &h21 + Private Shared mLock As CriticalSection + #tag EndProperty + + #tag Property, Flags = &h21 + Private Shared mPool As Palworld.DataSourcePool + #tag EndProperty + + + #tag Constant, Name = ConfigOptionSelectSQL, Type = String, Dynamic = False, Default = \"SELECT object_id\x2C label\x2C file\x2C header\x2C struct\x2C key\x2C value_type\x2C max_allowed\x2C description\x2C default_value\x2C nitrado_path\x2C nitrado_format\x2C nitrado_deploy_style\x2C native_editor_version\x2C ui_group\x2C custom_sort\x2C constraints\x2C content_pack_id FROM ini_options", Scope = Private + #tag EndConstant + + + #tag ViewBehavior + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="DebugIdentifier" + Visible=false + Group="Behavior" + InitialValue="" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag EndViewBehavior +End Class +#tag EndClass diff --git a/Project/Modules/Game Support/Palworld/DataSourcePool.xojo_code b/Project/Modules/Game Support/Palworld/DataSourcePool.xojo_code new file mode 100644 index 000000000..1c592e23f --- /dev/null +++ b/Project/Modules/Game Support/Palworld/DataSourcePool.xojo_code @@ -0,0 +1,61 @@ +#tag Class +Protected Class DataSourcePool +Inherits Beacon.DataSourcePool + #tag Event + Function NewInstance(Writeable As Boolean) As Beacon.DataSource + Return New Palworld.DataSource(Writeable) + End Function + #tag EndEvent + + + #tag Method, Flags = &h0 + Function Get(Writeable As Boolean) As Palworld.DataSource + Return Palworld.DataSource(Super.Get(Writeable)) + End Function + #tag EndMethod + + + #tag ViewBehavior + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag EndViewBehavior +End Class +#tag EndClass diff --git a/Project/Modules/Game Support/Palworld/DeployIntegration.xojo_code b/Project/Modules/Game Support/Palworld/DeployIntegration.xojo_code new file mode 100644 index 000000000..499c8458c --- /dev/null +++ b/Project/Modules/Game Support/Palworld/DeployIntegration.xojo_code @@ -0,0 +1,435 @@ +#tag Class +Protected Class DeployIntegration +Inherits Beacon.DeployIntegration + #tag CompatibilityFlags = ( TargetConsole and ( Target32Bit or Target64Bit ) ) or ( TargetWeb and ( Target32Bit or Target64Bit ) ) or ( TargetDesktop and ( Target32Bit or Target64Bit ) ) or ( TargetIOS and ( Target64Bit ) ) or ( TargetAndroid and ( Target64Bit ) ) + #tag Event + Sub Run() + Var InitialStatus As Beacon.ServerStatus = Self.Status + Var Project As Palworld.Project = Self.Project + Var Profile As Palworld.ServerProfile = Self.Profile + + Var SettingsIniPath, GameUserSettingsIniPath As String + + Select Case Self.Provider + Case IsA Nitrado.HostingProvider + Var GameServer As JSONItem = InitialStatus.UserData + Var GamePath As String = GameServer.Child("game_specific").Value("path").StringValue + If GamePath.EndsWith("/") Then + GamePath = GamePath.Left(GamePath.Length - 1) + End If + + Profile.SecondaryName = GameServer.Value("ip").StringValue + ":" + GameServer.Value("port").IntegerValue.ToString(Locale.Raw, "0") + " (" + GameServer.Value("service_id").IntegerValue.ToString(Locale.Raw, "0") + ")" + Profile.BasePath = GamePath + Profile.SettingsIniPath = GamePath + "/Pal/Saved/Config/WindowsServer/PalWorldSettings.ini" + Profile.LogsPath = GamePath + "/Pal/Saved/Logs" + End Select + + SettingsIniPath = Profile.SettingsIniPath + + Self.EnterResourceIntenseMode() + Var Organizer As Palworld.ConfigOrganizer = Project.CreateConfigOrganizer(Self.Identity, Profile) + Self.ExitResourceIntenseMode() + If Organizer Is Nil Then + Self.SetError("Could not generate new config data. Log files may have more info.") + Return + End If + + Var SettingsIniOriginal As String + // Download the ini files + Var DownloadSuccess As Boolean + SettingsIniOriginal = Self.GetFile(SettingsIniPath, Palworld.ConfigFileSettings, Beacon.Integration.DownloadFailureMode.MissingAllowed, False, DownloadSuccess) + If Self.Finished Or DownloadSuccess = False Then + Self.Finish() + Return + End If + + // Build the new ini files + Self.Log("Generating new ini files…") + + Var Format As Palworld.Rewriter.EncodingFormat = Palworld.Rewriter.EncodingFormat.ASCII + Var RewriteError As RuntimeException + + Self.EnterResourceIntenseMode() + Var SettingsIniRewritten As String = Palworld.Rewriter.Rewrite(Palworld.Rewriter.Sources.Deploy, SettingsIniOriginal, Palworld.HeaderPalworldSettings, Palworld.ConfigFileSettings, Organizer, Project.ProjectId, Project.LegacyTrustKey, Format, Self.NukeEnabled, RewriteError) + Self.ExitResourceIntenseMode() + If (RewriteError Is Nil) = False Then + Self.SetError(RewriteError) + Return + End If + + // Verify content looks acceptable + If Not Self.ValidateContent(SettingsIniRewritten, Palworld.ConfigFileSettings) Then + Return + End If + + // Allow the user to review the new files if requested + If Self.ReviewEnabled And Self.Identity.IsBanned = False Then + If Self.AnalyzeEnabled Then + // Analyzer would go here + End If + + Var Dict As New Dictionary + Dict.Value(Palworld.ConfigFileSettings) = SettingsIniRewritten + Dict.Value("Advice") = Nil // The results would go here + + Var Controller As New Beacon.TaskWaitController("Review Files", Dict) + + Self.Log("Waiting for user review") + Self.Wait(Controller) + If Controller.Cancelled Then + Return + End If + End If + + // Run the backup if requested + Var NitradoChanges As Dictionary + If Self.BackupEnabled Then + Var OldFiles As New Dictionary + OldFiles.Value(Palworld.ConfigFileSettings) = SettingsIniOriginal + + Var NewFiles As New Dictionary + NewFiles.Value(Palworld.ConfigFileSettings) = SettingsIniRewritten + + Select Case Self.Provider + Case IsA Nitrado.HostingProvider + Var GameServer As JSONItem = InitialStatus.UserData + Var Settings As JSONItem = GameServer.Child("settings") + Settings.Compact = False + OldFiles.Value("Config.json") = Settings.ToString + + NitradoChanges = Self.NitradoPrepareChanges(Organizer) + Var NewSettings As New JSONItem(Settings.ToString) + For Each Entry As DictionaryEntry In NitradoChanges + Try + Var Setting As Palworld.ConfigOption = Entry.Key + Var NewValue As String = Entry.Value + + Var Changes() As Nitrado.SettingChange = Nitrado.HostingProvider.PrepareSettingChanges(GameServer, Setting, NewValue) + For Each Change As Nitrado.SettingChange In Changes + Try + Var Parent As JSONItem = NewSettings.Child(Change.Category) + Parent.Value(Change.Key) = Change.Value + Catch ChangeErr As RuntimeException + App.Log(ChangeErr, CurrentMethodName, "Updating config json") + End Try + Next + Catch EntryErr As RuntimeException + App.Log(EntryErr, CurrentMethodName, "Processing Nitrado changes") + End Try + Next + NewSettings.Compact = False + NewFiles.Value("Config.json") = NewSettings.ToString + End Select + + Self.RunBackup(OldFiles, NewFiles) + + If Self.Finished Then + Return + End If + End If + + // Stop the server + If Self.Plan = Beacon.DeployPlan.StopUploadStart Then + Self.StopServer() + End If + + // Wait if necessary + If InitialStatus.State <> Beacon.ServerStatus.States.Stopped And Self.Provider IsA Nitrado.HostingProvider Then + Self.NitradoCooldownWait() + End If + + // Put the new files on the server + If Self.PutFile(SettingsIniRewritten, SettingsIniPath, Palworld.ConfigFileSettings) = False Or Self.Finished Then + Self.Finish() + Return + End If + + // Make command line changes + Select Case Self.Provider + Case IsA Nitrado.HostingProvider + Self.Log("Updating other settings…") + If NitradoChanges Is Nil Then + Call Self.NitradoApplySettings(Organizer) + Else + Call Self.NitradoApplySettings(NitradoChanges) + End If + If Self.Finished Then + Return + End If + End Select + End Sub + #tag EndEvent + + + #tag Method, Flags = &h1 + Protected Function NitradoApplySettings(Changes As Dictionary) As Boolean + If (Self.Provider IsA Nitrado.HostingProvider) = False Then + Return False + End If + + // Deploy changes + For Each Entry As DictionaryEntry In Changes + Var Setting As Palworld.ConfigOption = Entry.Key + Var NewValue As String = Entry.Value + + Try + Self.Provider.GameSetting(Project, Profile, Setting) = NewValue + Catch Err As RuntimeException + Self.SetError(Err) + Return False + End Try + + // So we don't go nuts + Self.Thread.Sleep(100) + Next + + Return True + End Function + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Function NitradoApplySettings(Organizer As Palworld.ConfigOrganizer) As Boolean + If (Self.Provider IsA Nitrado.HostingProvider) = False Then + Return False + End If + + Var Changes As Dictionary = Self.NitradoPrepareChanges(Organizer) + Return Self.NitradoApplySettings(Changes) + End Function + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Sub NitradoCooldownWait() + // Since the process is about to upload, we need to find the log file and determine how long to wait + // First we need to look up the current time, since we cannot trust the user's clock + Var Now As DateTime + Var Locked As Boolean + Try + Locked = Preferences.SignalConnection + Var Response As BeaconAPI.Response = BeaconAPI.SendSync(New BeaconAPI.Request("/now", "GET")) + If Locked Then + Preferences.ReleaseConnection() + Locked = False + End If + + Var Parsed As New JSONItem(Response.Content) + Now = New DateTime(Parsed.Value("unixEpoch").DoubleValue) + Catch Err As RuntimeException + App.Log(Err, CurrentMethodName, "Getting current time from API") + Now = DateTime.Now + + If Locked Then + Preferences.ReleaseConnection() + Locked = False + End If + End Try + + Var ServerStopTime As DateTime = Now + + // Now we can compute how long to wait. + Var WaitSeconds As Integer = Palworld.DataSource.Pool.Get(False).GetVariable("Nitrado Wait Seconds") + Var WaitUntil As DateTime = ServerStopTime + New DateInterval(0, 0, 0, 0, 0, WaitSeconds) + WaitUntil = New DateTime(WaitUntil.SecondsFrom1970, TimeZone.Current) + Var Diff As Double = WaitUntil.SecondsFrom1970 - Now.SecondsFrom1970 + + If Diff > 0 Then + Self.Log("Waiting until " + WaitUntil.ToString(Locale.Current, DateTime.FormatStyles.None, DateTime.FormatStyles.Medium) + " per Nitrado recommendation…") + Self.Wait(Diff * 1000) + End If + End Sub + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Function NitradoPrepareChanges(Organizer As Palworld.ConfigOrganizer) As Dictionary + Var Project As Palworld.Project = Self.Project + Var Profile As Palworld.ServerProfile = Self.Profile + Var Keys() As Palworld.ConfigOption = Organizer.DistinctKeys + Var NewValues As New Dictionary + Var Style As Palworld.ConfigOption.NitradoDeployStyles = Palworld.ConfigOption.NitradoDeployStyles.Expert + + For Each ConfigOption As Palworld.ConfigOption In Keys + If ConfigOption.HasNitradoEquivalent = False Then + Continue + End If + + Var SendToNitrado As Boolean = ConfigOption.NitradoDeployStyle = Palworld.ConfigOption.NitradoDeployStyles.Both Or ConfigOption.NitradoDeployStyle = Style + If SendToNitrado = False Then + Continue + End If + + Var Values() As Palworld.ConfigValue = Organizer.FilteredValues(ConfigOption) + + Select Case ConfigOption.NitradoFormat + Case Palworld.ConfigOption.NitradoFormats.Line + Var Lines() As String + For Each Value As Palworld.ConfigValue In Values + Lines.Add(Value.Command) + Next + NewValues.Value(ConfigOption) = Lines + Case Palworld.ConfigOption.NitradoFormats.Value + If Values.Count >= 1 Then + Var Value As String = Values(Values.LastIndex).Value + + If ConfigOption.ValueType = Palworld.ConfigOption.ValueTypes.TypeBoolean Then + Value = Value.Lowercase + + Var Reversed As NullableBoolean = NullableBoolean.FromVariant(ConfigOption.Constraint("nitrado.boolean.reversed")) + If (Reversed Is Nil) = False And Reversed.BooleanValue Then + Value = If(Value = "true", "false", "true") + End If + End If + + NewValues.Value(ConfigOption) = Value + Else + // This doesn't make sense + Break + End If + End Select + Next + + Var Changes As New Dictionary + For Each Entry As DictionaryEntry In NewValues + Var ConfigOption As Palworld.ConfigOption = Entry.Key + Var CurrentValue As Variant = Self.Provider.GameSetting(Project, Profile, ConfigOption) + Var FinishedValue As String + If Entry.Value.Type = Variant.TypeString Then + // Value comparison + If ConfigOption.ValuesEqual(Entry.Value, CurrentValue) Then + Continue + End If + FinishedValue = Entry.Value.StringValue + ElseIf Entry.Value.IsArray And Entry.Value.ArrayElementType = Variant.TypeString Then + // Line comparison, but if there is only one line, go back to value comparison + Var NewLines() As String = Entry.Value + FinishedValue = NewLines.Join(EndOfLine) // Prepare the finished value before sorting, even if we nay not use it + + Var CurrentLines() As String = CurrentValue.StringValue.ReplaceLineEndings(EndOfLine.UNIX).Split(EndOfLine.UNIX) + + If NewLines.Count = 1 And CurrentLines.Count = 1 And (ConfigOption.ValueType = Palworld.ConfigOption.ValueTypes.TypeNumeric Or ConfigOption.ValueType = Palworld.ConfigOption.ValueTypes.TypeBoolean Or ConfigOption.ValueType = Palworld.ConfigOption.ValueTypes.TypeBoolean) Then + Var NewValue As String = NewLines(0).Middle(NewLines(0).IndexOf("=") + 1) + CurrentValue = CurrentLines(0).Middle(CurrentLines(0).IndexOf("=") + 1) + If ConfigOption.ValuesEqual(NewValue, CurrentValue) Then + Continue + End If + FinishedValue = NewLines(0) + Else + NewLines.Sort + CurrentLines.Sort + Var NewHash As String = EncodeHex(Crypto.SHA1(NewLines.Join(EndOfLine.UNIX).Lowercase)) + Var CurrentHash As String = EncodeHex(Crypto.SHA1(CurrentLines.Join(EndOfLine.UNIX).Lowercase)) + If NewHash = CurrentHash Then + // No change + Continue + End If + End If + Else + Continue + End If + + Changes.Value(ConfigOption) = FinishedValue + Next + + Return Changes + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function Profile() As Palworld.ServerProfile + Return Palworld.ServerProfile(Super.Profile) + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function Project() As Palworld.Project + Return Palworld.Project(Super.Project) + End Function + #tag EndMethod + + #tag Method, Flags = &h21 + Private Function ValidateContent(Content As String, Filename As String) As Boolean + Var MissingHeaders() As String = Palworld.ValidateIniContent(Content, Filename) + + If MissingHeaders.Count = 0 Then + Return True + End If + + Var Dict As New Dictionary + Dict.Value("File") = Filename + Dict.Value("Groups") = MissingHeaders + If MissingHeaders.Count > 1 Then + Dict.Value("Message") = Filename + " is not valid because it is missing the following groups: " + MissingHeaders.EnglishOxfordList + "." + Else + Dict.Value("Message") = Filename + " is not valid because it is missing its " + MissingHeaders(0) + " group." + End If + + Var Controller As New Beacon.TaskWaitController("ValidationFailed", Dict) + + Self.Log("Content validation failed!") + Self.Wait(Controller) + If Controller.Cancelled Then + Self.SetError(Dict.Value("Message").StringValue) + Return False + End If + + Return True + End Function + #tag EndMethod + + + #tag Property, Flags = &h21 + Private mCheckpointCreated As Boolean + #tag EndProperty + + + #tag ViewBehavior + #tag ViewProperty + Name="ThreadPriority" + Visible=false + Group="Behavior" + InitialValue="" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag EndViewBehavior +End Class +#tag EndClass diff --git a/Project/Modules/Game Support/Palworld/DiscoverIntegration.xojo_code b/Project/Modules/Game Support/Palworld/DiscoverIntegration.xojo_code new file mode 100644 index 000000000..574743273 --- /dev/null +++ b/Project/Modules/Game Support/Palworld/DiscoverIntegration.xojo_code @@ -0,0 +1,134 @@ +#tag Class +Protected Class DiscoverIntegration +Inherits Beacon.DiscoverIntegration + #tag CompatibilityFlags = ( TargetConsole and ( Target32Bit or Target64Bit ) ) or ( TargetWeb and ( Target32Bit or Target64Bit ) ) or ( TargetDesktop and ( Target32Bit or Target64Bit ) ) or ( TargetIOS and ( Target64Bit ) ) or ( TargetAndroid and ( Target64Bit ) ) + #tag Event + Function Run() As Beacon.Project + Var Project As Beacon.Project = Self.Project + Var Provider As Beacon.HostingProvider = Self.Provider + Var GetMapFromLogs As Boolean = True + + Var Profile As Palworld.ServerProfile = Self.Profile + Var Data As New Palworld.DiscoveredData + Data.Profile = Profile + Select Case Provider + Case IsA Nitrado.HostingProvider + Self.Log("Checking server status…") + Try + Profile.BasePath = Provider.GameSetting(Project, Profile, New Beacon.GenericGameSetting(Beacon.GenericGameSetting.TypeString, "/game_specific.path")) + Catch Err As RuntimeException + Self.SetError("Could not find server base path: " + Err.Message) + Return Nil + End Try + End Select + + Var SettingsIniPath As String = Profile.SettingsIniPath + If SettingsIniPath.IsEmpty = False Then + Var DownloadSuccess As Boolean + Var IniContent As String = Self.GetFile(SettingsIniPath, Palworld.ConfigFileSettings, Beacon.Integration.DownloadFailureMode.Required, Profile, False, DownloadSuccess) + If DownloadSuccess Then + Data.SettingsIniContent = IniContent.GuessEncoding("/Script/") + Else + Return Nil + End If + End If + + Self.mImportProgress = New Beacon.DummyProgressDisplayer + Var NewProject As Palworld.Project = Palworld.ImportThread.RunSynchronous(Data, Self.mDestinationProject, Self.mImportProgress) + Self.mImportProgress = Nil + Return NewProject + End Function + #tag EndEvent + + + #tag Method, Flags = &h0 + Sub Begin(DestinationProject As Palworld.Project) + Self.mDestinationProject = DestinationProject + Super.Begin() + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Function DestinationProject() As Palworld.Project + Return Self.mDestinationProject + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function Profile() As Palworld.ServerProfile + Return Palworld.ServerProfile(Super.Profile) + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function StatusMessage() As String + If (Self.mImportProgress Is Nil) = False Then + Return Self.mImportProgress.Detail + Else + Return Super.StatusMessage + End If + End Function + #tag EndMethod + + + #tag Property, Flags = &h21 + Private mDestinationProject As Palworld.Project + #tag EndProperty + + #tag Property, Flags = &h21 + Private mImportProgress As Beacon.DummyProgressDisplayer + #tag EndProperty + + + #tag ViewBehavior + #tag ViewProperty + Name="ThreadPriority" + Visible=false + Group="Behavior" + InitialValue="" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag EndViewBehavior +End Class +#tag EndClass diff --git a/Project/Modules/Game Support/Palworld/DiscoveredData.xojo_code b/Project/Modules/Game Support/Palworld/DiscoveredData.xojo_code new file mode 100644 index 000000000..e9a369c1a --- /dev/null +++ b/Project/Modules/Game Support/Palworld/DiscoveredData.xojo_code @@ -0,0 +1,78 @@ +#tag Class +Protected Class DiscoveredData +Inherits Beacon.DiscoveredData + #tag CompatibilityFlags = ( TargetDesktop and ( Target32Bit or Target64Bit ) ) + #tag Method, Flags = &h0 + Function Profile() As Palworld.ServerProfile + Return Palworld.ServerProfile(Super.Profile) + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Profile(Assigns Value As Palworld.ServerProfile) + Super.Profile = Value + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Function SettingsIniContent() As String + Return Self.mSettingsIniContent + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub SettingsIniContent(Assigns Value As String) + Self.mSettingsIniContent = Value.GuessEncoding("/Script/") + End Sub + #tag EndMethod + + + #tag Property, Flags = &h21 + Private mSettingsIniContent As String + #tag EndProperty + + + #tag ViewBehavior + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag EndViewBehavior +End Class +#tag EndClass diff --git a/Project/Modules/Game Support/Palworld/ImportThread.xojo_code b/Project/Modules/Game Support/Palworld/ImportThread.xojo_code new file mode 100644 index 000000000..4898882de --- /dev/null +++ b/Project/Modules/Game Support/Palworld/ImportThread.xojo_code @@ -0,0 +1,568 @@ +#tag Class +Protected Class ImportThread +Inherits Beacon.Thread + #tag CompatibilityFlags = ( TargetConsole and ( Target32Bit or Target64Bit ) ) or ( TargetWeb and ( Target32Bit or Target64Bit ) ) or ( TargetDesktop and ( Target32Bit or Target64Bit ) ) or ( TargetIOS and ( Target64Bit ) ) or ( TargetAndroid and ( Target64Bit ) ) + #tag Event + Sub Run() + Self.mFinished = False + Self.mCancelled = False + Self.mCreatedProject = Nil + Self.mError = Nil + + If Self.mProgress Is Nil Then + Self.mProgress = New Beacon.DummyProgressDisplayer + End If + + Try + Var Project As Palworld.Project = Self.RunSynchronous(Self.mData, Self.mDestinationProject, Self.mProgress) + If Self.mCancelled Then + Return + End If + + Self.mCreatedProject = Project + Self.mFinished = True + Catch Err As RuntimeException + Self.mFinished = True + Self.mError = Err + End Try + + Var Dict As New Dictionary + Dict.Value("Event") = "Finished" + Self.AddUserInterfaceUpdate(Dict) + End Sub + #tag EndEvent + + #tag Event + Sub UserInterfaceUpdate(data() as Dictionary) + For Each Update As Dictionary In Data + If Update.Lookup("Event", "").StringValue = "Finished" Then + RaiseEvent Finished(Self.mCreatedProject) + End If + Next + End Sub + #tag EndEvent + + + #tag Method, Flags = &h21 + Private Shared Sub AddCharactersParsed(CharacterCount As Integer, TotalCharacters As Integer, Progress As Beacon.ProgressDisplayer, ByRef CharactersProcessed As Integer) + CharactersProcessed = CharactersProcessed + CharacterCount + Var Percent As Double = CharactersProcessed / TotalCharacters + If (Progress Is Nil) = False Then + Progress.Progress = Percent + Progress.Detail = "Parsing files (" + Percent.ToString(Locale.Current, "0%") + ")…" + End If + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Shared Function BuildProject(Data As Palworld.DiscoveredData, DestinationProject As Palworld.Project, Progress As Beacon.ProgressDisplayer, ParsedData As Dictionary) As Palworld.Project + Var Profile As Palworld.ServerProfile + If (Data Is Nil) = False And (Data.Profile Is Nil) = False Then + Profile = Data.Profile + End If + + Var Project As New Palworld.Project + Var OptionSettings As Dictionary + Try + OptionSettings = ParsedData.Value("OptionSettings") + Catch Err As RuntimeException + OptionSettings = New Dictionary + End Try + If OptionSettings.HasKey("ServerName") Then + Var SessionNames() As Variant = OptionSettings.AutoArrayValue("ServerName") + For Each SessionName As Variant In SessionNames + Try + Var SessionTitle As String = SessionName.StringValue + If SessionTitle.Encoding Is Nil Then + SessionTitle = SessionTitle.DefineEncoding(Encodings.UTF8) + ElseIf SessionTitle.Encoding <> Encodings.UTF8 Then + SessionTitle = SessionTitle.ConvertEncoding(Encodings.UTF8) + End If + Project.Title = SessionTitle + Exit + Catch Err As RuntimeException + End Try + Next + End If + + If (DestinationProject Is Nil) = False Then + Var DestinationPacks As Beacon.StringList = DestinationProject.ContentPacks + For Each PackUUID As String In DestinationPacks + Project.ContentPackEnabled(PackUUID) = True + Next PackUUID + End If + + If (Profile Is Nil) = False Then + Profile.Name = Project.Title + Profile.ServerDescription = OptionSettings.StringValue("ServerDescription", "") + Profile.AdminPassword = OptionSettings.StringValue("AdminPassword", "") + Profile.ServerPassword = OptionSettings.StringValue("ServerPassword", "") + Project.AddServerProfile(Profile) + End If + + Var ConfigNames() As String = Palworld.Configs.AllNames() + Var Identity As Beacon.Identity = App.IdentityManager.CurrentIdentity + Var Configs() As Palworld.ConfigGroup + For Each ConfigName As String In ConfigNames + If ConfigName = Palworld.Configs.NameCustomConfig Then + // Custom content is special + Continue For ConfigName + End If + + If Palworld.Configs.ShouldImport(ConfigName, Identity) = False Then + // Do not import code for groups that the user has not purchased + Continue For ConfigName + End If + + Progress.Message = "Building Beacon project… (" + Language.LabelForConfig(ConfigName) + ")" + Var Group As Palworld.ConfigGroup + Try + Group = Palworld.Configs.CreateInstance(ConfigName, ParsedData, Project) + Catch Err As RuntimeException + End Try + If (Group Is Nil) = False Then + Project.AddConfigGroup(Group) + Configs.Add(Group) + End If + Next + + // Now split the content into values and remove the ones controlled by the imported groups + Progress.Message = "Building Beacon project… (" + Language.LabelForConfig(Palworld.Configs.NameCustomConfig) + ")" + Var CustomConfigOrganizer As New Palworld.ConfigOrganizer(Palworld.ConfigFileSettings, Palworld.HeaderPalworldSettings, Data.SettingsIniContent) + For Each Config As Palworld.ConfigGroup In Configs + Var ManagedKeys() As Palworld.ConfigOption = Config.ManagedKeys() + CustomConfigOrganizer.Remove(ManagedKeys) + Next + CustomConfigOrganizer.Remove(Palworld.ConfigFileSettings, "Beacon") + + Var CustomContent As New Palworld.Configs.CustomContent + Try + CustomContent.SettingsIniContent() = CustomConfigOrganizer + Catch Err As RuntimeException + End Try + If CustomContent.Modified Then + Project.AddConfigGroup(CustomContent) + End If + + Return Project + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Cancel() + Self.mCancelled = True + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Constructor(Data As Palworld.DiscoveredData, DestinationProject As Palworld.Project) + Self.mData = Data + Self.mDestinationProject = DestinationProject + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Function Error() As RuntimeException + Return Self.mError + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function Errored() As Boolean + Return (Self.mError Is Nil) = False + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function Finished() As Boolean + Return Self.mFinished + End Function + #tag EndMethod + + #tag Method, Flags = &h21 + Private Shared Function Import(Content As String, TotalCharacters As Integer, Progress As Beacon.ProgressDisplayer, ByRef CharactersProcessed As Integer) As Variant + Var Parser As New Palworld.ConfigParser + Var Value As Variant + Var Characters() As String = Content.Split("") + For Each Char As String In Characters + If (Progress Is Nil) = False And Progress.CancelPressed Then + Return Nil + End If + + If Parser.AddCharacter(Char) Then + Value = Parser.Value + Exit + End If + + AddCharactersParsed(1, TotalCharacters, Progress, CharactersProcessed) + Next + + Return ToXojoType(Value) + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Shared Function LineEndingChar() As String + Return Encodings.UTF8.Chr(10) + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function Name() As String + Var Name As String + If (Self.mData Is Nil) = False And (Self.mData.Profile Is Nil) = False Then + Name = Self.mData.Profile.Name + End If + If Name.IsEmpty Then + Name = "Untitled Importer" + End If + Return Name + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Shared Function ParseLine(Content As String) As Variant + Var TotalCharacters, CharactersProcessed As Integer + Return Import(Content, TotalCharacters, Nil, CharactersProcessed) + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function Progress() As Beacon.ProgressDisplayer + Return Self.mProgress + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Progress(Assigns Displayer As Beacon.ProgressDisplayer) + Var OldDisplayer As Beacon.ProgressDisplayer = Self.mProgress + Self.mProgress = Displayer + If (OldDisplayer Is Nil) = False And (Self.mProgress Is Nil) = False Then + Self.mProgress.Detail = OldDisplayer.Detail + Self.mProgress.Message = OldDisplayer.Message + Self.mProgress.Progress = OldDisplayer.Progress + End If + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Function Project() As Palworld.Project + Return Self.mCreatedProject + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Shared Function RunSynchronous(Data As Palworld.DiscoveredData, DestinationProject As Palworld.Project, Progress As Beacon.ProgressDisplayer) As Palworld.Project + Progress.Message = "Importing…" + Progress.Detail = "Cleaning up files…" + Progress.Progress = Nil + Progress.ShowSubProgress = False + + Var LineEnding As String = LineEndingChar() + + // Normalize line endings + Var Content As String + If (Data Is Nil) = False Then + Var Blocks() As String + + Var SettingsIniContent As String = Data.SettingsIniContent.ReplaceLineEndings(LineEnding) + If SettingsIniContent.IsEmpty = False Then + If SettingsIniContent.BeginsWith("[") = False Then + SettingsIniContent = "[" + Palworld.HeaderPalworldSettings + "]" + LineEnding + SettingsIniContent + End If + Blocks.Add(SettingsIniContent) + End If + + Content = Blocks.Join(LineEnding) + End If + + // Fix smart quotes + Content = Content.SanitizeIni + + Var CharactersProcessed As Integer + Var CharactersTotal As Integer = Content.Length + Var CurrentHeader As String + Var ParsedData As New Dictionary + Var Lines() As String = Content.Split(LineEnding) + CharactersTotal = CharactersTotal + ((Lines.LastIndex + 1) * LineEnding.Length) // To account for the trailing line ending characters we're adding + + Progress.Detail = "Parsing files…" + AddCharactersParsed(0, CharactersTotal, Progress, CharactersProcessed) + + For Each Line As String In Lines + If Progress.CancelPressed Then + Return Nil + End If + + Var CharacterCount As Integer = Line.Length + LineEnding.Length + + If Line.BeginsWith("[") And Line.EndsWith("]") Then + CurrentHeader = Line.Middle(1, Line.Length - 2) + End If + + If Line.IsEmpty Or Line.BeginsWith(";") Then + AddCharactersParsed(CharacterCount, CharactersTotal, Progress, CharactersProcessed) + Continue + End If + + Try + Var Value As Variant = Import(Line + LineEnding, CharactersTotal, Progress, CharactersProcessed) + If Value = Nil Then + Continue + End If + If Value.Type <> Variant.TypeObject Or Value IsA Beacon.KeyValuePair = False Then + Continue + End If + + Var Key As String = Beacon.KeyValuePair(Value).Key + Var ExtendedKey As String = CurrentHeader + "." + Key + Value = Beacon.KeyValuePair(Value).Value + + If ParsedData.HasKey(Key) Then + Var ExistingValue As Variant = ParsedData.Value(Key) + + Var ValueArray() As Variant + If ExistingValue.IsArray Then + ValueArray = ExistingValue + Else + ValueArray.Add(ExistingValue) + End If + ValueArray.Add(Value) + ParsedData.Value(Key) = ValueArray + Else + ParsedData.Value(Key) = Value + End If + + If ParsedData.HasKey(ExtendedKey) Then + Var ExistingValue As Variant = ParsedData.Value(ExtendedKey) + + Var ValueArray() As Variant + If ExistingValue.IsArray Then + ValueArray = ExistingValue + Else + ValueArray.Add(ExistingValue) + End If + ValueArray.Add(Value) + ParsedData.Value(ExtendedKey) = ValueArray + Else + ParsedData.Value(ExtendedKey) = Value + End If + Catch Err As RuntimeException + // Don't let an error halt processing, skip and move on + End Try + + Thread.SleepCurrent(10) + Next + CharactersProcessed = CharactersTotal + + Progress.Detail = "Building Beacon project…" + Try + Return BuildProject(Data, DestinationProject, Progress, ParsedData) + Catch Err As RuntimeException + End Try + Progress.Detail = "Finished" + End Function + #tag EndMethod + + #tag Method, Flags = &h21 + Private Shared Function ToXojoType(Input As Variant) As Variant + If Input = Nil Then + Return Nil + End If + + If Input.IsArray And Input.ArrayElementType = Variant.TypeObject Then + Var ArrayValue() As Variant = Input + Var IsDict As Boolean = True + For Each Item As Variant In ArrayValue + IsDict = IsDict And Item.Type = Variant.TypeObject And Item.ObjectValue IsA Beacon.KeyValuePair + Next + If IsDict Then + Var Dict As New Dictionary + For Each Item As Beacon.KeyValuePair In ArrayValue + Dict.Value(Item.Key) = ToXojoType(Item.Value) + Next + Return Dict + Else + Var Items() As Variant + For Each Item As Variant In ArrayValue + Items.Add(ToXojoType(Item)) + Next + Return Items + End If + End If + + Select Case Input.Type + Case Variant.TypeObject + Var ObjectValue As Object = Input.ObjectValue + Select Case ObjectValue + Case IsA Beacon.KeyValuePair + Var Original As Beacon.KeyValuePair = Input + Return New Beacon.KeyValuePair(Original.Key, ToXojoType(Original.Value)) + End Select + Case Variant.TypeString + Var StringValue As String = Input.StringValue + If StringValue = "true" Then + Return True + ElseIf StringValue = "false" Then + Return False + Else + Var IsNumeric As Boolean + If StringValue.Length > 0 Then + IsNumeric = True + Var DecimalPoints As Integer + Var Characters() As String = StringValue.Split("") + For Each Char As String In Characters + Select Case Char + Case "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" + // Still a Number + Case "." + If DecimalPoints = 1 Then + IsNumeric = False + Exit + Else + DecimalPoints = 1 + End If + Else + IsNumeric = False + Exit + End Select + Next + End If + If IsNumeric Then + // Number + Return Double.FromString(StringValue, Locale.Raw) + Else + // Probably String + Return StringValue + End If + End If + End Select + End Function + #tag EndMethod + + + #tag Hook, Flags = &h0 + Event Finished(Project As Palworld.Project) + #tag EndHook + + + #tag Property, Flags = &h21 + Private mCancelled As Boolean + #tag EndProperty + + #tag Property, Flags = &h21 + Private mCreatedProject As Palworld.Project + #tag EndProperty + + #tag Property, Flags = &h21 + Private mData As Palworld.DiscoveredData + #tag EndProperty + + #tag Property, Flags = &h21 + Private mDestinationProject As Palworld.Project + #tag EndProperty + + #tag Property, Flags = &h21 + Private mError As RuntimeException + #tag EndProperty + + #tag Property, Flags = &h21 + Private mFinished As Boolean + #tag EndProperty + + #tag Property, Flags = &h21 + Private mProgress As Beacon.ProgressDisplayer + #tag EndProperty + + + #tag ViewBehavior + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="DebugIdentifier" + Visible=false + Group="Behavior" + InitialValue="" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="ThreadID" + Visible=false + Group="Behavior" + InitialValue="" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="ThreadState" + Visible=false + Group="Behavior" + InitialValue="" + Type="ThreadStates" + EditorType="Enum" + #tag EnumValues + "0 - Running" + "1 - Waiting" + "2 - Paused" + "3 - Sleeping" + "4 - NotRunning" + #tag EndEnumValues + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Priority" + Visible=false + Group="Behavior" + InitialValue="" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="StackSize" + Visible=false + Group="Behavior" + InitialValue="" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag EndViewBehavior +End Class +#tag EndClass diff --git a/Project/Modules/Game Support/Palworld/Project.xojo_code b/Project/Modules/Game Support/Palworld/Project.xojo_code new file mode 100644 index 000000000..d658faa65 --- /dev/null +++ b/Project/Modules/Game Support/Palworld/Project.xojo_code @@ -0,0 +1,310 @@ +#tag Class +Protected Class Project +Inherits Beacon.Project + #tag Event + Sub AddSaveData(ManifestData As Dictionary, PlainData As Dictionary, EncryptedData As Dictionary) + #Pragma Unused PlainData + #Pragma Unused EncryptedData + + Var ConfigSets() As Beacon.ConfigSet = Self.ConfigSets + Var Editors() As String + For Each ConfigSet As Beacon.ConfigSet In ConfigSets + Var SetDict As Dictionary = Self.ConfigSetData(ConfigSet) + For Each Entry As DictionaryEntry In SetDict + Var ConfigName As String = Entry.Key.StringValue + If Editors.IndexOf(ConfigName) = -1 Then + Editors.Add(ConfigName) + End If + Next Entry + Next ConfigSet + Editors.Sort + ManifestData.Value("editors") = Editors + End Sub + #tag EndEvent + + #tag Event + Function LoadConfigSet(PlainData As Dictionary, EncryptedData As Dictionary) As Dictionary + Var SetDict As New Dictionary + For Each Entry As DictionaryEntry In PlainData + Try + Var InternalName As String = Entry.Key + Var GroupData As Dictionary = Entry.Value + Var EncryptedGroupData As Dictionary + If EncryptedData.HasKey(InternalName) Then + Try + EncryptedGroupData = EncryptedData.Value(InternalName) + Catch EncGroupDataErr As RuntimeException + End Try + End If + + Var Instance As Palworld.ConfigGroup = Palworld.Configs.CreateInstance(InternalName, GroupData, EncryptedGroupData) + If (Instance Is Nil) = False Then + SetDict.Value(InternalName) = Instance + End If + Catch Err As RuntimeException + App.Log("Unable to load config group " + Entry.Key + " from project " + Self.ProjectId + " due to an unhandled " + Err.ClassName + ": " + Err.Message) + End Try + Next + Return SetDict + End Function + #tag EndEvent + + #tag Event + Sub SaveConfigSet(SetDict As Dictionary, PlainData As Dictionary, EncryptedData As Dictionary) + For Each Entry As DictionaryEntry In SetDict + Var Group As Palworld.ConfigGroup = Entry.Value + + Var GroupData As Dictionary = Group.SaveData() + If GroupData Is Nil Then + Continue + End If + + If GroupData.HasAllKeys("Plain", "Encrypted") Then + PlainData.Value(Group.InternalName) = GroupData.Value("Plain") + EncryptedData.Value(Group.InternalName) = GroupData.Value("Encrypted") + Else + PlainData.Value(Group.InternalName) = GroupData + End If + Next + End Sub + #tag EndEvent + + + #tag Method, Flags = &h0 + Function CombinedConfig(GroupName As String, Sets() As Beacon.ConfigSet) As Palworld.ConfigGroup + If Sets Is Nil Then + Sets = Array(Beacon.ConfigSet.BaseConfigSet) + ElseIf Sets.Count = 0 Then + Sets.Add(Beacon.ConfigSet.BaseConfigSet) + End If + + Var Siblings() As Palworld.ConfigGroup + For Idx As Integer = 0 To Sets.LastIndex + Var Set As Beacon.ConfigSet = Sets(Idx) + Var SetDict As Dictionary = Self.ConfigSetData(Set) + If SetDict Is Nil Or SetDict.HasKey(GroupName) = False Then + Continue + End If + + Var Group As Palworld.ConfigGroup = SetDict.Value(GroupName) + Siblings.Add(Group) + Next + + Return Palworld.Configs.Merge(Siblings, False) + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function CombinedConfig(GroupName As String, States() As Beacon.ConfigSetState) As Palworld.ConfigGroup + Var Sets() As Beacon.ConfigSet = Beacon.ConfigSetState.FilterSets(States, Self.ConfigSets) + Return Self.CombinedConfig(GroupName, Sets) + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function CombinedConfigs(Sets() As Beacon.ConfigSet) As Palworld.ConfigGroup() + If Sets Is Nil Then + Sets = Array(Beacon.ConfigSet.BaseConfigSet) + ElseIf Sets.Count = 0 Then + Sets.Add(Beacon.ConfigSet.BaseConfigSet) + End If + + Var Instances As New Dictionary + For Idx As Integer = 0 To Sets.LastIndex + Var Set As Beacon.ConfigSet = Sets(Idx) + For Each Group As Palworld.ConfigGroup In Self.ImplementedConfigs(Set) + Var Siblings() As Palworld.ConfigGroup + If Instances.HasKey(Group.InternalName) Then + Siblings = Instances.Value(Group.InternalName) + End If + Siblings.Add(Group) + Instances.Value(Group.InternalName) = Siblings + Next + Next + + Var Combined() As Palworld.ConfigGroup + For Each Entry As DictionaryEntry In Instances + Var Siblings() As Palworld.ConfigGroup = Entry.Value + Var Merged As Palworld.ConfigGroup = Palworld.Configs.Merge(Siblings, False) + If (Merged Is Nil) = False Then + Combined.Add(Merged) + End If + Next Entry + Return Combined + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function CombinedConfigs(States() As Beacon.ConfigSetState) As Palworld.ConfigGroup() + Var Sets() As Beacon.ConfigSet = Beacon.ConfigSetState.FilterSets(States, Self.ConfigSets) + Return Self.CombinedConfigs(Sets) + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function ConfigGroup(InternalName As String, Set As Beacon.ConfigSet, Create As Boolean = False) As Palworld.ConfigGroup + Var SetDict As Dictionary = Self.ConfigSetData(Set) + If (SetDict Is Nil) = False And SetDict.HasKey(InternalName) Then + Return SetDict.Value(InternalName) + End If + + If Create Then + Var Group As Palworld.ConfigGroup = Palworld.Configs.CreateInstance(InternalName) + If (Group Is Nil) = False Then + Group.IsImplicit = True + Self.AddConfigGroup(Group, Set) + End If + Return Group + End If + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function ConfigGroup(InternalName As String, Create As Boolean = False) As Palworld.ConfigGroup + Return Self.ConfigGroup(InternalName, Self.ActiveConfigSet, Create) + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Constructor() + Super.Constructor + + Self.ContentPackEnabled(Palworld.UserContentPackId) = True // Force it + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Function CreateConfigOrganizer(Identity As Beacon.Identity, Profile As Palworld.ServerProfile) As Palworld.ConfigOrganizer + Try + If Identity.IsBanned Then + Return Self.CreateTrollConfigOrganizer(Profile) + End If + + Var Organizer As New Palworld.ConfigOrganizer + Var Groups() As Palworld.ConfigGroup = Self.CombinedConfigs(Profile.ConfigSetStates) + + // Add custom content first so it can be overridden or removed later + For Idx As Integer = 0 To Groups.LastIndex + If Groups(Idx) Is Nil Then + Continue + End If + + If Groups(Idx).InternalName = Palworld.Configs.NameCustomConfig Then + Organizer.AddManagedKeys(Groups(Idx).ManagedKeys) + Organizer.Add(Groups(Idx).GenerateConfigValues(Self, Identity, Profile)) + Groups.RemoveAt(Idx) + Exit + End If + Next + + For Each Group As Palworld.ConfigGroup In Groups + If Group Is Nil Then + Continue + End If + + Var ManagedKeys() As Palworld.ConfigOption = Group.ManagedKeys + Organizer.AddManagedKeys(ManagedKeys) + Organizer.Remove(ManagedKeys) // Removes overlapping values found in custom config + Organizer.Add(Group.GenerateConfigValues(Self, Identity, Profile)) + Next + + Organizer.Add(Palworld.ConfigFileSettings, Palworld.HeaderPalworldSettings, "OptionSettings", "ServerName", Profile.Name, Palworld.ConfigOrganizer.OptionValueIsManaged) + Organizer.Add(Palworld.ConfigFileSettings, Palworld.HeaderPalworldSettings, "OptionSettings", "ServerDescription", Profile.ServerDescription, Palworld.ConfigOrganizer.OptionValueIsManaged) + If (Profile.AdminPassword Is Nil) = False Then + Organizer.Add(Palworld.ConfigFileSettings, Palworld.HeaderPalworldSettings, "OptionSettings", "AdminPassword", Profile.AdminPassword.StringValue, Palworld.ConfigOrganizer.OptionValueIsManaged) + End If + If (Profile.ServerPassword Is Nil) = False Then + Organizer.Add(Palworld.ConfigFileSettings, Palworld.HeaderPalworldSettings, "OptionSettings", "ServerPassword", Profile.ServerPassword.StringValue, Palworld.ConfigOrganizer.OptionValueIsManaged) + End If + + Return Organizer + Catch Err As RuntimeException + App.Log(Err, CurrentMethodName, "Generating a config organizer") + Return Nil + End Try + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function CreateTrollConfigOrganizer(Profile As Palworld.ServerProfile) As Palworld.ConfigOrganizer + Var Values As New Palworld.ConfigOrganizer + + #if DebugBuild + #Pragma Warning "Needs troll organizer" + #else + #Pragma Error "Needs troll organizer" + #endif + + Return Values + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function DataSource(AllowWriting As Boolean) As Beacon.DataSource + Return Palworld.DataSource.Pool.Get(AllowWriting) + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function GameId() As String + Return Palworld.Identifier + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function UsesOmniFeaturesWithoutOmni(Identity As Beacon.Identity) As Palworld.ConfigGroup() + Var ExcludedConfigs() As Palworld.ConfigGroup + For Each Config As Palworld.ConfigGroup In Self.ImplementedConfigs() + If Palworld.Configs.ConfigUnlocked(Config, Identity) = False Then + ExcludedConfigs.Add(Config) + End If + Next + Return ExcludedConfigs + End Function + #tag EndMethod + + + #tag ViewBehavior + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag EndViewBehavior +End Class +#tag EndClass diff --git a/Project/Modules/Game Support/Palworld/ProjectTool.xojo_code b/Project/Modules/Game Support/Palworld/ProjectTool.xojo_code new file mode 100644 index 000000000..e8d412fa7 --- /dev/null +++ b/Project/Modules/Game Support/Palworld/ProjectTool.xojo_code @@ -0,0 +1,134 @@ +#tag Class +Protected Class ProjectTool + #tag Method, Flags = &h0 + Function Caption() As String + Return Self.mCaption + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Constructor(Caption As String, ToolId As String, RelevantGroups() As String) + Self.mCaption = Caption + Self.mToolId = ToolId + + Self.mGroups.ResizeTo(RelevantGroups.LastIndex) + For Idx As Integer = 0 To Self.mGroups.LastIndex + Self.mGroups(Idx) = RelevantGroups(Idx) + Next + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Constructor(Caption As String, ToolId As String, ParamArray RelevantGroups() As String) + Self.Constructor(Caption, ToolId, RelevantGroups) + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Function FirstGroup() As String + If Self.mGroups.Count > 0 Then + Return Self.mGroups(0) + End If + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function IsGlobal() As Boolean + Return Self.mGroups.Count = 0 + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function IsRelevantForGroup(Group As Palworld.ConfigGroup) As Boolean + If Group Is Nil Then + Return False + End If + + Return Self.IsRelevantForGroup(Group.InternalName) + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function IsRelevantForGroup(GroupName As String) As Boolean + Return Self.mGroups.IndexOf(GroupName) > -1 + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function Operator_Compare(Other As Palworld.ProjectTool) As Integer + If Other Is Nil Then + Return 1 + End If + + If Self.mToolId = Other.mToolId Then + Return 0 + End If + + Return Self.mCaption.Compare(Other.mCaption, ComparisonOptions.CaseInsensitive) + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function ToolId() As String + Return Self.mToolId + End Function + #tag EndMethod + + + #tag Property, Flags = &h21 + Private mCaption As String + #tag EndProperty + + #tag Property, Flags = &h21 + Private mGroups() As String + #tag EndProperty + + #tag Property, Flags = &h21 + Private mToolId As String + #tag EndProperty + + + #tag ViewBehavior + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag EndViewBehavior +End Class +#tag EndClass diff --git a/Project/Modules/Game Support/Palworld/Rewriter.xojo_code b/Project/Modules/Game Support/Palworld/Rewriter.xojo_code new file mode 100644 index 000000000..9cc74543b --- /dev/null +++ b/Project/Modules/Game Support/Palworld/Rewriter.xojo_code @@ -0,0 +1,620 @@ +#tag Class +Protected Class Rewriter +Inherits Global.Thread + #tag CompatibilityFlags = ( TargetConsole and ( Target32Bit or Target64Bit ) ) or ( TargetWeb and ( Target32Bit or Target64Bit ) ) or ( TargetDesktop and ( Target32Bit or Target64Bit ) ) or ( TargetIOS and ( Target64Bit ) ) or ( TargetAndroid and ( Target64Bit ) ) + #tag Event + Sub Run() + Self.mFinished = False + Self.mTriggers.Add(CallLater.Schedule(1, WeakAddressOf TriggerStarted)) + + Self.mFinishedSettingsIniContent = "" + + // Load everything we need into local variables in case something changes while the process is running. + Var LegacyTrustKey As String = Self.mProject.LegacyTrustKey + + Var Format As EncodingFormat = EncodingFormat.ASCII + Var Project As Palworld.Project = Self.mProject + Var Identity As Beacon.Identity = Self.mIdentity + Var Profile As Palworld.ServerProfile = Self.mProfile + Var InitialSettingsIni As String = Self.mInitialSettingsIniContent + + If Self.mOrganizer Is Nil Or Self.mRebuildOrganizer Then + If (Self.mOutputFlags And Self.FlagForceTrollMode) = Self.FlagForceTrollMode Then + Self.mOrganizer = Project.CreateTrollConfigOrganizer(Profile) + Else + Self.mOrganizer = Project.CreateConfigOrganizer(Identity, Profile) + End If + Self.mRebuildOrganizer = False + End If + + Var Error As RuntimeException + + If (Self.mOutputFlags And Self.FlagCreateSettingsIni) = Self.FlagCreateSettingsIni Then + Var SettingsIni As String = Self.Rewrite(Self.mSource, InitialSettingsIni, Palworld.HeaderPalworldSettings, Palworld.ConfigFileSettings, Self.mOrganizer, Project.ProjectId, LegacyTrustKey, Format, False, Error) + If (Error Is Nil) = False Then + Self.mFinished = True + Self.mError = Error + Self.mTriggers.Add(CallLater.Schedule(1, WeakAddressOf TriggerFinished)) + Return + End If + Self.mFinishedSettingsIniContent = SettingsIni + End If + + Self.mFinished = True + Self.mError = Error + Self.mTriggers.Add(CallLater.Schedule(1, WeakAddressOf TriggerFinished)) + End Sub + #tag EndEvent + + + #tag Method, Flags = &h0 + Sub Cancel() + If Self.ThreadState <> Thread.ThreadStates.NotRunning Then + Self.Stop + End If + + For I As Integer = Self.mTriggers.LastIndex DownTo 0 + CallLater.Cancel(Self.mTriggers(I)) + Self.mTriggers.RemoveAt(I) + Next + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Shared Function ConvertEncoding(Content As String, Format As Palworld.Rewriter.EncodingFormat) As String + If Format = Palworld.Rewriter.EncodingFormat.Unicode Then + If Content.Encoding <> Encodings.UTF8 Then + Content = Content.ConvertEncoding(Encodings.UTF8) + End If + Return Content + End If + + If Content.Encoding <> Encodings.ASCII Then + Content = Content.ConvertEncoding(Encodings.ASCII) + End If + + Return Content + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Destructor() + For I As Integer = Self.mTriggers.LastIndex DownTo 0 + CallLater.Cancel(Self.mTriggers(I)) + Self.mTriggers.RemoveAt(I) + Next + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Function Error() As RuntimeException + Return Self.mError + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function Errored() As Boolean + Return (Self.mError Is Nil) = False + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function Finished() As Boolean + Return Self.mFinished + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function OutputFlags() As Integer + Return Self.mOutputFlags + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Rewrite(Flags As Integer) + Self.mOutputFlags = Flags And (Self.FlagCreateSettingsIni Or Self.FlagForceTrollMode) + Super.Start + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Shared Function Rewrite(Source As Palworld.Rewriter.Sources, InitialContent As String, DefaultHeader As String, File As String, Organizer As Palworld.ConfigOrganizer, Format As Palworld.Rewriter.EncodingFormat, Nuke As Boolean, ByRef Error As RuntimeException) As String + // This version will not contain the [Beacon] sections in the output + Try + Return Rewrite(Source, InitialContent, DefaultHeader, File, Organizer, "", "", Format, Nuke, Error) + Catch Err As RuntimeException + Error = Err + End Try + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Shared Function Rewrite(Source As Palworld.Rewriter.Sources, InitialContent As String, DefaultHeader As String, File As String, Organizer As Palworld.ConfigOrganizer, ProjectUUID As String, LegacyTrustKey As String, Format As Palworld.Rewriter.EncodingFormat, Nuke As Boolean, ByRef Error As RuntimeException) As String + // This is the new master method + + Try + // Even if we're about to nuke the file, determine the mode so the file can be rebuilt in the same format + InitialContent = InitialContent.GuessEncoding("/Script/").SanitizeIni + Var DesiredLineEnding As String = InitialContent.DetectLineEnding + If Nuke Then + InitialContent = "" + End If + + // Get the initial values into an organizer + Var ParsedValues As New Palworld.ConfigOrganizer(File, DefaultHeader, InitialContent) + ParsedValues.Remove(Organizer.ManagedKeys) // Remove anything our groups already generate + + // Use the old Beacon section to determine which values to remove + Var Trusted As Boolean + Var TrustValues() As Palworld.ConfigValue + If ProjectUUID.IsEmpty = False Then + TrustValues = ParsedValues.FilteredValues(File, "Beacon", "ProjectUUID") + For Each TrustValue As Palworld.ConfigValue In TrustValues + If TrustValue.Value = ProjectUUID Then + Trusted = True + Continue + End If + Next TrustValue + End If + If Trusted = False And LegacyTrustKey.IsEmpty = False Then + TrustValues = ParsedValues.FilteredValues(File, "Beacon", "Trust") + For Each TrustValue As Palworld.ConfigValue In TrustValues + If TrustValue.Value = LegacyTrustKey Then + Trusted = True + Continue + End If + Next + End If + + If Trusted Then + // Thanks to a deploy bug in how Beacon blends configs from InitialContent, do not trust if the deploy version is between > 1.4.8.4 and < 1.5.0.5 + Var TrustVersions() As Palworld.ConfigValue = ParsedValues.FilteredValues(File, "Beacon", "Build") + For Each TrustVersion As Palworld.ConfigValue In TrustVersions + Try + Var TrustBuild As Integer = TrustVersion.Value.ToInteger + If TrustBuild > 10408304 And TrustBuild < 10500305 Then + Trusted = False + End If + Catch Err As RuntimeException + // Let's err on the side of caution + Trusted = False + End Try + Next + End If + + If Trusted Then + Var ManagedKeys() As Palworld.ConfigValue = ParsedValues.FilteredValues(File, "Beacon", "ManagedKeys") + For Each ManagedKey As Palworld.ConfigValue In ManagedKeys + Var ManagedSectionStartPos As Integer = ManagedKey.Value.IndexOf("Section=""") + If ManagedSectionStartPos = -1 Then + Continue + End If + ManagedSectionStartPos = ManagedSectionStartPos + 9 + Var ManagedSectionEndPos As Integer = ManagedKey.Value.IndexOf(ManagedSectionStartPos, """") + If ManagedSectionEndPos = -1 Then + Continue + End If + Var ManagedSection As String = ManagedKey.Value.Middle(ManagedSectionStartPos, ManagedSectionEndPos - ManagedSectionStartPos) + + Var KeysStartPos As Integer = ManagedKey.Value.IndexOf("Keys=(") + If KeysStartPos = -1 Then + Continue + End If + KeysStartPos = KeysStartPos + 6 + Var KeysEndPos As Integer = ManagedKey.Value.IndexOf(KeysStartPos, ")") + If KeysEndPos = -1 Then + Continue + End If + Var KeysString As String = ManagedKey.Value.Middle(KeysStartPos, KeysEndPos - KeysStartPos) + + Var Keys() As String = KeysString.Split(",") + For Each Key As String In Keys + ParsedValues.Remove(File, ManagedSection, Key) + Next + Next + End If + + // Remove the old Beacon section + ParsedValues.Remove(File, "Beacon") + + // Create a new organizer with the values from the original and unique values from the parsed + Var FinalOrganizer As New Palworld.ConfigOrganizer + FinalOrganizer.Add(Organizer.FilteredValues(File)) // Automatically grabs command line options + + // Remove everything from Parsed that is in Final, but don't do it by hash + Var NewValues() As Palworld.ConfigValue = FinalOrganizer.FilteredValues(File) + For Each Value As Palworld.ConfigValue In NewValues + ParsedValues.Remove(Value.File, Value.Header, Value.SimplifiedKey) + Next + + If FinalOrganizer.Count = 0 And ParsedValues.Count = 0 Then + // Both the new stuff and the initial stuff are empty + Return "" + End If + + If ProjectUUID.IsEmpty = False Then + // Build the Beacon section + Var SourceString As String = CType(Source, Integer).ToString(Locale.Raw, "0") + Select Case Source + Case Sources.Deploy + SourceString = "Deploy" + Case Sources.Original + SourceString = "Original" + Case Sources.SmartCopy + SourceString = "Smart Copy" + Case Sources.SmartSave + SourceString = "Smart Save" + End Select + + FinalOrganizer.Add(New Palworld.ConfigValue(File, "Beacon", Nil, "Build=" + App.BuildNumber.ToString(Locale.Raw, "0"))) + FinalOrganizer.Add(New Palworld.ConfigValue(File, "Beacon", Nil, "LastUpdated=" + DateTime.Now.SQLDateTimeWithOffset)) + FinalOrganizer.Add(New Palworld.ConfigValue(File, "Beacon", Nil, "ProjectUUID=" + ProjectUUID)) + If LegacyTrustKey.IsEmpty = False Then + FinalOrganizer.Add(New Palworld.ConfigValue(File, "Beacon", Nil, "Trust=" + LegacyTrustKey)) + End If + FinalOrganizer.Add(New Palworld.ConfigValue(File, "Beacon", Nil, "Source=" + SourceString)) + FinalOrganizer.Add(New Palworld.ConfigValue(File, "Beacon", Nil, "InitialSize=" + InitialContent.Bytes.ToString(Locale.Raw, "0"))) + FinalOrganizer.Add(New Palworld.ConfigValue(File, "Beacon", Nil, "InitialHash=" + EncodeHex(Crypto.SHA2_256(InitialContent)).Lowercase)) + FinalOrganizer.Add(New Palworld.ConfigValue(File, "Beacon", Nil, "WasTrusted=" + If(Trusted, "True", "False"))) + Var ManagedHeaders() As String = Organizer.Headers(File) + For HeaderIdx As Integer = 0 To ManagedHeaders.LastIndex + Var Header As String = ManagedHeaders(HeaderIdx) + If Header = "Beacon" Then + Continue + End If + + Var Keys() As String = FinalOrganizer.Keys(File, Header) + FinalOrganizer.Add(New Palworld.ConfigValue(File, "Beacon", Nil, "ManagedKeys=(Section=""" + Header + """,Keys=(" + Keys.Join(",") + "))", "ManagedKeys:" + Header)) + Next + + Var BeaconKeys() As String = Organizer.BeaconKeys + For Each BeaconKey As String In BeaconKeys + Var BeaconKeyValue As String = Organizer.BeaconKey(BeaconKey) + FinalOrganizer.Add(New Palworld.ConfigValue(File, "Beacon", Nil, BeaconKey + "=" + BeaconKeyValue)) + Next BeaconKey + End If + + // Now add everything remaining in Parsed to Final + FinalOrganizer.Add(ParsedValues.FilteredValues(File)) + + Return ConvertEncoding(FinalOrganizer.Build(File).ReplaceLineEndings(DesiredLineEnding), Format) + Catch Err As RuntimeException + Error = Err + End Try + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Shared Function Rewrite(Source As Palworld.Rewriter.Sources, InitialContent As String, DefaultHeader As String, File As String, Project As Palworld.Project, Identity As Beacon.Identity, Profile As Palworld.ServerProfile, Format As Palworld.Rewriter.EncodingFormat, Nuke As Boolean, ByRef Error As RuntimeException) As String + Try + Var Organizer As Palworld.ConfigOrganizer = Project.CreateConfigOrganizer(Identity, Profile) + Return Rewrite(Source, InitialContent, DefaultHeader, File, Organizer, Project.ProjectId, Project.LegacyTrustKey, Format, Nuke, Error) + Catch Err As RuntimeException + Error = Err + End Try + End Function + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub Start() + Super.Start() + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub TriggerFinished() + RaiseEvent Finished + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub TriggerStarted() + RaiseEvent Started + End Sub + #tag EndMethod + + + #tag Hook, Flags = &h0 + Event Finished() + #tag EndHook + + #tag Hook, Flags = &h0 + Event Started() + #tag EndHook + + + #tag ComputedProperty, Flags = &h0 + #tag Getter + Get + Return Self.mFinishedSettingsIniContent + End Get + #tag EndGetter + FinishedSettingsIniContent As String + #tag EndComputedProperty + + #tag ComputedProperty, Flags = &h0 + #tag Getter + Get + Return Self.mIdentity + End Get + #tag EndGetter + #tag Setter + Set + If Self.mIdentity <> Value Then + Self.mIdentity = Value + Self.mRebuildOrganizer = True + End If + End Set + #tag EndSetter + Identity As Beacon.Identity + #tag EndComputedProperty + + #tag ComputedProperty, Flags = &h0 + #tag Getter + Get + Return Self.mInitialSettingsIniContent + End Get + #tag EndGetter + #tag Setter + Set + Self.mInitialSettingsIniContent = Value.SanitizeIni + End Set + #tag EndSetter + InitialSettingsIniContent As String + #tag EndComputedProperty + + #tag Property, Flags = &h21 + Private mError As RuntimeException + #tag EndProperty + + #tag Property, Flags = &h21 + Private mFinished As Boolean + #tag EndProperty + + #tag Property, Flags = &h21 + Private mFinishedSettingsIniContent As String + #tag EndProperty + + #tag Property, Flags = &h21 + Private mIdentity As Beacon.Identity + #tag EndProperty + + #tag Property, Flags = &h21 + Private mInitialSettingsIniContent As String + #tag EndProperty + + #tag Property, Flags = &h21 + Private mOrganizer As Palworld.ConfigOrganizer + #tag EndProperty + + #tag Property, Flags = &h21 + Private mOutputFlags As Integer + #tag EndProperty + + #tag Property, Flags = &h21 + Private mProfile As Palworld.ServerProfile + #tag EndProperty + + #tag Property, Flags = &h21 + Private mProject As Palworld.Project + #tag EndProperty + + #tag Property, Flags = &h21 + Private mRebuildOrganizer As Boolean + #tag EndProperty + + #tag Property, Flags = &h21 + Private mSource As Palworld.Rewriter.Sources + #tag EndProperty + + #tag Property, Flags = &h21 + Private mTriggers() As String + #tag EndProperty + + #tag ComputedProperty, Flags = &h0 + #tag Getter + Get + Return Self.mProfile + End Get + #tag EndGetter + #tag Setter + Set + // Don't just use <> here, it does not compare correctly with Nitrado + + If Value Is Nil Then + If Self.mProfile Is Nil Then + Return + End If + + Self.mProfile = Nil + Self.mRebuildOrganizer = True + Return + End If + + If (Self.mProfile Is Nil) = False And Self.mProfile.Hash = Value.Hash Then + Return + End If + + Self.mProfile = Value + Self.mRebuildOrganizer = True + End Set + #tag EndSetter + Profile As Palworld.ServerProfile + #tag EndComputedProperty + + #tag ComputedProperty, Flags = &h0 + #tag Getter + Get + Return Self.mProject + End Get + #tag EndGetter + #tag Setter + Set + If Self.mProject <> Value Then + Self.mProject = Value + Self.mRebuildOrganizer = True + End If + End Set + #tag EndSetter + Project As Palworld.Project + #tag EndComputedProperty + + #tag ComputedProperty, Flags = &h0 + #tag Getter + Get + Return Self.mSource + End Get + #tag EndGetter + #tag Setter + Set + Self.mSource = Value + End Set + #tag EndSetter + Source As Palworld.Rewriter.Sources + #tag EndComputedProperty + + + #tag Constant, Name = FlagCreateSettingsIni, Type = Double, Dynamic = False, Default = \"1", Scope = Public + #tag EndConstant + + #tag Constant, Name = FlagForceTrollMode, Type = Double, Dynamic = False, Default = \"2", Scope = Public + #tag EndConstant + + #tag Constant, Name = ModeSettingsIni, Type = String, Dynamic = False, Default = \"PalWorldSettings.ini", Scope = Public + #tag EndConstant + + + #tag Enum, Name = EncodingFormat, Type = Integer, Flags = &h0 + Unicode + ASCII + #tag EndEnum + + #tag Enum, Name = Sources, Type = Integer, Flags = &h0 + Original + Deploy + SmartCopy + SmartSave + #tag EndEnum + + + #tag ViewBehavior + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="DebugIdentifier" + Visible=false + Group="Behavior" + InitialValue="" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="ThreadID" + Visible=false + Group="Behavior" + InitialValue="" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="ThreadState" + Visible=false + Group="Behavior" + InitialValue="" + Type="ThreadStates" + EditorType="Enum" + #tag EnumValues + "0 - Running" + "1 - Waiting" + "2 - Paused" + "3 - Sleeping" + "4 - NotRunning" + #tag EndEnumValues + #tag EndViewProperty + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Priority" + Visible=true + Group="Behavior" + InitialValue="5" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="StackSize" + Visible=true + Group="Behavior" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="InitialSettingsIniContent" + Visible=false + Group="Behavior" + InitialValue="" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="FinishedSettingsIniContent" + Visible=false + Group="Behavior" + InitialValue="" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="Source" + Visible=false + Group="Behavior" + InitialValue="" + Type="Palworld.Rewriter.Sources" + EditorType="Enum" + #tag EnumValues + "0 - Original" + "1 - Deploy" + "2 - SmartCopy" + "3 - SmartSave" + #tag EndEnumValues + #tag EndViewProperty + #tag EndViewBehavior +End Class +#tag EndClass diff --git a/Project/Modules/Game Support/Palworld/ServerProfile.xojo_code b/Project/Modules/Game Support/Palworld/ServerProfile.xojo_code new file mode 100644 index 000000000..66789176a --- /dev/null +++ b/Project/Modules/Game Support/Palworld/ServerProfile.xojo_code @@ -0,0 +1,306 @@ +#tag Class +Protected Class ServerProfile +Inherits Beacon.ServerProfile + #tag Event + Sub ReadFromDictionary(Dict As Dictionary, Version As Integer) + Select Case Version + Case 2 + If Dict.HasKey("adminPassword") Then + Self.mAdminPassword = NullableString.FromVariant(Dict.Value("adminPassword")) + End If + + If Dict.HasKey("serverPassword") Then + Self.mServerPassword = NullableString.FromVariant(Dict.Value("serverPassword")) + End If + + If Dict.HasKey("serverDescription") Then + Self.mServerDescription = Dict.Value("serverDescription").StringValue + End If + + Self.mBasePath = Dict.Lookup("basePath", "").StringValue + Self.mSettingsIniPath = Dict.Lookup("palWorldSettingsIniPath", "").StringValue + Self.mLogsPath = Dict.Lookup("logsPath", "").StringValue + End Select + End Sub + #tag EndEvent + + #tag Event + Sub WriteToDictionary(Dict As Dictionary) + Dict.Value("adminPassword") = NullableString.ToVariant(Self.mAdminPassword) + Dict.Value("serverPassword") = NullableString.ToVariant(Self.mServerPassword) + Dict.Value("serverDescription") = Self.mServerDescription + + If Self.mBasePath.IsEmpty = False Then + Dict.Value("basePath") = Self.mBasePath + End If + If Self.mSettingsIniPath.IsEmpty = False Then + Dict.Value("palWorldSettingsIniPath") = Self.mSettingsIniPath + End If + If Self.mLogsPath.IsEmpty = False Then + Dict.Value("logsPath") = Self.mLogsPath + End If + End Sub + #tag EndEvent + + + #tag Method, Flags = &h0 + Function AdminPassword() As NullableString + Return Self.mAdminPassword + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub AdminPassword(Assigns Value As NullableString) + If NullableString.Compare(Self.mAdminPassword, Value, ComparisonOptions.CaseSensitive) = 0 Then + Return + End If + + Self.mAdminPassword = Value + Self.Modified = True + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Function BasePath() As String + Return Self.mBasePath + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub BasePath(Assigns Value As String) + Value = Self.CleanupPath(Value) + + If Self.mBasePath.Compare(Value, ComparisonOptions.CaseSensitive, Locale.Raw) = 0 Then + Return + End If + + Self.mBasePath = Value + Self.Modified = True + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Shared Function CleanupPath(Path As String) As String + If Path.EndsWith("/") Or Path.EndsWith("\") Then + Path = Path.Left(Path.Length - 1) + End If + Return Path + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function Clone() As Palworld.ServerProfile + Return Palworld.ServerProfile(Super.Clone) + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Constructor(Provider As String, Name As String) + // Making the constructor public + Super.Constructor(Provider, Name) + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Constructor(Provider As String, ProfileId As String, Name As String, Nickname As String, SecondaryName As String) + // Making the constructor public + Super.Constructor(Provider, ProfileId, Name, Nickname, SecondaryName) + + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Function DeployCapable() As Boolean + Select Case Self.ProviderId + Case Nitrado.Identifier, GameServerApp.Identifier + Return True + Case FTP.Identifier, Local.Identifier + Return Self.mSettingsIniPath.IsEmpty = False + End Select + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function GameId() As String + Return Palworld.Identifier + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function IsConsole() As Boolean + Return False + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function LogsPath() As String + If Self.mLogsPath.IsEmpty = False Then + Return Self.mLogsPath + ElseIf Self.mBasePath.IsEmpty = False Then + Return Self.mBasePath + "/ShooterGame/Saved/Logs" + End If + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub LogsPath(Assigns Value As String) + Value = Self.CleanupPath(Value) + + If Self.mLogsPath.Compare(Value, ComparisonOptions.CaseSensitive, Locale.Raw) = 0 Then + Return + End If + + Self.mLogsPath = Value + Self.Modified = True + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Function Platform() As Integer + Return Beacon.PlatformPC + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Platform(Assigns Value As Integer) + #Pragma Unused Value + Super.Platform = Beacon.PlatformPC + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Function ServerDescription() As String + Return Self.mServerDescription + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub ServerDescription(Assigns Value As String) + If Self.mServerDescription.Compare(Value, ComparisonOptions.CaseSensitive) <> 0 Then + Self.mServerDescription = Value + Self.Modified = True + End If + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Function ServerPassword() As NullableString + Return Self.mServerPassword + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub ServerPassword(Assigns Value As NullableString) + If NullableString.Compare(Self.mServerPassword, Value, ComparisonOptions.CaseSensitive) = 0 Then + Return + End If + + Self.mServerPassword = Value + Self.Modified = True + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Function SettingsIniPath() As String + If Self.mSettingsIniPath.IsEmpty = False Then + Return Self.mSettingsIniPath + ElseIf Self.mBasePath.IsEmpty = False Then + Return Self.mBasePath + "/Pal/Saved/Config/WindowsServer/" + Palworld.ConfigFileSettings + End If + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub SettingsIniPath(Assigns Value As String) + Value = Self.CleanupPath(Value) + + If Self.mSettingsIniPath.Compare(Value, ComparisonOptions.CaseSensitive, Locale.Raw) = 0 Then + Return + End If + + Self.mSettingsIniPath = Value + Self.Modified = True + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Function SupportedDeployPlans() As Beacon.DeployPlan() + Var Config As Beacon.HostConfig = Self.HostConfig + If (Config Is Nil) = False And Config IsA Nitrado.HostConfig Then + Return Array(Beacon.DeployPlan.StopUploadStart) + Else + Return Array(Beacon.DeployPlan.UploadOnly) + End If + End Function + #tag EndMethod + + + #tag Property, Flags = &h21 + Private mAdminPassword As NullableString + #tag EndProperty + + #tag Property, Flags = &h21 + Private mBasePath As String + #tag EndProperty + + #tag Property, Flags = &h21 + Private mLogsPath As String + #tag EndProperty + + #tag Property, Flags = &h21 + Private mServerDescription As String + #tag EndProperty + + #tag Property, Flags = &h21 + Private mServerPassword As NullableString + #tag EndProperty + + #tag Property, Flags = &h21 + Private mSettingsIniPath As String + #tag EndProperty + + + #tag ViewBehavior + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag EndViewBehavior +End Class +#tag EndClass diff --git a/Project/Modules/Hosting Providers/GameServerApp/HostingProvider.xojo_code b/Project/Modules/Hosting Providers/GameServerApp/HostingProvider.xojo_code index 1d1dda8df..2745e67bb 100644 --- a/Project/Modules/Hosting Providers/GameServerApp/HostingProvider.xojo_code +++ b/Project/Modules/Hosting Providers/GameServerApp/HostingProvider.xojo_code @@ -53,6 +53,8 @@ Implements Beacon.HostingProvider Return SDTD.SteamAppId Case ArkSA.Identifier Return ArkSA.SteamServerId + Case Palworld.Identifier + Return Palworld.SteamServerId End Select End Function #tag EndMethod @@ -181,6 +183,9 @@ Implements Beacon.HostingProvider ArkSA.ServerProfile(Profile).GameUserSettingsIniPath = ArkSA.ConfigFileGameUserSettings Case SDTD.Identifier Profile = New SDTD.ServerProfile(Self.Identifier, ProfileId, TemplateName, "", TemplateId.ToString(Locale.Raw, "0")) + Case Palworld.Identifier + Profile = New Palworld.ServerProfile(Self.Identifier, ProfileId, TemplateName, "", TemplateId.ToString(Locale.Raw, "0")) + Palworld.ServerProfile(Profile).SettingsIniPath = Palworld.ConfigFileSettings End Select Profile.Platform = Beacon.PlatformPC Profile.HostConfig = ProfileConfig diff --git a/Project/Modules/Hosting Providers/Nitrado/HostingProvider.xojo_code b/Project/Modules/Hosting Providers/Nitrado/HostingProvider.xojo_code index 2b571cc90..550e3cd01 100644 --- a/Project/Modules/Hosting Providers/Nitrado/HostingProvider.xojo_code +++ b/Project/Modules/Hosting Providers/Nitrado/HostingProvider.xojo_code @@ -397,6 +397,8 @@ Implements Beacon.HostingProvider Profile = New SDTD.ServerProfile(Self.Identifier, ProfileId, Name, Nickname, SecondaryName) Case ArkSA.Identifier Profile = New ArkSA.ServerProfile(Self.Identifier, ProfileId, Name, Nickname, SecondaryName) + Case Palworld.Identifier + Profile = New Palworld.ServerProfile(Self.Identifier, ProfileId, Name, Nickname, SecondaryName) End Select Var ProfileConfig As New Nitrado.HostConfig @@ -516,6 +518,8 @@ Implements Beacon.HostingProvider Return SDTD.Identifier Case "arksa" Return ArkSA.Identifier + Case "palworld" + Return Palworld.Identifier End Select End Function #tag EndMethod @@ -529,7 +533,7 @@ Implements Beacon.HostingProvider Return Beacon.PlatformPlayStation Case "arkswitch", "arkswitchjp" Return Beacon.PlatformSwitch - Case "arkse", "arksotf", "arkseosg", "7daystodie", "sevendaysexperimental", "sevendtd" + Case "arkse", "arksotf", "arkseosg", "7daystodie", "sevendaysexperimental", "sevendtd", "palworld" Return Beacon.PlatformPC Case "arkmobile" Return Beacon.PlatformUnsupported diff --git a/Project/Modules/Language.xojo_code b/Project/Modules/Language.xojo_code index 796b59daf..9673c778d 100644 --- a/Project/Modules/Language.xojo_code +++ b/Project/Modules/Language.xojo_code @@ -127,7 +127,7 @@ Protected Module Language Select Case GameId Case Ark.Identifier, ArkSA.Identifier Return "an" - Case SDTD.Identifier + Case SDTD.Identifier, Palworld.Identifier Return "a" End Select End Function @@ -142,6 +142,8 @@ Protected Module Language Return SDTD.FullName Case ArkSA.Identifier Return ArkSA.FullName + Case Palworld.Identifier + Return Palworld.Identifier Else Return GameId End Select @@ -163,7 +165,7 @@ Protected Module Language Return "Loot Drops" Case Ark.Configs.NameLevelsAndXP, ArkSA.Configs.NameLevelsAndXP Return "Levels and XP" - Case Ark.Configs.NameCustomConfig, SDTD.Configs.NameCustomConfig, ArkSA.Configs.NameCustomConfig + Case Ark.Configs.NameCustomConfig, SDTD.Configs.NameCustomConfig, ArkSA.Configs.NameCustomConfig, Palworld.Configs.NameCustomConfig Return "Custom Config" Case Ark.Configs.NameCraftingCosts, ArkSA.Configs.NameCraftingCosts Return "Crafting Costs" @@ -187,13 +189,13 @@ Protected Module Language Return "Engram Control" Case Ark.Configs.NameDecayAndSpoil, ArkSA.Configs.NameDecayAndSpoil Return "Decay and Spoil" - Case Ark.Configs.NameGeneralSettings, SDTD.Configs.NameGeneralSettings, ArkSA.Configs.NameGeneralSettings + Case Ark.Configs.NameGeneralSettings, SDTD.Configs.NameGeneralSettings, ArkSA.Configs.NameGeneralSettings, Palworld.Configs.NameGeneralSettings Return "General Settings" - Case Ark.Configs.NameServers, SDTD.Configs.NameServers, ArkSA.Configs.NameServers + Case Ark.Configs.NameServers, SDTD.Configs.NameServers, ArkSA.Configs.NameServers, Palworld.Configs.NameServers Return "Servers" - Case Ark.Configs.NameAccounts, SDTD.Configs.NameAccounts, ArkSA.Configs.NameAccounts + Case Ark.Configs.NameAccounts, SDTD.Configs.NameAccounts, ArkSA.Configs.NameAccounts, Palworld.Configs.NameAccounts Return "Accounts" - Case Ark.Configs.NameProjectSettings, SDTD.Configs.NameProjectSettings, ArkSA.Configs.NameProjectSettings + Case Ark.Configs.NameProjectSettings, SDTD.Configs.NameProjectSettings, ArkSA.Configs.NameProjectSettings, Palworld.Configs.NameProjectSettings Return "Project Settings" End Select End Function diff --git a/Project/Views/Ark/ArkDocumentEditorView.xojo_window b/Project/Views/Ark/ArkDocumentEditorView.xojo_window index c8fc5d865..39d0cdd84 100644 --- a/Project/Views/Ark/ArkDocumentEditorView.xojo_window +++ b/Project/Views/Ark/ArkDocumentEditorView.xojo_window @@ -1245,7 +1245,7 @@ End #tag Events OmniNoticeBanner #tag Event Sub Pressed() - System.GotoURL(Beacon.WebURL("/omni")) + System.GotoURL(Beacon.WebURL("/omni#Ark")) End Sub #tag EndEvent #tag Event diff --git a/Project/Views/Ark/Import/Discovery Views/ArkFilesDiscoveryView.xojo_window b/Project/Views/Ark/Import/Discovery Views/ArkFilesDiscoveryView.xojo_window index bcf171a61..5583a1848 100644 --- a/Project/Views/Ark/Import/Discovery Views/ArkFilesDiscoveryView.xojo_window +++ b/Project/Views/Ark/Import/Discovery Views/ArkFilesDiscoveryView.xojo_window @@ -1,16 +1,16 @@ #tag DesktopWindow Begin ArkDiscoveryView ArkFilesDiscoveryView - AllowAutoDeactivate= True - AllowFocus = False - AllowFocusRing = False - AllowTabs = True + AllowAutoDeactivate= "True" + AllowFocus = "False" + AllowFocusRing = "False" + AllowTabs = "True" Backdrop = 0 BackgroundColor = &cFFFFFF00 Composite = False - Composited = False + Composited = "False" DefaultLocation = 2 DoubleBuffer = "False" - Enabled = True + Enabled = "True" EraseBackground = "True" FullScreen = False HasBackgroundColor= False @@ -18,15 +18,15 @@ Begin ArkDiscoveryView ArkFilesDiscoveryView HasFullScreenButton= False HasMaximizeButton= True HasMinimizeButton= True - Height = 200 + Height = 208 ImplicitInstance= True - Index = -2147483648 + Index = "-2147483648" InitialParent = "" - Left = 0 - LockBottom = True - LockLeft = True - LockRight = True - LockTop = True + Left = "0" + LockBottom = "True" + LockLeft = "True" + LockRight = "True" + LockTop = "True" MacProcID = 0 MaximumHeight = 32000 MaximumWidth = 32000 @@ -35,13 +35,13 @@ Begin ArkDiscoveryView ArkFilesDiscoveryView MinimumHeight = 64 MinimumWidth = 64 Resizeable = True - TabIndex = 0 - TabPanelIndex = 0 - TabStop = True + TabIndex = "0" + TabPanelIndex = "0" + TabStop = "True" Title = "Untitled" Tooltip = "" - Top = 0 - Transparent = True + Top = "0" + Transparent = "True" Type = 0 Visible = True Width = 600 @@ -71,7 +71,7 @@ Begin ArkDiscoveryView ArkFilesDiscoveryView TabPanelIndex = 0 TabStop = True Tooltip = "" - Top = 160 + Top = 168 Transparent = False Underline = False Visible = True @@ -103,7 +103,7 @@ Begin ArkDiscoveryView ArkFilesDiscoveryView TabPanelIndex = 0 TabStop = True Tooltip = "" - Top = 160 + Top = 168 Transparent = False Underline = False Visible = True diff --git a/Project/Views/ArkSA/ArkSADocumentEditorView.xojo_window b/Project/Views/ArkSA/ArkSADocumentEditorView.xojo_window index 09a25f87b..ed9d6ed7d 100644 --- a/Project/Views/ArkSA/ArkSADocumentEditorView.xojo_window +++ b/Project/Views/ArkSA/ArkSADocumentEditorView.xojo_window @@ -49,7 +49,7 @@ Begin DocumentEditorView ArkSADocumentEditorView Tooltip = "" Top = 41 Transparent = False - Value = 0 + Value = 1 Visible = True Width = 627 Begin OmniNoticeBar OmniNoticeBanner @@ -270,7 +270,6 @@ Begin DocumentEditorView ArkSADocumentEditorView End Begin Thread GFIComputeThread DebugIdentifier = "" - Enabled = True Index = -2147483648 LockedInPosition= False Priority = 5 @@ -1325,7 +1324,7 @@ End #tag Events OmniNoticeBanner #tag Event Sub Pressed() - System.GotoURL(Beacon.WebURL("/omni")) + System.GotoURL(Beacon.WebURL("/omni#ArkSA")) End Sub #tag EndEvent #tag Event diff --git a/Project/Views/ArkSA/Import/Discovery Views/ArkSAFilesDiscoveryView.xojo_window b/Project/Views/ArkSA/Import/Discovery Views/ArkSAFilesDiscoveryView.xojo_window index b80cfaefd..88b68ba3b 100644 --- a/Project/Views/ArkSA/Import/Discovery Views/ArkSAFilesDiscoveryView.xojo_window +++ b/Project/Views/ArkSA/Import/Discovery Views/ArkSAFilesDiscoveryView.xojo_window @@ -16,7 +16,7 @@ Begin ArkSADiscoveryView ArkSAFilesDiscoveryView HasFullScreenButton= False HasMaximizeButton= True HasMinimizeButton= True - Height = 200 + Height = 208 ImplicitInstance= True Index = "-2147483648" InitialParent = "" @@ -69,7 +69,7 @@ Begin ArkSADiscoveryView ArkSAFilesDiscoveryView TabPanelIndex = 0 TabStop = True Tooltip = "" - Top = 160 + Top = 168 Transparent = False Underline = False Visible = True @@ -101,7 +101,7 @@ Begin ArkSADiscoveryView ArkSAFilesDiscoveryView TabPanelIndex = 0 TabStop = True Tooltip = "" - Top = 160 + Top = 168 Transparent = False Underline = False Visible = True diff --git a/Project/Views/DeployManager.xojo_window b/Project/Views/DeployManager.xojo_window index ed1ad4f15..efd05056d 100644 --- a/Project/Views/DeployManager.xojo_window +++ b/Project/Views/DeployManager.xojo_window @@ -136,7 +136,7 @@ Begin BeaconAutopositionWindow DeployManager Tooltip = "" Top = 0 Transparent = False - Value = 0 + Value = 2 Visible = True Width = 500 Begin DesktopLabel OptionsMessageLabel @@ -1048,6 +1048,8 @@ End #endif Case IsA ArkSA.ServerProfile Engine = New ArkSA.DeployIntegration(Project, Profile) + Case IsA Palworld.ServerProfile + Engine = New Palworld.DeployIntegration(Project, Profile) End Select If Engine Is Nil Then Self.ShowAlert("The developer messed up.", "There is no DeployIntegration defined for server profile " + Profile.Name + ".") @@ -1425,7 +1427,11 @@ End Var State As New TextAreaState State.ApplyTo(Self.ReviewArea) Self.ReviewSwitcher.SelectedIndex = 1 - Self.ReviewArea.Text = UserData.Lookup(Ark.ConfigFileGameUserSettings, "").StringValue + Try + Self.ReviewArea.Text = UserData.Lookup(Self.ReviewSwitcher.SelectedItem.Caption, "").StringValue + Catch Err As RuntimeException + Self.ReviewArea.Text = "" + End Try Self.UpdatingReviewContent = False End Sub #tag EndMethod @@ -1627,10 +1633,27 @@ End #tag Event Sub Opening() Me.Add(ShelfItem.NewFlexibleSpacer) - Me.Add(IconFileIniFilled, Ark.ConfigFileGameUserSettings, Ark.ConfigFileGameUserSettings) - Me.Add(IconFileIni, Ark.ConfigFileGame, Ark.ConfigFileGame) + Select Case Self.Project.GameId + Case Ark.Identifier + Me.Add(IconFileIniFilled, Ark.ConfigFileGameUserSettings, Ark.ConfigFileGameUserSettings) + Me.Add(IconFileIni, Ark.ConfigFileGame, Ark.ConfigFileGame) + Case ArkSA.Identifier + Me.Add(IconFileIniFilled, ArkSA.ConfigFileGameUserSettings, ArkSA.ConfigFileGameUserSettings) + Me.Add(IconFileIni, ArkSA.ConfigFileGame, ArkSA.ConfigFileGame) + Case Palworld.Identifier + Me.Add(IconFileIniFilled, Palworld.ConfigFileSettings, Palworld.ConfigFileSettings) + End Select Me.Add(ShelfItem.NewFlexibleSpacer) Me.SelectedIndex = 1 + + Select Case Me.Count + Case 3 + Self.ReviewConfirmationCheck.Caption = "The config file is correct" + Case 4 + Self.ReviewConfirmationCheck.Caption = "Both config files are correct" + Else + Self.ReviewConfirmationCheck.Caption = "All config files are correct" + End Select End Sub #tag EndEvent #tag Event @@ -1650,12 +1673,12 @@ End Self.UpdatingReviewContent = True Var UserData As Dictionary = Controller.UserData - Select Case Me.SelectedIndex - Case 1 - Self.ReviewArea.Text = UserData.Lookup(Ark.ConfigFileGameUserSettings, "").StringValue - Case 2 - Self.ReviewArea.Text = UserData.Lookup(Ark.ConfigFileGame, "").StringValue - End Select + Try + Var Filename As String = Me.SelectedItem.Caption + Self.ReviewArea.Text = UserData.Lookup(Filename, "").StringValue + Catch Err As RuntimeException + Self.ReviewArea.Text = "" + End Try Self.UpdatingReviewContent = False End Sub #tag EndEvent diff --git a/Project/Views/DocumentEditorView.xojo_code b/Project/Views/DocumentEditorView.xojo_code index 1b8141d63..d7df71058 100644 --- a/Project/Views/DocumentEditorView.xojo_code +++ b/Project/Views/DocumentEditorView.xojo_code @@ -218,6 +218,8 @@ Implements NotificationKit.Receiver,ObservationKit.Observer Return New SDTDDocumentEditorView(Controller) Case ArkSA.Identifier Return New ArkSADocumentEditorView(Controller) + Case Palworld.Identifier + Return New PalworldDocumentEditorView(Controller) End Select End Function #tag EndMethod @@ -716,16 +718,16 @@ Implements NotificationKit.Receiver,ObservationKit.Observer #tag Constant, Name = LocalMinWidth, Type = Double, Dynamic = False, Default = \"500", Scope = Protected #tag EndConstant - #tag Constant, Name = OmniWarningPluralExplanation, Type = String, Dynamic = False, Default = \"The \?1 editors require Beacon Omni for \?2\x2C which you have not purchased. Beacon will not generate their content for your config files. Do you still want to continue\?", Scope = Protected + #tag Constant, Name = OmniWarningPluralExplanation, Type = String, Dynamic = True, Default = \"The \?1 editors require a \'Beacon Omni for \?2\' license\x2C which you have not purchased. Beacon will not generate their content for your config files. Do you still want to continue\?", Scope = Protected #tag EndConstant - #tag Constant, Name = OmniWarningPluralMessage, Type = String, Dynamic = False, Default = \"You are using editors that will not be included in your config files.", Scope = Protected + #tag Constant, Name = OmniWarningPluralMessage, Type = String, Dynamic = True, Default = \"You are using editors that will not be included in your config files.", Scope = Protected #tag EndConstant - #tag Constant, Name = OmniWarningSingularExplanation, Type = String, Dynamic = False, Default = \"The \?1 editor requires Beacon Omni for \?2\x2C which you have not purchased. Beacon will not generate its content for your config files. Do you still want to continue\?", Scope = Protected + #tag Constant, Name = OmniWarningSingularExplanation, Type = String, Dynamic = True, Default = \"The \?1 editor requires a \'Beacon Omni for \?2\' license\x2C which you have not purchased. Beacon will not generate its content for your config files. Do you still want to continue\?", Scope = Protected #tag EndConstant - #tag Constant, Name = OmniWarningSingularMessage, Type = String, Dynamic = False, Default = \"You are using an editor that will not be included in your config files.", Scope = Protected + #tag Constant, Name = OmniWarningSingularMessage, Type = String, Dynamic = True, Default = \"You are using an editor that will not be included in your config files.", Scope = Protected #tag EndConstant diff --git a/Project/Views/Palworld/Config Editors/Accounts/PalworldAccountsEditor.xojo_window b/Project/Views/Palworld/Config Editors/Accounts/PalworldAccountsEditor.xojo_window new file mode 100644 index 000000000..79cc1a3b8 --- /dev/null +++ b/Project/Views/Palworld/Config Editors/Accounts/PalworldAccountsEditor.xojo_window @@ -0,0 +1,403 @@ +#tag DesktopWindow +Begin PalworldConfigEditor PalworldAccountsEditor + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = False + AllowTabs = True + Backdrop = 0 + BackgroundColor = &cFFFFFF + Composited = False + Enabled = True + HasBackgroundColor= False + Height = 508 + Index = -2147483648 + InitialParent = "" + Left = 0 + LockBottom = True + LockLeft = True + LockRight = True + LockTop = True + TabIndex = 0 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 0 + Transparent = True + Visible = True + Width = 784 + Begin AccountsEditor Editor + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = False + AllowTabs = True + Backdrop = 0 + BackgroundColor = &cFFFFFF + Composited = False + Enabled = True + HasBackgroundColor= False + Height = 508 + Index = -2147483648 + InitialParent = "" + IsFrontmost = False + Left = 0 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + MinimumHeight = 0 + MinimumWidth = 0 + Modified = False + Progress = 0.0 + Scope = 2 + TabIndex = 0 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 0 + Transparent = True + ViewIcon = 0 + ViewTitle = "Untitled" + Visible = True + Width = 784 + End +End +#tag EndDesktopWindow + +#tag WindowCode + #tag Event + Sub Hidden() + If (Self.Editor Is Nil) = False Then + Self.Editor.SwitchedFrom() + End If + RaiseEvent Hidden() + End Sub + #tag EndEvent + + #tag Event + Sub SetupUI() + If (Self.Editor Is Nil) = False Then + Self.Editor.UpdateUI + End If + End Sub + #tag EndEvent + + #tag Event + Sub Shown(UserData As Variant, ByRef FireSetupUI As Boolean) + RaiseEvent Shown(UserData, FireSetupUI) + If (Self.Editor Is Nil) = False Then + Self.Editor.SwitchedTo(UserData) + End If + End Sub + #tag EndEvent + + + #tag Method, Flags = &h0 + Function InternalName() As String + Return Palworld.Configs.NameAccounts + End Function + #tag EndMethod + + + #tag Hook, Flags = &h0 + Event Hidden() + #tag EndHook + + #tag Hook, Flags = &h0 + Event Shown(UserData As Variant, ByRef FireSetupUI As Boolean) + #tag EndHook + + +#tag EndWindowCode + +#tag Events Editor + #tag Event + Function GetConfigLabel() As String + Return Self.ConfigLabel + End Function + #tag EndEvent + #tag Event + Function GetProject() As Beacon.Project + Return Self.Project + End Function + #tag EndEvent + #tag Event + Sub ContentsChanged() + Self.Modified = Self.Editor.Modified + End Sub + #tag EndEvent +#tag EndEvents +#tag ViewBehavior + #tag ViewProperty + Name="Modified" + Visible=false + Group="Behavior" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="MinimumWidth" + Visible=false + Group="Behavior" + InitialValue="" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="MinimumHeight" + Visible=false + Group="Behavior" + InitialValue="" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Progress" + Visible=false + Group="Behavior" + InitialValue="" + Type="Double" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="ViewTitle" + Visible=true + Group="Behavior" + InitialValue="Untitled" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="ViewIcon" + Visible=false + Group="Behavior" + InitialValue="" + Type="Picture" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="IsFrontmost" + Visible=false + Group="Behavior" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Width" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Height" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="InitialParent" + Visible=false + Group="Position" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockLeft" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockTop" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockRight" + Visible=true + Group="Position" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockBottom" + Visible=true + Group="Position" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabIndex" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabPanelIndex" + Visible=false + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabStop" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowAutoDeactivate" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Enabled" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Tooltip" + Visible=true + Group="Appearance" + InitialValue="" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocusRing" + Visible=true + Group="Appearance" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Visible" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="BackgroundColor" + Visible=true + Group="Background" + InitialValue="&hFFFFFF" + Type="ColorGroup" + EditorType="ColorGroup" + #tag EndViewProperty + #tag ViewProperty + Name="Backdrop" + Visible=true + Group="Background" + InitialValue="" + Type="Picture" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="HasBackgroundColor" + Visible=true + Group="Background" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocus" + Visible=true + Group="Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowTabs" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Transparent" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Composited" + Visible=true + Group="Window Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty +#tag EndViewBehavior diff --git a/Project/Views/Palworld/Config Editors/Custom Config/PalworldCustomConfigEditor.xojo_window b/Project/Views/Palworld/Config Editors/Custom Config/PalworldCustomConfigEditor.xojo_window new file mode 100644 index 000000000..e5438f8e3 --- /dev/null +++ b/Project/Views/Palworld/Config Editors/Custom Config/PalworldCustomConfigEditor.xojo_window @@ -0,0 +1,938 @@ +#tag DesktopWindow +Begin PalworldConfigEditor PalworldCustomConfigEditor Implements NotificationKit.Receiver + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = False + AllowTabs = True + Backdrop = 0 + BackgroundColor = &cFFFFFF00 + Composited = False + Enabled = True + HasBackgroundColor= False + Height = 382 + Index = -2147483648 + InitialParent = "" + Left = 0 + LockBottom = True + LockLeft = True + LockRight = True + LockTop = True + TabIndex = 0 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 0 + Transparent = True + Visible = True + Width = 608 + Begin CodeEditor ConfigArea + AutoDeactivate = True + Enabled = True + HasBorder = False + Height = 341 + HorizontalScrollPosition= 0 + Index = -2147483648 + InitialParent = "" + Left = 0 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + Scope = 2 + SelectionLength = 0 + ShowInfoBar = False + TabIndex = 1 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 41 + VerticalScrollPosition= 0 + Visible = True + Width = 608 + End + Begin OmniBar ConfigToolbar + Alignment = 0 + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = True + AllowTabs = False + Backdrop = 0 + BackgroundColor = "" + ContentHeight = 0 + Enabled = True + Height = 41 + Index = -2147483648 + InitialParent = "" + Left = 0 + LeftPadding = -1 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + RightPadding = -1 + Scope = 2 + ScrollActive = False + ScrollingEnabled= False + ScrollSpeed = 20 + TabIndex = 4 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 0 + Transparent = True + Visible = True + Width = 608 + End +End +#tag EndDesktopWindow + +#tag WindowCode + #tag Event + Sub Closing() + NotificationKit.Ignore(Self, App.Notification_AppearanceChanged) + End Sub + #tag EndEvent + + #tag Event + Sub Opening() + NotificationKit.Watch(Self, App.Notification_AppearanceChanged) + End Sub + #tag EndEvent + + #tag Event + Function ParsingFinished(Project As Palworld.Project) As Boolean + Var Identity As Beacon.Identity = App.IdentityManager.CurrentIdentity + Var SelfProject As Palworld.Project = Self.Project + Var CreatedEditorNames() As String + Var Config As Palworld.Configs.CustomContent = Self.Config(False) + Var Organizer As New Palworld.ConfigOrganizer(Palworld.ConfigFileSettings, Palworld.HeaderPalworldSettings, Config.SettingsIniContent) + + For Each CreatedConfig As Palworld.ConfigGroup In Project.ImplementedConfigs(Project.ActiveConfigSet) + Var InternalName As String = CreatedConfig.InternalName + If InternalName = Palworld.Configs.NameCustomConfig Then + Continue + End If + + If Palworld.Configs.ConfigUnlocked(InternalName, Identity) = False Then + // Do not import code for groups that the user has not purchased + Continue + End If + + If CreatedConfig.IsImplicit Then + // This was just there to support other editors, don't import it. + Continue + End If + + If SelfProject.HasConfigGroup(InternalName) Then + Var CurrentConfig As Palworld.ConfigGroup = SelfProject.ConfigGroup(InternalName, False) + If (CurrentConfig Is Nil) = False Then + Var Configs(1) As Palworld.ConfigGroup + Configs(0) = CurrentConfig + Configs(1) = CreatedConfig + + Var Merged As Palworld.ConfigGroup = Palworld.Configs.Merge(Configs, True) + SelfProject.AddConfigGroup(Merged) + Else + SelfProject.AddConfigGroup(CreatedConfig) + End If + Else + SelfProject.AddConfigGroup(CreatedConfig) + End If + + Organizer.Remove(CreatedConfig.ManagedKeys) + + CreatedEditorNames.Add(Language.LabelForConfig(InternalName)) + Next + + If CreatedEditorNames.Count > 0 Then + Call Self.Config(True) + Config.SettingsIniContent() = Organizer + Self.Modified = True + End If + + Self.SetupUI() + + If CreatedEditorNames.Count = 0 Then + Self.ShowAlert("No supported editor content found", "Beacon was unable to find any lines in PalWorldSettings.ini that it has a guided editor for.") + ElseIf CreatedEditorNames.Count = 1 Then + Self.ShowAlert("Finished converting Custom Config Content", "Beacon found and setup the a guided editor for " + CreatedEditorNames(0) + ".") + Else + Self.ShowAlert("Finished converting Custom Config Content", "Beacon found and setup the following guided editors: " + Language.EnglishOxfordList(CreatedEditorNames) + ".") + End If + + Return True + End Function + #tag EndEvent + + #tag Event + Sub RunTool(Tool As Palworld.ProjectTool) + Select Case Tool.ToolId + Case "4add8d2b-fca9-4f37-9eb3-aae75b2c21fc" + Self.LookForSupportedContent() + End Select + End Sub + #tag EndEvent + + #tag Event + Sub SetupUI() + Select Case Self.ActiveButtonName + Case "SettingsIniButton" + Self.ConfigArea.Text = Self.Config(False).SettingsIniContent + Self.mSettingsIniState.ApplyTo(Self.ConfigArea) + End Select + End Sub + #tag EndEvent + + + #tag Method, Flags = &h1 + Protected Function Config(ForWriting As Boolean) As Palworld.Configs.CustomContent + Return Palworld.Configs.CustomContent(Super.Config(ForWriting)) + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Constructor(Project As Palworld.Project) + Self.mSettingsIniState = New TextAreaState + + Super.Constructor(Project) + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Function CurrentFile() As String + Select Case Self.ActiveButtonName + Case "SettingsIniButton" + Return Palworld.ConfigFileSettings + End Select + End Function + #tag EndMethod + + #tag Method, Flags = &h21 + Private Function CurrentHeader() As String + Var CurrentLineNum As Integer = Self.ConfigArea.LineFromPosition(Self.ConfigArea.Position) + If Self.mCachedHeaderLine = CurrentLineNum Then + Return Self.mCachedHeader + End If + + Var Header As String + Select Case Self.ActiveButtonName + Case "SettingsIniButton" + Header = Palworld.HeaderPalworldSettings + End Select + + For LineNum As Integer = CurrentLineNum DownTo 0 + Var Line As String = Self.ConfigArea.Line(LineNum).Trim + If Line.BeginsWith("[") And Line.EndsWith("]") Then + Header = Line.Middle(1, Line.Length - 2) + Exit For LineNum + End If + Next LineNum + + Self.mCachedHeader = Header + Self.mCachedHeaderLine = CurrentLineNum + + Return Header + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function InternalName() As String + Return Palworld.Configs.NameCustomConfig + End Function + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub LookForSupportedContent(Confirm As Boolean = True) + If Confirm Then + If Not Self.ShowConfirm("Do you want Beacon to search your Custom Config Content for lines that are supported by guided editors?", "Beacon will import your Custom Config Content and automatically setup guided editors for the lines it can support. Config lines will be merged according to Beacon's standard config merging guidelines.", "Continue", "Cancel") Then + Return + End If + End If + + Var Config As Palworld.Configs.CustomContent = Self.Config(False) + Self.Parse(Config.SettingsIniContent, "Custom Config Content") + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Sub NotificationKit_NotificationReceived(Notification As NotificationKit.Notification) + // Part of the NotificationKit.Receiver interface. + + Select Case Notification.Name + Case App.Notification_AppearanceChanged + Self.UpdateTextColors() + End Select + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Function SelectionIsCommented() As Boolean + Var FirstLineNum As Integer = Self.ConfigArea.LineFromPosition(Self.ConfigArea.SelectionStart) + Var FirstLine As String = Self.ConfigArea.Line(FirstLineNum) + Return FirstLine.BeginsWith("//") + End Function + #tag EndMethod + + #tag Method, Flags = &h21 + Private Function SelectionIsEncrypted() As Boolean + If Self.ConfigArea.SelectionStart >= Self.ConfigArea.Text.Length Then + Return False + End If + + Var StartPos As Integer = Self.ConfigArea.SelectionStart + Var EndPos As Integer = StartPos + Max(Self.ConfigArea.SelectionLength, 1) + For Each Range As Beacon.Range In Self.mEncryptedRanges + If StartPos >= Range.Min And EndPos <= Range.Max Then + Return True + End If + Next + End Function + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub ShowAutocomplete() + Var CurrentLine As String = Self.ConfigArea.CurrentLine.Trim + Var Position As Integer = Self.ConfigArea.PositionInLine + If Position = 0 Or Position <> CurrentLine.Length Or CurrentLine.IndexOf("=") > -1 Then + // Only show autocomplete if we are at the end of the line, and the line + // does not contain an equal sign + Self.ConfigArea.AutoCompleteCancel + Return + End If + + // Next, the word list should be based on the entire typed line + Var Matches() As String + Var MaxWidth As Integer + For Each Possible As String In Self.mAutocompleteWords + If Possible.BeginsWith(CurrentLine) Then + Matches.Add(Possible) + MaxWidth = Max(MaxWidth, Possible.Length) + End If + Next Possible + + If Matches.Count = 0 Then + Self.ConfigArea.AutoCompleteCancel + Return + End If + + Self.ConfigArea.AutoCompleteIgnoreCase = True + Self.ConfigArea.AutoCompleteMaxHeight = 9 + Self.ConfigArea.AutoCompleteMaxWidth = 0 + Self.ConfigArea.AutoCompleteSeparator = 32 + Self.ConfigArea.AutoCompleteSetFillUps("=") + Self.ConfigArea.AutoCompleteShow(CurrentLine.Length, String.FromArray(Matches, " ")) + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub ToggleComment() + Var FirstLineNum As Integer = Self.ConfigArea.LineFromPosition(Self.ConfigArea.SelectionStart) + Var LastLineNum As Integer = Self.ConfigArea.LineFromPosition(Self.ConfigArea.SelectionEnd) + Var AddComment As Boolean = Not Self.SelectionIsCommented + + Var NewLines() As String + For LineNum As Integer = FirstLineNum To LastLineNum + Var Line As String = Self.ConfigArea.Line(LineNum) + If Line.BeginsWith("//") = True And AddComment = False Then + Line = Line.Middle(2).Trim + ElseIf Line.BeginsWith("//") = False And AddComment = True Then + Line = "// " + Line.Trim + Else + Line = Line.Trim + End If + NewLines.Add(Line) + Next LineNum + + Var EOL As String = Self.ConfigArea.Text.DetectLineEnding + Self.ConfigArea.SelectionStart = Self.ConfigArea.LineStart(FirstLineNum) + Self.ConfigArea.SelectionEnd = Self.ConfigArea.LineEndPosition(LastLineNum) + Self.ConfigArea.ReplaceSelection(String.FromArray(NewLines, EOL)) + + // Select again when done + Self.ConfigArea.SelectionStart = Self.ConfigArea.LineStart(FirstLineNum) + Self.ConfigArea.SelectionEnd = Self.ConfigArea.LineEndPosition(LastLineNum) + + Self.UpdateCommentButton() + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub ToggleEncryption() + Var Tag As String = Palworld.Configs.CustomContent.EncryptedTag + Var TagLen As Integer = Tag.Length + Var Source As String = Self.ConfigArea.Text + + If Self.SelectionIsEncrypted Then + Var StartPos As Integer = Self.ConfigArea.SelectionStart + For I As Integer = StartPos DownTo TagLen + If Source.Middle((I - TagLen), TagLen) = Tag Then + StartPos = I + Exit For I + End If + Next + Var EndPos As Integer = Source.IndexOf(StartPos, Tag) + If EndPos = -1 Then + EndPos = Source.Length + Source = Source + Tag + End If + + Var ContentLen As Integer = EndPos - StartPos + Var Prefix As String = Source.Left(StartPos - TagLen) + Var Content As String = Source.Middle(StartPos, ContentLen) + Var Suffix As String = Source.Middle(EndPos + TagLen) + + Self.ConfigArea.Text = Prefix + Content + Suffix + Self.ConfigArea.SelectionStart = Prefix.Length + Self.ConfigArea.SelectionLength = Content.Length + Else + Var Start As Integer = Self.ConfigArea.SelectionStart + Var Length As Integer = Self.ConfigArea.SelectionLength + Var Prefix As String = Source.Left(Start) + Var Content As String = Source.Middle(Start, Length) + Var Suffix As String = Source.Right(Source.Length - (Start + Length)) + + Self.ConfigArea.Text = Prefix + Tag + Content + Tag + Suffix + Self.ConfigArea.SelectionStart = Prefix.Length + TagLen + Self.ConfigArea.SelectionLength = Content.Length + End If + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub UpdateAutoComplete() + Var CurrentFile As String = Self.CurrentFile + Var CurrentHeader As String = Self.CurrentHeader + Var CurrentLocation As String = CurrentFile + ":" + CurrentHeader + If CurrentLocation = Self.mLastAutocompleteHeader Then + Return + End If + + Var Configs() As Palworld.ConfigOption = Palworld.DataSource.Pool.Get(False).GetConfigOptions(CurrentFile, CurrentHeader, "", "", False) + Self.mAutocompleteWords.ResizeTo(Configs.LastIndex) + For Idx As Integer = Configs.FirstIndex To Configs.LastIndex + Self.mAutocompleteWords(Idx) = Configs(Idx).Key + Next Idx + + Self.mLastAutocompleteHeader = CurrentLocation + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub UpdateCommentButton() + Var CommentButton As OmniBarItem = Self.ConfigToolbar.Item("CommentButton") + If CommentButton Is Nil Then + Return + End If + + If Self.SelectionIsCommented Then + CommentButton.HelpTag = "Uncomment the current line" + CommentButton.Caption = "Uncomment" + CommentButton.Icon = IconToolbarUncomment + Else + CommentButton.HelpTag = "Comment the current line" + CommentButton.Caption = "Comment" + CommentButton.Icon = IconToolbarComment + End If + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub UpdateEncryptButton() + Var EncryptButton As OmniBarItem = Self.ConfigToolbar.Item("EncryptButton") + If EncryptButton Is Nil Then + Return + End If + + If Self.SelectionIsEncrypted Then + EncryptButton.HelpTag = "Convert the encrypted value to plain text" + EncryptButton.Caption = "Decrypt" + EncryptButton.Enabled = True + EncryptButton.Icon = IconToolbarUnlock + Else + EncryptButton.HelpTag = "Encrypt the selected text when saving" + EncryptButton.Caption = "Encrypt" + EncryptButton.Enabled = Self.ConfigArea.SelectionLength > 0 + EncryptButton.Icon = IconToolbarLock + End If + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub UpdateTextColors() + Self.mEncryptedRanges.ResizeTo(-1) + + Var Pos As Integer + Var Source As String = Self.ConfigArea.Text + Var Tag As String = Palworld.Configs.CustomContent.EncryptedTag + Var TagLen As Integer = Tag.Length + + Do + If Pos > Source.Length Then + Exit + End If + Pos = Source.IndexOf(Pos, Tag) + If Pos = -1 Then + Exit + End If + + Var StartPos As Integer = Pos + TagLen + Var EndPos As Integer = Source.IndexOf(StartPos, Tag) + If EndPos = -1 Then + EndPos = Source.Length + End If + + Self.mEncryptedRanges.Add(New Beacon.Range(StartPos, EndPos)) + + Pos = EndPos + TagLen + Loop + + Self.UpdateEncryptButton() + End Sub + #tag EndMethod + + + #tag ComputedProperty, Flags = &h21 + #tag Getter + Get + Return "SettingsIniButton" + End Get + #tag EndGetter + #tag Setter + Set + Var SettingsIniButton As OmniBarItem = Self.ConfigToolbar.Item("SettingsIniButton") + If (SettingsIniButton Is Nil) = False Then + SettingsIniButton.Toggled = True + End If + End Set + #tag EndSetter + Private ActiveButtonName As String + #tag EndComputedProperty + + #tag Property, Flags = &h21 + Private mAutocompleteWords() As String + #tag EndProperty + + #tag Property, Flags = &h21 + Private mCachedHeader As String + #tag EndProperty + + #tag Property, Flags = &h21 + Private mCachedHeaderLine As Integer = -1 + #tag EndProperty + + #tag Property, Flags = &h21 + Private mEncryptedRanges() As Beacon.Range + #tag EndProperty + + #tag Property, Flags = &h21 + Private mLastAutocompleteHeader As String + #tag EndProperty + + #tag Property, Flags = &h21 + Private mSettingsIniState As TextAreaState + #tag EndProperty + + +#tag EndWindowCode + +#tag Events ConfigArea + #tag Event + Sub TextChanged() + Self.UpdateTextColors() + Self.UpdateAutoComplete() + + If Self.SettingUp Then + Return + End If + + Var SanitizedText As String = Beacon.SanitizeText(Me.Text) + If SanitizedText <> Me.Text Then + Var SelectionStart As Integer = Me.SelectionStart + Var SelectionLength As Integer = Me.SelectionLength + Me.Text = SanitizedText + Me.SelectionStart = SelectionStart + Me.SelectionLength = SelectionLength + End If + + Select Case Self.ActiveButtonName + Case "SettingsIniButton" + Self.Config(True).SettingsIniContent = SanitizedText + Self.Modified = True + End Select + End Sub + #tag EndEvent + #tag Event + Sub SelectionChanged() + Self.UpdateEncryptButton() + Self.UpdateCommentButton() + Self.UpdateAutoComplete() + End Sub + #tag EndEvent + #tag Event + Sub CharacterAdded(Character as Integer, CharacterSource as Integer) + #Pragma Unused Character + #Pragma Unused CharacterSource + + If Me.SelectionEnd = Me.SelectionStart And Me.SelectionStart > 0 And Me.AutoCompleteActive = False Then + Self.ShowAutocomplete() + End If + End Sub + #tag EndEvent + #tag Event + Sub SetupNeeded() + Palworld.SetupCodeEditor(Me) + End Sub + #tag EndEvent + #tag Event + Sub DWellEnd(Position as Integer, X as Integer, Y as Integer) + #Pragma Unused Position + #Pragma Unused X + #Pragma Unused Y + + Me.CallTipCancel + End Sub + #tag EndEvent + #tag Event + Sub DWellStart(Position as Integer, X as Integer, Y as Integer) + #Pragma Unused Position + #Pragma Unused X + #Pragma Unused Y + + If Position = -1 Then + Me.CallTipCancel + Return + End If + Var LineNum As Integer = Me.LineFromPosition(Position) + Var PositionInLine As Integer = Position - Me.LineStart(LineNum) + Var Line As String = Me.Line(LineNum).Trim + + Var EqualsPosition As Integer = Line.IndexOf("=") + If EqualsPosition = -1 Then + EqualsPosition = Line.Length + End If + + If PositionInLine > EqualsPosition Then + Me.CallTipCancel + Return + End If + + Var Key As Palworld.ConfigOption = Palworld.DataSource.Pool.Get(False).GetConfigOption(Self.CurrentFile, Self.CurrentHeader, "", Line.Left(EqualsPosition)) + If Key Is Nil Or Key.Description.IsEmpty Then + Me.CallTipCancel + Return + End If + + Me.CallTipShow(Me.LineStart(LineNum), Key.Description) + End Sub + #tag EndEvent + #tag Event + Sub Opening() + Me.MouseDwellTime = 1500 + End Sub + #tag EndEvent +#tag EndEvents +#tag Events ConfigToolbar + #tag Event + Sub Opening() + Me.Append(OmniBarItem.CreateTitle("ConfigTitle", Self.ConfigLabel)) + Me.Append(OmniBarItem.CreateSeparator) + Me.Append(OmniBarItem.CreateTab("SettingsIniButton", "PalWorldSettings.ini")) + Me.Append(OmniBarItem.CreateSeparator) + Me.Append(OmniBarItem.CreateButton("EncryptButton", "Encrypt", IconToolbarLock, "Encrypt the selected text when saving", False)) + Me.Append(OmniBarItem.CreateButton("CommentButton", "Comment", IconToolbarComment, "Comment the current line")) + + Me.Item("SettingsIniButton").Toggled = True + End Sub + #tag EndEvent + #tag Event + Sub ItemPressed(Item As OmniBarItem, ItemRect As Rect) + #Pragma Unused ItemRect + + Select Case Item.Name + Case "EncryptButton" + Self.ToggleEncryption() + Case "CommentButton" + Self.ToggleComment() + Case "SettingsIniButton" + // Don't do anything + Return + End Select + End Sub + #tag EndEvent +#tag EndEvents +#tag ViewBehavior + #tag ViewProperty + Name="Modified" + Visible=false + Group="Behavior" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Composited" + Visible=true + Group="Window Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="IsFrontmost" + Visible=false + Group="Behavior" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="ViewTitle" + Visible=true + Group="Behavior" + InitialValue="Untitled" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="ViewIcon" + Visible=false + Group="Behavior" + InitialValue="" + Type="Picture" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Progress" + Visible=false + Group="Behavior" + InitialValue="" + Type="Double" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Tooltip" + Visible=true + Group="Appearance" + InitialValue="" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="AllowAutoDeactivate" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocusRing" + Visible=true + Group="Appearance" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="BackgroundColor" + Visible=true + Group="Background" + InitialValue="&hFFFFFF" + Type="ColorGroup" + EditorType="ColorGroup" + #tag EndViewProperty + #tag ViewProperty + Name="HasBackgroundColor" + Visible=true + Group="Background" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocus" + Visible=true + Group="Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowTabs" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="MinimumWidth" + Visible=true + Group="Behavior" + InitialValue="400" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="MinimumHeight" + Visible=true + Group="Behavior" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Width" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Height" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="InitialParent" + Visible=false + Group="Position" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockLeft" + Visible=true + Group="Position" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockTop" + Visible=true + Group="Position" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockRight" + Visible=true + Group="Position" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockBottom" + Visible=true + Group="Position" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabPanelIndex" + Visible=false + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabIndex" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabStop" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Visible" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Enabled" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Backdrop" + Visible=true + Group="Background" + InitialValue="" + Type="Picture" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Transparent" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty +#tag EndViewBehavior diff --git a/Project/Views/Palworld/Config Editors/General Settings/PalworldGeneralSettingsEditor.xojo_window b/Project/Views/Palworld/Config Editors/General Settings/PalworldGeneralSettingsEditor.xojo_window new file mode 100644 index 000000000..62546257e --- /dev/null +++ b/Project/Views/Palworld/Config Editors/General Settings/PalworldGeneralSettingsEditor.xojo_window @@ -0,0 +1,520 @@ +#tag DesktopWindow +Begin PalworldConfigEditor PalworldGeneralSettingsEditor + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = False + AllowTabs = True + Backdrop = 0 + BackgroundColor = &cFFFFFF00 + Composited = False + Enabled = True + HasBackgroundColor= False + Height = 432 + Index = -2147483648 + InitialParent = "" + Left = 0 + LockBottom = True + LockLeft = True + LockRight = True + LockTop = True + TabIndex = 0 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 0 + Transparent = True + Visible = True + Width = 792 + Begin OmniBar ConfigToolbar + Alignment = 0 + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = True + AllowTabs = False + Backdrop = 0 + BackgroundColor = "" + ContentHeight = 0 + Enabled = True + Height = 41 + Index = -2147483648 + InitialParent = "" + Left = 0 + LeftPadding = -1 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + RightPadding = -1 + Scope = 2 + ScrollActive = False + ScrollingEnabled= False + ScrollSpeed = 20 + TabIndex = 0 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 0 + Transparent = True + Visible = True + Width = 484 + End + Begin PalworldSettingsListContainer List + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = False + AllowTabs = True + Backdrop = 0 + BackgroundColor = &cFFFFFF00 + Composited = False + Enabled = True + HasBackgroundColor= False + Height = 391 + Index = -2147483648 + InitialParent = "" + Left = 0 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + Scope = 2 + ShowOfficialNames= False + TabIndex = 1 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 41 + Transparent = True + Visible = True + Width = 792 + End + Begin OmniBarSeparator OmniBarSeparator1 + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = True + AllowTabs = False + Backdrop = 0 + ContentHeight = 0 + Enabled = True + Height = 1 + Index = -2147483648 + InitialParent = "" + Left = 484 + LockBottom = False + LockedInPosition= False + LockLeft = False + LockRight = True + LockTop = True + Scope = 2 + ScrollActive = False + ScrollingEnabled= False + ScrollSpeed = 20 + TabIndex = 2 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 40 + Transparent = True + Visible = True + Width = 308 + End + Begin DelayedSearchField SearchField1 + Active = False + AllowAutoDeactivate= True + AllowFocusRing = True + AllowRecentItems= False + AllowTabStop = True + ClearMenuItemValue= "Clear" + DelayPeriod = 250 + Enabled = True + Height = 22 + Hint = "Filter Settings" + Index = -2147483648 + InitialParent = "" + Left = 493 + LockBottom = False + LockedInPosition= False + LockLeft = False + LockRight = True + LockTop = True + MaximumRecentItems= -1 + PanelIndex = 0 + RecentItemsValue= "Recent Searches" + Scope = 2 + TabIndex = 3 + TabPanelIndex = 0 + Text = "" + Tooltip = "" + Top = 9 + Transparent = False + Visible = True + Width = 290 + _mIndex = 0 + _mInitialParent = "" + _mName = "" + _mPanelIndex = 0 + End +End +#tag EndDesktopWindow + +#tag WindowCode + #tag Event + Sub SetupUI() + Self.List.ForceReload + End Sub + #tag EndEvent + + + #tag Method, Flags = &h1 + Protected Function Config(ForWriting As Boolean) As Palworld.Configs.OtherSettings + Return Palworld.Configs.OtherSettings(Super.Config(ForWriting)) + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function InternalName() As String + Return Palworld.Configs.NameGeneralSettings + End Function + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub SettingChanged(Key As Palworld.ConfigOption, NewValue As Variant) + Var Config As Palworld.Configs.OtherSettings = Self.Config(True) + Config.Value(Key) = NewValue + Self.Modified = Config.Modified + End Sub + #tag EndMethod + + #tag DelegateDeclaration, Flags = &h0 + Delegate Sub SettingChangeDelegate(Key As Palworld.ConfigOption, Value As Variant) + #tag EndDelegateDeclaration + + + #tag Property, Flags = &h21 + Private mConfigRef As WeakRef + #tag EndProperty + + +#tag EndWindowCode + +#tag Events ConfigToolbar + #tag Event + Sub Opening() + Me.Append(OmniBarItem.CreateTitle("ConfigTitle", Self.ConfigLabel)) + Me.Append(OmniBarItem.CreateSeparator) + Me.Append(OmniBarItem.CreateButton("OfficialNamesButton", "Official Names", IconToolbarView, "Show official names instead of human names")) + End Sub + #tag EndEvent + #tag Event + Sub ItemPressed(Item As OmniBarItem, ItemRect As Rect) + #Pragma Unused ItemRect + + Select Case Item.Name + Case "OfficialNamesButton" + Var ShowOfficialNames As Boolean = Not Item.Toggled + Self.List.ShowOfficialNames = ShowOfficialNames + Item.Toggled = Not Item.Toggled + End Select + End Sub + #tag EndEvent +#tag EndEvents +#tag Events List + #tag Event + Function GetProject() As Palworld.Project + Return Self.Project + End Function + #tag EndEvent + #tag Event + Function GetConfig(ForWriting As Boolean) As Palworld.Configs.OtherSettings + Return Self.Config(ForWriting) + End Function + #tag EndEvent + #tag Event + Sub Opening() + Me.SettingChangeDelegate = WeakAddressOf SettingChanged + Me.Filter = "" + End Sub + #tag EndEvent +#tag EndEvents +#tag Events SearchField1 + #tag Event + Sub TextChanged() + Self.List.Filter = Me.Text.MakeUTF8 + End Sub + #tag EndEvent +#tag EndEvents +#tag ViewBehavior + #tag ViewProperty + Name="Modified" + Visible=false + Group="Behavior" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Composited" + Visible=true + Group="Window Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="IsFrontmost" + Visible=false + Group="Behavior" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="ViewTitle" + Visible=true + Group="Behavior" + InitialValue="Untitled" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="ViewIcon" + Visible=false + Group="Behavior" + InitialValue="" + Type="Picture" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Progress" + Visible=false + Group="Behavior" + InitialValue="" + Type="Double" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="MinimumWidth" + Visible=true + Group="Behavior" + InitialValue="400" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="MinimumHeight" + Visible=true + Group="Behavior" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Width" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Height" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="InitialParent" + Visible=false + Group="Position" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockLeft" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockTop" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockRight" + Visible=true + Group="Position" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockBottom" + Visible=true + Group="Position" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabIndex" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabPanelIndex" + Visible=false + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabStop" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowAutoDeactivate" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Enabled" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Tooltip" + Visible=true + Group="Appearance" + InitialValue="" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocusRing" + Visible=true + Group="Appearance" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Visible" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="BackgroundColor" + Visible=true + Group="Background" + InitialValue="&hFFFFFF" + Type="ColorGroup" + EditorType="ColorGroup" + #tag EndViewProperty + #tag ViewProperty + Name="Backdrop" + Visible=true + Group="Background" + InitialValue="" + Type="Picture" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="HasBackgroundColor" + Visible=true + Group="Background" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocus" + Visible=true + Group="Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowTabs" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Transparent" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty +#tag EndViewBehavior diff --git a/Project/Views/Palworld/Config Editors/General Settings/PalworldSettingsListBooleanElement.xojo_window b/Project/Views/Palworld/Config Editors/General Settings/PalworldSettingsListBooleanElement.xojo_window new file mode 100644 index 000000000..4141ba6d8 --- /dev/null +++ b/Project/Views/Palworld/Config Editors/General Settings/PalworldSettingsListBooleanElement.xojo_window @@ -0,0 +1,482 @@ +#tag DesktopWindow +Begin PalworldSettingsListElement PalworldSettingsListBooleanElement + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = False + AllowTabs = True + Backdrop = 0 + BackgroundColor = &cFFFFFF00 + Composited = False + Enabled = True + HasBackgroundColor= False + Height = 62 + Index = -2147483648 + InitialParent = "" + Left = 0 + LockBottom = False + LockLeft = True + LockRight = True + LockTop = True + TabIndex = 0 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 0 + Transparent = True + Visible = True + Width = 300 + Begin SwitchControl Switch + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = True + AllowTabs = False + Backdrop = 0 + ContentHeight = 0 + Enabled = True + Height = 20 + Index = -2147483648 + InitialParent = "" + Left = 212 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Scope = 2 + ScrollActive = False + ScrollingEnabled= False + ScrollSpeed = 20 + TabIndex = 0 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 7 + Transparent = True + Visible = True + Width = 40 + End + Begin DesktopLabel mNameLabel + AllowAutoDeactivate= True + Bold = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 22 + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 20 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Multiline = False + Scope = 2 + Selectable = True + TabIndex = 1 + TabPanelIndex = 0 + TabStop = True + Text = "Untitled" + TextAlignment = 3 + TextColor = &c00000000 + Tooltip = "" + Top = 6 + Transparent = False + Underline = False + Visible = True + Width = 180 + End + Begin DesktopLabel mDescriptionLabel + AllowAutoDeactivate= True + Bold = False + Enabled = True + FontName = "SmallSystem" + FontSize = 0.0 + FontUnit = 0 + Height = 16 + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 20 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + Multiline = False + Scope = 2 + Selectable = True + TabIndex = 2 + TabPanelIndex = 0 + TabStop = True + Text = "Untitled" + TextAlignment = 0 + TextColor = &c00000000 + Tooltip = "" + Top = 40 + Transparent = False + Underline = False + Visible = True + Width = 260 + End + Begin IconCanvas mDismissButton + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = True + AllowTabs = False + Backdrop = 0 + Clickable = True + ContentHeight = 0 + Enabled = True + Height = 16 + Icon = 1389395967 + IconColor = 8 + Index = -2147483648 + InitialParent = "" + Left = 264 + LockBottom = False + LockedInPosition= False + LockLeft = False + LockRight = True + LockTop = True + Scope = 2 + ScrollActive = False + ScrollingEnabled= False + ScrollSpeed = 20 + TabIndex = 3 + TabPanelIndex = 0 + TabStop = True + Tooltip = "Restore this setting to the default." + Top = 9 + Transparent = True + Visible = False + Width = 16 + End +End +#tag EndDesktopWindow + +#tag WindowCode + #tag Event + Sub LockStateChanged() + Self.Switch.Enabled = Self.Unlocked + End Sub + #tag EndEvent + + + #tag Method, Flags = &h1 + Protected Function DescriptionLabel() As DesktopLabel + Return Self.mDescriptionLabel + End Function + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Function DismissButton() As IconCanvas + Return Self.mDismissButton + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub KeyNameWidth(Assigns KeyNameWidth As Integer) + Super.KeyNameWidth = KeyNameWidth + + Self.mNameLabel.Width = KeyNameWidth + Self.Switch.Left = Self.mNameLabel.Left + Self.mNameLabel.Width + 12 + Self.mDescriptionLabel.Left = Self.Switch.Left + Self.mDescriptionLabel.Width = (Self.Width - 20) - Self.mDescriptionLabel.Left + End Sub + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Function NameLabel() As DesktopLabel + Return Self.mNameLabel + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function Value() As Variant + If Self.IsOverloaded Then + Return Self.Switch.Value + Else + Return Nil + End If + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Value(Animated As Boolean = False, Assigns NewValue As Variant) + Var BlockChanges As Boolean = Self.mBlockChanges + Self.mBlockChanges = True + + Var BooleanValue As Boolean + Try + BooleanValue = NewValue.BooleanValue + Catch Err As RuntimeException + End Try + + Self.Switch.Value(Animated) = BooleanValue + Self.mBlockChanges = BlockChanges + End Sub + #tag EndMethod + + + #tag Property, Flags = &h21 + Private mBlockChanges As Boolean + #tag EndProperty + + +#tag EndWindowCode + +#tag Events Switch + #tag Event + Sub Pressed() + If Self.mBlockChanges Then + Return + End If + + Self.UserValueChange(Me.Value) + End Sub + #tag EndEvent +#tag EndEvents +#tag Events mDismissButton + #tag Event + Sub Pressed() + Self.Delete() + End Sub + #tag EndEvent +#tag EndEvents +#tag ViewBehavior + #tag ViewProperty + Name="Composited" + Visible=true + Group="Window Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="ShowOfficialName" + Visible=false + Group="Behavior" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="IsOverloaded" + Visible=false + Group="Behavior" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Width" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Height" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="InitialParent" + Visible=false + Group="Position" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockLeft" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockTop" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockRight" + Visible=true + Group="Position" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockBottom" + Visible=true + Group="Position" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabIndex" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabPanelIndex" + Visible=false + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabStop" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowAutoDeactivate" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Enabled" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Tooltip" + Visible=true + Group="Appearance" + InitialValue="" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocusRing" + Visible=true + Group="Appearance" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Visible" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="BackgroundColor" + Visible=true + Group="Background" + InitialValue="&hFFFFFF" + Type="ColorGroup" + EditorType="ColorGroup" + #tag EndViewProperty + #tag ViewProperty + Name="Backdrop" + Visible=true + Group="Background" + InitialValue="" + Type="Picture" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="HasBackgroundColor" + Visible=true + Group="Background" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocus" + Visible=true + Group="Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowTabs" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Transparent" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty +#tag EndViewBehavior diff --git a/Project/Views/Palworld/Config Editors/General Settings/PalworldSettingsListContainer.xojo_window b/Project/Views/Palworld/Config Editors/General Settings/PalworldSettingsListContainer.xojo_window new file mode 100644 index 000000000..b5e064c98 --- /dev/null +++ b/Project/Views/Palworld/Config Editors/General Settings/PalworldSettingsListContainer.xojo_window @@ -0,0 +1,851 @@ +#tag DesktopWindow +Begin DesktopContainer PalworldSettingsListContainer + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = False + AllowTabs = True + Backdrop = 0 + BackgroundColor = &cFFFFFF00 + Composited = False + Enabled = True + HasBackgroundColor= False + Height = 376 + Index = -2147483648 + InitialParent = "" + Left = 0 + LockBottom = True + LockLeft = True + LockRight = True + LockTop = True + TabIndex = 0 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 0 + Transparent = True + Visible = True + Width = 594 + Begin DesktopScrollbar Scroller + Active = False + AllowAutoDeactivate= True + AllowFocus = True + AllowLiveScrolling= True + AllowTabStop = True + Enabled = True + Height = 376 + Index = -2147483648 + InitialParent = "" + Left = 579 + LineStep = 88 + LockBottom = True + LockedInPosition= False + LockLeft = False + LockRight = True + LockTop = True + MaximumValue = 100 + MinimumValue = 0 + PageStep = 20 + PanelIndex = 0 + Scope = 2 + TabIndex = 0 + TabPanelIndex = 0 + Tooltip = "" + Top = 0 + Transparent = False + Value = 0 + Visible = True + Width = 15 + _mIndex = 0 + _mInitialParent = "" + _mName = "" + _mPanelIndex = 0 + End + Begin LogoFillCanvas FillCanvas + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = True + AllowTabs = False + Backdrop = 0 + Caption = "No Results" + ContentHeight = 0 + Enabled = True + Height = 376 + Index = -2147483648 + InitialParent = "" + Left = 0 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + Scope = 2 + ScrollActive = False + ScrollingEnabled= False + ScrollSpeed = 20 + TabIndex = 1 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 0 + Transparent = True + Visible = False + Width = 579 + End +End +#tag EndDesktopWindow + +#tag WindowCode + #tag Event + Function MouseWheel(x As Integer, y As Integer, deltaX As Integer, deltaY As Integer) As Boolean + #Pragma Unused X + #Pragma Unused Y + + Var WheelData As New BeaconUI.ScrollEvent(Self.Scroller.LineStep, DeltaX, DeltaY) + Var ScrollPosition As Integer = Min(Max(Self.Scroller.Value + WheelData.ScrollY, 0), Self.Scroller.MaximumValue) + If Self.Scroller.Value <> ScrollPosition Then + Self.Scroller.Value = ScrollPosition + End If + Return True + End Function + #tag EndEvent + + #tag Event + Sub Resized() + Self.SetVisibilities() + End Sub + #tag EndEvent + + #tag Event + Sub Resizing() + Self.SetVisibilities() + End Sub + #tag EndEvent + + + #tag Method, Flags = &h1 + Protected Function Config(ForWriting As Boolean) As Palworld.Configs.OtherSettings + Return RaiseEvent GetConfig(ForWriting) + End Function + #tag EndMethod + + #tag Method, Flags = &h21 + Private Function CreateElement(Key As Palworld.ConfigOption, Left As Integer, Top As Integer, Width As Integer, Height As Integer) As PalworldSettingsListElement + Var Element As PalworldSettingsListElement + + Select Case Key.ValueType + Case Palworld.ConfigOption.ValueTypes.TypeBoolean + Element = New PalworldSettingsListBooleanElement(Key) + Case Palworld.ConfigOption.ValueTypes.TypeNumeric + Element = New PalworldSettingsListNumberElement(Key) + Case Palworld.ConfigOption.ValueTypes.TypeText + Element = New PalworldSettingsListStringElement(Key) + Else + Return Nil + End Select + + // Get open events to fire now + Element.EmbedWithin(Self, Left, Top, Width, Height) + + Element.ShowOfficialName = Self.ShowOfficialNames + + Var Config As Palworld.Configs.OtherSettings = Self.Config(False) + Var Value As Variant = Config.Value(Key) + If IsNull(Value) Then + Element.Value(False) = Key.DefaultValue + Element.IsOverloaded = False + Else + Element.Value(False) = Value + Element.IsOverloaded = True + End If + Element.SettingChangeDelegate = WeakAddressOf SettingChanged + + Return Element + End Function + #tag EndMethod + + #tag Method, Flags = &h21 + Private Function ElementForKey(Key As Palworld.ConfigOption) As PalworldSettingsListElement + For Each Element As PalworldSettingsListElement In Self.mElements + If Element Is Nil Then + Continue + End If + + If Element.Key = Key Then + Return Element + End If + Next Element + Return Nil + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function Filter() As String + Return Self.mFilter + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Filter(Assigns Value As String) + // Case in-sensitive is fine + If Self.Filter = Value Then + Return + End If + + Self.mFilter = Value + + Var AllKeys() As Palworld.ConfigOption = Palworld.DataSource.Pool.Get(False).GetConfigOptions("", "", "", "", True) + If Self.mDependencies Is Nil Then + Self.mDependencies = New Dictionary + For Each Key As Palworld.ConfigOption In AllKeys + Var Other As Variant = Key.Constraint("other") + If IsNull(Other) Or (Other IsA Dictionary) = False Then + Continue + End If + + Var Dict As Dictionary = Other + Var ReliesOnKey As String = Dict.Value("key") + Var RequiredValue As Boolean = Dict.Value("value") + Var Dependents() As Dictionary + If Self.mDependencies.HasKey(ReliesOnKey) Then + Dependents = Self.mDependencies.Value(ReliesOnKey) + End If + Dependents.Add(New Dictionary("Target": Key, "RequiredValue": RequiredValue)) + Self.mDependencies.Value(ReliesOnKey) = Dependents + Next Key + End If + Var GroupNames() As String + Var Groups As New Dictionary + Var Filtered As Boolean = Value.IsEmpty = False + Var DesiredBound As Integer = -1 + Var ContentPacks As Beacon.StringList = Self.Project.ContentPacks + Var MeasurePic As New Picture(20, 20) + MeasurePic.Graphics.FontName = "System" + MeasurePic.Graphics.FontSize = 0 + MeasurePic.Graphics.Bold = True + Var KeyNameWidth As Integer + Var ConsoleSafe As Boolean = Self.Project.ConsoleSafe + + For Each Key As Palworld.ConfigOption In AllKeys + If Palworld.Configs.OtherSettings.KeySupported(Key, ContentPacks) = False Then + Continue + End If + + Var RequiredPlatform As Variant = Key.Constraint("platform") + If IsNull(RequiredPlatform) = False Then + Select Case RequiredPlatform.StringValue + Case "pc", "steam", "epic" + If ConsoleSafe = True Then + Continue + End If + End Select + End If + + // See if this key matches the search + If Filtered And Key.Label.IndexOf(Value) = -1 And Key.Description.IndexOf(Value) = -1 And Key.Key.IndexOf(Value) = -1 Then + Continue + End If + + Var GroupName As String = Self.EverythingElseGroupName + If (Key.UIGroup Is Nil) = False Then + GroupName = Key.UIGroup.StringValue + End If + + Var Members() As Palworld.ConfigOption + If Groups.HasKey(GroupName) Then + Members = Groups.Value(GroupName) + Else + GroupNames.Add(GroupName) + End If + Members.Add(Key) + Groups.Value(GroupName) = Members + + KeyNameWidth = Max(KeyNameWidth, MeasurePic.Graphics.TextWidth(If(Self.mShowOfficialNames, Key.Key, Key.Label) + ":")) + + DesiredBound = DesiredBound + 1 + Next Key + + // The "everything else" group should not be sorted normally. It'll always be at the end. + Var EverythingIdx As Integer = GroupNames.IndexOf(Self.EverythingElseGroupName) + If EverythingIdx > -1 Then + GroupNames.RemoveAt(EverythingIdx) + End If + GroupNames.Sort + If EverythingIdx > -1 Then + GroupNames.Add(Self.EverythingElseGroupName) + End If + + Self.mVisibleGroups = GroupNames + Self.mVisibleKeys = Groups + Self.mKeyNameWidth = KeyNameWidth + + // Remove elements until the bounds match + For Idx As Integer = Self.mElements.LastIndex DownTo Max(DesiredBound, 0) + If (Self.mElements(Idx) Is Nil) = False Then + Self.mElements(Idx).Close + Self.mElements(Idx) = Nil + End If + Next Idx + For Idx As Integer = Self.mGroupHeaders.LastIndex DownTo Max(GroupNames.LastIndex, 0) + If (Self.mGroupHeaders(Idx) Is Nil) = False Then + Self.mGroupHeaders(Idx).Close + Self.mGroupHeaders(Idx) = Nil + End If + Next Idx + + Self.mElements.ResizeTo(DesiredBound) + Self.mGroupHeaders.ResizeTo(GroupNames.LastIndex) + + Self.mContentHeight = (Self.mGroupHeaders.Count * Self.HeaderHeight) + (Self.mElements.Count * Self.ElementHeight) + Self.SetVisibilities(True) + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Sub ForceReload() + Var Filter As String = Self.Filter + Self.mFilter = Crypto.GenerateRandomBytes(8) + Self.Filter = Filter + + // Update the field values + Var Config As Palworld.Configs.OtherSettings = Self.Config(False) + For Each Element As PalworldSettingsListElement In Self.mElements + If Element Is Nil Then + Continue + End If + + Var Key As Palworld.ConfigOption = Element.Key + Var Value As Variant = Config.Value(Key) + If IsNull(Value) Then + Element.Value(True) = Key.DefaultValue + Element.IsOverloaded = False + Else + Element.Value(True) = Value + Element.IsOverloaded = True + End If + Next Element + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Function IsCorrectElementClass(Element As PalworldSettingsListElement, Key As Palworld.ConfigOption) As Boolean + Return (Element Is Nil) = False And (Key Is Nil) = False And Element.Key = Key + End Function + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Function Project() As Palworld.Project + Return RaiseEvent GetProject() + End Function + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub SettingChanged(Key As Palworld.ConfigOption, Value As Variant) + If Beacon.SafeToInvoke(Self.SettingChangeDelegate) Then + Self.SettingChangeDelegate.Invoke(Key, Value) + End If + + If Self.mDependencies Is Nil Or Self.mDependencies.HasKey(Key.ConfigOptionId) = False Then + Return + End If + + If IsNull(Value) Then + Value = Key.DefaultValue + End If + + Var Dependents() As Dictionary = Self.mDependencies.Value(Key.ConfigOptionId) + For Each Dict As Dictionary In Dependents + Var TargetKey As Palworld.ConfigOption = Dict.Value("Target") + Var RequiredValue As Variant = Dict.Value("RequiredValue") + Var ShouldBeEnabled As Boolean = (Value = RequiredValue) + Var Element As PalworldSettingsListElement = Self.ElementForKey(TargetKey) + If (Element Is Nil) = False Then + Element.Unlocked = ShouldBeEnabled + End If + Next Dict + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub SetVisibilities(Force As Boolean = False) + If Force = False And Self.Scroller.Value = Self.mLastScrollPosition And Self.Height = Self.mLastViewHeight Then + #if TargetWindows + Self.Refresh(False) + #endif + Return + End If + + Var OverflowHeight As Integer = Max(Self.mContentHeight - Self.Height, 0) + Var ScrollPosition As Integer = Min(Max(Self.Scroller.Value, 0), OverflowHeight) + Self.mLastViewHeight = Self.Height + Self.mLastScrollPosition = ScrollPosition + Var NoScrollEvents As Boolean = Self.mNoScrollEvents + Self.mNoScrollEvents = True + If Self.Scroller.MaximumValue <> OverflowHeight Then + Self.Scroller.MaximumValue = OverflowHeight + End If + If Self.Scroller.Value <> ScrollPosition Then + Self.Scroller.Value = ScrollPosition + End If + If Self.Scroller.PageStep <> Self.Height Then + Self.Scroller.PageStep = Self.Height + End If + Self.mNoScrollEvents = NoScrollEvents + + If Self.mVisibleKeys Is Nil Or Self.mVisibleKeys.KeyCount = 0 Then + If Not Self.FillCanvas.Visible Then + Self.FillCanvas.Visible = True + End If + #if TargetWindows + Self.Refresh(False) + #endif + Return + End If + + If Self.FillCanvas.Visible Then + Self.FillCanvas.Visible = False + End If + + Var NextTop As Integer = ScrollPosition * -1 + Var ElementIdx As Integer + Var ElementWidth As Integer = Self.Width - Self.Scroller.Width + For GroupIdx As Integer = 0 To Self.mVisibleGroups.LastIndex + Var GroupName As String = Self.mVisibleGroups(GroupIdx) + Var Members() As Palworld.ConfigOption = Self.mVisibleKeys.Value(GroupName) + + Var Header As ArkSettingsListHeader = Self.mGroupHeaders(GroupIdx) + Var HeaderTop As Integer = NextTop + NextTop = NextTop + Self.HeaderHeight + Var HeaderBottom As Integer = NextTop + + If HeaderBottom > 0 And HeaderTop < Self.Height Then + // Needs to be visible + If Header Is Nil Then + Header = New ArkSettingsListHeader + Header.Name = GroupName + Header.EmbedWithin(Self, 0, HeaderTop, ElementWidth, Self.HeaderHeight) + Header.Visible = True + Self.mGroupHeaders(GroupIdx) = Header + Else + If Header.Name <> GroupName Then + Header.Name = GroupName + End If + If Header.Visible = False Then + Header.Visible = True + End If + If Header.Top <> HeaderTop Then + Header.Top = HeaderTop + End If + End If + ElseIf (Header Is Nil) = False And Header.Visible = True Then + Header.Visible = False + End If + + For Each Member As Palworld.ConfigOption In Members + Var Element As PalworldSettingsListElement = Self.mElements(ElementIdx) + Var ElementTop As Integer = NextTop + NextTop = NextTop + Self.ElementHeight + Var ElementBottom As Integer = NextTop + + If ElementBottom > 0 And ElementTop < Self.Height Then + If (Element Is Nil) = False And Self.IsCorrectElementClass(Element, Member) = False Then + Element.Close + Element = Nil + Self.mElements(ElementIdx) = Nil + End If + If Element Is Nil Then + Element = Self.CreateElement(Member, 0, ElementTop, ElementWidth, Self.ElementHeight) + Element.Visible = True + Element.TabIndex = ElementIdx + Self.mElements(ElementIdx) = Element + Else + If Element.Visible = False Then + Element.Visible = True + End If + If Element.Top <> ElementTop Then + Element.Top = ElementTop + End If + If Element.ShowOfficialName <> Self.mShowOfficialNames Then + Element.ShowOfficialName = Self.mShowOfficialNames + End If + End If + If Element.KeyNameWidth <> Self.mKeyNameWidth Then + Element.KeyNameWidth = Self.mKeyNameWidth + End If + Element.Unlocked = Self.ShouldBeEnabled(Member) + ElseIf (Element Is Nil) = False And Element.Visible = True Then + Element.Visible = False + End If + + ElementIdx = ElementIdx + 1 + Next Member + Next GroupIdx + + #if TargetWindows + Self.Refresh(False) + #endif + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Function ShouldBeEnabled(Key As Palworld.ConfigOption) As Boolean + Var Requirements As Variant = Key.Constraint("other") + If IsNull(Requirements) Or (Requirements IsA Dictionary) = False Then + Return True + End If + + Var RequiredKey As Palworld.ConfigOption = Palworld.DataSource.Pool.Get(False).GetConfigOption(Dictionary(Requirements).Value("key").StringValue) + If RequiredKey Is Nil Then + Return True + End If + + Var RequiredValue As Variant = Dictionary(Requirements).Value("value") + + Var Config As Palworld.Configs.OtherSettings = Self.Config(False) + Var CurrentValue As Variant = Config.Value(RequiredKey) + If IsNull(CurrentValue) Then + CurrentValue = RequiredKey.DefaultValue + End If + Return CurrentValue = RequiredValue + End Function + #tag EndMethod + + + #tag Hook, Flags = &h0 + Event GetConfig(ForWriting As Boolean) As Palworld.Configs.OtherSettings + #tag EndHook + + #tag Hook, Flags = &h0 + Event GetProject() As Palworld.Project + #tag EndHook + + #tag Hook, Flags = &h0 + Event ValueChanged(Key As Palworld.ConfigOption, NewValue As Variant) + #tag EndHook + + + #tag Property, Flags = &h21 + Private mContentHeight As Integer + #tag EndProperty + + #tag Property, Flags = &h21 + Private mDependencies As Dictionary + #tag EndProperty + + #tag Property, Flags = &h21 + Private mElements() As PalworldSettingsListElement + #tag EndProperty + + #tag Property, Flags = &h21 + Private mFilter As String = "a51899b2-14cf-4014-b8f7-b94ada6c5923" + #tag EndProperty + + #tag Property, Flags = &h21 + Private mGroupHeaders() As ArkSettingsListHeader + #tag EndProperty + + #tag Property, Flags = &h21 + Private mKeyNameWidth As Integer + #tag EndProperty + + #tag Property, Flags = &h21 + Private mLastScrollPosition As Integer + #tag EndProperty + + #tag Property, Flags = &h21 + Private mLastViewHeight As Integer + #tag EndProperty + + #tag Property, Flags = &h21 + Private mNoScrollEvents As Boolean + #tag EndProperty + + #tag Property, Flags = &h21 + Private mShowOfficialNames As Boolean + #tag EndProperty + + #tag Property, Flags = &h21 + Private mVisibleGroups() As String + #tag EndProperty + + #tag Property, Flags = &h21 + Private mVisibleKeys As Dictionary + #tag EndProperty + + #tag Property, Flags = &h0 + SettingChangeDelegate As PalworldGeneralSettingsEditor.SettingChangeDelegate + #tag EndProperty + + #tag ComputedProperty, Flags = &h0 + #tag Getter + Get + Return Self.mShowOfficialNames + End Get + #tag EndGetter + #tag Setter + Set + If Self.mShowOfficialNames = Value Then + Return + End If + + Self.mShowOfficialNames = Value + Self.ForceReload() + End Set + #tag EndSetter + ShowOfficialNames As Boolean + #tag EndComputedProperty + + + #tag Constant, Name = ElementHeight, Type = Double, Dynamic = False, Default = \"62", Scope = Private + #tag EndConstant + + #tag Constant, Name = EverythingElseGroupName, Type = String, Dynamic = False, Default = \"Everything Else", Scope = Private + #tag EndConstant + + #tag Constant, Name = HeaderHeight, Type = Double, Dynamic = False, Default = \"30", Scope = Private + #tag EndConstant + + +#tag EndWindowCode + +#tag Events Scroller + #tag Event + Sub ValueChanged() + If Self.mNoScrollEvents Then + Return + End If + + Self.SetVisibilities() + End Sub + #tag EndEvent + #tag Event + Sub Opening() + Me.LineStep = Self.ElementHeight + End Sub + #tag EndEvent +#tag EndEvents +#tag ViewBehavior + #tag ViewProperty + Name="Composited" + Visible=true + Group="Window Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Width" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Height" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="InitialParent" + Visible=false + Group="Position" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockLeft" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockTop" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockRight" + Visible=true + Group="Position" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockBottom" + Visible=true + Group="Position" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabIndex" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabPanelIndex" + Visible=false + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabStop" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowAutoDeactivate" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Enabled" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Tooltip" + Visible=true + Group="Appearance" + InitialValue="" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocusRing" + Visible=true + Group="Appearance" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Visible" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="BackgroundColor" + Visible=true + Group="Background" + InitialValue="&hFFFFFF" + Type="ColorGroup" + EditorType="ColorGroup" + #tag EndViewProperty + #tag ViewProperty + Name="Backdrop" + Visible=true + Group="Background" + InitialValue="" + Type="Picture" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="HasBackgroundColor" + Visible=true + Group="Background" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocus" + Visible=true + Group="Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowTabs" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Transparent" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="ShowOfficialNames" + Visible=false + Group="Behavior" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty +#tag EndViewBehavior diff --git a/Project/Views/Palworld/Config Editors/General Settings/PalworldSettingsListElement.xojo_code b/Project/Views/Palworld/Config Editors/General Settings/PalworldSettingsListElement.xojo_code new file mode 100644 index 000000000..7c91b9d15 --- /dev/null +++ b/Project/Views/Palworld/Config Editors/General Settings/PalworldSettingsListElement.xojo_code @@ -0,0 +1,437 @@ +#tag Class +Protected Class PalworldSettingsListElement +Inherits DesktopContainer + #tag CompatibilityFlags = ( TargetDesktop and ( Target32Bit or Target64Bit ) ) + #tag Event + Sub Resized() + RaiseEvent Resize() + End Sub + #tag EndEvent + + #tag Event + Sub Resizing() + RaiseEvent Resize() + End Sub + #tag EndEvent + + + #tag Method, Flags = &h0 + Sub Constructor(Key As Palworld.ConfigOption) + Self.mKey = Key + Super.Constructor + Self.NameLabel.Text = Key.Label.ReplaceAll("&", "&&").Trim + ":" + Self.NameLabel.Tooltip = Key.Key.Trim + Self.DescriptionLabel.Text = Key.Description.ReplaceAll("&", "&&").Trim + Self.DescriptionLabel.Tooltip = Key.Description.Trim + End Sub + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Sub Delete() + Self.UserValueChange(Nil) + Self.Value(True) = Self.mKey.DefaultValue + End Sub + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Function DescriptionLabel() As DesktopLabel + + End Function + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Function DismissButton() As IconCanvas + + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function Key() As Palworld.ConfigOption + Return Self.mKey + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function KeyNameWidth() As Integer + Return Self.mKeyNameWidth + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub KeyNameWidth(Assigns KeyNameWidth As Integer) + Self.mKeyNameWidth = KeyNameWidth + End Sub + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Function NameLabel() As DesktopLabel + + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function Unlocked() As Boolean + Return Self.mUnlocked + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Unlocked(Assigns NewValue As Boolean) + If Self.mUnlocked = NewValue Then + Return + End If + + Self.mUnlocked = NewValue + + Self.NameLabel.Enabled = NewValue + Self.DescriptionLabel.Enabled = NewValue + + RaiseEvent LockStateChanged() + End Sub + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Sub UserValueChange(NewValue As Variant) + Self.IsOverloaded = IsNull(NewValue) = False + + If Beacon.SafeToInvoke(Self.SettingChangeDelegate) Then + Self.SettingChangeDelegate.Invoke(Self.mKey, NewValue) + End If + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Function Value() As Variant + + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Value(Animated As Boolean = False, Assigns NewValue As Variant) + #Pragma Unused Animated + #Pragma Unused NewValue + End Sub + #tag EndMethod + + + #tag Hook, Flags = &h0 + Event LockStateChanged() + #tag EndHook + + #tag Hook, Flags = &h0 + Event Resize() + #tag EndHook + + #tag Hook, Flags = &h0 + Event ShouldDelete() + #tag EndHook + + + #tag ComputedProperty, Flags = &h0 + #tag Getter + Get + Return Self.mOverloaded + End Get + #tag EndGetter + #tag Setter + Set + Self.mOverloaded = Value + + If Self.NameLabel.Bold <> Value Then + Self.NameLabel.Bold = Value + End If + If Self.DismissButton.Visible <> Value Then + Self.DismissButton.Visible = Value + End If + End Set + #tag EndSetter + IsOverloaded As Boolean + #tag EndComputedProperty + + #tag Property, Flags = &h1 + Protected mKey As Palworld.ConfigOption + #tag EndProperty + + #tag Property, Flags = &h21 + Private mKeyNameWidth As Integer + #tag EndProperty + + #tag Property, Flags = &h21 + Private mOverloaded As Boolean + #tag EndProperty + + #tag Property, Flags = &h21 + Private mShowOfficialName As Boolean + #tag EndProperty + + #tag Property, Flags = &h21 + Private mUnlocked As Boolean = True + #tag EndProperty + + #tag Property, Flags = &h0 + SettingChangeDelegate As PalworldGeneralSettingsEditor.SettingChangeDelegate + #tag EndProperty + + #tag ComputedProperty, Flags = &h0 + #tag Getter + Get + Return Self.mShowOfficialName + End Get + #tag EndGetter + #tag Setter + Set + If Self.mShowOfficialName = Value Then + Return + End If + + If Value Then + Self.NameLabel.Text = Self.mKey.Key.ReplaceAll("&", "&&").Trim + ":" + Self.NameLabel.Tooltip = Self.mKey.Label + Else + Self.NameLabel.Text = Self.mKey.Label.ReplaceAll("&", "&&").Trim + ":" + Self.NameLabel.Tooltip = Self.mKey.Key + End If + + Self.mShowOfficialName = Value + End Set + #tag EndSetter + ShowOfficialName As Boolean + #tag EndComputedProperty + + + #tag ViewBehavior + #tag ViewProperty + Name="Composited" + Visible=true + Group="Window Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Width" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Height" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="InitialParent" + Visible=false + Group="Position" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockLeft" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockTop" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockRight" + Visible=true + Group="Position" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockBottom" + Visible=true + Group="Position" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabIndex" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabPanelIndex" + Visible=false + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabStop" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowAutoDeactivate" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Enabled" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Tooltip" + Visible=true + Group="Appearance" + InitialValue="" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocusRing" + Visible=true + Group="Appearance" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Visible" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="BackgroundColor" + Visible=true + Group="Background" + InitialValue="&hFFFFFF" + Type="ColorGroup" + EditorType="ColorGroup" + #tag EndViewProperty + #tag ViewProperty + Name="Backdrop" + Visible=true + Group="Background" + InitialValue="" + Type="Picture" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="HasBackgroundColor" + Visible=true + Group="Background" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocus" + Visible=true + Group="Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowTabs" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Transparent" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="IsOverloaded" + Visible=false + Group="Behavior" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="ShowOfficialName" + Visible=false + Group="Behavior" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag EndViewBehavior +End Class +#tag EndClass diff --git a/Project/Views/Palworld/Config Editors/General Settings/PalworldSettingsListHeader.xojo_window b/Project/Views/Palworld/Config Editors/General Settings/PalworldSettingsListHeader.xojo_window new file mode 100644 index 000000000..42266ee82 --- /dev/null +++ b/Project/Views/Palworld/Config Editors/General Settings/PalworldSettingsListHeader.xojo_window @@ -0,0 +1,317 @@ +#tag DesktopWindow +Begin DesktopContainer PalworldSettingsListHeader + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = False + AllowTabs = True + Backdrop = 0 + BackgroundColor = &cFFFFFF00 + Composited = False + Enabled = True + HasBackgroundColor= False + Height = 30 + Index = -2147483648 + InitialParent = "" + Left = 0 + LockBottom = False + LockLeft = True + LockRight = True + LockTop = True + TabIndex = 0 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 0 + Transparent = True + Visible = True + Width = 300 + Begin DesktopCanvas Canvas1 + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = True + AllowTabs = False + Backdrop = 0 + Enabled = True + Height = 30 + Index = -2147483648 + InitialParent = "" + Left = 0 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + Scope = 2 + TabIndex = 1 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 0 + Transparent = True + Visible = True + Width = 300 + End +End +#tag EndDesktopWindow + +#tag WindowCode + #tag Method, Flags = &h0 + Function Name() As String + Return Self.mName + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Name(Assigns NewValue As String) + Self.mName = NewValue + Self.Canvas1.Refresh + End Sub + #tag EndMethod + + + #tag Property, Flags = &h21 + Private mName As String + #tag EndProperty + + +#tag EndWindowCode + +#tag Events Canvas1 + #tag Event + Sub Paint(g As Graphics, areas() As Rect) + #Pragma Unused Areas + + Var CapHeight As Double = G.CapHeight + Var CaptionLeft As Integer = 20 + Var CaptionBaseline As Integer = (G.Height / 2) + (CapHeight / 2) + Var HeaderColor As Color = SystemColors.LabelColor.AtOpacity(0.1)//New ColorGroup(&c00000099, &cFFFFFFCC) + + G.DrawingColor = HeaderColor + G.FillRectangle(0, 0, G.Width, G.Height) + G.DrawingColor = SystemColors.LabelColor + G.Bold = True + If Color.IsDarkMode Then + G.ShadowBrush = New ShadowBrush(0, 1, &c000000AA, 3) + End If + G.DrawText(Self.mName, CaptionLeft, CaptionBaseline) + End Sub + #tag EndEvent +#tag EndEvents +#tag ViewBehavior + #tag ViewProperty + Name="Composited" + Visible=true + Group="Window Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Width" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Height" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="InitialParent" + Visible=false + Group="Position" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockLeft" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockTop" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockRight" + Visible=true + Group="Position" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockBottom" + Visible=true + Group="Position" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabIndex" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabPanelIndex" + Visible=false + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabStop" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowAutoDeactivate" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Enabled" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Tooltip" + Visible=true + Group="Appearance" + InitialValue="" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocusRing" + Visible=true + Group="Appearance" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Visible" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="BackgroundColor" + Visible=true + Group="Background" + InitialValue="&hFFFFFF" + Type="ColorGroup" + EditorType="ColorGroup" + #tag EndViewProperty + #tag ViewProperty + Name="Backdrop" + Visible=true + Group="Background" + InitialValue="" + Type="Picture" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="HasBackgroundColor" + Visible=true + Group="Background" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocus" + Visible=true + Group="Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowTabs" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Transparent" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty +#tag EndViewBehavior diff --git a/Project/Views/Palworld/Config Editors/General Settings/PalworldSettingsListNumberElement.xojo_window b/Project/Views/Palworld/Config Editors/General Settings/PalworldSettingsListNumberElement.xojo_window new file mode 100644 index 000000000..2256d8865 --- /dev/null +++ b/Project/Views/Palworld/Config Editors/General Settings/PalworldSettingsListNumberElement.xojo_window @@ -0,0 +1,517 @@ +#tag DesktopWindow +Begin PalworldSettingsListElement PalworldSettingsListNumberElement + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = False + AllowTabs = True + Backdrop = 0 + BackgroundColor = &cFFFFFF00 + Composited = False + Enabled = True + HasBackgroundColor= False + Height = 62 + Index = -2147483648 + InitialParent = "" + Left = 0 + LockBottom = False + LockLeft = True + LockRight = True + LockTop = True + TabIndex = 0 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 0 + Transparent = True + Visible = True + Width = 300 + Begin DesktopLabel mDescriptionLabel + AllowAutoDeactivate= True + Bold = False + Enabled = True + FontName = "SmallSystem" + FontSize = 0.0 + FontUnit = 0 + Height = 16 + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 20 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + Multiline = False + Scope = 2 + Selectable = True + TabIndex = 0 + TabPanelIndex = 0 + TabStop = True + Text = "Untitled" + TextAlignment = 0 + TextColor = &c00000000 + Tooltip = "" + Top = 40 + Transparent = False + Underline = False + Visible = True + Width = 260 + End + Begin DesktopLabel mNameLabel + AllowAutoDeactivate= True + Bold = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 22 + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 20 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Multiline = False + Scope = 2 + Selectable = True + TabIndex = 1 + TabPanelIndex = 0 + TabStop = True + Text = "Untitled" + TextAlignment = 3 + TextColor = &c00000000 + Tooltip = "" + Top = 6 + Transparent = False + Underline = False + Visible = True + Width = 168 + End + Begin DelayedTextField mValueField + AllowAutoDeactivate= True + AllowFocusRing = True + AllowSpellChecking= False + AllowTabs = False + BackgroundColor = &cFFFFFF00 + Bold = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Format = "" + HasBorder = True + Height = 22 + Hint = "" + Index = -2147483648 + Italic = False + Left = 200 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + MaximumCharactersAllowed= 0 + Password = False + ReadOnly = False + Scope = 2 + TabIndex = 2 + TabPanelIndex = 0 + TabStop = True + Text = "" + TextAlignment = 2 + TextColor = &c00000000 + Tooltip = "" + Top = 6 + Transparent = False + Underline = False + ValidationMask = "" + Visible = True + Width = 52 + End + Begin IconCanvas mDismissButton + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = True + AllowTabs = False + Backdrop = 0 + Clickable = True + ContentHeight = 0 + Enabled = True + Height = 16 + Icon = 1389395967 + IconColor = 8 + Index = -2147483648 + InitialParent = "" + Left = 264 + LockBottom = False + LockedInPosition= False + LockLeft = False + LockRight = True + LockTop = True + Scope = 2 + ScrollActive = False + ScrollingEnabled= False + ScrollSpeed = 20 + TabIndex = 3 + TabPanelIndex = 0 + TabStop = True + Tooltip = "Restore this setting to the default." + Top = 9 + Transparent = True + Visible = False + Width = 16 + End +End +#tag EndDesktopWindow + +#tag WindowCode + #tag Event + Sub LockStateChanged() + Self.mValueField.Enabled = Self.Unlocked + End Sub + #tag EndEvent + + + #tag Method, Flags = &h1 + Protected Function DescriptionLabel() As DesktopLabel + Return Self.mDescriptionLabel + End Function + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Function DismissButton() As IconCanvas + Return Self.mDismissButton + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub KeyNameWidth(Assigns KeyNameWidth As Integer) + Super.KeyNameWidth = KeyNameWidth + + Self.mNameLabel.Width = KeyNameWidth + Self.mValueField.Left = Self.mNameLabel.Left + Self.mNameLabel.Width + 12 + Self.mValueField.Width = Min((Self.mDismissButton.Left - 12) - Self.mValueField.Left, 100) + Self.mDescriptionLabel.Left = Self.mValueField.Left + Self.mDescriptionLabel.Width = (Self.Width - 20) - Self.mDescriptionLabel.Left + End Sub + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Function NameLabel() As DesktopLabel + Return Self.mNameLabel + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function Value() As Variant + If IsNumeric(Self.mValueField.Text) = False Then + Return Nil + End If + + Var DoubleValue As Double + Try + DoubleValue = Double.FromString(Self.mValueField.Text, Locale.Current) + Catch Err As RuntimeException + Return Nil + End Try + + Var ShouldLimitMin As Variant = Self.mKey.Constraint("min") + Var ShouldLimitMax As Variant = Self.mKey.Constraint("max") + If IsNull(ShouldLimitMin) = False Then + DoubleValue = Max(DoubleValue, ShouldLimitMin) + End If + If IsNull(ShouldLimitMax) = False Then + DoubleValue = Min(DoubleValue, ShouldLimitMax) + End If + + Return DoubleValue + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Value(Animated As Boolean = False, Assigns NewValue As Variant) + #Pragma Unused Animated + + Var BlockChanges As Boolean = Self.mBlockChanges + If IsNull(NewValue) = False Then + Var NumericValue As Double + Try + NumericValue = NewValue.DoubleValue + Self.mBlockChanges = True + Self.mValueField.SetImmediately(NumericValue.PrettyText(6, True)) + Self.mBlockChanges = BlockChanges + Return + Catch Err As RuntimeException + End Try + End If + + Self.mBlockChanges = True + Self.mValueField.SetImmediately("") + Self.mBlockChanges = BlockChanges + End Sub + #tag EndMethod + + + #tag Property, Flags = &h21 + Private mBlockChanges As Boolean + #tag EndProperty + + +#tag EndWindowCode + +#tag Events mValueField + #tag Event + Sub TextChanged() + If Self.mBlockChanges Then + Return + End If + + Self.UserValueChange(Self.Value) + End Sub + #tag EndEvent +#tag EndEvents +#tag Events mDismissButton + #tag Event + Sub Pressed() + Self.Delete() + End Sub + #tag EndEvent +#tag EndEvents +#tag ViewBehavior + #tag ViewProperty + Name="Composited" + Visible=true + Group="Window Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="ShowOfficialName" + Visible=false + Group="Behavior" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="IsOverloaded" + Visible=false + Group="Behavior" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Width" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Height" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="InitialParent" + Visible=false + Group="Position" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockLeft" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockTop" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockRight" + Visible=true + Group="Position" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockBottom" + Visible=true + Group="Position" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabIndex" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabPanelIndex" + Visible=false + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabStop" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowAutoDeactivate" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Enabled" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Tooltip" + Visible=true + Group="Appearance" + InitialValue="" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocusRing" + Visible=true + Group="Appearance" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Visible" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="BackgroundColor" + Visible=true + Group="Background" + InitialValue="&hFFFFFF" + Type="ColorGroup" + EditorType="ColorGroup" + #tag EndViewProperty + #tag ViewProperty + Name="Backdrop" + Visible=true + Group="Background" + InitialValue="" + Type="Picture" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="HasBackgroundColor" + Visible=true + Group="Background" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocus" + Visible=true + Group="Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowTabs" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Transparent" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty +#tag EndViewBehavior diff --git a/Project/Views/Palworld/Config Editors/General Settings/PalworldSettingsListStringElement.xojo_window b/Project/Views/Palworld/Config Editors/General Settings/PalworldSettingsListStringElement.xojo_window new file mode 100644 index 000000000..21abee745 --- /dev/null +++ b/Project/Views/Palworld/Config Editors/General Settings/PalworldSettingsListStringElement.xojo_window @@ -0,0 +1,718 @@ +#tag DesktopWindow +Begin PalworldSettingsListElement PalworldSettingsListStringElement + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = False + AllowTabs = True + Backdrop = 0 + BackgroundColor = &cFFFFFF00 + Composited = False + Enabled = True + HasBackgroundColor= False + Height = 62 + Index = -2147483648 + InitialParent = "" + Left = 0 + LockBottom = False + LockLeft = True + LockRight = True + LockTop = True + TabIndex = 0 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 0 + Transparent = True + Visible = True + Width = 300 + Begin DesktopLabel mDescriptionLabel + AllowAutoDeactivate= True + Bold = False + Enabled = True + FontName = "SmallSystem" + FontSize = 0.0 + FontUnit = 0 + Height = 16 + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 20 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + Multiline = False + Scope = 2 + Selectable = True + TabIndex = 0 + TabPanelIndex = 0 + TabStop = True + Text = "Untitled" + TextAlignment = 0 + TextColor = &c00000000 + Tooltip = "" + Top = 40 + Transparent = False + Underline = False + Visible = True + Width = 260 + End + Begin DesktopLabel mNameLabel + AllowAutoDeactivate= True + Bold = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 22 + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 20 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Multiline = False + Scope = 2 + Selectable = True + TabIndex = 1 + TabPanelIndex = 0 + TabStop = True + Text = "Untitled" + TextAlignment = 3 + TextColor = &c00000000 + Tooltip = "" + Top = 6 + Transparent = False + Underline = False + Visible = True + Width = 168 + End + Begin DelayedTextField mValueField + AllowAutoDeactivate= True + AllowFocusRing = True + AllowSpellChecking= False + AllowTabs = False + BackgroundColor = &cFFFFFF00 + Bold = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Format = "" + HasBorder = True + Height = 22 + Hint = "" + Index = -2147483648 + Italic = False + Left = 200 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + MaximumCharactersAllowed= 0 + Password = False + ReadOnly = False + Scope = 2 + TabIndex = 2 + TabPanelIndex = 0 + TabStop = True + Text = "" + TextAlignment = 0 + TextColor = &c00000000 + Tooltip = "" + Top = 6 + Transparent = False + Underline = False + ValidationMask = "" + Visible = True + Width = 52 + End + Begin IconCanvas mDismissButton + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = True + AllowTabs = False + Backdrop = 0 + Clickable = True + ContentHeight = 0 + Enabled = True + Height = 16 + Icon = 1389395967 + IconColor = 8 + Index = -2147483648 + InitialParent = "" + Left = 264 + LockBottom = False + LockedInPosition= False + LockLeft = False + LockRight = True + LockTop = True + Scope = 2 + ScrollActive = False + ScrollingEnabled= False + ScrollSpeed = 20 + TabIndex = 3 + TabPanelIndex = 0 + TabStop = True + Tooltip = "Restore this setting to the default." + Top = 9 + Transparent = True + Visible = False + Width = 16 + End + Begin UITweaks.ResizedPopupMenu mChoiceMenu + AllowAutoDeactivate= True + Bold = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 20 + Index = -2147483648 + InitialParent = "" + InitialValue = "" + Italic = False + Left = 200 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + Scope = 2 + SelectedRowIndex= 0 + TabIndex = 4 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = -167 + Transparent = False + Underline = False + Visible = False + Width = 80 + End + Begin DelayedComboBox mInputMenu + AllowAutoComplete= True + AllowAutoDeactivate= True + AllowFocusRing = True + Bold = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 22 + Hint = "" + Index = -2147483648 + InitialValue = "sock" + Italic = False + Left = 200 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Scope = 2 + SelectedRowIndex= 0 + TabIndex = 5 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = -135 + Transparent = False + Underline = False + Visible = False + Width = 80 + End +End +#tag EndDesktopWindow + +#tag WindowCode + #tag Event + Sub LockStateChanged() + Self.mValueField.Enabled = Self.Unlocked + Self.mChoiceMenu.Enabled = Self.Unlocked + Self.mInputMenu.Enabled = Self.Unlocked + End Sub + #tag EndEvent + + #tag Event + Sub Opening() + Const VisibleTop = 6 + Const HiddenTop = -30000 + + Select Case Self.mMode + Case Self.PlainMode + Self.mChoiceMenu.Visible = False + Self.mChoiceMenu.Top = HiddenTop + Self.mInputMenu.Visible = False + Self.mInputMenu.Top = HiddenTop + Self.mValueField.Visible = True + Self.mValueField.Top = VisibleTop + Case Self.MenuMode + Self.mChoiceMenu.Visible = True + Self.mChoiceMenu.Top = VisibleTop + 1 + Self.mInputMenu.Visible = False + Self.mInputMenu.Top = HiddenTop + Self.mValueField.Visible = False + Self.mValueField.Top = HiddenTop + + Var Choices() As Variant = Self.mKey.Constraint("oneof") + Self.mChoiceMenu.RemoveAllRows + For Each Choice As String In Choices + Self.mChoiceMenu.AddRow(Choice) + Next Choice + Case Self.ComboMode + Self.mChoiceMenu.Visible = False + Self.mChoiceMenu.Top = HiddenTop + Self.mInputMenu.Visible = True + Self.mInputMenu.Top = VisibleTop + Self.mValueField.Visible = False + Self.mValueField.Top = HiddenTop + + Var Choices() As Variant = Self.mKey.Constraint("oneof") + Self.mInputMenu.RemoveAllRows + For Each Choice As String In Choices + Self.mInputMenu.AddRow(Choice) + Next Choice + End Select + End Sub + #tag EndEvent + + + #tag Method, Flags = &h0 + Sub Constructor(Key As Palworld.ConfigOption) + If IsNull(Key.Constraint("oneof")) = False Then + Var AllowCustom As Variant = Key.Constraint("allowcustom") + If IsNull(AllowCustom) = False And AllowCustom.BooleanValue = True Then + Self.mMode = Self.ComboMode + Else + Self.mMode = Self.MenuMode + End If + Else + Self.mMode = Self.PlainMode + End If + + Super.Constructor(Key) + End Sub + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Function DescriptionLabel() As DesktopLabel + Return Self.mDescriptionLabel + End Function + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Function DismissButton() As IconCanvas + Return Self.mDismissButton + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub KeyNameWidth(Assigns KeyNameWidth As Integer) + Super.KeyNameWidth = KeyNameWidth + + Var ValueControl As DesktopUIControl + Select Case Self.mMode + Case Self.PlainMode + ValueControl = Self.mValueField + Case Self.MenuMode + ValueControl = Self.mChoiceMenu + Case Self.ComboMode + ValueControl = Self.mInputMenu + End Select + + Self.mNameLabel.Width = KeyNameWidth + ValueControl.Left = Self.mNameLabel.Left + Self.mNameLabel.Width + 12 + ValueControl.Width = (Self.mDismissButton.Left - 12) - ValueControl.Left + Self.mDescriptionLabel.Left = ValueControl.Left + Self.mDescriptionLabel.Width = ValueControl.Width + End Sub + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Function NameLabel() As DesktopLabel + Return Self.mNameLabel + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function Value() As Variant + Var StringValue As String + + Select Case Self.mMode + Case Self.PlainMode + StringValue = Self.mValueField.Text + Case Self.MenuMode + StringValue = Self.mChoiceMenu.SelectedRowText + Case Self.ComboMode + StringValue = Self.mInputMenu.Text + End Select + + Var ShouldBeginWith As Variant = Self.mKey.Constraint("beginswith") + Var ShouldEndWith As Variant = Self.mKey.Constraint("endswith") + + If IsNull(ShouldBeginWith) = False And StringValue.BeginsWith(ShouldBeginWith) = False Then + StringValue = ShouldBeginWith + StringValue + End If + If IsNull(ShouldEndWith) = False And StringValue.EndsWith(ShouldEndWith) = False Then + StringValue = StringValue + ShouldEndWith + End If + + Return StringValue + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Value(Animated As Boolean = False, Assigns NewValue As Variant) + #Pragma Unused Animated + + Var BlockChanges As Boolean = Self.mBlockChanges + Self.mBlockChanges = True + + Var StringValue As String + Try + StringValue = NewValue.StringValue + Catch Err As RuntimeException + End Try + + Select Case Self.mMode + Case Self.PlainMode + Self.mValueField.SetImmediately(StringValue) + Case Self.MenuMode + Self.mChoiceMenu.SelectByCaption(StringValue) + Case Self.ComboMode + Self.mInputMenu.SetImmediately(StringValue) + End Select + Self.mBlockChanges = BlockChanges + End Sub + #tag EndMethod + + + #tag Property, Flags = &h21 + Private mBlockChanges As Boolean + #tag EndProperty + + #tag Property, Flags = &h21 + Private mMode As Integer + #tag EndProperty + + + #tag Constant, Name = ComboMode, Type = Double, Dynamic = False, Default = \"2", Scope = Private + #tag EndConstant + + #tag Constant, Name = MenuMode, Type = Double, Dynamic = False, Default = \"1", Scope = Private + #tag EndConstant + + #tag Constant, Name = PlainMode, Type = Double, Dynamic = False, Default = \"0", Scope = Private + #tag EndConstant + + +#tag EndWindowCode + +#tag Events mValueField + #tag Event + Sub TextChanged() + If Self.mBlockChanges Then + Return + End If + + Self.UserValueChange(Self.Value) + End Sub + #tag EndEvent +#tag EndEvents +#tag Events mDismissButton + #tag Event + Sub Pressed() + Self.Delete() + End Sub + #tag EndEvent +#tag EndEvents +#tag Events mChoiceMenu + #tag Event + Function MouseWheel(x As Integer, y As Integer, deltaX As Integer, deltaY As Integer) As Boolean + #Pragma Unused X + #Pragma Unused Y + #Pragma Unused DeltaX + #Pragma Unused DeltaY + + #if TargetWindows + // Capture the scroll wheel to prevent accidental changing while scrolling the settings list + Return True + #endif + End Function + #tag EndEvent + #tag Event + Sub SelectionChanged(item As DesktopMenuItem) + #Pragma Unused Item + + If Self.mBlockChanges Then + Return + End If + + Self.UserValueChange(Self.Value) + End Sub + #tag EndEvent +#tag EndEvents +#tag Events mInputMenu + #tag Event + Sub TextChanged() + If Self.mBlockChanges Then + Return + End If + + Self.UserValueChange(Self.Value) + End Sub + #tag EndEvent + #tag Event + Function MouseWheel(x As Integer, y As Integer, deltaX As Integer, deltaY As Integer) As Boolean + #Pragma Unused X + #Pragma Unused Y + #Pragma Unused DeltaX + #Pragma Unused DeltaY + + #if TargetWindows + // Capture the scroll wheel to prevent accidental changing while scrolling the settings list + Return True + #endif + End Function + #tag EndEvent +#tag EndEvents +#tag ViewBehavior + #tag ViewProperty + Name="Composited" + Visible=true + Group="Window Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="ShowOfficialName" + Visible=false + Group="Behavior" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="IsOverloaded" + Visible=false + Group="Behavior" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Width" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Height" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="InitialParent" + Visible=false + Group="Position" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockLeft" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockTop" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockRight" + Visible=true + Group="Position" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockBottom" + Visible=true + Group="Position" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabIndex" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabPanelIndex" + Visible=false + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabStop" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowAutoDeactivate" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Enabled" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Tooltip" + Visible=true + Group="Appearance" + InitialValue="" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocusRing" + Visible=true + Group="Appearance" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Visible" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="BackgroundColor" + Visible=true + Group="Background" + InitialValue="&hFFFFFF" + Type="ColorGroup" + EditorType="ColorGroup" + #tag EndViewProperty + #tag ViewProperty + Name="Backdrop" + Visible=true + Group="Background" + InitialValue="" + Type="Picture" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="HasBackgroundColor" + Visible=true + Group="Background" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocus" + Visible=true + Group="Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowTabs" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Transparent" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty +#tag EndViewBehavior diff --git a/Project/Views/Palworld/Config Editors/PalworldConfigEditor.xojo_code b/Project/Views/Palworld/Config Editors/PalworldConfigEditor.xojo_code new file mode 100644 index 000000000..c66972297 --- /dev/null +++ b/Project/Views/Palworld/Config Editors/PalworldConfigEditor.xojo_code @@ -0,0 +1,529 @@ +#tag Class +Protected Class PalworldConfigEditor +Inherits BeaconSubview + #tag Event + Sub Opening() + RaiseEvent Opening + // Do not call SetupUI here, it's redundant. Shown will handle that. + Self.SettingUp = False + End Sub + #tag EndEvent + + #tag Event + Sub Shown(UserData As Variant = Nil) + Var FireSetupUI As Boolean = True + RaiseEvent Shown(UserData, FireSetupUI) + If FireSetupUI Then + Self.SetupUI() + End If + End Sub + #tag EndEvent + + + #tag Method, Flags = &h1 + Protected Function Config(ForWriting As Boolean) As Palworld.ConfigGroup + Var Config As Palworld.ConfigGroup + Var InternalName As String = Self.InternalName + + If (Self.mConfigRef Is Nil) = False And (Self.mConfigRef.Value Is Nil) = False Then + Config = Palworld.ConfigGroup(Self.mConfigRef.Value) + ElseIf Self.mProject.HasConfigGroup(InternalName) Then + Config = Self.mProject.ConfigGroup(InternalName, False) + Self.mConfigRef = New WeakRef(Config) + Else + Config = Palworld.Configs.CreateInstance(InternalName) + Config.IsImplicit = False + Self.mConfigRef = New WeakRef(Config) + End If + + If ForWriting And Self.mProject.HasConfigGroup(InternalName) = False Then + Self.mProject.AddConfigGroup(Config) + End If + + Return Config + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function ConfigLabel() As String + Return Language.LabelForConfig(Self.InternalName) + End Function + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub Constructor() + Self.SettingUp = True + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Constructor(Project As Palworld.Project) + Self.mProject = Project + Self.Constructor() + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Sub GoToIssue(Issue As Beacon.Issue) + RaiseEvent ShowIssue(Issue) + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Sub ImportFinished() + Self.SetupUI() + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Function InternalName() As String + + End Function + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Sub InvalidateConfigRef() + Self.mConfigRef = Nil + End Sub + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Sub Parse(SettingsIniContent As String, Source As String) + #Pragma Unused Source + + Var Data As New Palworld.DiscoveredData + Data.SettingsIniContent = SettingsIniContent + + Var Parser As New Palworld.ImportThread(Data, Self.mProject) + Parser.DebugIdentifier = CurrentMethodName + Parser.Priority = Thread.NormalPriority + AddHandler Parser.Finished, AddressOf Parser_Finished + + Var Win As New ProgressWindow + Win.ShowDelayed(Self.TrueWindow) + + If Self.mParserWindows = Nil Then + Self.mParserWindows = New Dictionary + End If + Self.mParserWindows.Value(Parser) = Win + + Parser.Progress = Win + Parser.Start + End Sub + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Function ParseDouble(Input As String, ByRef Value As Double) As Boolean + If IsNumeric(Input) Then + Value = CDbl(Input) + Return True + Else + Return False + End If + End Function + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub Parser_Finished(Sender As Palworld.ImportThread, Project As Palworld.Project) + RemoveHandler Sender.Finished, AddressOf Parser_Finished + + Var Win As ProgressWindow = Self.mParserWindows.Value(Sender) + Win.Close + Self.mParserWindows.Remove(Sender) + + If Project Is Nil Then + Return + End If + + Var Imported As Boolean + Try + Imported = RaiseEvent ParsingFinished(Project) + Catch Err As RuntimeException + ExceptionWindow.Report(Err) + End Try + If Imported Then + Self.SetupUI() + Return + End If + + Var InternalName As String = Self.InternalName + If Project.HasConfigGroup(InternalName) = False Then + Return + End If + + Var NewGroup As Palworld.ConfigGroup = Project.ConfigGroup(InternalName) + + If Self.Project.HasConfigGroup(InternalName) And Self.Project.ConfigGroup(InternalName).SupportsMerging Then + Var Choice As BeaconUI.ConfirmResponses = Self.ShowConfirm("How would you like to merge " + Self.ConfigLabel() + " into your existing editor?", "In the case of overlapping content, which should take priority?", "New Content", "Cancel", "Existing Content") + If Choice = BeaconUI.ConfirmResponses.Cancel Then + Return + End If + + Var OriginalGroup As Palworld.ConfigGroup = Self.Project.ConfigGroup(InternalName) + Var Editors(1) As Palworld.ConfigGroup + Editors(0) = OriginalGroup + Editors(1) = NewGroup + Var ZeroHasPriority As Boolean = Choice = BeaconUI.ConfirmResponses.Alternate + Var Merged As Palworld.ConfigGroup = Palworld.Configs.Merge(Editors, ZeroHasPriority) + + Self.Project.AddConfigGroup(Merged) + Else + Self.Project.AddConfigGroup(NewGroup) + End If + + Self.SetupUI() + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Function Project() As Palworld.Project + Return Self.mProject + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function RunTool(ToolId As String) As Boolean + Var Tools() As Palworld.ProjectTool = Palworld.Configs.AllTools + For Each Tool As Palworld.ProjectTool In Tools + If Tool.ToolId = ToolId Then + RaiseEvent RunTool(Tool) + Return True + End If + Next + Return False + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub SetupUI() + Var SettingUp As Boolean = Self.SettingUp + Self.SettingUp = True + Var FocusControl As DesktopUIControl = Self.Focus + Self.SetFocus() + RaiseEvent SetupUI + If FocusControl <> Nil Then + FocusControl.SetFocus() + End If + Self.SettingUp = SettingUp + End Sub + #tag EndMethod + + + #tag Hook, Flags = &h0 + Event Opening() + #tag EndHook + + #tag Hook, Flags = &h0 + Event ParsingFinished(Project As Palworld.Project) As Boolean + #tag EndHook + + #tag Hook, Flags = &h0 + Event RunTool(Tool As Palworld.ProjectTool) + #tag EndHook + + #tag Hook, Flags = &h0 + Event SetupUI() + #tag EndHook + + #tag Hook, Flags = &h0 + Event ShowIssue(Issue As Beacon.Issue) + #tag EndHook + + #tag Hook, Flags = &h0 + Event Shown(UserData As Variant, ByRef FireSetupUI As Boolean) + #tag EndHook + + + #tag Property, Flags = &h21 + Private mConfigRef As WeakRef + #tag EndProperty + + #tag Property, Flags = &h21 + Private mParserWindows As Dictionary + #tag EndProperty + + #tag Property, Flags = &h21 + Private mProject As Palworld.Project + #tag EndProperty + + #tag Property, Flags = &h1 + Protected SettingUp As Boolean + #tag EndProperty + + + #tag ViewBehavior + #tag ViewProperty + Name="Modified" + Visible=false + Group="Behavior" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Composited" + Visible=true + Group="Window Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Height" + Visible=true + Group="Size" + InitialValue="500" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Width" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockBottom" + Visible=true + Group="Position" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockLeft" + Visible=true + Group="Position" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockRight" + Visible=true + Group="Position" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockTop" + Visible=true + Group="Position" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabIndex" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabStop" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Tooltip" + Visible=true + Group="Appearance" + InitialValue="" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="AllowAutoDeactivate" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocusRing" + Visible=true + Group="Appearance" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Enabled" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Visible" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="BackgroundColor" + Visible=true + Group="Background" + InitialValue="&hFFFFFF" + Type="ColorGroup" + EditorType="ColorGroup" + #tag EndViewProperty + #tag ViewProperty + Name="HasBackgroundColor" + Visible=true + Group="Background" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Backdrop" + Visible=true + Group="Background" + InitialValue="" + Type="Picture" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocus" + Visible=true + Group="Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowTabs" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Transparent" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="MinimumWidth" + Visible=false + Group="Behavior" + InitialValue="" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="MinimumHeight" + Visible=false + Group="Behavior" + InitialValue="" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Progress" + Visible=false + Group="Behavior" + InitialValue="" + Type="Double" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="ViewTitle" + Visible=true + Group="Behavior" + InitialValue="Untitled" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="ViewIcon" + Visible=false + Group="Behavior" + InitialValue="" + Type="Picture" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="InitialParent" + Visible=false + Group="Position" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabPanelIndex" + Visible=false + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="IsFrontmost" + Visible=false + Group="Behavior" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag EndViewBehavior +End Class +#tag EndClass diff --git a/Project/Views/Palworld/Config Editors/Project Settings/PalworldProjectSettingsEditor.xojo_window b/Project/Views/Palworld/Config Editors/Project Settings/PalworldProjectSettingsEditor.xojo_window new file mode 100644 index 000000000..58a79d95f --- /dev/null +++ b/Project/Views/Palworld/Config Editors/Project Settings/PalworldProjectSettingsEditor.xojo_window @@ -0,0 +1,715 @@ +#tag DesktopWindow +Begin PalworldConfigEditor PalworldProjectSettingsEditor + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = False + AllowTabs = True + Backdrop = 0 + BackgroundColor = &cFFFFFF00 + Composited = False + Enabled = True + HasBackgroundColor= False + Height = 554 + Index = -2147483648 + InitialParent = "" + Left = 0 + LockBottom = True + LockLeft = True + LockRight = True + LockTop = True + TabIndex = 0 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 0 + Transparent = True + Visible = True + Width = 628 + Begin UITweaks.ResizedTextField TitleField + AllowAutoDeactivate= True + AllowFocusRing = True + AllowSpellChecking= False + AllowTabs = False + BackgroundColor = &cFFFFFF + Bold = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Format = "" + HasBorder = True + Height = 22 + Hint = "" + Index = -2147483648 + Italic = False + Left = 221 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + MaximumCharactersAllowed= 0 + Password = False + ReadOnly = False + Scope = 2 + TabIndex = 2 + TabPanelIndex = 0 + TabStop = True + Text = "" + TextAlignment = 0 + TextColor = &c00000000 + Tooltip = "" + Top = 61 + Transparent = False + Underline = False + ValidationMask = "" + Visible = True + Width = 387 + End + Begin UITweaks.ResizedLabel TitleLabel + AllowAutoDeactivate= True + Bold = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 22 + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 20 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Multiline = False + Scope = 2 + Selectable = False + TabIndex = 1 + TabPanelIndex = 0 + TabStop = True + Text = "Title:" + TextAlignment = 3 + TextColor = &c00000000 + Tooltip = "" + Top = 61 + Transparent = False + Underline = False + Visible = True + Width = 189 + End + Begin DesktopTextArea DescriptionArea + AllowAutoDeactivate= True + AllowFocusRing = True + AllowSpellChecking= True + AllowStyledText = True + AllowTabs = False + BackgroundColor = &cFFFFFF + Bold = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Format = "" + HasBorder = True + HasHorizontalScrollbar= False + HasVerticalScrollbar= True + Height = 375 + HideSelection = True + Index = -2147483648 + Italic = False + Left = 221 + LineHeight = 0.0 + LineSpacing = 1.0 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + MaximumCharactersAllowed= 0 + Multiline = True + ReadOnly = False + Scope = 2 + TabIndex = 4 + TabPanelIndex = 0 + TabStop = True + Text = "" + TextAlignment = 0 + TextColor = &c00000000 + Tooltip = "" + Top = 95 + Transparent = False + Underline = False + UnicodeMode = 0 + ValidationMask = "" + Visible = True + Width = 387 + End + Begin UITweaks.ResizedLabel DescriptionLabel + AllowAutoDeactivate= True + Bold = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 22 + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 20 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Multiline = False + Scope = 2 + Selectable = False + TabIndex = 3 + TabPanelIndex = 0 + TabStop = True + Text = "Description:" + TextAlignment = 3 + TextColor = &c00000000 + Tooltip = "" + Top = 95 + Transparent = False + Underline = False + Visible = True + Width = 189 + End + Begin OmniBar ConfigToolbar + Alignment = 0 + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = True + AllowTabs = False + Backdrop = 0 + BackgroundColor = "" + ContentHeight = 0 + Enabled = True + Height = 41 + Index = -2147483648 + InitialParent = "" + Left = 0 + LeftPadding = -1 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + RightPadding = -1 + Scope = 2 + ScrollActive = False + ScrollingEnabled= False + ScrollSpeed = 20 + TabIndex = 0 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 0 + Transparent = True + Visible = True + Width = 628 + End + Begin DesktopLabel CompressedLabel + AllowAutoDeactivate= True + Bold = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 20 + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 20 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = False + Multiline = False + Scope = 2 + Selectable = False + TabIndex = 5 + TabPanelIndex = 0 + TabStop = True + Text = "Compress Project:" + TextAlignment = 3 + TextColor = &c00000000 + Tooltip = "#HelpCompressed" + Top = 482 + Transparent = False + Underline = False + Visible = True + Width = 189 + End + Begin SwitchControl CompressedSwitch + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = True + AllowTabs = False + Backdrop = 0 + ContentHeight = 0 + Enabled = True + Height = 20 + Index = -2147483648 + InitialParent = "" + Left = 221 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = False + Scope = 2 + ScrollActive = False + ScrollingEnabled= False + ScrollSpeed = 20 + TabIndex = 6 + TabPanelIndex = 0 + TabStop = True + Tooltip = "#HelpCompressed" + Top = 482 + Transparent = True + Visible = True + Width = 40 + End + Begin DesktopLabel LocalBackupLabel + AllowAutoDeactivate= True + Bold = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 20 + Index = -2147483648 + Italic = False + Left = 20 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = False + Multiline = False + Scope = 2 + Selectable = False + TabIndex = 7 + TabPanelIndex = 0 + TabStop = True + Text = "Keep Local Project Backup:" + TextAlignment = 3 + TextColor = &c00000000 + Tooltip = "#HelpLocalBackup" + Top = 514 + Transparent = False + Underline = False + Visible = True + Width = 189 + End + Begin SwitchControl LocalBackupSwitch + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = True + AllowTabs = False + Backdrop = 0 + ContentHeight = 0 + Enabled = True + Height = 20 + Index = -2147483648 + Left = 221 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = False + Scope = 2 + ScrollActive = False + ScrollingEnabled= False + ScrollSpeed = 20 + TabIndex = 8 + TabPanelIndex = 0 + TabStop = True + Tooltip = "#HelpLocalBackup" + Top = 514 + Transparent = True + Visible = True + Width = 40 + End +End +#tag EndDesktopWindow + +#tag WindowCode + #tag Event + Sub SetupUI() + Self.TitleField.Text = Self.Project.Title + Self.DescriptionArea.Text = Self.Project.Description + Self.CompressedSwitch.Value(False) = Self.Project.UseCompression + Self.LocalBackupSwitch.Value(False) = Self.Project.KeepLocalBackup + + BeaconUI.SizeToFit(Self.TitleLabel, Self.DescriptionLabel, Self.CompressedLabel, Self.LocalBackupLabel) + Var ControlLeft As Integer = Self.TitleLabel.Right + 12 + Self.TitleField.Left = ControlLeft + Self.TitleField.Width = Self.Width - (ControlLeft + 20) + Self.DescriptionArea.Left = ControlLeft + Self.DescriptionArea.Width = Self.TitleField.Width + Self.CompressedSwitch.Left = ControlLeft + Self.LocalBackupSwitch.Left = ControlLeft + End Sub + #tag EndEvent + + + #tag Method, Flags = &h0 + Function InternalName() As String + Return Palworld.Configs.NameProjectSettings + End Function + #tag EndMethod + + + #tag Constant, Name = HelpCompressed, Type = String, Dynamic = True, Default = \"When enabled\x2C the project will be compressed to save disk space. Turn this setting off to save the project as a plain text JSON file.", Scope = Private + #tag EndConstant + + #tag Constant, Name = HelpLocalBackup, Type = String, Dynamic = True, Default = \"When turned on\x2C projects saved to the cloud will also save a backup copy to your computer.", Scope = Private + #tag EndConstant + + +#tag EndWindowCode + +#tag Events TitleField + #tag Event + Sub TextChanged() + If Self.SettingUp Then + Return + End If + + Self.SettingUp = True + Self.Project.Title = Me.Text.Trim + Self.Modified = True + Self.SettingUp = False + End Sub + #tag EndEvent +#tag EndEvents +#tag Events DescriptionArea + #tag Event + Sub TextChanged() + If Self.SettingUp Then + Return + End If + + Self.SettingUp = True + Self.Project.Description = Beacon.SanitizeText(Me.Text.Trim, False) + Self.Modified = True + Self.SettingUp = False + End Sub + #tag EndEvent +#tag EndEvents +#tag Events ConfigToolbar + #tag Event + Sub Opening() + Me.Append(OmniBarItem.CreateTitle("ConfigTitle", Self.ConfigLabel)) + End Sub + #tag EndEvent +#tag EndEvents +#tag Events CompressedSwitch + #tag Event + Sub Pressed() + If Self.SettingUp Then + Return + End If + + Self.SettingUp = True + Self.Project.UseCompression = Me.Value + Self.Modified = True + Self.SettingUp = False + End Sub + #tag EndEvent +#tag EndEvents +#tag Events LocalBackupSwitch + #tag Event + Sub Pressed() + If Self.SettingUp Then + Return + End If + + Self.SettingUp = True + Self.Project.KeepLocalBackup = Me.Value + Self.Modified = True + Self.SettingUp = False + End Sub + #tag EndEvent +#tag EndEvents +#tag ViewBehavior + #tag ViewProperty + Name="Modified" + Visible=false + Group="Behavior" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Composited" + Visible=true + Group="Window Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="IsFrontmost" + Visible=false + Group="Behavior" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="ViewTitle" + Visible=true + Group="Behavior" + InitialValue="Untitled" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="ViewIcon" + Visible=false + Group="Behavior" + InitialValue="" + Type="Picture" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Progress" + Visible=false + Group="Behavior" + InitialValue="" + Type="Double" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Tooltip" + Visible=true + Group="Appearance" + InitialValue="" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="AllowAutoDeactivate" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocusRing" + Visible=true + Group="Appearance" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="BackgroundColor" + Visible=true + Group="Background" + InitialValue="&hFFFFFF" + Type="ColorGroup" + EditorType="ColorGroup" + #tag EndViewProperty + #tag ViewProperty + Name="HasBackgroundColor" + Visible=true + Group="Background" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocus" + Visible=true + Group="Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowTabs" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="MinimumWidth" + Visible=true + Group="Behavior" + InitialValue="400" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="MinimumHeight" + Visible=true + Group="Behavior" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Width" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Height" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="InitialParent" + Visible=false + Group="Position" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockLeft" + Visible=true + Group="Position" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockTop" + Visible=true + Group="Position" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockRight" + Visible=true + Group="Position" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockBottom" + Visible=true + Group="Position" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabPanelIndex" + Visible=false + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabIndex" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabStop" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Visible" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Enabled" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Backdrop" + Visible=true + Group="Background" + InitialValue="" + Type="Picture" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Transparent" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty +#tag EndViewBehavior diff --git a/Project/Views/Palworld/Config Editors/Servers/PalworldCommonServerSettingsView.xojo_window b/Project/Views/Palworld/Config Editors/Servers/PalworldCommonServerSettingsView.xojo_window new file mode 100644 index 000000000..61e8d2acf --- /dev/null +++ b/Project/Views/Palworld/Config Editors/Servers/PalworldCommonServerSettingsView.xojo_window @@ -0,0 +1,1137 @@ +#tag DesktopWindow +Begin BeaconContainer PalworldCommonServerSettingsView + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = False + AllowTabs = True + Backdrop = 0 + BackgroundColor = &cFFFFFF00 + Composited = False + Enabled = True + HasBackgroundColor= False + Height = 532 + Index = -2147483648 + InitialParent = "" + Left = 0 + LockBottom = True + LockLeft = True + LockRight = True + LockTop = True + TabIndex = 0 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 0 + Transparent = True + Visible = True + Width = 600 + Begin UITweaks.ResizedLabel ServerNameLabel + AllowAutoDeactivate= True + Bold = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 22 + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 20 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Multiline = False + Scope = 2 + Selectable = False + TabIndex = 0 + TabPanelIndex = 0 + TabStop = True + Text = "Server Name:" + TextAlignment = 3 + TextColor = &c00000000 + Tooltip = "" + Top = 20 + Transparent = False + Underline = False + Visible = True + Width = 136 + End + Begin UITweaks.ResizedTextField ServerNameField + AllowAutoDeactivate= True + AllowFocusRing = True + AllowSpellChecking= False + AllowTabs = False + BackgroundColor = &cFFFFFF00 + Bold = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Format = "" + HasBorder = True + Height = 22 + Hint = "" + Index = -2147483648 + Italic = False + Left = 168 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + MaximumCharactersAllowed= 0 + Password = False + ReadOnly = False + Scope = 2 + TabIndex = 1 + TabPanelIndex = 0 + TabStop = True + Text = "" + TextAlignment = 0 + TextColor = &c00000000 + Tooltip = "" + Top = 20 + Transparent = False + Underline = False + ValidationMask = "" + Visible = True + Width = 412 + End + Begin UITweaks.ResizedLabel ConfigSetLabel + AllowAutoDeactivate= True + Bold = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 20 + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 20 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Multiline = False + Scope = 2 + Selectable = False + TabIndex = 6 + TabPanelIndex = 0 + TabStop = True + Text = "Config Sets:" + TextAlignment = 3 + TextColor = &c00000000 + Tooltip = "" + Top = 122 + Transparent = False + Underline = False + Visible = True + Width = 136 + End + Begin UITweaks.ResizedLabel ConfigSetField + AllowAutoDeactivate= True + Bold = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 20 + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 168 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + Multiline = False + Scope = 2 + Selectable = False + TabIndex = 7 + TabPanelIndex = 0 + TabStop = True + Text = "Base" + TextAlignment = 0 + TextColor = &c00000000 + Tooltip = "" + Top = 122 + Transparent = False + Underline = False + Visible = True + Width = 320 + End + Begin UITweaks.ResizedPushButton ConfigSetChooseButton + AllowAutoDeactivate= True + Bold = False + Cancel = False + Caption = "Choose…" + Default = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 20 + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 500 + LockBottom = False + LockedInPosition= False + LockLeft = False + LockRight = True + LockTop = True + MacButtonStyle = 0 + Scope = 2 + TabIndex = 8 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 122 + Transparent = False + Underline = False + Visible = True + Width = 80 + End + Begin UITweaks.ResizedTextField AdminPasswordField + AllowAutoDeactivate= True + AllowFocusRing = True + AllowSpellChecking= False + AllowTabs = False + BackgroundColor = &cFFFFFF00 + Bold = False + Enabled = False + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Format = "" + HasBorder = True + Height = 22 + Hint = "" + Index = -2147483648 + Italic = False + Left = 220 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + MaximumCharactersAllowed= 0 + Password = False + ReadOnly = False + Scope = 2 + TabIndex = 13 + TabPanelIndex = 0 + TabStop = True + Text = "" + TextAlignment = 0 + TextColor = &c00000000 + Tooltip = "" + Top = 230 + Transparent = False + Underline = False + ValidationMask = "" + Visible = True + Width = 360 + End + Begin UITweaks.ResizedTextField ServerPasswordField + AllowAutoDeactivate= True + AllowFocusRing = True + AllowSpellChecking= False + AllowTabs = False + BackgroundColor = &cFFFFFF00 + Bold = False + Enabled = False + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Format = "" + HasBorder = True + Height = 22 + Hint = "" + Index = -2147483648 + Italic = False + Left = 220 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + MaximumCharactersAllowed= 0 + Password = False + ReadOnly = False + Scope = 2 + TabIndex = 16 + TabPanelIndex = 0 + TabStop = True + Text = "" + TextAlignment = 0 + TextColor = &c00000000 + Tooltip = "" + Top = 264 + Transparent = False + Underline = False + ValidationMask = "" + Visible = True + Width = 360 + End + Begin UITweaks.ResizedLabel AdminPasswordLabel + AllowAutoDeactivate= True + Bold = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 22 + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 20 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Multiline = False + Scope = 2 + Selectable = False + TabIndex = 11 + TabPanelIndex = 0 + TabStop = True + Text = "Admin Password:" + TextAlignment = 3 + TextColor = &c00000000 + Tooltip = "" + Top = 230 + Transparent = True + Underline = False + Visible = True + Width = 136 + End + Begin UITweaks.ResizedLabel ServerPasswordLabel + AllowAutoDeactivate= True + Bold = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 22 + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 20 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Multiline = False + Scope = 2 + Selectable = False + TabIndex = 14 + TabPanelIndex = 0 + TabStop = True + Text = "Server Password:" + TextAlignment = 3 + TextColor = &c00000000 + Tooltip = "" + Top = 264 + Transparent = True + Underline = False + Visible = True + Width = 136 + End + Begin ProfileColorChooser ColorChooser + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = True + AllowTabs = False + Backdrop = 0 + ContentHeight = 0 + Enabled = True + Height = 22 + Index = -2147483648 + InitialParent = "" + Left = 168 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Scope = 0 + ScrollActive = False + ScrollingEnabled= False + ScrollSpeed = 20 + SelectedColor = "" + TabIndex = 5 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 88 + Transparent = True + Visible = True + Width = 220 + End + Begin UITweaks.ResizedLabel ColorLabel + AllowAutoDeactivate= True + Bold = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 22 + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 20 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Multiline = False + Scope = 2 + Selectable = False + TabIndex = 4 + TabPanelIndex = 0 + TabStop = True + Text = "Color:" + TextAlignment = 3 + TextColor = &c00000000 + Tooltip = "" + Top = 88 + Transparent = False + Underline = False + Visible = True + Width = 136 + End + Begin UITweaks.ResizedLabel ServerNicknameLabel + AllowAutoDeactivate= True + Bold = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 22 + Index = -2147483648 + Italic = False + Left = 20 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Multiline = False + Scope = 2 + Selectable = False + TabIndex = 2 + TabPanelIndex = 0 + TabStop = True + Text = "Nickname:" + TextAlignment = 3 + TextColor = &c00000000 + Tooltip = "#TooltipNickname" + Top = 54 + Transparent = False + Underline = False + Visible = True + Width = 136 + End + Begin UITweaks.ResizedTextField ServerNicknameField + AllowAutoDeactivate= True + AllowFocusRing = True + AllowSpellChecking= False + AllowTabs = False + BackgroundColor = &cFFFFFF00 + Bold = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Format = "" + HasBorder = True + Height = 22 + Hint = "" + Index = -2147483648 + Italic = False + Left = 168 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + MaximumCharactersAllowed= 0 + Password = False + ReadOnly = False + Scope = 2 + TabIndex = 3 + TabPanelIndex = 0 + TabStop = True + Text = "" + TextAlignment = 0 + TextColor = &c00000000 + Tooltip = "#TooltipNickname" + Top = 54 + Transparent = False + Underline = False + ValidationMask = "" + Visible = True + Width = 412 + End + Begin DesktopTextArea DescriptionField + AllowAutoDeactivate= True + AllowFocusRing = True + AllowSpellChecking= True + AllowStyledText = True + AllowTabs = False + BackgroundColor = &cFFFFFF + Bold = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Format = "" + HasBorder = True + HasHorizontalScrollbar= False + HasVerticalScrollbar= True + Height = 64 + HideSelection = True + Index = -2147483648 + Italic = False + Left = 168 + LineHeight = 0.0 + LineSpacing = 1.0 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + MaximumCharactersAllowed= 0 + Multiline = True + ReadOnly = False + Scope = 2 + TabIndex = 10 + TabPanelIndex = 0 + TabStop = True + Text = "" + TextAlignment = 0 + TextColor = &c000000 + Tooltip = "" + Top = 154 + Transparent = False + Underline = False + UnicodeMode = 1 + ValidationMask = "" + Visible = True + Width = 412 + End + Begin UITweaks.ResizedLabel DescriptionLabel + AllowAutoDeactivate= True + Bold = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 22 + Index = -2147483648 + Italic = False + Left = 20 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Multiline = False + Scope = 2 + Selectable = False + TabIndex = 9 + TabPanelIndex = 0 + TabStop = True + Text = "Description:" + TextAlignment = 3 + TextColor = &c00000000 + Tooltip = "" + Top = 154 + Transparent = True + Underline = False + Visible = True + Width = 136 + End + Begin SwitchControl AdminPasswordSwitch + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = True + AllowTabs = False + Backdrop = 0 + ContentHeight = 0 + Enabled = True + Height = 20 + Index = -2147483648 + Left = 168 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Scope = 2 + ScrollActive = False + ScrollingEnabled= False + ScrollSpeed = 20 + TabIndex = 12 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 231 + Transparent = True + Visible = True + Width = 40 + End + Begin SwitchControl ServerPasswordSwitch + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = True + AllowTabs = False + Backdrop = 0 + ContentHeight = 0 + Enabled = True + Height = 20 + Index = -2147483648 + Left = 168 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Scope = 2 + ScrollActive = False + ScrollingEnabled= False + ScrollSpeed = 20 + TabIndex = 15 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 265 + Transparent = True + Visible = True + Width = 40 + End +End +#tag EndDesktopWindow + +#tag WindowCode + #tag Method, Flags = &h21 + Private Function Modified() As Boolean + For Each Profile As Palworld.ServerProfile In Self.mProfiles + If (Profile Is Nil) = False And Profile.Modified Then + Return True + End If + Next + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function Profile() As Palworld.ServerProfile + For Each Profile As Palworld.ServerProfile In Self.mProfiles + If (Profile Is Nil) = False Then + Return Profile + End If + Next + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Profile(Assigns Profile As Palworld.ServerProfile) + Var Items(0) As Palworld.ServerProfile + Items(0) = Profile + Self.Profiles = Items + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Function Profiles() As Palworld.ServerProfile() + Return Self.mProfiles + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Profiles(Assigns Items() As Palworld.ServerProfile) + Self.mProfiles = Items + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Function Project() As Palworld.Project + Return RaiseEvent GetProject + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub RefreshUI() + Var Profile As Palworld.ServerProfile = Self.Profile + + If Self.SettingUp Or Profile Is Nil Then + Return + End If + + Self.mSettingUp = True + + Self.ServerNameField.Text = Profile.Name + Self.ServerNicknameField.Text = Profile.Nickname + Self.DescriptionField.Text = Profile.ServerDescription + Self.ColorChooser.SelectedColor = Profile.ProfileColor + + If Profile.AdminPassword Is Nil Then + Self.AdminPasswordSwitch.Value(False) = False + Self.AdminPasswordField.Text = "" + Else + Self.AdminPasswordSwitch.Value(False) = True + Self.AdminPasswordField.Text = Profile.AdminPassword.StringValue + End If + + If Profile.ServerPassword Is Nil Then + Self.ServerPasswordSwitch.Value(False) = False + Self.ServerPasswordField.Text = "" + Else + Self.ServerPasswordSwitch.Value(False) = True + Self.ServerPasswordField.Text = Profile.ServerPassword.StringValue + End If + + Self.UpdateConfigSetUI() + Self.Resize(True) + + Self.mSettingUp = False + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub UpdateConfigSetUI() + Var Project As Palworld.Project = Self.Project + + If Project.ConfigSetCount > 1 Then + Var Sets() As Beacon.ConfigSet = Beacon.ConfigSetState.FilterSets(Self.Profile.ConfigSetStates, Project.ConfigSets) + + Var EnabledSets() As String + For Each Set As Beacon.ConfigSet In Sets + EnabledSets.Add(Set.Name) + Next + + Self.ConfigSetField.Text = EnabledSets.EnglishOxfordList() + Self.ConfigSetChooseButton.Enabled = True + Else + Self.ConfigSetField.Text = Beacon.ConfigSet.BaseConfigSet.Name + Self.ConfigSetChooseButton.Enabled = False + End If + End Sub + #tag EndMethod + + + #tag Hook, Flags = &h0 + Event GetProject() As Palworld.Project + #tag EndHook + + + #tag Property, Flags = &h21 + Private mProfiles() As Palworld.ServerProfile + #tag EndProperty + + #tag Property, Flags = &h21 + Private mSettingUp As Boolean + #tag EndProperty + + #tag ComputedProperty, Flags = &h0 + #tag Getter + Get + Return Self.mSettingUp + End Get + #tag EndGetter + SettingUp As Boolean + #tag EndComputedProperty + + + #tag Constant, Name = TooltipNickname, Type = String, Dynamic = False, Default = \"The server nickname is only used by Beacon to help you organize.", Scope = Private + #tag EndConstant + + +#tag EndWindowCode + +#tag Events ServerNameField + #tag Event + Sub TextChanged() + If Self.mSettingUp Then + Return + End If + + For Each Profile As Palworld.ServerProfile In Self.mProfiles + Profile.Name = Me.Text + Next + Self.Modified = Self.Modified + End Sub + #tag EndEvent +#tag EndEvents +#tag Events ConfigSetChooseButton + #tag Event + Sub Pressed() + Var MainProfile As Palworld.ServerProfile = Self.Profile + If MainProfile Is Nil Then + Return + End If + + Var Sets() As Beacon.ConfigSet = Self.Project.ConfigSets + Var States() As Beacon.ConfigSetState = MainProfile.ConfigSetStates(Self.Project) + If ConfigSetSelectorDialog.Present(Self, Sets, States) Then + Profile.ConfigSetStates = States + End If + + Self.UpdateConfigSetUI() + Self.Modified = Self.Modified + End Sub + #tag EndEvent +#tag EndEvents +#tag Events AdminPasswordField + #tag Event + Sub TextChanged() + If Self.SettingUp Or Self.AdminPasswordSwitch.Value = False Then + Return + End If + + For Each Profile As Palworld.ServerProfile In Self.mProfiles + Profile.AdminPassword = Me.Text + Next + Self.Modified = Self.Modified + End Sub + #tag EndEvent +#tag EndEvents +#tag Events ServerPasswordField + #tag Event + Sub TextChanged() + If Self.SettingUp Or Self.ServerPasswordSwitch.Value = False Then + Return + End If + + For Each Profile As Palworld.ServerProfile In Self.mProfiles + Profile.ServerPassword = Me.Text + Next + Self.Modified = Self.Modified + End Sub + #tag EndEvent +#tag EndEvents +#tag Events ColorChooser + #tag Event + Sub Change() + If Self.mSettingUp Then + Return + End If + + For Each Profile As Palworld.ServerProfile In Self.mProfiles + Profile.ProfileColor = Me.SelectedColor + Next + Self.Modified = Self.Modified + End Sub + #tag EndEvent +#tag EndEvents +#tag Events ServerNicknameField + #tag Event + Sub TextChanged() + If Self.mSettingUp Then + Return + End If + + For Each Profile As Palworld.ServerProfile In Self.mProfiles + Profile.Nickname = Me.Text + Next + Self.Modified = Self.Modified + End Sub + #tag EndEvent +#tag EndEvents +#tag Events DescriptionField + #tag Event + Sub TextChanged() + If Self.SettingUp Then + Return + End If + + For Each Profile As Palworld.ServerProfile In Self.mProfiles + Profile.ServerDescription = Me.Text + Next + Self.Modified = Self.Modified + End Sub + #tag EndEvent +#tag EndEvents +#tag Events AdminPasswordSwitch + #tag Event + Sub Pressed() + Self.AdminPasswordField.Enabled = Me.Value + + If Self.mSettingUp Then + Return + End If + + For Each Profile As Palworld.ServerProfile In Self.mProfiles + If Me.Value Then + Profile.AdminPassword = Self.AdminPasswordField.Text + Else + Profile.AdminPassword = Nil + End If + Next + Self.Modified = Self.Modified + End Sub + #tag EndEvent +#tag EndEvents +#tag Events ServerPasswordSwitch + #tag Event + Sub Pressed() + Self.ServerPasswordField.Enabled = Me.Value + + If Self.mSettingUp Then + Return + End If + + For Each Profile As Palworld.ServerProfile In Self.mProfiles + If Me.Value Then + Profile.ServerPassword = Self.ServerPasswordField.Text + Else + Profile.ServerPassword = Nil + End If + Next + Self.Modified = Self.Modified + End Sub + #tag EndEvent +#tag EndEvents +#tag ViewBehavior + #tag ViewProperty + Name="Modified" + Visible=false + Group="Behavior" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Composited" + Visible=true + Group="Window Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Width" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Height" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockLeft" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockTop" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockRight" + Visible=true + Group="Position" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockBottom" + Visible=true + Group="Position" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabIndex" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabStop" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowAutoDeactivate" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Enabled" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Tooltip" + Visible=true + Group="Appearance" + InitialValue="" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocusRing" + Visible=true + Group="Appearance" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Visible" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="BackgroundColor" + Visible=true + Group="Background" + InitialValue="&hFFFFFF" + Type="ColorGroup" + EditorType="ColorGroup" + #tag EndViewProperty + #tag ViewProperty + Name="Backdrop" + Visible=true + Group="Background" + InitialValue="" + Type="Picture" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="HasBackgroundColor" + Visible=true + Group="Background" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocus" + Visible=true + Group="Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowTabs" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Transparent" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="SettingUp" + Visible=false + Group="Behavior" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="InitialParent" + Visible=false + Group="Position" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabPanelIndex" + Visible=false + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty +#tag EndViewBehavior diff --git a/Project/Views/Palworld/Config Editors/Servers/PalworldFTPServerView.xojo_window b/Project/Views/Palworld/Config Editors/Servers/PalworldFTPServerView.xojo_window new file mode 100644 index 000000000..df87cd977 --- /dev/null +++ b/Project/Views/Palworld/Config Editors/Servers/PalworldFTPServerView.xojo_window @@ -0,0 +1,860 @@ +#tag DesktopWindow +Begin PalworldServerViewContainer PalworldFTPServerView + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = False + AllowTabs = True + Backdrop = 0 + BackgroundColor = &cFFFFFF00 + Composited = False + Enabled = True + HasBackgroundColor= False + Height = 600 + Index = -2147483648 + InitialParent = "" + Left = 0 + LockBottom = True + LockLeft = True + LockRight = True + LockTop = True + TabIndex = 0 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 0 + Transparent = True + Visible = True + Width = 600 + Begin DesktopPagePanel Pages + AllowAutoDeactivate= True + Enabled = True + Height = 559 + Index = -2147483648 + InitialParent = "" + Left = 0 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + PanelCount = 3 + Panels = "" + Scope = 2 + SelectedPanelIndex= 0 + TabIndex = 1 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 41 + Transparent = False + Value = 1 + Visible = True + Width = 600 + Begin BeaconTextArea AdminNotesField + AllowAutoDeactivate= True + AllowFocusRing = True + AllowSpellChecking= True + AllowStyledText = True + AllowTabs = False + BackgroundColor = &cFFFFFF00 + Bold = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Format = "" + HasBorder = True + HasHorizontalScrollbar= False + HasVerticalScrollbar= True + Height = 519 + HideSelection = True + Index = -2147483648 + InitialParent = "Pages" + Italic = False + Left = 20 + LineHeight = 0.0 + LineSpacing = 1.0 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + MaximumCharactersAllowed= 0 + Multiline = True + ReadOnly = False + Scope = 2 + TabIndex = 0 + TabPanelIndex = 3 + TabStop = True + Text = "" + TextAlignment = 0 + TextColor = &c00000000 + Tooltip = "" + Top = 61 + Transparent = False + Underline = False + UnicodeMode = 1 + ValidationMask = "" + Visible = True + Width = 560 + End + Begin PalworldCommonServerSettingsView SettingsView + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = False + AllowTabs = True + Backdrop = 0 + BackgroundColor = &cFFFFFF00 + Composited = False + Enabled = True + HasBackgroundColor= False + Height = 559 + Index = -2147483648 + InitialParent = "Pages" + Left = 0 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + Modified = False + Scope = 2 + SettingUp = False + TabIndex = 0 + TabPanelIndex = 1 + TabStop = True + Tooltip = "" + Top = 41 + Transparent = True + Visible = True + Width = 600 + End + Begin DesktopGroupBox ConnectionGroup + AllowAutoDeactivate= True + Bold = False + Caption = "Connection" + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 310 + Index = -2147483648 + InitialParent = "Pages" + Italic = False + Left = 20 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + Scope = 2 + TabIndex = 0 + TabPanelIndex = 2 + TabStop = True + Tooltip = "" + Top = 61 + Transparent = False + Underline = False + Visible = True + Width = 560 + Begin FTPSettingsView ConnectionView + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = False + AllowTabs = True + Backdrop = 0 + BackgroundColor = &cFFFFFF + Composited = False + Enabled = True + HasBackgroundColor= False + Height = 294 + Host = "" + Index = -2147483648 + InitialParent = "ConnectionGroup" + InternalizeKey = False + Left = 20 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + Mode = "" + Modified = False + Password = "" + Port = 0 + Scope = 2 + TabIndex = 0 + TabPanelIndex = 2 + TabStop = True + Tooltip = "" + Top = 77 + Transparent = True + UsePublicKeyAuth= False + Username = "" + VerifyTLSCertificate= False + Visible = True + Width = 560 + End + End + Begin DesktopGroupBox PathsGroup + AllowAutoDeactivate= True + Bold = False + Caption = "Paths" + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 114 + Index = -2147483648 + InitialParent = "Pages" + Italic = False + Left = 20 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + Scope = 2 + TabIndex = 1 + TabPanelIndex = 2 + TabStop = True + Tooltip = "" + Top = 383 + Transparent = False + Underline = False + Visible = True + Width = 560 + Begin UITweaks.ResizedTextField SettingsIniPathField + AllowAutoDeactivate= True + AllowFocusRing = True + AllowSpellChecking= False + AllowTabs = False + BackgroundColor = &cFFFFFF + Bold = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Format = "" + HasBorder = True + Height = 22 + Hint = "" + Index = -2147483648 + InitialParent = "PathsGroup" + Italic = False + Left = 224 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + MaximumCharactersAllowed= 0 + Password = False + ReadOnly = False + Scope = 2 + TabIndex = 1 + TabPanelIndex = 2 + TabStop = True + Text = "" + TextAlignment = 0 + TextColor = &c00000000 + Tooltip = "" + Top = 419 + Transparent = False + Underline = False + ValidationMask = "" + Visible = True + Width = 336 + End + Begin UITweaks.ResizedLabel SettingsIniPathLabel + AllowAutoDeactivate= True + Bold = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 22 + Index = -2147483648 + InitialParent = "PathsGroup" + Italic = False + Left = 40 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Multiline = False + Scope = 2 + Selectable = False + TabIndex = 0 + TabPanelIndex = 2 + TabStop = True + Text = "PalWorldSettings.ini Path:" + TextAlignment = 3 + TextColor = &c00000000 + Tooltip = "" + Top = 419 + Transparent = False + Underline = False + Visible = True + Width = 172 + End + Begin UITweaks.ResizedTextField LogsPathField + AllowAutoDeactivate= True + AllowFocusRing = True + AllowSpellChecking= False + AllowTabs = False + BackgroundColor = &cFFFFFF + Bold = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Format = "" + HasBorder = True + Height = 22 + Hint = "" + Index = -2147483648 + InitialParent = "PathsGroup" + Italic = False + Left = 224 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + MaximumCharactersAllowed= 0 + Password = False + ReadOnly = False + Scope = 2 + TabIndex = 3 + TabPanelIndex = 2 + TabStop = True + Text = "" + TextAlignment = 0 + TextColor = &c00000000 + Tooltip = "" + Top = 453 + Transparent = False + Underline = False + ValidationMask = "" + Visible = True + Width = 336 + End + Begin UITweaks.ResizedLabel LogsPathLabel + AllowAutoDeactivate= True + Bold = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 22 + Index = -2147483648 + InitialParent = "PathsGroup" + Italic = False + Left = 40 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Multiline = False + Scope = 2 + Selectable = False + TabIndex = 2 + TabPanelIndex = 2 + TabStop = True + Text = "Logs Path:" + TextAlignment = 3 + TextColor = &c00000000 + Tooltip = "" + Top = 453 + Transparent = False + Underline = False + Visible = True + Width = 172 + End + End + End + Begin OmniBar ControlToolbar + Alignment = 0 + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = True + AllowTabs = False + Backdrop = 0 + BackgroundColor = "" + ContentHeight = 0 + Enabled = True + Height = 41 + Index = -2147483648 + InitialParent = "" + Left = 0 + LeftPadding = -1 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + RightPadding = -1 + Scope = 2 + ScrollActive = False + ScrollingEnabled= False + ScrollSpeed = 20 + TabIndex = 0 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 0 + Transparent = True + Visible = True + Width = 600 + End +End +#tag EndDesktopWindow + +#tag WindowCode + #tag Event + Sub Shown(UserData As Variant = Nil) + #Pragma Unused UserData + + BeaconUI.SizeToFit(Self.SettingsIniPathLabel, Self.LogsPathLabel) + Var FieldsLeft As Integer = Self.SettingsIniPathLabel.Right + 12 + Var FieldsWidth As Integer = Self.PathsGroup.Right - (20 + FieldsLeft) + Var Fields() As DesktopUIControl = Array(Self.SettingsIniPathField, Self.LogsPathField) + For Each Field As DesktopUIControl In Fields + Field.Left = FieldsLeft + Field.Width = FieldsWidth + Next + + Self.mSettingUp = True + Self.AdminNotesField.Text = Self.Profile.AdminNotes + Self.SettingsIniPathField.Text = Self.Profile.SettingsIniPath + Self.LogsPathField.Text = Self.Profile.LogsPath + + Var Config As Beacon.HostConfig = Self.Profile.HostConfig + If (Config Is Nil) = False And Config IsA FTP.HostConfig Then + Var FTPConfig As FTP.HostConfig = FTP.HostConfig(Config) + Self.ConnectionView.Mode = FTPConfig.Mode + Self.ConnectionView.Host = FTPConfig.Host + Self.ConnectionView.Port = FTPConfig.Port + Self.ConnectionView.Username = FTPConfig.Username + Self.ConnectionView.Password = FTPConfig.Password + Self.ConnectionView.VerifyTLSCertificate = FTPConfig.VerifyHost + Self.ConnectionView.UsePublicKeyAuth = (FTPConfig.PrivateKeyFile Is Nil) = False + Self.ConnectionView.PrivateKeyFile = FTPConfig.PrivateKeyFile + Self.ConnectionView.InternalizeKey = FTPConfig.IsPrivateKeyInternal + End If + + Self.SettingsView.RefreshUI() + Self.mSettingUp = False + End Sub + #tag EndEvent + + + #tag Property, Flags = &h21 + Private mSettingUp As Boolean + #tag EndProperty + + +#tag EndWindowCode + +#tag Events Pages + #tag Event + Sub PanelChanged() + For Idx As Integer = 0 To Self.ControlToolbar.LastIndex + Self.ControlToolbar.Item(Idx).Toggled = Me.SelectedPanelIndex = Idx + Next + End Sub + #tag EndEvent +#tag EndEvents +#tag Events AdminNotesField + #tag Event + Sub TextChanged() + If Self.mSettingUp Then + Return + End If + + Self.Profile.AdminNotes = Me.Text + Self.Modified = Self.Profile.Modified + End Sub + #tag EndEvent +#tag EndEvents +#tag Events SettingsView + #tag Event + Sub ContentsChanged() + If Self.mSettingUp Then + Return + End If + + Self.Modified = Me.Modified + End Sub + #tag EndEvent + #tag Event + Function GetProject() As Palworld.Project + Return Self.Project + End Function + #tag EndEvent + #tag Event + Sub Opening() + Me.Profile = Self.Profile + End Sub + #tag EndEvent +#tag EndEvents +#tag Events ConnectionView + #tag Event + Sub WantsHeightChange(NewDesiredHeight As Integer) + Self.ConnectionGroup.Height = NewDesiredHeight + 16 + Self.PathsGroup.Top = Self.ConnectionGroup.Bottom + 12 + End Sub + #tag EndEvent + #tag Event + Sub ContentsChanged() + If Me.Ready = False Or Self.mSettingUp Then + Return + End If + + Self.mSettingUp = True + + Var Config As FTP.HostConfig + If (Self.Profile.HostConfig Is Nil) = False And Self.Profile.HostConfig IsA FTP.HostConfig Then + Config = FTP.HostConfig(Self.Profile.HostConfig) + Else + Config = New FTP.HostConfig + Self.Profile.HostConfig = Config + End If + + Config.Mode = Me.Mode + Config.Host = Me.Host + Config.Port = Me.Port + Config.Username = Me.Username + Config.Password = Me.Password + Config.VerifyHost = Me.VerifyTLSCertificate + + If Me.UsePublicKeyAuth Then + Config.PrivateKeyFile(Me.InternalizeKey) = Me.PrivateKeyFile + Me.PrivateKeyFile = Config.PrivateKeyFile // Just to make sure the path shows correctly + Else + Config.PrivateKeyFile = Nil + End If + + Self.mSettingUp = False + + Self.Modified = True + End Sub + #tag EndEvent +#tag EndEvents +#tag Events SettingsIniPathField + #tag Event + Sub TextChanged() + Self.Profile.SettingsIniPath = Me.Text + Self.Modified = Self.Profile.Modified + End Sub + #tag EndEvent +#tag EndEvents +#tag Events LogsPathField + #tag Event + Sub TextChanged() + Self.Profile.LogsPath = Me.Text + Self.Modified = Self.Profile.Modified + End Sub + #tag EndEvent +#tag EndEvents +#tag Events ControlToolbar + #tag Event + Sub Opening() + Me.Append(OmniBarItem.CreateTab("PageGeneral", "General")) + Me.Append(OmniBarItem.CreateTab("PageConnection", "Connection")) + Me.Append(OmniBarItem.CreateTab("PageNotes", "Notes")) + Me.Item("PageGeneral").Toggled = True + End Sub + #tag EndEvent + #tag Event + Sub ItemPressed(Item As OmniBarItem, ItemRect As Rect) + #Pragma Unused ItemRect + + Select Case Item.Name + Case "PageGeneral" + Self.Pages.SelectedPanelIndex = 0 + Case "PageConnection" + Self.Pages.SelectedPanelIndex = 1 + Case "PageNotes" + Self.Pages.SelectedPanelIndex = 2 + End Select + End Sub + #tag EndEvent +#tag EndEvents +#tag ViewBehavior + #tag ViewProperty + Name="Modified" + Visible=false + Group="Behavior" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Composited" + Visible=true + Group="Window Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="IsFrontmost" + Visible=false + Group="Behavior" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="ViewTitle" + Visible=true + Group="Behavior" + InitialValue="Untitled" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="ViewIcon" + Visible=false + Group="Behavior" + InitialValue="" + Type="Picture" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Progress" + Visible=false + Group="Behavior" + InitialValue="" + Type="Double" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Tooltip" + Visible=true + Group="Appearance" + InitialValue="" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="AllowAutoDeactivate" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocusRing" + Visible=true + Group="Appearance" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="BackgroundColor" + Visible=true + Group="Background" + InitialValue="&hFFFFFF" + Type="ColorGroup" + EditorType="ColorGroup" + #tag EndViewProperty + #tag ViewProperty + Name="HasBackgroundColor" + Visible=true + Group="Background" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocus" + Visible=true + Group="Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowTabs" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="MinimumWidth" + Visible=true + Group="Behavior" + InitialValue="400" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="MinimumHeight" + Visible=true + Group="Behavior" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Width" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Height" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="InitialParent" + Visible=false + Group="Position" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockLeft" + Visible=true + Group="Position" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockTop" + Visible=true + Group="Position" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockRight" + Visible=true + Group="Position" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockBottom" + Visible=true + Group="Position" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabPanelIndex" + Visible=false + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabIndex" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabStop" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Visible" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Enabled" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Backdrop" + Visible=true + Group="Background" + InitialValue="" + Type="Picture" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Transparent" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty +#tag EndViewBehavior diff --git a/Project/Views/Palworld/Config Editors/Servers/PalworldGSAServerView.xojo_window b/Project/Views/Palworld/Config Editors/Servers/PalworldGSAServerView.xojo_window new file mode 100644 index 000000000..43269ccb3 --- /dev/null +++ b/Project/Views/Palworld/Config Editors/Servers/PalworldGSAServerView.xojo_window @@ -0,0 +1,512 @@ +#tag DesktopWindow +Begin PalworldServerViewContainer PalworldGSAServerView + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = False + AllowTabs = True + Backdrop = 0 + BackgroundColor = &cFFFFFF00 + Composited = False + Enabled = True + HasBackgroundColor= False + Height = 600 + Index = -2147483648 + InitialParent = "" + Left = 0 + LockBottom = True + LockLeft = True + LockRight = True + LockTop = True + TabIndex = 0 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 0 + Transparent = True + Visible = True + Width = 600 + Begin DesktopPagePanel Pages + AllowAutoDeactivate= True + Enabled = True + Height = 559 + Index = -2147483648 + InitialParent = "" + Left = 0 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + PanelCount = 2 + Panels = "" + Scope = 2 + SelectedPanelIndex= 0 + TabIndex = 1 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 41 + Transparent = False + Value = 0 + Visible = True + Width = 600 + Begin BeaconTextArea AdminNotesField + AllowAutoDeactivate= True + AllowFocusRing = True + AllowSpellChecking= True + AllowStyledText = True + AllowTabs = False + BackgroundColor = &cFFFFFF00 + Bold = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Format = "" + HasBorder = True + HasHorizontalScrollbar= False + HasVerticalScrollbar= True + Height = 519 + HideSelection = True + Index = -2147483648 + InitialParent = "Pages" + Italic = False + Left = 20 + LineHeight = 0.0 + LineSpacing = 1.0 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + MaximumCharactersAllowed= 0 + Multiline = True + ReadOnly = False + Scope = 2 + TabIndex = 0 + TabPanelIndex = 2 + TabStop = True + Text = "" + TextAlignment = 0 + TextColor = &c00000000 + Tooltip = "" + Top = 61 + Transparent = False + Underline = False + UnicodeMode = 1 + ValidationMask = "" + Visible = True + Width = 560 + End + Begin PalworldCommonServerSettingsView SettingsView + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = False + AllowTabs = True + Backdrop = 0 + BackgroundColor = &cFFFFFF00 + Composited = False + Enabled = True + HasBackgroundColor= False + Height = 559 + Index = -2147483648 + InitialParent = "Pages" + Left = 0 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + Modified = False + Scope = 2 + SettingUp = False + ShowsMapMenu = True + TabIndex = 0 + TabPanelIndex = 1 + TabStop = True + Tooltip = "" + Top = 41 + Transparent = True + Visible = True + Width = 600 + End + End + Begin OmniBar ControlToolbar + Alignment = 0 + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = True + AllowTabs = False + Backdrop = 0 + BackgroundColor = "" + ContentHeight = 0 + Enabled = True + Height = 41 + Index = -2147483648 + InitialParent = "" + Left = 0 + LeftPadding = -1 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + RightPadding = -1 + Scope = 2 + ScrollActive = False + ScrollingEnabled= False + ScrollSpeed = 20 + TabIndex = 0 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 0 + Transparent = True + Visible = True + Width = 600 + End +End +#tag EndDesktopWindow + +#tag WindowCode + #tag Event + Sub Shown(UserData As Variant = Nil) + #Pragma Unused UserData + + Self.AdminNotesField.Text = Self.Profile.AdminNotes + Self.SettingsView.RefreshUI() + End Sub + #tag EndEvent + + +#tag EndWindowCode + +#tag Events Pages + #tag Event + Sub PanelChanged() + For Idx As Integer = 0 To Self.ControlToolbar.LastIndex + Self.ControlToolbar.Item(Idx).Toggled = Me.SelectedPanelIndex = Idx + Next + End Sub + #tag EndEvent +#tag EndEvents +#tag Events AdminNotesField + #tag Event + Sub TextChanged() + Self.Profile.AdminNotes = Me.Text + Self.Modified = Self.Profile.Modified + End Sub + #tag EndEvent +#tag EndEvents +#tag Events SettingsView + #tag Event + Sub Opening() + Me.Profile = Self.Profile + End Sub + #tag EndEvent + #tag Event + Sub ContentsChanged() + Self.Modified = Me.Modified + End Sub + #tag EndEvent + #tag Event + Function GetProject() As Palworld.Project + Return Self.Project + End Function + #tag EndEvent +#tag EndEvents +#tag Events ControlToolbar + #tag Event + Sub Opening() + Me.Append(OmniBarItem.CreateTab("PageGeneral", "General")) + Me.Append(OmniBarItem.CreateTab("PageNotes", "Notes")) + Me.Item("PageGeneral").Toggled = True + End Sub + #tag EndEvent + #tag Event + Sub ItemPressed(Item As OmniBarItem, ItemRect As Rect) + #Pragma Unused ItemRect + + Select Case Item.Name + Case "PageGeneral" + Self.Pages.SelectedPanelIndex = 0 + Case "PageNotes" + Self.Pages.SelectedPanelIndex = 1 + End Select + End Sub + #tag EndEvent +#tag EndEvents +#tag ViewBehavior + #tag ViewProperty + Name="Modified" + Visible=false + Group="Behavior" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Composited" + Visible=true + Group="Window Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="IsFrontmost" + Visible=false + Group="Behavior" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="ViewTitle" + Visible=true + Group="Behavior" + InitialValue="Untitled" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="ViewIcon" + Visible=false + Group="Behavior" + InitialValue="" + Type="Picture" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Progress" + Visible=false + Group="Behavior" + InitialValue="" + Type="Double" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="MinimumWidth" + Visible=true + Group="Behavior" + InitialValue="400" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="MinimumHeight" + Visible=true + Group="Behavior" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Width" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Height" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="InitialParent" + Visible=false + Group="Position" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockLeft" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockTop" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockRight" + Visible=true + Group="Position" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockBottom" + Visible=true + Group="Position" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabIndex" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabPanelIndex" + Visible=false + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabStop" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowAutoDeactivate" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Enabled" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Tooltip" + Visible=true + Group="Appearance" + InitialValue="" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocusRing" + Visible=true + Group="Appearance" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Visible" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="BackgroundColor" + Visible=true + Group="Background" + InitialValue="&hFFFFFF" + Type="ColorGroup" + EditorType="ColorGroup" + #tag EndViewProperty + #tag ViewProperty + Name="Backdrop" + Visible=true + Group="Background" + InitialValue="" + Type="Picture" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="HasBackgroundColor" + Visible=true + Group="Background" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocus" + Visible=true + Group="Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowTabs" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Transparent" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty +#tag EndViewBehavior diff --git a/Project/Views/Palworld/Config Editors/Servers/PalworldLocalServerView.xojo_window b/Project/Views/Palworld/Config Editors/Servers/PalworldLocalServerView.xojo_window new file mode 100644 index 000000000..5da1ffd36 --- /dev/null +++ b/Project/Views/Palworld/Config Editors/Servers/PalworldLocalServerView.xojo_window @@ -0,0 +1,681 @@ +#tag DesktopWindow +Begin PalworldServerViewContainer PalworldLocalServerView + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = False + AllowTabs = True + Backdrop = 0 + BackgroundColor = &cFFFFFF00 + Composited = False + Enabled = True + HasBackgroundColor= False + Height = 600 + Index = -2147483648 + InitialParent = "" + Left = 0 + LockBottom = True + LockLeft = True + LockRight = True + LockTop = True + TabIndex = 0 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 0 + Transparent = True + Visible = True + Width = 600 + Begin DesktopPagePanel Pages + AllowAutoDeactivate= True + Enabled = True + Height = 559 + Index = -2147483648 + InitialParent = "" + Left = 0 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + PanelCount = 3 + Panels = "" + Scope = 2 + SelectedPanelIndex= 0 + TabIndex = 1 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 41 + Transparent = False + Value = 1 + Visible = True + Width = 600 + Begin BeaconTextArea AdminNotesField + AllowAutoDeactivate= True + AllowFocusRing = True + AllowSpellChecking= True + AllowStyledText = True + AllowTabs = False + BackgroundColor = &cFFFFFF00 + Bold = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Format = "" + HasBorder = True + HasHorizontalScrollbar= False + HasVerticalScrollbar= True + Height = 519 + HideSelection = True + Index = -2147483648 + InitialParent = "Pages" + Italic = False + Left = 20 + LineHeight = 0.0 + LineSpacing = 1.0 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + MaximumCharactersAllowed= 0 + Multiline = True + ReadOnly = False + Scope = 2 + TabIndex = 0 + TabPanelIndex = 3 + TabStop = True + Text = "" + TextAlignment = 0 + TextColor = &c00000000 + Tooltip = "" + Top = 61 + Transparent = False + Underline = False + UnicodeMode = 1 + ValidationMask = "" + Visible = True + Width = 560 + End + Begin UITweaks.ResizedLabel SettingsIniPathLabel + AllowAutoDeactivate= True + Bold = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 22 + Index = -2147483648 + InitialParent = "Pages" + Italic = False + Left = 20 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Multiline = False + Scope = 2 + Selectable = False + TabIndex = 0 + TabPanelIndex = 2 + TabStop = True + Text = "PalWorldSettings.ini File:" + TextAlignment = 3 + TextColor = &c00000000 + Tooltip = "" + Top = 61 + Transparent = False + Underline = False + Visible = True + Width = 172 + End + Begin UITweaks.ResizedTextField SettingsIniPathField + AllowAutoDeactivate= True + AllowFocusRing = True + AllowSpellChecking= False + AllowTabs = False + BackgroundColor = &cFFFFFF00 + Bold = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Format = "" + HasBorder = True + Height = 22 + Hint = "" + Index = -2147483648 + InitialParent = "Pages" + Italic = False + Left = 204 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + MaximumCharactersAllowed= 0 + Password = False + ReadOnly = True + Scope = 2 + TabIndex = 1 + TabPanelIndex = 2 + TabStop = True + Text = "" + TextAlignment = 0 + TextColor = &c00000000 + Tooltip = "" + Top = 61 + Transparent = False + Underline = False + ValidationMask = "" + Visible = True + Width = 274 + End + Begin UITweaks.ResizedPushButton SettingsIniChooseButton + AllowAutoDeactivate= True + Bold = False + Cancel = False + Caption = "Choose…" + Default = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 20 + Index = -2147483648 + InitialParent = "Pages" + Italic = False + Left = 490 + LockBottom = False + LockedInPosition= False + LockLeft = False + LockRight = True + LockTop = True + MacButtonStyle = 0 + Scope = 2 + TabIndex = 2 + TabPanelIndex = 2 + TabStop = True + Tooltip = "" + Top = 62 + Transparent = False + Underline = False + Visible = True + Width = 90 + End + Begin PalworldCommonServerSettingsView SettingsView + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = False + AllowTabs = True + Backdrop = 0 + BackgroundColor = &cFFFFFF00 + Composited = False + Enabled = True + HasBackgroundColor= False + Height = 559 + Index = -2147483648 + InitialParent = "Pages" + Left = 0 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + Modified = False + Scope = 2 + SettingUp = False + TabIndex = 0 + TabPanelIndex = 1 + TabStop = True + Tooltip = "" + Top = 41 + Transparent = True + Visible = True + Width = 600 + End + End + Begin OmniBar ControlToolbar + Alignment = 0 + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = True + AllowTabs = False + Backdrop = 0 + BackgroundColor = "" + ContentHeight = 0 + Enabled = True + Height = 41 + Index = -2147483648 + InitialParent = "" + Left = 0 + LeftPadding = -1 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + RightPadding = -1 + Scope = 2 + ScrollActive = False + ScrollingEnabled= False + ScrollSpeed = 20 + TabIndex = 0 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 0 + Transparent = True + Visible = True + Width = 600 + End +End +#tag EndDesktopWindow + +#tag WindowCode + #tag Event + Sub Shown(UserData As Variant = Nil) + #Pragma Unused UserData + + Self.AdminNotesField.Text = Self.Profile.AdminNotes + + Var SettingsIniPath As String = Self.Profile.SettingsIniPath + If SettingsIniPath.IsEmpty = False Then + Try + Var File As BookmarkedFolderItem = BookmarkedFolderItem.FromSaveInfo(SettingsIniPath) + If (File Is Nil) = False Then + Self.SettingsIniPathField.Text = File.NativePath + End If + Catch Err As RuntimeException + End Try + End If + + Self.SettingsView.RefreshUI() + End Sub + #tag EndEvent + + + #tag Method, Flags = &h21 + Private Function DetectConfigType(File As FolderItem) As String + Var Content As String = File.Read + Content = Content.GuessEncoding("/Script/") + Var SettingsIniPos As Integer = Content.IndexOf(Palworld.HeaderPalworldSettings) + + If SettingsIniPos > -1 Then + Return Palworld.ConfigFileSettings + ElseIf (File Is Nil) = False Then + Select Case File.Name + Case Palworld.ConfigFileSettings + Return File.Name + End Select + End If + + Return "Unknown" + End Function + #tag EndMethod + + +#tag EndWindowCode + +#tag Events Pages + #tag Event + Sub PanelChanged() + For Idx As Integer = 0 To Self.ControlToolbar.LastIndex + Self.ControlToolbar.Item(Idx).Toggled = Me.SelectedPanelIndex = Idx + Next + End Sub + #tag EndEvent +#tag EndEvents +#tag Events AdminNotesField + #tag Event + Sub TextChanged() + Self.Profile.AdminNotes = Me.Text + Self.Modified = Self.Profile.Modified + End Sub + #tag EndEvent +#tag EndEvents +#tag Events SettingsIniChooseButton + #tag Event + Sub Pressed() + Var Dialog As New OpenFileDialog + Dialog.Filter = BeaconFileTypes.IniFile + Dialog.SuggestedFileName = Palworld.ConfigFileSettings + + Var File As FolderItem = Dialog.ShowModal(Self.TrueWindow) + If File Is Nil Then + Return + End If + + Var DetectedType As String = Self.DetectConfigType(File) + If DetectedType <> Palworld.ConfigFileSettings And Self.ShowConfirm("The selected file does not appear to be a PalWorldSettings.ini file.", "You can still choose this file if you really want, but its content does not look like a PalWorldSettings.ini file. Proceed with caution.", "Use Anyway", "Cancel") = False Then + Return + End If + + Var Bookmark As New BookmarkedFolderItem(File.NativePath, FolderItem.PathModes.Native) + Self.Profile.SettingsIniPath = Bookmark.SaveInfo() + If Self.Profile.SecondaryName.IsEmpty Then + Self.Profile.SecondaryName = File.PartialPath + End If + + Self.SettingsIniPathField.Text = File.NativePath + Self.Modified = Self.Profile.Modified + End Sub + #tag EndEvent +#tag EndEvents +#tag Events SettingsView + #tag Event + Sub Opening() + Me.Profile = Self.Profile + End Sub + #tag EndEvent + #tag Event + Sub ContentsChanged() + Self.Modified = Me.Modified + End Sub + #tag EndEvent + #tag Event + Function GetProject() As Palworld.Project + Return Self.Project + End Function + #tag EndEvent +#tag EndEvents +#tag Events ControlToolbar + #tag Event + Sub Opening() + Me.Append(OmniBarItem.CreateTab("PageGeneral", "General")) + Me.Append(OmniBarItem.CreateTab("PageFiles", "Files")) + Me.Append(OmniBarItem.CreateTab("PageNotes", "Notes")) + Me.Item("PageGeneral").Toggled = True + End Sub + #tag EndEvent + #tag Event + Sub ItemPressed(Item As OmniBarItem, ItemRect As Rect) + #Pragma Unused ItemRect + + Select Case Item.Name + Case "PageGeneral" + Self.Pages.SelectedPanelIndex = 0 + Case "PageFiles" + Self.Pages.SelectedPanelIndex = 1 + Case "PageNotes" + Self.Pages.SelectedPanelIndex = 2 + End Select + End Sub + #tag EndEvent +#tag EndEvents +#tag ViewBehavior + #tag ViewProperty + Name="Modified" + Visible=false + Group="Behavior" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Composited" + Visible=true + Group="Window Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="IsFrontmost" + Visible=false + Group="Behavior" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="ViewTitle" + Visible=true + Group="Behavior" + InitialValue="Untitled" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="ViewIcon" + Visible=false + Group="Behavior" + InitialValue="" + Type="Picture" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Progress" + Visible=false + Group="Behavior" + InitialValue="" + Type="Double" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="MinimumWidth" + Visible=true + Group="Behavior" + InitialValue="400" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="MinimumHeight" + Visible=true + Group="Behavior" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Width" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Height" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="InitialParent" + Visible=false + Group="Position" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockLeft" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockTop" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockRight" + Visible=true + Group="Position" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockBottom" + Visible=true + Group="Position" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabIndex" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabPanelIndex" + Visible=false + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabStop" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowAutoDeactivate" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Enabled" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Tooltip" + Visible=true + Group="Appearance" + InitialValue="" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocusRing" + Visible=true + Group="Appearance" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Visible" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="BackgroundColor" + Visible=true + Group="Background" + InitialValue="&hFFFFFF" + Type="ColorGroup" + EditorType="ColorGroup" + #tag EndViewProperty + #tag ViewProperty + Name="Backdrop" + Visible=true + Group="Background" + InitialValue="" + Type="Picture" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="HasBackgroundColor" + Visible=true + Group="Background" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocus" + Visible=true + Group="Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowTabs" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Transparent" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty +#tag EndViewBehavior diff --git a/Project/Views/Palworld/Config Editors/Servers/PalworldMultiServerView.xojo_window b/Project/Views/Palworld/Config Editors/Servers/PalworldMultiServerView.xojo_window new file mode 100644 index 000000000..02c14b27a --- /dev/null +++ b/Project/Views/Palworld/Config Editors/Servers/PalworldMultiServerView.xojo_window @@ -0,0 +1,426 @@ +#tag DesktopWindow +Begin PalworldServerViewContainer PalworldMultiServerView + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = False + AllowTabs = True + Backdrop = 0 + BackgroundColor = &cFFFFFF00 + Composited = False + Enabled = True + HasBackgroundColor= False + Height = 600 + Index = -2147483648 + InitialParent = "" + Left = 0 + LockBottom = True + LockLeft = True + LockRight = True + LockTop = True + TabIndex = 0 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 0 + Transparent = True + Visible = True + Width = 600 + Begin OmniBar ConfigToolbar + Alignment = 0 + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = True + AllowTabs = False + Backdrop = 0 + BackgroundColor = "" + ContentHeight = 0 + Enabled = True + Height = 41 + Index = -2147483648 + InitialParent = "" + Left = 0 + LeftPadding = -1 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + RightPadding = -1 + Scope = 2 + ScrollActive = False + ScrollingEnabled= False + ScrollSpeed = 20 + TabIndex = 0 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 0 + Transparent = True + Visible = True + Width = 600 + End + Begin PalworldCommonServerSettingsView SettingsView + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = False + AllowTabs = True + Backdrop = 0 + BackgroundColor = &cFFFFFF00 + Composited = False + Enabled = True + HasBackgroundColor= False + Height = 559 + Index = -2147483648 + InitialParent = "" + Left = 0 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + Modified = False + Scope = 2 + SettingUp = False + ShowsMapMenu = True + TabIndex = 1 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 41 + Transparent = True + Visible = True + Width = 600 + End +End +#tag EndDesktopWindow + +#tag WindowCode + #tag Event + Sub Shown(UserData As Variant = Nil) + #Pragma Unused UserData + + Self.SettingsView.RefreshUI() + End Sub + #tag EndEvent + + + #tag Method, Flags = &h0 + Sub Constructor(Project As Palworld.Project, Profiles() As Palworld.ServerProfile) + Self.mProfiles = Profiles + Super.Constructor(Project, Nil) + End Sub + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Sub Constructor(Project As Palworld.Project, Profile As Palworld.ServerProfile) + // Hide this constructor + Super.Constructor(Project, Profile) + End Sub + #tag EndMethod + + + #tag Property, Flags = &h21 + Private mProfiles() As Palworld.ServerProfile + #tag EndProperty + + +#tag EndWindowCode + +#tag Events ConfigToolbar + #tag Event + Sub Opening() + Me.Append(OmniBarItem.CreateTitle("ConfigTitle", "Multiple Servers")) + End Sub + #tag EndEvent +#tag EndEvents +#tag Events SettingsView + #tag Event + Sub ContentsChanged() + Self.Modified = Me.Modified + End Sub + #tag EndEvent + #tag Event + Function GetProject() As Palworld.Project + Return Self.Project + End Function + #tag EndEvent + #tag Event + Sub Opening() + Me.Profiles = Self.mProfiles + End Sub + #tag EndEvent +#tag EndEvents +#tag ViewBehavior + #tag ViewProperty + Name="Modified" + Visible=false + Group="Behavior" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Composited" + Visible=true + Group="Window Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="IsFrontmost" + Visible=false + Group="Behavior" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="ViewTitle" + Visible=true + Group="Behavior" + InitialValue="Untitled" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="ViewIcon" + Visible=false + Group="Behavior" + InitialValue="" + Type="Picture" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Progress" + Visible=false + Group="Behavior" + InitialValue="ProgressNone" + Type="Double" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="MinimumWidth" + Visible=true + Group="Behavior" + InitialValue="400" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="MinimumHeight" + Visible=true + Group="Behavior" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Width" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Height" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="InitialParent" + Visible=false + Group="Position" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockLeft" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockTop" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockRight" + Visible=true + Group="Position" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockBottom" + Visible=true + Group="Position" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabIndex" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabPanelIndex" + Visible=false + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabStop" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowAutoDeactivate" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Enabled" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Tooltip" + Visible=true + Group="Appearance" + InitialValue="" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocusRing" + Visible=true + Group="Appearance" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Visible" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="BackgroundColor" + Visible=true + Group="Background" + InitialValue="&hFFFFFF" + Type="ColorGroup" + EditorType="ColorGroup" + #tag EndViewProperty + #tag ViewProperty + Name="Backdrop" + Visible=true + Group="Background" + InitialValue="" + Type="Picture" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="HasBackgroundColor" + Visible=true + Group="Background" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocus" + Visible=true + Group="Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowTabs" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Transparent" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty +#tag EndViewBehavior diff --git a/Project/Views/Palworld/Config Editors/Servers/PalworldNitradoServerView.xojo_window b/Project/Views/Palworld/Config Editors/Servers/PalworldNitradoServerView.xojo_window new file mode 100644 index 000000000..6e6a5ce47 --- /dev/null +++ b/Project/Views/Palworld/Config Editors/Servers/PalworldNitradoServerView.xojo_window @@ -0,0 +1,924 @@ +#tag DesktopWindow +Begin PalworldServerViewContainer PalworldNitradoServerView + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = False + AllowTabs = True + Backdrop = 0 + BackgroundColor = &cFFFFFF00 + Composited = False + Enabled = True + HasBackgroundColor= False + Height = 600 + Index = -2147483648 + InitialParent = "" + Left = 0 + LockBottom = True + LockLeft = True + LockRight = True + LockTop = True + TabIndex = 0 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 0 + Transparent = True + Visible = True + Width = 600 + Begin DesktopPagePanel Pages + AllowAutoDeactivate= True + Enabled = True + Height = 559 + Index = -2147483648 + InitialParent = "" + Left = 0 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + PanelCount = 2 + Panels = "" + Scope = 2 + SelectedPanelIndex= 0 + TabIndex = 1 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 41 + Transparent = False + Value = 0 + Visible = True + Width = 600 + Begin FadedSeparator FadedSeparator1 + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = True + AllowTabs = False + Backdrop = 0 + ContentHeight = 0 + Enabled = True + Height = 1 + Index = -2147483648 + InitialParent = "Pages" + Left = 10 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + Scope = 2 + ScrollActive = False + ScrollingEnabled= False + ScrollSpeed = 20 + TabIndex = 3 + TabPanelIndex = 1 + TabStop = True + Tooltip = "" + Top = 103 + Transparent = True + Visible = True + Width = 580 + End + Begin UITweaks.ResizedLabel ServerStatusField + AllowAutoDeactivate= True + Bold = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 22 + Index = -2147483648 + InitialParent = "Pages" + Italic = False + Left = 168 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + Multiline = False + Scope = 2 + Selectable = False + TabIndex = 1 + TabPanelIndex = 1 + TabStop = True + Text = "Checking…" + TextAlignment = 0 + TextColor = &c00000000 + Tooltip = "" + Top = 61 + Transparent = False + Underline = False + Visible = True + Width = 412 + End + Begin UITweaks.ResizedLabel ServerStatusLabel + AllowAutoDeactivate= True + Bold = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 22 + Index = -2147483648 + InitialParent = "Pages" + Italic = False + Left = 20 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Multiline = False + Scope = 2 + Selectable = False + TabIndex = 0 + TabPanelIndex = 1 + TabStop = True + Text = "Server Status:" + TextAlignment = 3 + TextColor = &c00000000 + Tooltip = "" + Top = 61 + Transparent = False + Underline = False + Visible = True + Width = 136 + End + Begin BeaconTextArea AdminNotesField + AllowAutoDeactivate= True + AllowFocusRing = True + AllowSpellChecking= True + AllowStyledText = True + AllowTabs = False + BackgroundColor = &cFFFFFF00 + Bold = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Format = "" + HasBorder = True + HasHorizontalScrollbar= False + HasVerticalScrollbar= True + Height = 519 + HideSelection = True + Index = -2147483648 + InitialParent = "Pages" + Italic = False + Left = 20 + LineHeight = 0.0 + LineSpacing = 1.0 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + MaximumCharactersAllowed= 0 + Multiline = True + ReadOnly = False + Scope = 2 + TabIndex = 0 + TabPanelIndex = 2 + TabStop = True + Text = "" + TextAlignment = 0 + TextColor = &c00000000 + Tooltip = "" + Top = 61 + Transparent = False + Underline = False + UnicodeMode = 1 + ValidationMask = "" + Visible = True + Width = 560 + End + Begin PalworldCommonServerSettingsView SettingsView + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = False + AllowTabs = True + Backdrop = 0 + BackgroundColor = &cFFFFFF00 + Composited = False + Enabled = True + HasBackgroundColor= False + Height = 496 + Index = -2147483648 + InitialParent = "Pages" + Left = 0 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + Modified = False + Scope = 2 + SettingUp = False + ShowsMapMenu = False + TabIndex = 2 + TabPanelIndex = 1 + TabStop = True + Tooltip = "" + Top = 104 + Transparent = True + Visible = True + Width = 600 + End + End + Begin OmniBar ControlToolbar + Alignment = 0 + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = True + AllowTabs = False + Backdrop = 0 + BackgroundColor = "" + ContentHeight = 0 + Enabled = True + Height = 41 + Index = -2147483648 + InitialParent = "" + Left = 0 + LeftPadding = -1 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + RightPadding = -1 + Scope = 2 + ScrollActive = False + ScrollingEnabled= False + ScrollSpeed = 20 + TabIndex = 0 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 0 + Transparent = True + Visible = True + Width = 600 + End + Begin Thread RefreshThread + DebugIdentifier = "" + Index = -2147483648 + LockedInPosition= False + Priority = 5 + Scope = 2 + StackSize = 0 + TabPanelIndex = 0 + ThreadID = 0 + ThreadState = 0 + End + Begin Beacon.Thread ToggleThread + DebugIdentifier = "" + Index = -2147483648 + LockedInPosition= False + Priority = 5 + Scope = 2 + StackSize = 0 + TabPanelIndex = 0 + ThreadID = 0 + ThreadState = 0 + End +End +#tag EndDesktopWindow + +#tag WindowCode + #tag Event + Sub Closing() + Self.CancelRefresh() + End Sub + #tag EndEvent + + #tag Event + Sub Hidden() + Self.CancelRefresh() + End Sub + #tag EndEvent + + #tag Event + Sub Opening() + Self.ToggleThread.DebugIdentifier = "ArkNitradoServerView.ToggleThread" + Self.RefreshThread.DebugIdentifier = "ArkNitradoServerView.RefreshThread" + End Sub + #tag EndEvent + + #tag Event + Sub Shown(UserData As Variant = Nil) + #Pragma Unused UserData + + If Self.mRefreshKey.IsEmpty Then + Self.RefreshServerStatus() + End If + + Self.AdminNotesField.Text = Self.Profile.AdminNotes + Self.SettingsView.RefreshUI() + Self.UpdateStatusDisplay() + End Sub + #tag EndEvent + + + #tag Method, Flags = &h21 + Private Sub CancelRefresh() + If Self.mRefreshKey.IsEmpty = False Then + CallLater.Cancel(Self.mRefreshKey) + Self.mRefreshKey = "" + End If + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Constructor(Project As Palworld.Project, Profile As Palworld.ServerProfile) + Self.mLock = New CriticalSection + Self.mServerStatus = New Beacon.ServerStatus("Checking…") + Super.Constructor(Project, Profile) + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub RefreshNow(Provider As Beacon.HostingProvider) + Try + Self.mServerStatus = Provider.GetServerStatus(Self.Project, Self.Profile) + Catch Err As RuntimeException + Self.mServerStatus = New Beacon.ServerStatus(Err.Message) + End Try + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub RefreshServerStatus() + Self.CancelRefresh() + + If Self.RefreshThread.ThreadState = Thread.ThreadStates.NotRunning Then + Self.RefreshThread.Start + End If + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub ScheduleRefresh() + Self.CancelRefresh() + + If Self.IsFrontmost Then + Self.mRefreshKey = CallLater.Schedule(5000, WeakAddressOf RefreshServerStatus) + Else + Self.mRefreshKey = "" + End If + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Shared Function StatusFromHTTPCode(HTTPStatus As Integer) As String + Select Case HTTPStatus + Case 401, 403 + Return StatusUnauthorized + Case 429 + Return StatusRateLimited + Case 502 + Return StatusBadGateway + Case 503 + Return StatusMaintenance + Else + #if DebugBuild + System.DebugLog("Nitrado status " + HTTPStatus.ToString) + #endif + Return StatusNitradoOther + End Select + End Function + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub UpdateStatusDisplay() + Var Started, ButtonEnabled As Boolean + Var Message As String + Select Case Self.mServerStatus.State + Case Beacon.ServerStatus.States.Running + Message = "Running" + ButtonEnabled = True + Started = True + Case Beacon.ServerStatus.States.Stopped + Message = "Stopped" + ButtonEnabled = True + Case Beacon.ServerStatus.States.Stopping + Message = "Stopping" + Case Beacon.ServerStatus.States.Starting + Message = "Starting" + Case Beacon.ServerStatus.States.Other + Message = Self.mServerStatus.Message + Else + Message = "Something else?" + End Select + If Self.ServerStatusField.Text <> Message Then + Self.ServerStatusField.Text = Message + End If + If Self.ServerStatusField.Tooltip <> Message Then + Self.ServerStatusField.Tooltip = Message + End If + + Self.ControlToolbar.Item("PowerButton").Enabled = ButtonEnabled + Self.ControlToolbar.Item("PowerButton").Toggled = Started + Self.ControlToolbar.Item("PowerButton").HelpTag = If(Started, "Stop the server", "Start the server") + Self.ControlToolbar.Item("PowerButton").Caption = If(Started, "Stop", "Start") + End Sub + #tag EndMethod + + + #tag Property, Flags = &h21 + Private mLock As CriticalSection + #tag EndProperty + + #tag Property, Flags = &h21 + Private mRefreshKey As String + #tag EndProperty + + #tag Property, Flags = &h21 + Private mServerStatus As Beacon.ServerStatus + #tag EndProperty + + + #tag Constant, Name = StatusBackupCreation, Type = String, Dynamic = False, Default = \"backup_creation", Scope = Private + #tag EndConstant + + #tag Constant, Name = StatusBackupRestore, Type = String, Dynamic = False, Default = \"backup_restore", Scope = Private + #tag EndConstant + + #tag Constant, Name = StatusBadGateway, Type = String, Dynamic = False, Default = \"nitrado_bad_gateway", Scope = Private + #tag EndConstant + + #tag Constant, Name = StatusChecking, Type = String, Dynamic = False, Default = \"beacon_checking", Scope = Private + #tag EndConstant + + #tag Constant, Name = StatusEncryptedToken, Type = String, Dynamic = False, Default = \"beacon_encrypted_token", Scope = Private + #tag EndConstant + + #tag Constant, Name = StatusException, Type = String, Dynamic = False, Default = \"beacon_exception", Scope = Private + #tag EndConstant + + #tag Constant, Name = StatusGameserverInstallation, Type = String, Dynamic = False, Default = \"gs_installation", Scope = Private + #tag EndConstant + + #tag Constant, Name = StatusGuardianLocked, Type = String, Dynamic = False, Default = \"guardian_locked", Scope = Private + #tag EndConstant + + #tag Constant, Name = StatusMaintenance, Type = String, Dynamic = False, Default = \"nitrado_maintenance", Scope = Private + #tag EndConstant + + #tag Constant, Name = StatusMissingProfile, Type = String, Dynamic = False, Default = \"beacon_missing_profile", Scope = Private + #tag EndConstant + + #tag Constant, Name = StatusMissingToken, Type = String, Dynamic = False, Default = \"beacon_missing_token", Scope = Private + #tag EndConstant + + #tag Constant, Name = StatusNetworkError, Type = String, Dynamic = False, Default = \"network_error", Scope = Private + #tag EndConstant + + #tag Constant, Name = StatusNitradoOther, Type = String, Dynamic = False, Default = \"nitrado_other", Scope = Private + #tag EndConstant + + #tag Constant, Name = StatusRateLimited, Type = String, Dynamic = False, Default = \"nitrado_rate_limited", Scope = Private + #tag EndConstant + + #tag Constant, Name = StatusRestarting, Type = String, Dynamic = False, Default = \"restarting", Scope = Private + #tag EndConstant + + #tag Constant, Name = StatusStarted, Type = String, Dynamic = False, Default = \"started", Scope = Private + #tag EndConstant + + #tag Constant, Name = StatusStopped, Type = String, Dynamic = False, Default = \"stopped", Scope = Private + #tag EndConstant + + #tag Constant, Name = StatusStopping, Type = String, Dynamic = False, Default = \"stopping", Scope = Private + #tag EndConstant + + #tag Constant, Name = StatusSuspended, Type = String, Dynamic = False, Default = \"suspended", Scope = Private + #tag EndConstant + + #tag Constant, Name = StatusUnauthorized, Type = String, Dynamic = False, Default = \"nitrado_unauthorized", Scope = Private + #tag EndConstant + + +#tag EndWindowCode + +#tag Events AdminNotesField + #tag Event + Sub TextChanged() + Self.Profile.AdminNotes = Me.Text + Self.Modified = Self.Profile.Modified + End Sub + #tag EndEvent +#tag EndEvents +#tag Events SettingsView + #tag Event + Sub ContentsChanged() + Self.Modified = Me.Modified + End Sub + #tag EndEvent + #tag Event + Function GetProject() As Palworld.Project + Return Self.Project + End Function + #tag EndEvent + #tag Event + Sub Opening() + Me.Profile = Self.Profile + End Sub + #tag EndEvent +#tag EndEvents +#tag Events ControlToolbar + #tag Event + Sub Opening() + Me.Append(OmniBarItem.CreateTab("PageGeneral", "General")) + Me.Append(OmniBarItem.CreateTab("PageNotes", "Notes")) + Me.Append(OmniBarItem.CreateSeparator) + Me.Append(OmniBarItem.CreateButton("PowerButton", "Stop", IconToolbarPower, "Stop the server", False)) + Me.Item("PageGeneral").Toggled = True + End Sub + #tag EndEvent + #tag Event + Sub ItemPressed(Item As OmniBarItem, ItemRect As Rect) + #Pragma Unused ItemRect + + Select Case Item.Name + Case "PageGeneral" + Self.Pages.SelectedPanelIndex = 0 + Item.Toggled = True + Me.Item("PageNotes").Toggled = False + Case "PageNotes" + Self.Pages.SelectedPanelIndex = 1 + Item.Toggled = True + Me.Item("PageGeneral").Toggled = False + Case "PowerButton" + If ToggleThread.ThreadState <> Thread.ThreadStates.NotRunning Then + Self.ShowAlert("An action is already running", "Wait a moment for the current action to complete.") + Return + End If + + If Self.mServerStatus.State = Beacon.ServerStatus.States.Running Then + Var StopMessage As String = StopMessageDialog.Present(Self) + If StopMessage.IsEmpty Then + Return + End If + ToggleThread.UserData = StopMessage + End If + + ToggleThread.Start + End Select + End Sub + #tag EndEvent +#tag EndEvents +#tag Events RefreshThread + #tag Event + Sub Run() + Self.mLock.Enter + + Var Provider As New Nitrado.HostingProvider + Self.RefreshNow(Provider) + + Me.AddUserInterfaceUpdate(New Dictionary("RefreshStatus": True)) + Self.ScheduleRefresh() + Self.mLock.Leave + End Sub + #tag EndEvent + #tag Event + Sub UserInterfaceUpdate(data() as Dictionary) + For Each Update As Dictionary In Data + If Update.Lookup("RefreshStatus", False).BooleanValue = True Then + Self.UpdateStatusDisplay() + End If + Next + End Sub + #tag EndEvent +#tag EndEvents +#tag Events ToggleThread + #tag Event + Sub Run() + Self.mLock.Enter + Self.CancelRefresh() + + Var Provider As New Nitrado.HostingProvider + Self.RefreshNow(Provider) + + Select Case Self.mServerStatus.State + Case Beacon.ServerStatus.States.Running + Try + Provider.StopServer(Nil, Self.Profile, Me.UserData.StringValue) + Self.mServerStatus = New Beacon.ServerStatus(Beacon.ServerStatus.States.Stopping) + Catch Err As RuntimeException + Self.mServerStatus = New Beacon.ServerStatus("Unhandled Beacon Exception") + Me.AddUserInterfaceUpdate(New Dictionary("RefreshStatus": True)) + Self.ScheduleRefresh() + Self.mLock.Leave + Return + End Try + Case Beacon.ServerStatus.States.Stopped + Try + Provider.StartServer(Nil, Self.Profile) + Self.mServerStatus = New Beacon.ServerStatus(Beacon.ServerStatus.States.Starting) + Catch Err As RuntimeException + Self.mServerStatus = New Beacon.ServerStatus("Unhandled Beacon Exception") + Me.AddUserInterfaceUpdate(New Dictionary("RefreshStatus": True)) + Self.ScheduleRefresh() + Self.mLock.Leave + Return + End Try + Else + Me.AddUserInterfaceUpdate(New Dictionary("ShowAlert": True, "AlertMessage": "Cannot do that right now.", "AlertExplanation": "The server is neither started nor stopped. Please wait for the current process to finish.")) + End Select + + Me.AddUserInterfaceUpdate(New Dictionary("RefreshStatus": True)) + Self.ScheduleRefresh() + Self.mLock.Leave + End Sub + #tag EndEvent + #tag Event + Sub UserInterfaceUpdate(data() as Dictionary) + For Each Update As Dictionary In Data + If Update.Lookup("RefreshStatus", False).BooleanValue = True Then + Self.UpdateStatusDisplay() + End If + If Update.Lookup("ShowAlert", False).BooleanValue = True Then + Var AlertMessage As String = Update.Lookup("AlertMessage", "") + Var AlertExplanation As String = Update.Lookup("AlertExplanation", "") + Self.ShowAlert(AlertMessage, AlertExplanation) + End If + Next + End Sub + #tag EndEvent +#tag EndEvents +#tag ViewBehavior + #tag ViewProperty + Name="Modified" + Visible=false + Group="Behavior" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Composited" + Visible=true + Group="Window Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="IsFrontmost" + Visible=false + Group="Behavior" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="ViewTitle" + Visible=true + Group="Behavior" + InitialValue="Untitled" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="ViewIcon" + Visible=false + Group="Behavior" + InitialValue="" + Type="Picture" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Progress" + Visible=false + Group="Behavior" + InitialValue="" + Type="Double" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Tooltip" + Visible=true + Group="Appearance" + InitialValue="" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="AllowAutoDeactivate" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocusRing" + Visible=true + Group="Appearance" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="BackgroundColor" + Visible=true + Group="Background" + InitialValue="&hFFFFFF" + Type="ColorGroup" + EditorType="ColorGroup" + #tag EndViewProperty + #tag ViewProperty + Name="HasBackgroundColor" + Visible=true + Group="Background" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocus" + Visible=true + Group="Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowTabs" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="MinimumWidth" + Visible=true + Group="Behavior" + InitialValue="400" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="MinimumHeight" + Visible=true + Group="Behavior" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Width" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Height" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="InitialParent" + Visible=false + Group="Position" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockLeft" + Visible=true + Group="Position" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockTop" + Visible=true + Group="Position" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockRight" + Visible=true + Group="Position" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockBottom" + Visible=true + Group="Position" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabPanelIndex" + Visible=false + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabIndex" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabStop" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Visible" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Enabled" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Backdrop" + Visible=true + Group="Background" + InitialValue="" + Type="Picture" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Transparent" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty +#tag EndViewBehavior diff --git a/Project/Views/Palworld/Config Editors/Servers/PalworldServerViewContainer.xojo_code b/Project/Views/Palworld/Config Editors/Servers/PalworldServerViewContainer.xojo_code new file mode 100644 index 000000000..ae69f5d89 --- /dev/null +++ b/Project/Views/Palworld/Config Editors/Servers/PalworldServerViewContainer.xojo_code @@ -0,0 +1,310 @@ +#tag Class +Protected Class PalworldServerViewContainer +Inherits BeaconSubview + #tag CompatibilityFlags = ( TargetConsole and ( Target32Bit or Target64Bit ) ) or ( TargetWeb and ( Target32Bit or Target64Bit ) ) or ( TargetDesktop and ( Target32Bit or Target64Bit ) ) or ( TargetIOS and ( Target64Bit ) ) + #tag Method, Flags = &h0 + Sub Constructor(Project As Palworld.Project, Profile As Palworld.ServerProfile) + Self.mProject = Project + Self.mProfile = Profile + Super.Constructor + End Sub + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Function Profile() As Palworld.ServerProfile + Return Self.mProfile + End Function + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Function Project() As Palworld.Project + Return Self.mProject + End Function + #tag EndMethod + + + #tag Property, Flags = &h21 + Private mProfile As Palworld.ServerProfile + #tag EndProperty + + #tag Property, Flags = &h21 + Private mProject As Palworld.Project + #tag EndProperty + + + #tag ViewBehavior + #tag ViewProperty + Name="Modified" + Visible=false + Group="Behavior" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Composited" + Visible=true + Group="Window Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="IsFrontmost" + Visible=false + Group="Behavior" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="ViewTitle" + Visible=true + Group="Behavior" + InitialValue="Untitled" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="ViewIcon" + Visible=false + Group="Behavior" + InitialValue="" + Type="Picture" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Tooltip" + Visible=true + Group="Appearance" + InitialValue="" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="AllowAutoDeactivate" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocusRing" + Visible=true + Group="Appearance" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="BackgroundColor" + Visible=true + Group="Background" + InitialValue="&hFFFFFF" + Type="ColorGroup" + EditorType="ColorGroup" + #tag EndViewProperty + #tag ViewProperty + Name="HasBackgroundColor" + Visible=true + Group="Background" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocus" + Visible=true + Group="Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowTabs" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Progress" + Visible=false + Group="Behavior" + InitialValue="ProgressNone" + Type="Double" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="MinimumWidth" + Visible=true + Group="Behavior" + InitialValue="400" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="MinimumHeight" + Visible=true + Group="Behavior" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Width" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Height" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="InitialParent" + Visible=false + Group="Position" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockLeft" + Visible=true + Group="Position" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockTop" + Visible=true + Group="Position" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockRight" + Visible=true + Group="Position" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockBottom" + Visible=true + Group="Position" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabPanelIndex" + Visible=false + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabIndex" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabStop" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Visible" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Enabled" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Backdrop" + Visible=true + Group="Background" + InitialValue="" + Type="Picture" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Transparent" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag EndViewBehavior +End Class +#tag EndClass diff --git a/Project/Views/Palworld/Config Editors/Servers/PalworldServersEditor.xojo_window b/Project/Views/Palworld/Config Editors/Servers/PalworldServersEditor.xojo_window new file mode 100644 index 000000000..a7ac1e71a --- /dev/null +++ b/Project/Views/Palworld/Config Editors/Servers/PalworldServersEditor.xojo_window @@ -0,0 +1,1074 @@ +#tag DesktopWindow +Begin PalworldConfigEditor PalworldServersEditor + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = False + AllowTabs = True + Backdrop = 0 + BackgroundColor = &cFFFFFF00 + Composited = False + Enabled = True + HasBackgroundColor= False + Height = 500 + Index = -2147483648 + InitialParent = "" + Left = 0 + LockBottom = True + LockLeft = True + LockRight = True + LockTop = True + TabIndex = 0 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 0 + Transparent = True + Visible = True + Width = 800 + Begin ServersListbox ServerList + AllowAutoDeactivate= True + AllowAutoHideScrollbars= True + AllowExpandableRows= False + AllowFocusRing = False + AllowInfiniteScroll= False + AllowResizableColumns= False + AllowRowDragging= False + AllowRowReordering= False + Bold = False + ColumnCount = 1 + ColumnWidths = "" + DefaultRowHeight= 40 + DefaultSortColumn= 0 + DefaultSortDirection= 1 + DropIndicatorVisible= False + EditCaption = "Edit" + Enabled = True + Filter = "" + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + GridLineStyle = 0 + HasBorder = False + HasHeader = False + HasHorizontalScrollbar= False + HasVerticalScrollbar= True + HeadingIndex = 0 + Height = 418 + Index = -2147483648 + InitialParent = "" + InitialValue = "" + Italic = False + Left = 0 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + PageSize = 100 + PreferencesKey = "" + RequiresSelection= False + RowSelectionType= 1 + Scope = 2 + SingleLineMode = False + TabIndex = 4 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 82 + TotalPages = -1 + Transparent = True + TypeaheadColumn = 0 + Underline = False + Visible = True + VisibleRowCount = 0 + Width = 299 + _ScrollOffset = 0 + _ScrollWidth = -1 + End + Begin FadedSeparator FadedSeparator1 + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = True + AllowTabs = False + Backdrop = 0 + ContentHeight = 0 + Enabled = True + Height = 500 + Index = -2147483648 + InitialParent = "" + Left = 299 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Scope = 2 + ScrollActive = False + ScrollingEnabled= False + ScrollSpeed = 20 + TabIndex = 1 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 0 + Transparent = True + Visible = True + Width = 1 + End + Begin OmniBar ConfigToolbar + Alignment = 0 + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = True + AllowTabs = False + Backdrop = 0 + BackgroundColor = "" + ContentHeight = 0 + Enabled = True + Height = 41 + Index = -2147483648 + InitialParent = "" + Left = 0 + LeftPadding = -1 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + RightPadding = -1 + Scope = 2 + ScrollActive = False + ScrollingEnabled= False + ScrollSpeed = 20 + TabIndex = 0 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 0 + Transparent = True + Visible = True + Width = 299 + End + Begin DelayedSearchField FilterField + Active = False + AllowAutoDeactivate= True + AllowFocusRing = True + AllowRecentItems= False + AllowTabStop = True + ClearMenuItemValue= "Clear" + DelayPeriod = 250 + Enabled = True + Height = 22 + Hint = "Filter Servers" + Index = -2147483648 + InitialParent = "" + Left = 9 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + MaximumRecentItems= -1 + PanelIndex = 0 + RecentItemsValue= "Recent Searches" + Scope = 2 + TabIndex = 2 + TabPanelIndex = 0 + Text = "" + Tooltip = "" + Top = 50 + Transparent = False + Visible = True + Width = 280 + _mIndex = 0 + _mInitialParent = "" + _mName = "" + _mPanelIndex = 0 + End + Begin OmniBarSeparator FilterSeparator + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = True + AllowTabs = False + Backdrop = 0 + ContentHeight = 0 + Enabled = True + Height = 1 + Index = -2147483648 + InitialParent = "" + Left = 0 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Scope = 2 + ScrollActive = False + ScrollingEnabled= False + ScrollSpeed = 20 + TabIndex = 3 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 81 + Transparent = True + Visible = True + Width = 299 + End + Begin Thread RefreshThread + DebugIdentifier = "" + Index = -2147483648 + LockedInPosition= False + Priority = 5 + Scope = 2 + StackSize = 0 + TabPanelIndex = 0 + ThreadID = 0 + ThreadState = 0 + End +End +#tag EndDesktopWindow + +#tag WindowCode + #tag Event + Sub Hidden() + Var Container As PalworldServerViewContainer = Self.CurrentView + If (Container Is Nil) = False Then + Container.SwitchedFrom() + End If + End Sub + #tag EndEvent + + #tag Event + Sub Opening() + Self.ServerList.UpdateList() + End Sub + #tag EndEvent + + #tag Event + Sub SetupUI() + Self.ServerList.UpdateList() + Self.UpdateRefreshButton() + End Sub + #tag EndEvent + + #tag Event + Sub Shown(UserData As Variant, ByRef FireSetupUI As Boolean) + #Pragma Unused FireSetupUI + + Var Container As PalworldServerViewContainer = Self.CurrentView + If (Container Is Nil) = False Then + Container.SwitchedTo(UserData) + End If + End Sub + #tag EndEvent + + + #tag Method, Flags = &h0 + Sub Constructor(Project As Palworld.Project) + Self.mViews = New Dictionary + Super.Constructor(Project) + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub FinishRefreshingDetails() + Var Profiles() As Beacon.ServerProfile = Self.ServerList.SelectedProfiles + Self.ServerList.SelectedRowIndex = -1 + + Var ViewIds() As Variant = Self.mViews.Keys + For Each ViewId As Variant In ViewIds + Var Panel As PalworldServerViewContainer = Self.mViews.Value(ViewId) + Panel.Close + Self.mViews.Remove(ViewId) + Next + + Self.mRefreshing = False + Self.UpdateRefreshButton() + Self.ServerList.UpdateList(Profiles, True) + + Var Explanation As String = "The information shown in the list is the most up-to-date Beacon has available." + If Self.Modified Then + Explanation = Explanation + " Don't forget to save your project." + End If + Self.ShowAlert("Server Refresh Finished", Explanation) + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub HandleViewMenu(ItemRect As Rect) + Var Base As New DesktopMenuItem + + Var ViewFullNames As New DesktopMenuItem("Use Full Server Names", ServersListbox.NamesFull) + Var ViewAbbreviatedNames As New DesktopMenuItem("Use Abbreviated Server Names", ServersListbox.NamesAbbreviated) + Var SortByName As New DesktopMenuItem("Sort By Name", ServersListbox.SortByName) + Var SortByAddress As New DesktopMenuItem("Sort By Address", ServersListbox.SortByAddress) + Var SortByColor As New DesktopMenuItem("Sort By Color", ServersListbox.SortByColor) + Var ShowServerIds As New DesktopMenuItem("Show Server Ids") + ViewFullNames.HasCheckMark = Preferences.ServersListNameStyle = ServersListbox.NamesFull + ViewAbbreviatedNames.HasCheckMark = Preferences.ServersListNameStyle = ServersListbox.NamesAbbreviated + SortByName.HasCheckMark = Preferences.ServersListSortedValue = ServersListbox.SortByName + SortByAddress.HasCheckMark = Preferences.ServersListSortedValue = ServersListbox.SortByAddress + SortByColor.HasCheckMark = Preferences.ServersListSortedValue = ServersListbox.SortByColor + ShowServerIds.HasCheckMark = Preferences.ServersListShowIds + Base.AddMenu(ViewFullNames) + Base.AddMenu(ViewAbbreviatedNames) + Base.AddMenu(New DesktopMenuItem(DesktopMenuItem.TextSeparator)) + Base.AddMenu(SortByName) + Base.AddMenu(SortByAddress) + Base.AddMenu(SortByColor) + Base.AddMenu(New DesktopMenuItem(DesktopMenuItem.TextSeparator)) + Base.AddMenu(ShowServerIds) + + Var Position As Point = Self.ConfigToolbar.GlobalPosition + Var Choice As DesktopMenuItem = Base.PopUp(Position.X + ItemRect.Left, Position.Y + ItemRect.Bottom) + If Choice Is Nil Then + Return + End If + + Select Case Choice + Case ViewFullNames, ViewAbbreviatedNames + Preferences.ServersListNameStyle = Choice.Tag.StringValue + Self.ServerList.UpdateList() + Case SortByName, SortByAddress, SortByColor + Preferences.ServersListSortedValue = Choice.Tag.StringValue + Self.ServerList.UpdateList() + Case ShowServerIds + Preferences.ServersListShowIds = Not Preferences.ServersListShowIds + Self.ServerList.UpdateList() + End Select + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Function InternalName() As String + Return Palworld.Configs.NameServers + End Function + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Sub RefreshDetails() + If Self.mRefreshing = True Then + Return + End If + + Self.RefreshThread.Start + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub UpdateRefreshButton() + If Self.ConfigToolbar Is Nil Then + Return + End If + + Var RefreshButton As OmniBarItem = Self.ConfigToolbar.Item("RefreshButton") + If RefreshButton Is Nil Then + Return + End If + + RefreshButton.HasProgressIndicator = Self.mRefreshing + RefreshButton.Progress = OmniBarItem.ProgressIndeterminate + RefreshButton.ActiveColor = If(Self.mRefreshing, OmniBarItem.ActiveColors.Blue, OmniBarItem.ActiveColors.Accent) + RefreshButton.AlwaysUseActiveColor = Self.mRefreshing + RefreshButton.Enabled = Self.mRefreshing = False And Self.Project.ServerProfileCount > 0 + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub View_ContentsChanged(Sender As PalworldServerViewContainer) + Self.Modified = Sender.Modified + Self.ServerList.UpdateList() + End Sub + #tag EndMethod + + + #tag Hook, Flags = &h0 + Event ShouldDeployProfiles(SelectedProfiles() As Beacon.ServerProfile) + #tag EndHook + + + #tag ComputedProperty, Flags = &h0 + #tag Getter + Get + Return Self.mCurrentProfileID + End Get + #tag EndGetter + #tag Setter + Set + If Self.mCurrentProfileID = Value Then + Return + End If + + If Self.mCurrentProfileID.IsEmpty = False Then + Var View As PalworldServerViewContainer = Self.mViews.Value(Self.mCurrentProfileID) + View.Visible = False + View.SwitchedFrom() + Self.mCurrentProfileID = "" + End If + + If Not Self.mViews.HasKey(Value) Then + Return + End If + + Var View As PalworldServerViewContainer = Self.mViews.Value(Value) + View.SwitchedTo() + View.Visible = True + Self.mCurrentProfileID = Value + End Set + #tag EndSetter + CurrentProfileID As String + #tag EndComputedProperty + + #tag ComputedProperty, Flags = &h0 + #tag Getter + Get + If Self.mViews.HasKey(Self.mCurrentProfileID) Then + Return PalworldServerViewContainer(Self.mViews.Value(Self.mCurrentProfileID)) + End If + End Get + #tag EndGetter + CurrentView As PalworldServerViewContainer + #tag EndComputedProperty + + #tag Property, Flags = &h21 + Private mCurrentProfileID As String + #tag EndProperty + + #tag Property, Flags = &h21 + Private mRefreshing As Boolean + #tag EndProperty + + #tag Property, Flags = &h21 + Private mViews As Dictionary + #tag EndProperty + + +#tag EndWindowCode + +#tag Events ServerList + #tag Event + Sub SelectionChanged() + Select Case Me.SelectedRowCount + Case 0 + Self.CurrentProfileID = "" + Return + Case 1 + Var Profile As Palworld.ServerProfile = Me.RowTagAt(Me.SelectedRowIndex) + Var ProfileID As String = Profile.ProfileID + If Not Self.mViews.HasKey(ProfileID) Then + // Create the view + Var View As PalworldServerViewContainer + Select Case Profile.ProviderId + Case Nitrado.Identifier + View = New PalworldNitradoServerView(Self.Project, Profile) + Case FTP.Identifier + View = New PalworldFTPServerView(Self.Project, Profile) + Case Local.Identifier + View = New PalworldLocalServerView(Self.Project, Profile) + Case GameServerApp.Identifier + View = New PalworldGSAServerView(Self.Project, Profile) + Else + Self.CurrentProfileID = "" + Return + End Select + + View.EmbedWithin(Self, FadedSeparator1.Left + FadedSeparator1.Width, FadedSeparator1.Top, Self.Width - (FadedSeparator1.Left + FadedSeparator1.Width), FadedSeparator1.Height) + AddHandler View.ContentsChanged, WeakAddressOf View_ContentsChanged + Self.mViews.Value(ProfileID) = View + End If + Self.CurrentProfileID = ProfileID + Else + Var Profiles() As Palworld.ServerProfile + Var Parts() As String + For Idx As Integer = 0 To Me.LastRowIndex + If Not Me.RowSelectedAt(Idx) Then + Continue + End If + + Profiles.Add(Me.RowTagAt(Idx)) + Parts.Add(Profiles(Profiles.LastIndex).ProfileID) + Next + + Var ProfileID As String = Parts.Join(",") + If Not Self.mViews.HasKey(ProfileID) Then + Var View As New PalworldMultiServerView(Self.Project, Profiles) + View.EmbedWithin(Self, FadedSeparator1.Left + FadedSeparator1.Width, FadedSeparator1.Top, Self.Width - (FadedSeparator1.Left + FadedSeparator1.Width), FadedSeparator1.Height) + AddHandler View.ContentsChanged, WeakAddressOf View_ContentsChanged + Self.mViews.Value(ProfileID) = View + End If + + Self.CurrentProfileID = ProfileID + End Select + End Sub + #tag EndEvent + #tag Event + Function CanDelete() As Boolean + Return True + End Function + #tag EndEvent + #tag Event + Sub PerformClear(Warn As Boolean) + Var SelCount As Integer = Me.SelectedRowCount + If SelCount = 0 Then + Return + End If + + If Warn Then + Var Subject As String = If(SelCount = 1, "server", "servers") + Var DemonstrativeAdjective As String = If(SelCount = 1, "this", "these " + SelCount.ToString) + If Not Self.ShowConfirm("Are you sure you want to delete " + DemonstrativeAdjective + " " + Subject + "?", "The " + Subject + " can be added again later using the ""Import"" feature next to the ""Config Type"" menu.", "Delete", "Cancel") Then + Return + End If + End If + + For I As Integer = Me.LastRowIndex DownTo 0 + If Me.RowSelectedAt(I) Then + Var Profile As Palworld.ServerProfile = Me.RowTagAt(I) + If Self.mViews.HasKey(Profile.ProfileID) Then + If Self.CurrentProfileID = Profile.ProfileID Then + Self.CurrentProfileID = "" + End If + + Var Panel As PalworldServerViewContainer = Self.mViews.Value(Profile.ProfileID) + Panel.Close + Self.mViews.Remove(Profile.ProfileID) + End If + Self.Project.RemoveServerProfile(Profile) + Self.Modified = True + Me.RemoveRowAt(I) + End If + Next + + Self.UpdateRefreshButton() + End Sub + #tag EndEvent + #tag Event + Function ConstructContextualMenu(Base As DesktopMenuItem, X As Integer, Y As Integer) As Boolean + #Pragma Unused X + #Pragma Unused Y + + Var CopyProfileMenuItem As New DesktopMenuItem("Copy Profile ID") + CopyProfileMenuItem.Enabled = False + Base.AddMenu(CopyProfileMenuItem) + + Var DeployItem As DesktopMenuItem + If Me.SelectedRowCount > 1 Then + DeployItem = New DesktopMenuItem("Deploy These Servers…") + Else + DeployItem = New DesktopMenuItem("Deploy This Server…") + End If + Var DeployProfiles() As Beacon.ServerProfile + Var NitradoProfiles() As Palworld.ServerProfile + Var LocalProfiles() As Palworld.ServerProfile + For Idx As Integer = 0 To Me.LastRowIndex + If Me.RowSelectedAt(Idx) = False Then + Continue + End If + Var Profile As Palworld.ServerProfile = Me.RowTagAt(Idx) + If Profile.DeployCapable Then + DeployProfiles.Add(Profile) + End If + Select Case Profile.ProviderId + Case Nitrado.Identifier + NitradoProfiles.Add(Profile) + Case Local.Identifier + LocalProfiles.Add(Profile) + End Select + Next Idx + DeployItem.Enabled = DeployProfiles.Count > 0 + DeployItem.Tag = DeployProfiles + Base.AddMenu(DeployItem) + + Var BackupsItem As New DesktopMenuItem("Show Config Backups") + Base.AddMenu(BackupsItem) + + If NitradoProfiles.Count > 0 Then + Base.AddMenu(New DesktopMenuItem("Open Nitrado Dashboard", NitradoProfiles)) + End If + + If LocalProfiles.Count > 0 Then + #if Not TargetMacOS + // Sandbox prevents this from working on macOS + Base.AddMenu(New DesktopMenuItem("Show Config Files", LocalProfiles)) + #endif + End If + + If Me.SelectedRowCount = 1 Then + Var Profile As Palworld.ServerProfile = Me.RowTagAt(Me.SelectedRowIndex) + CopyProfileMenuItem.Tag = Profile.ProfileID.Left(8) + CopyProfileMenuItem.Enabled = True + + BackupsItem.Tag = App.BackupsFolder.Child(Profile.BackupFolderName) + Else + BackupsItem.Tag = App.BackupsFolder + End If + + Return True + End Function + #tag EndEvent + #tag Event + Function ContextualMenuItemSelected(HitItem As DesktopMenuItem) As Boolean + Select Case HitItem.Text + Case "Show Config Backups" + Var Folder As FolderItem = HitItem.Tag + If Folder = Nil Then + Return True + End If + If Not Folder.Exists Then + Folder.CreateFolder + End If + Folder.Open + Case "Copy Profile ID" + Var ProfileID As String = HitItem.Tag + Var Board As New Clipboard + Board.Text = ProfileID + Case "Deploy These Servers…", "Deploy This Server…" + Var SelectedProfiles() As Beacon.ServerProfile = HitItem.Tag + RaiseEvent ShouldDeployProfiles(SelectedProfiles) + Case "Open Nitrado Dashboard" + Var NitradoProfiles() As Palworld.ServerProfile = HitItem.Tag + For Idx As Integer = 0 To NitradoProfiles.LastIndex + System.GotoURL(Beacon.WebURL("/redirect?destination=nitradodash&serviceid=" + Nitrado.HostConfig(NitradoProfiles(Idx).HostConfig).ServiceID.ToString(Locale.Raw, "0"))) + Next Idx + Case "Show Config Files" + Var LocalProfiles() As Palworld.ServerProfile = HitItem.Tag + For Idx As Integer = 0 To LocalProfiles.LastIndex + Var File As BookmarkedFolderItem = BookmarkedFolderItem.FromSaveInfo(LocalProfiles(Idx).SettingsIniPath) + If (File Is Nil) = False And File.Exists Then + File.Parent.Open + End If + Next Idx + End Select + + Return True + End Function + #tag EndEvent + #tag Event + Function GetProject() As Beacon.Project + Return Self.Project + End Function + #tag EndEvent +#tag EndEvents +#tag Events ConfigToolbar + #tag Event + Sub Opening() + Me.Append(OmniBarItem.CreateTitle("ConfigTitle", Self.ConfigLabel)) + Me.Append(OmniBarItem.CreateSeparator("ConfigTitleSeparator")) + Me.Append(OmniBarItem.CreateButton("AddServerButton", "New Server", IconToolbarAdd, "Add a new simple server.")) + Me.Append(OmniBarItem.CreateFlexibleSpace) + Me.Append(OmniBarItem.CreateButton("RefreshButton", "Refresh", IconToolbarRefresh, "Refresh server details.", Self.Project.ServerProfileCount > 0)) + Me.Append(OmniBarItem.CreateButton("ViewOptionsButton", "View", IconToolbarView, "Change server list view options.")) + + Me.Item("ConfigTitle").Priority = 5 + Me.Item("ConfigTitleSeparator").Priority = 5 + End Sub + #tag EndEvent + #tag Event + Sub ItemPressed(Item As OmniBarItem, ItemRect As Rect) + Select Case Item.Name + Case "AddServerButton" + Var Profile As New Palworld.ServerProfile(Local.Identifier, Language.DefaultServerName(Palworld.Identifier)) + + Self.Project.AddServerProfile(Profile) + Self.ServerList.UpdateList(Profile, True) + Self.UpdateRefreshButton() + Case "ViewOptionsButton" + Self.HandleViewMenu(ItemRect) + Case "RefreshButton" + Self.RefreshDetails() + End Select + End Sub + #tag EndEvent + #tag Event + Function ItemHeld(Item As OmniBarItem, ItemRect As Rect) As Boolean + Select Case Item.Name + Case "ViewOptionsButton" + Self.HandleViewMenu(ItemRect) + Return True + End Select + End Function + #tag EndEvent +#tag EndEvents +#tag Events FilterField + #tag Event + Sub TextChanged() + If Self.SettingUp Then + Return + End If + + Self.ServerList.Filter = Me.Text.Trim + End Sub + #tag EndEvent +#tag EndEvents +#tag Events RefreshThread + #tag Event + Sub Run() + Var Identity As Beacon.Identity = App.IdentityManager.CurrentIdentity + If Identity Is Nil Then + Me.AddUserInterfaceUpdate(New Dictionary("UpdateUI": True, "Finished": True)) + Return + End If + + Self.mRefreshing = True + Me.AddUserInterfaceUpdate(New Dictionary("UpdateUI": True)) + + Var Tokens() As BeaconAPI.ProviderToken = BeaconAPI.GetProviderTokens(Identity.UserId) + Var Filter As New Dictionary + For Each Token As BeaconAPI.ProviderToken In Tokens + Filter.Value(Token.TokenId) = Token + Next + + Var TokenIds() As String = Self.Project.ProviderTokenIds + For Each TokenId As String In TokenIds + If Filter.HasKey(TokenId) Then + Continue + End If + + Var Token As BeaconAPI.ProviderToken = BeaconAPI.GetProviderToken(TokenId, Self.Project, True) + If (Token Is Nil) = False Then + Tokens.Add(Token) + Filter.Value(Token.TokenId) = Token + End If + Next + + Var AllProfiles() As Beacon.ServerProfile = Self.Project.ServerProfiles + Var ProfileMap As New Dictionary + For Each Profile As Beacon.ServerProfile in AllProfiles + ProfileMap.Value(Profile.ProfileId) = Profile + Next + + For Each Token As BeaconAPI.ProviderToken In Tokens + Var Provider As Beacon.HostingProvider + Var Config As Beacon.HostConfig + Select Case Token.Provider + Case BeaconAPI.ProviderToken.ProviderNitrado + Provider = New Nitrado.HostingProvider + Config = New Nitrado.HostConfig + Nitrado.HostConfig(Config).TokenId = Token.TokenId + Case BeaconAPI.ProviderToken.ProviderGameServerApp + Provider = New GameServerApp.HostingProvider + Config = New GameServerApp.HostConfig + GameServerApp.HostConfig(Config).TokenId = Token.TokenId + End Select + If Provider Is Nil Then + Continue + End If + + Var Profiles() As Beacon.ServerProfile + Try + Profiles = Provider.ListServers(Config, Palworld.Identifier) + Catch Err As RuntimeException + App.Log(Err, CurrentMethodName, "Refreshing server profiles") + Continue + End Try + + For Each Profile As Beacon.ServerProfile In Profiles + If ProfileMap.HasKey(Profile.ProfileId) Then + Beacon.ServerProfile(ProfileMap.Value(Profile.ProfileId).ObjectValue).SecondaryName = Profile.SecondaryName + End If + Next + Next + + Me.AddUserInterfaceUpdate(New Dictionary("UpdateUI": True, "Finished": True)) + End Sub + #tag EndEvent + #tag Event + Sub UserInterfaceUpdate(data() as Dictionary) + For Each Update As Dictionary In Data + Var UpdateUI As Boolean = Update.Lookup("UpdateUI", False).BooleanValue + Var Finished As Boolean = Update.Lookup("Finished", False).BooleanValue + + If UpdateUI Then + If Finished Then + Self.FinishRefreshingDetails() + Else + Self.UpdateRefreshButton() + End If + End If + Next + End Sub + #tag EndEvent +#tag EndEvents +#tag ViewBehavior + #tag ViewProperty + Name="Modified" + Visible=false + Group="Behavior" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Composited" + Visible=true + Group="Window Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="IsFrontmost" + Visible=false + Group="Behavior" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="ViewTitle" + Visible=true + Group="Behavior" + InitialValue="Untitled" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="ViewIcon" + Visible=false + Group="Behavior" + InitialValue="" + Type="Picture" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Progress" + Visible=false + Group="Behavior" + InitialValue="" + Type="Double" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Tooltip" + Visible=true + Group="Appearance" + InitialValue="" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="AllowAutoDeactivate" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocusRing" + Visible=true + Group="Appearance" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="BackgroundColor" + Visible=true + Group="Background" + InitialValue="&hFFFFFF" + Type="ColorGroup" + EditorType="ColorGroup" + #tag EndViewProperty + #tag ViewProperty + Name="HasBackgroundColor" + Visible=true + Group="Background" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocus" + Visible=true + Group="Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowTabs" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="MinimumWidth" + Visible=true + Group="Behavior" + InitialValue="400" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="MinimumHeight" + Visible=true + Group="Behavior" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Width" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Height" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="InitialParent" + Visible=false + Group="Position" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockLeft" + Visible=true + Group="Position" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockTop" + Visible=true + Group="Position" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockRight" + Visible=true + Group="Position" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockBottom" + Visible=true + Group="Position" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabPanelIndex" + Visible=false + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabIndex" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabStop" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Visible" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Enabled" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Backdrop" + Visible=true + Group="Background" + InitialValue="" + Type="Picture" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Transparent" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="CurrentProfileID" + Visible=false + Group="Behavior" + InitialValue="" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty +#tag EndViewBehavior diff --git a/Project/Views/Palworld/General Dialogs/PalworldExportWindow.xojo_window b/Project/Views/Palworld/General Dialogs/PalworldExportWindow.xojo_window new file mode 100644 index 000000000..9521483bc --- /dev/null +++ b/Project/Views/Palworld/General Dialogs/PalworldExportWindow.xojo_window @@ -0,0 +1,1442 @@ +#tag DesktopWindow +Begin BeaconDialog PalworldExportWindow + BackColor = &cFFFFFF00 + Backdrop = 0 + CloseButton = False + Composite = False + Frame = 8 + FullScreen = False + FullScreenButton= False + HasBackColor = False + Height = 600 + ImplicitInstance= False + LiveResize = "True" + MacProcID = 0 + MaxHeight = 32000 + MaximizeButton = True + MaxWidth = 32000 + MenuBar = 0 + MenuBarVisible = True + MinHeight = 600 + MinimizeButton = False + MinWidth = 848 + Placement = 1 + Resizable = "True" + Resizeable = True + SystemUIVisible = "True" + Title = "Export" + Visible = True + Width = 848 + Begin UITweaks.ResizedPushButton ActionButton + AllowAutoDeactivate= True + Bold = False + Cancel = False + Caption = "Finished" + Default = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 20 + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 732 + LockBottom = True + LockedInPosition= False + LockLeft = False + LockRight = True + LockTop = False + MacButtonStyle = 0 + Scope = 2 + TabIndex = 18 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 560 + Transparent = False + Underline = False + Visible = True + Width = 96 + End + Begin CodeEditor ContentArea + AutoDeactivate = True + Enabled = True + HasBorder = False + Height = 428 + HorizontalScrollPosition= 0 + Index = -2147483648 + InitialParent = "" + Left = 251 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + Scope = 2 + SelectionLength = 0 + ShowInfoBar = False + TabIndex = 7 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 111 + VerticalScrollPosition= 0 + Visible = True + Width = 597 + End + Begin FadedSeparator TopSeparator + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = True + AllowTabs = False + Backdrop = 0 + ContentHeight = 0 + Enabled = True + Height = 1 + Index = -2147483648 + InitialParent = "" + Left = 251 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + Scope = 2 + ScrollActive = False + ScrollingEnabled= False + ScrollSpeed = 20 + TabIndex = 4 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 60 + Transparent = True + Visible = True + Width = 597 + End + Begin UITweaks.ResizedPopupMenu ProfileMenu + AllowAutoDeactivate= True + Bold = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 20 + Index = -2147483648 + InitialParent = "" + InitialValue = "" + Italic = False + Left = 20 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Scope = 2 + SelectedRowIndex= 0 + TabIndex = 8 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 86 + Transparent = False + Underline = False + Visible = True + Width = 210 + End + Begin DesktopProgressWheel RewritingSpinner + Active = False + AllowAutoDeactivate= True + AllowTabStop = True + Enabled = True + Height = 16 + Index = -2147483648 + InitialParent = "" + Left = 20 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = False + PanelIndex = 0 + Scope = 2 + TabIndex = 16 + TabPanelIndex = 0 + Tooltip = "" + Top = 564 + Transparent = False + Visible = False + Width = 16 + _mIndex = 0 + _mInitialParent = "" + _mName = "" + _mPanelIndex = 0 + End + Begin DesktopLabel RewritingStatusLabel + AllowAutoDeactivate= True + Bold = False + Enabled = True + FontName = "SmallSystem" + FontSize = 0.0 + FontUnit = 0 + Height = 16 + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 48 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = False + Multiline = False + Scope = 2 + Selectable = False + TabIndex = 17 + TabPanelIndex = 0 + TabStop = True + Text = "Building config…" + TextAlignment = 0 + TextColor = &c00000000 + Tooltip = "" + Top = 564 + Transparent = False + Underline = False + Visible = False + Width = 182 + End + Begin FadedSeparator LeftSeparator + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = True + AllowTabs = False + Backdrop = 0 + ContentHeight = 0 + Enabled = True + Height = 600 + Index = -2147483648 + InitialParent = "" + Left = 250 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Scope = 2 + ScrollActive = False + ScrollingEnabled= False + ScrollSpeed = 20 + TabIndex = 0 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 0 + Transparent = True + Visible = True + Width = 1 + End + Begin Shelf Switcher + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = True + AllowTabs = False + Backdrop = 0 + ContentHeight = 0 + DrawCaptions = True + Enabled = True + Height = 60 + Index = -2147483648 + InitialParent = "" + IsVertical = False + Left = 251 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + RequiresSelection= True + Scope = 2 + ScrollActive = False + ScrollingEnabled= False + ScrollSpeed = 20 + TabIndex = 1 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 0 + Transparent = False + Visible = True + Width = 597 + End + Begin OmniBar ExportToolbar + Alignment = 0 + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = True + AllowTabs = False + Backdrop = 0 + BackgroundColor = "" + ContentHeight = 0 + Enabled = True + Height = 50 + Index = -2147483648 + InitialParent = "" + Left = 251 + LeftPadding = -1 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + RightPadding = -1 + Scope = 2 + ScrollActive = False + ScrollingEnabled= False + ScrollSpeed = 20 + TabIndex = 5 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 61 + Transparent = True + Visible = True + Width = 597 + End + Begin FadedSeparator BottomSeparator + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = True + AllowTabs = False + Backdrop = 0 + ContentHeight = 0 + Enabled = True + Height = 1 + Index = -2147483648 + InitialParent = "" + Left = 251 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = False + Scope = 2 + ScrollActive = False + ScrollingEnabled= False + ScrollSpeed = 20 + TabIndex = 15 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 539 + Transparent = True + Visible = True + Width = 597 + End + Begin DesktopLabel SettingsHeaderLabel + AllowAutoDeactivate= True + Bold = True + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 20 + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 20 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Multiline = False + Scope = 2 + Selectable = False + TabIndex = 2 + TabPanelIndex = 0 + TabStop = True + Text = "Export Settings" + TextAlignment = 0 + TextColor = &c00000000 + Tooltip = "" + Top = 20 + Transparent = False + Underline = False + Visible = True + Width = 210 + End + Begin DesktopLabel ProfileLabel + AllowAutoDeactivate= True + Bold = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 20 + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 20 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Multiline = False + Scope = 2 + Selectable = False + TabIndex = 3 + TabPanelIndex = 0 + TabStop = True + Text = "Server:" + TextAlignment = 0 + TextColor = &c00000000 + Tooltip = "" + Top = 60 + Transparent = False + Underline = False + Visible = True + Width = 210 + End + Begin DesktopLabel ConfigSetsLabel + AllowAutoDeactivate= True + Bold = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 20 + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 20 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Multiline = False + Scope = 2 + Selectable = False + TabIndex = 12 + TabPanelIndex = 0 + TabStop = True + Text = "Config Sets:" + TextAlignment = 0 + TextColor = &c00000000 + Tooltip = "" + Top = 126 + Transparent = False + Underline = False + Visible = True + Width = 210 + End + Begin UITweaks.ResizedPushButton ConfigSetsButton + AllowAutoDeactivate= True + Bold = False + Cancel = False + Caption = "Choose…" + Default = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 20 + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 140 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + MacButtonStyle = 0 + Scope = 2 + TabIndex = 14 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 178 + Transparent = False + Underline = False + Visible = True + Width = 90 + End + Begin UITweaks.ResizedLabel ConfigSetsField + AllowAutoDeactivate= True + Bold = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 20 + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 20 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Multiline = False + Scope = 2 + Selectable = False + TabIndex = 13 + TabPanelIndex = 0 + TabStop = True + Text = "Base" + TextAlignment = 0 + TextColor = &c00000000 + Tooltip = "" + Top = 152 + Transparent = False + Underline = False + Visible = True + Width = 210 + End + Begin DesktopCheckBox ConfigSetsOverrideCheck + AllowAutoDeactivate= True + Bold = False + Caption = "Override Server's Config Sets" + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 20 + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 20 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Scope = 2 + TabIndex = 6 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = -81 + Transparent = False + Underline = False + Value = False + Visible = True + VisualState = 0 + Width = 210 + End + Begin Palworld.Rewriter SharedRewriter + DebugIdentifier = "" + FinishedSettingsIniContent= "" + Index = -2147483648 + InitialSettingsIniContent= "" + LockedInPosition= False + Priority = 5 + Scope = 2 + Source = "" + StackSize = 0 + TabPanelIndex = 0 + ThreadID = 0 + ThreadState = 0 + End +End +#tag EndDesktopWindow + +#tag WindowCode + #tag Event + Sub Closing() + Self.SharedRewriter.Cancel + End Sub + #tag EndEvent + + + #tag Method, Flags = &h21 + Private Function CanCopy(Content As String, Filename As String, Verb As String = "Copy") As Boolean + Var MissingHeaders() As String = Palworld.ValidateIniContent(Content, Filename) + Var MissingCount As Integer = MissingHeaders.Count + If MissingCount > 0 Then + Var Message As String = "This content is missing required groups. Do you want to " + Verb.Lowercase + " it anyway?" + Var Explanation As String = "The " + MissingHeaders.EnglishOxfordList + " " + If(MissingCount = 1, "group is", "groups are") + " missing. Ark will not start up correctly if this file is used in its current state. Use the ""Smart Copy"" or ""Smart Save"" buttons to have Beacon correctly update your existing ini content." + Var Choice As BeaconUI.ConfirmResponses = Self.ShowConfirm(Message, Explanation, Verb + " Anyway", "Cancel", "Help") + Select Case Choice + Case BeaconUI.ConfirmResponses.Action + Return True + Case BeaconUI.ConfirmResponses.Cancel + Return False + Case BeaconUI.ConfirmResponses.Alternate + System.GotoURL(Beacon.WebURL("/help/updating_your_server_manually")) + Return False + End Select + End If + + Return True + End Function + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub CheckButtons() + If Self.ExportToolbar.IndexOf("SmartCopy") = -1 Then + Return + End If + + Var SmartButtonsEnabled As Boolean = Self.IsRewriting = False And Self.CurrentMode.IsEmpty = False + Self.ExportToolbar.Item("SmartCopy").Enabled = SmartButtonsEnabled + Self.ExportToolbar.Item("SmartSave").Enabled = SmartButtonsEnabled + + Self.ExportToolbar.Item("LazyCopy").Enabled = Self.CurrentContent.IsEmpty = False + Self.ExportToolbar.Item("LazySave").Enabled = Self.ExportToolbar.Item("LazyCopy").Enabled And Self.CurrentMode <> "" + + Var Rewriting As Boolean = Self.IsRewriting + If Self.RewritingSpinner.Visible <> Rewriting Then + Self.RewritingSpinner.Visible = Rewriting + Self.RewritingStatusLabel.Visible = Rewriting + End If + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub DoCopy() + Var Content As String = Self.CurrentContent + If Not Self.CanCopy(Content, Self.CurrentFilename) Then + Return + End If + + Var Board As New Clipboard + Board.Text = Content + Self.mLastRewrittenHash = EncodeHex(MD5(Board.Text)) + + Self.ShowAlert(Self.CurrentFilename + " has been copied!", "You are ready to paste it wherever you need it.") + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub DoSave() + If Not Self.CanCopy(Self.CurrentContent, Self.CurrentFilename, "Save") Then + Return + End If + + Var Dialog As New SaveFileDialog + Dialog.Filter = BeaconFileTypes.IniFile + Dialog.SuggestedFileName = Self.CurrentFilename + If Dialog.SuggestedFileName = "" Then + Return + End If + + Var File As FolderItem = Dialog.ShowModal() + If File Is Nil Then + Return + End If + + Try + File.Write(Self.CurrentContent) + Catch Err As RuntimeException + Self.ShowAlert("Unable to write to " + File.DisplayName, "Check file permissions and disk space.") + End Try + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub DoSmartCopy() + If Self.IsRewriting Then + // Busy + Self.ShowAlert(Self.RewriterBusyMessage, Self.RewriterBusyExplanation) + Return + End If + + Var Mode As String = Self.CurrentMode + If Mode.IsEmpty Then + Self.ShowAlert(SmartCopyUnavailableMessage, SmartCopyUnavailableExplanation) + Return + End If + + Var Filename As String = Self.CurrentFilename + Var Board As New Clipboard + If Board.TextAvailable = False Then + Self.ShowAlert(Language.ReplacePlaceholders(SmartCopyInstructionsMessage, Filename), Language.ReplacePlaceholders(SmartCopyInstructionsExplanation, Filename)) + Return + End If + + Var RequiredHeader() As String + Select Case Self.CurrentMode + Case Palworld.Rewriter.ModeSettingsIni + RequiredHeader = Array(Palworld.HeaderPalworldSettings) + Else + Return + End Select + + Var ClipboardContents As String = Board.Text + Var HeaderFound As Boolean + For Each Header As String In RequiredHeader + If ClipboardContents.IndexOf("[" + Header + "]") > -1 Then + HeaderFound = True + Exit + End If + Next Header + If HeaderFound = False Then + Self.ShowAlert(Language.ReplacePlaceholders(SmartCopyInstructionsMessage, Filename), Language.ReplacePlaceholders(SmartCopyInstructionsExplanation, Filename)) + Return + End If + + If EncodeHex(Crypto.SHA2_256(ClipboardContents)) = Self.mLastRewrittenHash Then + Self.ShowAlert(Language.ReplacePlaceholders(SmartCopyReadyMessage, Filename), Language.ReplacePlaceholders(SmartCopyReadyExplanation, Filename)) + Return + End If + + Self.mLastRewrittenHash = "" + Self.mCopyWhenFinished = True + + Select Case Self.CurrentMode + Case Palworld.Rewriter.ModeSettingsIni + Self.SharedRewriter.InitialSettingsIniContent = ClipboardContents + Self.SharedRewriter.Source = Palworld.Rewriter.Sources.SmartCopy + Self.SharedRewriter.Rewrite(Palworld.Rewriter.FlagCreateSettingsIni Or If(Self.mForceTrollMode, Palworld.Rewriter.FlagForceTrollMode, 0)) + End Select + + Self.CheckButtons() + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub DoSmartSave() + If Self.IsRewriting Then + // Busy + Self.ShowAlert(Self.RewriterBusyMessage, Self.RewriterBusyExplanation) + Return + End If + + Var RequiredHeader() As String + Select Case Self.CurrentMode + Case Palworld.Rewriter.ModeSettingsIni + RequiredHeader = Array(Palworld.HeaderPalworldSettings) + Else + Return + End Select + + Var Dialog As New OpenFileDialog + Dialog.Filter = BeaconFileTypes.IniFile + Dialog.SuggestedFileName = Self.CurrentFilename + Dialog.ActionButtonCaption = "Update" + + Var File As FolderItem = Dialog.ShowModal() + If File = Nil Or Not File.Exists Then + Return + End If + + Var Content As String + Try + Var InStream As TextInputStream = TextInputStream.Open(File) + Content = InStream.ReadAll() + InStream.Close + Catch Err As RuntimeException + Self.ShowAlert("Unable to open " + File.DisplayName, "Beacon was unable to read the current content of the file to rewriting. The file has not been changed.") + Return + End Try + Content = Content.GuessEncoding("/Script/") + + Var HeaderFound As Boolean + For Each Header As String In RequiredHeader + If Content.IndexOf("[" + Header + "]") > -1 Then + HeaderFound = True + Exit + End If + Next Header + If HeaderFound = False Then + Self.ShowAlert("Incorrect ini file detected.", "Beacon is expecting to find the " + Language.EnglishOxfordList(RequiredHeader, "or") + " header in this file before rewriting, but cannot find it. Make sure you select the correct file config file.") + Return + End If + + Self.mFileDestination = File + + Select Case Self.CurrentMode + Case Palworld.Rewriter.ModeSettingsIni + Self.SharedRewriter.InitialSettingsIniContent = Content + Self.SharedRewriter.Source = Palworld.Rewriter.Sources.SmartSave + Self.SharedRewriter.Rewrite(Palworld.Rewriter.FlagCreateSettingsIni Or If(Self.mForceTrollMode, Palworld.Rewriter.FlagForceTrollMode, 0)) + End Select + + Self.CheckButtons() + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub Finish(Content As String) + If Self.mCopyWhenFinished Then + Var Board As New Clipboard + Board.Text = Content + Self.mLastRewrittenHash = EncodeHex(Crypto.SHA2_256(Content)).Lowercase + Self.ShowAlert(Language.ReplacePlaceholders(SmartCopyReadyMessage, "ini"), Language.ReplacePlaceholders(SmartCopyReadyExplanation, "ini")) + Return + End If + + If (Self.mFileDestination Is Nil) = False Then + Try + Self.mFileDestination.Write(Content) + Catch Err As RuntimeException + Self.ShowAlert("Unable to update file", "There was an error trying to rewrite the ini content in the selected file.") + End Try + Return + End If + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Function IsRewriting() As Boolean + Return Self.SharedRewriter.ThreadState <> Thread.ThreadStates.NotRunning + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Shared Sub Present(Parent As DesktopWindow, Project As Palworld.Project, ForceTrollMode As Boolean = False) + If (Parent Is Nil) = False Then + Parent = Parent.TrueWindow + End If + + Var Win As New PalworldExportWindow + Win.mProject = Project + Win.mForceTrollMode = ForceTrollMode + + If ForceTrollMode Then + TrollActivated.Play + End If + + Var ProfileBound As Integer = Project.ServerProfileCount - 1 + If ProfileBound > -1 Then + For I As Integer = 0 To ProfileBound + Var Profile As Beacon.ServerProfile = Project.ServerProfile(I) + Win.ProfileMenu.AddRow(Profile.Name, Profile) + Next + + Var ConfigLabelTop As Integer = Win.ConfigSetsLabel.Top + Win.ConfigSetsLabel.Top = Win.ConfigSetsOverrideCheck.Top + Win.ConfigSetsOverrideCheck.Top = ConfigLabelTop + Win.ConfigSetsField.Enabled = Win.ConfigSetsOverrideCheck.Value + Win.ConfigSetsButton.Enabled = Win.ConfigSetsOverrideCheck.Value + + Win.ProfileMenu.SelectedRowIndex = 0 + Else + Win.ProfileMenu.SelectedRowIndex = -1 + Win.ProfileMenu.Enabled = False + Win.ProfileLabel.Enabled = False + End If + + Win.UpdateConfigSetControls() + + Win.Setup() + Win.ShowModal(Parent) + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub RefreshContentArea() + Var IntendedContent As String + Select Case Self.Switcher.SelectedIndex + Case 1 + IntendedContent = Self.mSettingsIniContent + End Select + If Self.CurrentContent <> IntendedContent Then + Self.CurrentContent = IntendedContent + Self.ContentArea.HorizontalScrollPosition = 0 + End If + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub SetClipboardButtonCaption(Enabled As Boolean, Caption As String) + If Self.ExportToolbar = Nil Then + Return + End If + + If Self.ExportToolbar.Item("SmartCopy").Enabled <> Enabled Then + Self.ExportToolbar.Item("SmartCopy").Enabled = Enabled + End If + If Self.ExportToolbar.Item("SmartCopy").Caption <> Caption Then + Self.ExportToolbar.Item("SmartCopy").Caption = Caption + End If + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub Setup() + If Self.mProject = Nil Or Self.IsRewriting Then + Return + End If + + Var Profile As Palworld.ServerProfile + If Self.ProfileMenu.SelectedRowIndex > -1 Then + Profile = Palworld.ServerProfile(Self.ProfileMenu.RowTagAt(Self.ProfileMenu.SelectedRowIndex)).Clone // We need to work on a copy + Else + Profile = New Palworld.ServerProfile(Local.Identifier, Self.mProject.Title) + End If + If Self.ConfigSetsOverrideCheck.Value Then + Profile.ConfigSetStates = Self.mProject.ConfigSetPriorities + End If + Self.mCurrentProfile = Profile + + Self.mSettingsIniContent = "" + Self.mLastRewrittenHash = "" + Self.mFileDestination = Nil + Self.mCopyWhenFinished = False + + Self.SharedRewriter.Cancel + Self.SharedRewriter.InitialSettingsIniContent = "" + Self.SharedRewriter.Source = Palworld.Rewriter.Sources.Original + Self.SharedRewriter.Project = Self.mProject + Self.SharedRewriter.Identity = App.IdentityManager.CurrentIdentity + Self.SharedRewriter.Profile = Self.mCurrentProfile + + Try + If Profile.ProviderId = Local.Identifier Then + Var SettingsIniPath As String = Profile.SettingsIniPath + + If SettingsIniPath.IsEmpty = False Then + Var SettingsIniFile As BookmarkedFolderItem = BookmarkedFolderItem.FromSaveInfo(SettingsIniPath) + If (SettingsIniFile Is Nil) = False And SettingsIniFile.Exists Then + Self.SharedRewriter.InitialSettingsIniContent = SettingsIniFile.Read + End If + End If + End If + Catch Err As RuntimeException + // It's not important + End Try + + Self.SharedRewriter.Rewrite(Palworld.Rewriter.FlagCreateSettingsIni Or If(Self.mForceTrollMode, Palworld.Rewriter.FlagForceTrollMode, 0)) + + Self.RefreshContentArea() + Self.CheckButtons() + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub UpdateConfigSetControls() + If Self.mProject.ConfigSetCount > 1 Then + Var Sets() As Beacon.ConfigSet = Beacon.ConfigSetState.FilterSets(Self.mProject.ConfigSetPriorities, Self.mProject.ConfigSets) + Var EnabledSets() As String + For Each Set As Beacon.ConfigSet In Sets + EnabledSets.Add(Set.Name) + Next + + Self.ConfigSetsField.Text = EnabledSets.EnglishOxfordList() + Else + Self.ConfigSetsField.Text = Beacon.ConfigSet.BaseConfigSet.Name + Self.ConfigSetsField.Enabled = False + Self.ConfigSetsButton.Enabled = False + Self.ConfigSetsLabel.Enabled = False + End If + End Sub + #tag EndMethod + + + #tag ComputedProperty, Flags = &h0 + #tag Getter + Get + Return Self.mCurrentContent + End Get + #tag EndGetter + #tag Setter + Set + Value = Value.ReplaceLineEndings(EndOfLine) + If Self.mCurrentContent <> Value Then + Self.mCurrentContent = Value + Self.ContentArea.Text = Value + End If + End Set + #tag EndSetter + CurrentContent As String + #tag EndComputedProperty + + #tag ComputedProperty, Flags = &h21 + #tag Getter + Get + If Self.Switcher = Nil Then + Return "" + End If + + Select Case Self.Switcher.SelectedIndex + Case 1 + Return Palworld.HeaderPalworldSettings + End Select + End Get + #tag EndGetter + Private CurrentDefaultHeader As String + #tag EndComputedProperty + + #tag ComputedProperty, Flags = &h21 + #tag Getter + Get + Select Case Self.Switcher.SelectedIndex + Case 1 + Return Palworld.ConfigFileSettings + Else + Return "" + End Select + End Get + #tag EndGetter + Private CurrentFilename As String + #tag EndComputedProperty + + #tag ComputedProperty, Flags = &h21 + #tag Getter + Get + If Self.Switcher = Nil Then + Return "" + End If + + Select Case Self.Switcher.SelectedIndex + Case 1 + Return Palworld.Rewriter.ModeSettingsIni + End Select + End Get + #tag EndGetter + Private CurrentMode As String + #tag EndComputedProperty + + #tag Property, Flags = &h21 + Private mCopyWhenFinished As Boolean + #tag EndProperty + + #tag Property, Flags = &h21 + Private mCurrentContent As String + #tag EndProperty + + #tag Property, Flags = &h21 + Private mCurrentProfile As Palworld.ServerProfile + #tag EndProperty + + #tag Property, Flags = &h21 + Private mFileDestination As FolderItem + #tag EndProperty + + #tag Property, Flags = &h21 + Private mForceTrollMode As Boolean + #tag EndProperty + + #tag Property, Flags = &h21 + Private mLastRewrittenHash As String + #tag EndProperty + + #tag Property, Flags = &h21 + Private mProject As Palworld.Project + #tag EndProperty + + #tag Property, Flags = &h21 + Private mSettingsIniContent As String + #tag EndProperty + + + #tag Constant, Name = RewriterBusyExplanation, Type = String, Dynamic = False, Default = \"Beacon\'s ini generator is busy creating your content. Wait a moment\x2C and try again.", Scope = Private + #tag EndConstant + + #tag Constant, Name = RewriterBusyMessage, Type = String, Dynamic = False, Default = \"Please try again in a moment", Scope = Private + #tag EndConstant + + #tag Constant, Name = SmartCopyErrorExplanation, Type = String, Dynamic = False, Default = \"Smart Copy was unable to prepare your \?1 file. No changes have been made.", Scope = Private + #tag EndConstant + + #tag Constant, Name = SmartCopyErrorMessage, Type = String, Dynamic = False, Default = \"Smart Copy encountered an error.", Scope = Private + #tag EndConstant + + #tag Constant, Name = SmartCopyInstructionsExplanation, Type = String, Dynamic = False, Default = \"Once you have copied your \?1 contents\x2C return here and press the Smart Copy button again.", Scope = Private + #tag EndConstant + + #tag Constant, Name = SmartCopyInstructionsMessage, Type = String, Dynamic = False, Default = \"To use Smart Copy\x2C first copy your entire \?1 file.", Scope = Private + #tag EndConstant + + #tag Constant, Name = SmartCopyReadyExplanation, Type = String, Dynamic = False, Default = \"You are now ready to paste your \?1 content wherever you need it.", Scope = Private + #tag EndConstant + + #tag Constant, Name = SmartCopyReadyMessage, Type = String, Dynamic = False, Default = \"Your \?1 content has been copied.", Scope = Private + #tag EndConstant + + #tag Constant, Name = SmartCopyUnavailableExplanation, Type = String, Dynamic = False, Default = \"Smart Copy can only be used with PalWorldSettings.ini content.", Scope = Private + #tag EndConstant + + #tag Constant, Name = SmartCopyUnavailableMessage, Type = String, Dynamic = False, Default = \"Smart Copy is not available for this content.", Scope = Private + #tag EndConstant + + #tag Constant, Name = UpdatingClipboard, Type = String, Dynamic = False, Default = \"Working\xE2\x80\xA6", Scope = Private + #tag EndConstant + + +#tag EndWindowCode + +#tag Events ActionButton + #tag Event + Sub Pressed() + Self.Close() + End Sub + #tag EndEvent +#tag EndEvents +#tag Events ContentArea + #tag Event + Function ShouldCopy() As Boolean + If Me.SelectionLength <> Me.Text.Length Then + Return True + End If + + Var Content As String = Me.Text + Return Self.CanCopy(Content, Self.CurrentFilename) + End Function + #tag EndEvent + #tag Event + Sub SetupNeeded() + Palworld.SetupCodeEditor(Me) + End Sub + #tag EndEvent + #tag Event + Sub Opening() + Me.ReadOnly = True + End Sub + #tag EndEvent +#tag EndEvents +#tag Events ProfileMenu + #tag Event + Sub SelectionChanged(item As DesktopMenuItem) + #Pragma Unused Item + + Self.Setup() + End Sub + #tag EndEvent +#tag EndEvents +#tag Events Switcher + #tag Event + Sub Opening() + Me.Add(ShelfItem.NewFlexibleSpacer) + Me.Add(IconFileIni, Palworld.ConfigFileSettings, Palworld.ConfigFileSettings) + Me.Add(ShelfItem.NewFlexibleSpacer) + Me.SelectedIndex = 1 + End Sub + #tag EndEvent + #tag Event + Sub Pressed() + Self.RefreshContentArea() + Self.CheckButtons() + End Sub + #tag EndEvent +#tag EndEvents +#tag Events ExportToolbar + #tag Event + Sub Opening() + Me.Append(OmniBarItem.CreateButton("SmartCopy", "Smart Copy", IconToolbarSmartCopy, "Uses your copied ini file to correctly copy the updated version.")) + Me.Append(OmniBarItem.CreateButton("SmartSave", "Smart Save", IconToolbarSmartSaveToDisk, "Allows you to select a file on your computer which Beacon will update with the latest changes.")) + Me.Append(OmniBarItem.CreateSeparator) + Me.Append(OmniBarItem.CreateButton("LazyCopy", "Copy", IconToolbarCopy, "Copies the current ini content to your clipboard.")) + Me.Append(OmniBarItem.CreateButton("LazySave", "Save", IconToolbarSaveToDisk, "Saves the current ini content to your computer.")) + End Sub + #tag EndEvent + #tag Event + Sub ItemPressed(Item As OmniBarItem, ItemRect As Rect) + #Pragma Unused ItemRect + + Select Case Item.Name + Case "SmartCopy" + Self.DoSmartCopy() + Case "SmartSave" + Self.DoSmartSave() + Case "LazyCopy" + Self.DoCopy() + Case "LazySave" + Self.DoSave() + End Select + End Sub + #tag EndEvent +#tag EndEvents +#tag Events ConfigSetsButton + #tag Event + Sub Pressed() + Var Sets() As Beacon.ConfigSet = Self.mProject.ConfigSets + Var States() As Beacon.ConfigSetState = Self.mProject.ConfigSetPriorities + If ConfigSetSelectorDialog.Present(Self, Sets, States) Then + Self.mProject.ConfigSetPriorities = States + End If + + Self.UpdateConfigSetControls() + Self.Setup() + End Sub + #tag EndEvent +#tag EndEvents +#tag Events ConfigSetsOverrideCheck + #tag Event + Sub ValueChanged() + Self.ConfigSetsField.Enabled = Me.Value + Self.ConfigSetsButton.Enabled = Me.Value + Self.Setup() + End Sub + #tag EndEvent +#tag EndEvents +#tag Events SharedRewriter + #tag Event + Sub Finished() + If Me.Errored Then + Break + Else + Var SpecialFinish As Boolean = (Self.mFileDestination Is Nil) = False Or Self.mCopyWhenFinished + If (Me.OutputFlags And Palworld.Rewriter.FlagCreateSettingsIni) = Palworld.Rewriter.FlagCreateSettingsIni Then + If SpecialFinish Then + Self.Finish(Me.FinishedSettingsIniContent) + Else + Self.mSettingsIniContent = Me.FinishedSettingsIniContent + End If + End If + End If + + Self.mFileDestination = Nil + Self.mCopyWhenFinished = False + + Self.RefreshContentArea() + Self.CheckButtons() + End Sub + #tag EndEvent + #tag Event + Sub Started() + Self.CheckButtons() + End Sub + #tag EndEvent +#tag EndEvents +#tag ViewBehavior + #tag ViewProperty + Name="Resizeable" + Visible=true + Group="Frame" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="MenuBarVisible" + Visible=true + Group="Deprecated" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="MinimumWidth" + Visible=true + Group="Size" + InitialValue="64" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="MinimumHeight" + Visible=true + Group="Size" + InitialValue="64" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="MaximumWidth" + Visible=true + Group="Size" + InitialValue="32000" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="MaximumHeight" + Visible=true + Group="Size" + InitialValue="32000" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Type" + Visible=true + Group="Frame" + InitialValue="0" + Type="Types" + EditorType="Enum" + #tag EnumValues + "0 - Document" + "1 - Movable Modal" + "2 - Modal Dialog" + "3 - Floating Window" + "4 - Plain Box" + "5 - Shadowed Box" + "6 - Rounded Window" + "7 - Global Floating Window" + "8 - Sheet Window" + "9 - Modeless Dialog" + #tag EndEnumValues + #tag EndViewProperty + #tag ViewProperty + Name="HasCloseButton" + Visible=true + Group="Frame" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="HasMaximizeButton" + Visible=true + Group="Frame" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="HasMinimizeButton" + Visible=true + Group="Frame" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="HasFullScreenButton" + Visible=true + Group="Frame" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="DefaultLocation" + Visible=true + Group="Behavior" + InitialValue="0" + Type="Locations" + EditorType="Enum" + #tag EnumValues + "0 - Default" + "1 - Parent Window" + "2 - Main Screen" + "3 - Parent Window Screen" + "4 - Stagger" + #tag EndEnumValues + #tag EndViewProperty + #tag ViewProperty + Name="HasBackgroundColor" + Visible=true + Group="Background" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="BackgroundColor" + Visible=true + Group="Background" + InitialValue="&hFFFFFF" + Type="ColorGroup" + EditorType="ColorGroup" + #tag EndViewProperty + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Interfaces" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Width" + Visible=true + Group="Size" + InitialValue="600" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Height" + Visible=true + Group="Size" + InitialValue="400" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Title" + Visible=true + Group="Frame" + InitialValue="Untitled" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Composite" + Visible=false + Group="OS X (Carbon)" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="MacProcID" + Visible=false + Group="OS X (Carbon)" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="ImplicitInstance" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Visible" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="FullScreen" + Visible=false + Group="Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Backdrop" + Visible=true + Group="Background" + InitialValue="" + Type="Picture" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="MenuBar" + Visible=true + Group="Menus" + InitialValue="" + Type="DesktopMenuBar" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="CurrentContent" + Visible=false + Group="Behavior" + InitialValue="" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty +#tag EndViewBehavior diff --git a/Project/Views/Palworld/Import/Discovery Views/PalworldClipboardDiscoveryView.xojo_window b/Project/Views/Palworld/Import/Discovery Views/PalworldClipboardDiscoveryView.xojo_window new file mode 100644 index 000000000..03b2be2ae --- /dev/null +++ b/Project/Views/Palworld/Import/Discovery Views/PalworldClipboardDiscoveryView.xojo_window @@ -0,0 +1,663 @@ +#tag DesktopWindow +Begin PalworldDiscoveryView PalworldClipboardDiscoveryView + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = False + AllowTabs = True + Backdrop = 0 + BackgroundColor = &cFFFFFF00 + Composited = False + Enabled = True + HasBackgroundColor= False + Height = 396 + Index = -2147483648 + InitialParent = "" + Left = 0 + LockBottom = True + LockLeft = True + LockRight = True + LockTop = True + TabIndex = 0 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 0 + Transparent = True + Visible = True + Width = 600 + Begin UITweaks.ResizedPushButton ActionButton + AllowAutoDeactivate= True + Bold = False + Cancel = False + Caption = "Next" + Default = True + Enabled = False + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 20 + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 500 + LockBottom = True + LockedInPosition= False + LockLeft = False + LockRight = True + LockTop = False + MacButtonStyle = 0 + Scope = 2 + TabIndex = 7 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 356 + Transparent = False + Underline = False + Visible = True + Width = 80 + End + Begin UITweaks.ResizedPushButton CancelButton + AllowAutoDeactivate= True + Bold = False + Cancel = True + Caption = "Cancel" + Default = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 20 + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 408 + LockBottom = True + LockedInPosition= False + LockLeft = False + LockRight = True + LockTop = False + MacButtonStyle = 0 + Scope = 2 + TabIndex = 6 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 356 + Transparent = False + Underline = False + Visible = True + Width = 80 + End + Begin Shelf Switcher + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = True + AllowTabs = False + Backdrop = 0 + ContentHeight = 0 + DrawCaptions = True + Enabled = True + Height = 60 + Index = -2147483648 + InitialParent = "" + IsVertical = False + Left = 0 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + RequiresSelection= True + Scope = 2 + ScrollActive = False + ScrollingEnabled= False + ScrollSpeed = 20 + TabIndex = 0 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 0 + Transparent = False + Visible = True + Width = 600 + End + Begin FadedSeparator FadedSeparator1 + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = True + AllowTabs = False + Backdrop = 0 + ContentHeight = 0 + Enabled = True + Height = 1 + Index = -2147483648 + InitialParent = "" + Left = 0 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + Scope = 2 + ScrollActive = False + ScrollingEnabled= False + ScrollSpeed = 20 + TabIndex = 1 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 60 + Transparent = True + Visible = True + Width = 600 + End + Begin DesktopLabel ExplanationLabel + AllowAutoDeactivate= True + Bold = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 38 + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 20 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + Multiline = True + Scope = 2 + Selectable = False + TabIndex = 2 + TabPanelIndex = 0 + TabStop = True + Text = "Paste the contents of your PalWorldSettings.ini file here." + TextAlignment = 0 + TextColor = &c00000000 + Tooltip = "" + Top = 73 + Transparent = True + Underline = False + Visible = True + Width = 560 + End + Begin ClipboardWatcher Watcher + Enabled = True + Index = -2147483648 + LockedInPosition= False + Period = 500 + RunMode = 2 + Scope = 2 + TabPanelIndex = 0 + End + Begin Timer TextChangeDelayTrigger + Enabled = True + Index = -2147483648 + LockedInPosition= False + Period = 100 + RunMode = 0 + Scope = 2 + TabPanelIndex = 0 + End + Begin DesktopRectangle BorderRect + AllowAutoDeactivate= True + BorderColor = &c000000 + BorderThickness = 1.0 + CornerSize = 0.0 + Enabled = True + FillColor = &cFFFFFF + Height = 221 + Index = -2147483648 + Left = 20 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + Scope = 2 + TabIndex = 8 + TabPanelIndex = 0 + Tooltip = "" + Top = 123 + Transparent = False + Visible = True + Width = 560 + Begin CodeEditor ConfigArea + AutoDeactivate = True + Enabled = True + HasBorder = False + Height = 219 + HorizontalScrollPosition= 0 + Index = -2147483648 + InitialParent = "BorderRect" + Left = 21 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + Scope = 2 + SelectionLength = 0 + ShowInfoBar = False + TabIndex = 0 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 124 + VerticalScrollPosition= 0 + Visible = True + Width = 558 + End + End +End +#tag EndDesktopWindow + +#tag WindowCode + #tag Event + Sub Begin() + Self.DesiredHeight = 450 + Self.ConfigArea.Text = "" + End Sub + #tag EndEvent + + #tag Event + Sub Cleanup() + For Idx As Integer = Self.mTempFiles.LastIndex DownTo 0 + If (Self.mTempFiles(Idx) Is Nil) = False And Self.mTempFiles(Idx).Exists Then + Try + Self.mTempFiles(Idx).Remove + Catch Err As RuntimeException + App.Log(Err, CurrentMethodName, "Removing temporary file") + End Try + End If + Self.mTempFiles.RemoveAt(Idx) + Next + End Sub + #tag EndEvent + + #tag Event + Sub Opening() + RaiseEvent Opening + Self.SwapButtons() + End Sub + #tag EndEvent + + + #tag Method, Flags = &h21 + Private Function DetectConfigType(Content As String) As ConfigFileType + Content = Content.GuessEncoding("/Script/") + Var SettingsIniPos As Integer = Content.IndexOf(Palworld.HeaderPalworldSettings) + + If SettingsIniPos > -1 Then + Return ConfigFileType.SettingsIni + End If + + Return ConfigFileType.Other + End Function + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub RefreshContentArea() + Var SettingUp As Boolean = Self.mSettingUp + Self.mSettingUp = True + Select Case Self.Switcher.SelectedIndex + Case Self.SettingsIniIndex + Self.ConfigArea.Text = Self.mSettingsIniContent + Else + Self.ConfigArea.Text = "" + End Select + Self.mSettingUp = SettingUp + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub SetSwitcherForType(Type As ConfigFileType) + If Type = ConfigFileType.SettingsIni And Self.Switcher.SelectedIndex <> Self.SettingsIniIndex Then + Self.Switcher.SelectedIndex = Self.SettingsIniIndex + End If + End Sub + #tag EndMethod + + + #tag Hook, Flags = &h0 + Event Opening() + #tag EndHook + + + #tag Property, Flags = &h21 + Private mSettingsIniContent As String + #tag EndProperty + + #tag Property, Flags = &h21 + Private mSettingUp As Boolean + #tag EndProperty + + #tag Property, Flags = &h21 + Private mTempFiles() As FolderItem + #tag EndProperty + + + #tag Constant, Name = SettingsIniIndex, Type = Double, Dynamic = False, Default = \"1", Scope = Private + #tag EndConstant + + + #tag Enum, Name = ConfigFileType, Flags = &h21 + Other + SettingsIni + GameUserSettingsIni + #tag EndEnum + + +#tag EndWindowCode + +#tag Events ActionButton + #tag Event + Sub Pressed() + Var Profile As New Palworld.ServerProfile(Local.Identifier, Language.DefaultServerName(Palworld.Identifier)) + + If Self.mSettingsIniContent.IsEmpty = False Then + Var TempFile As FolderItem = FolderItem.TemporaryFile + Self.mTempFiles.Add(TempFile) + Try + TempFile.Write(Self.mSettingsIniContent) + Catch Err As RuntimeException + Self.ShowAlert("Import error", "Beacon was unable to write the temporary " + Palworld.ConfigFileSettings + " file necessary to import.") + Return + End Try + Profile.SettingsIniPath = BookmarkedFolderItem.CreateSaveInfo(TempFile) + End If + + Self.ShouldFinish(Profile) + End Sub + #tag EndEvent +#tag EndEvents +#tag Events CancelButton + #tag Event + Sub Pressed() + Self.ShouldCancel() + End Sub + #tag EndEvent +#tag EndEvents +#tag Events Switcher + #tag Event + Sub Opening() + Me.Add(ShelfItem.NewFlexibleSpacer) + Me.Add(IconFileIni, Palworld.ConfigFileSettings, Palworld.ConfigFileSettings) + Me.Add(ShelfItem.NewFlexibleSpacer) + Me.SelectedIndex = Self.SettingsIniIndex + End Sub + #tag EndEvent + #tag Event + Sub Pressed() + Self.RefreshContentArea() + End Sub + #tag EndEvent +#tag EndEvents +#tag Events Watcher + #tag Event + Sub ClipboardChanged(Content As String) + Var Type As ConfigFileType = Self.DetectConfigType(Content) + Self.SetSwitcherForType(Type) + End Sub + #tag EndEvent +#tag EndEvents +#tag Events TextChangeDelayTrigger + #tag Event + Sub Action() + Self.ActionButton.Enabled = (Self.mSettingsIniContent.IsEmpty = False) + End Sub + #tag EndEvent +#tag EndEvents +#tag Events BorderRect + #tag Event + Sub Opening() + Me.BorderColor = SystemColors.SeparatorColor + End Sub + #tag EndEvent +#tag EndEvents +#tag Events ConfigArea + #tag Event + Sub TextChanged() + If Not Self.mSettingUp Then + Var TextValue As String = Me.Text.Trim(Chr(0), Chr(10), Chr(13), Chr(32)) + Select Case Self.Switcher.SelectedIndex + Case Self.SettingsIniIndex + Self.mSettingsIniContent = TextValue + End Select + End If + + If Self.TextChangeDelayTrigger.RunMode = Timer.RunModes.Single Then + Self.TextChangeDelayTrigger.Reset + Else + Self.TextChangeDelayTrigger.RunMode = Timer.RunModes.Single + End If + End Sub + #tag EndEvent + #tag Event + Sub SetupNeeded() + Palworld.SetupCodeEditor(Me) + End Sub + #tag EndEvent +#tag EndEvents +#tag ViewBehavior + #tag ViewProperty + Name="Composited" + Visible=true + Group="Window Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Tooltip" + Visible=true + Group="Appearance" + InitialValue="" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="AllowAutoDeactivate" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocusRing" + Visible=true + Group="Appearance" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="BackgroundColor" + Visible=true + Group="Background" + InitialValue="&hFFFFFF" + Type="ColorGroup" + EditorType="ColorGroup" + #tag EndViewProperty + #tag ViewProperty + Name="HasBackgroundColor" + Visible=true + Group="Background" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocus" + Visible=true + Group="Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowTabs" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Backdrop" + Visible=true + Group="Background" + InitialValue="" + Type="Picture" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Enabled" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Height" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="InitialParent" + Visible=false + Group="Position" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockBottom" + Visible=true + Group="Position" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockLeft" + Visible=true + Group="Position" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockRight" + Visible=true + Group="Position" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockTop" + Visible=true + Group="Position" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabIndex" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabPanelIndex" + Visible=false + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabStop" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Transparent" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Visible" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Width" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty +#tag EndViewBehavior diff --git a/Project/Views/Palworld/Import/Discovery Views/PalworldDiscoveryView.xojo_code b/Project/Views/Palworld/Import/Discovery Views/PalworldDiscoveryView.xojo_code new file mode 100644 index 000000000..a8b48122e --- /dev/null +++ b/Project/Views/Palworld/Import/Discovery Views/PalworldDiscoveryView.xojo_code @@ -0,0 +1,241 @@ +#tag Class +Protected Class PalworldDiscoveryView +Inherits DiscoveryView + #tag CompatibilityFlags = ( TargetDesktop and ( Target32Bit or Target64Bit ) ) + #tag Event + Function GameId() As String + Return Palworld.Identifier + End Function + #tag EndEvent + + + #tag Method, Flags = &h1 + Protected Function Project() As Palworld.Project + Var Temp As Beacon.Project = Super.Project + If Temp IsA Palworld.Project Then + Return Palworld.Project(Temp) + End If + End Function + #tag EndMethod + + + #tag ViewBehavior + #tag ViewProperty + Name="Composited" + Visible=true + Group="Window Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Tooltip" + Visible=true + Group="Appearance" + InitialValue="" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="AllowAutoDeactivate" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocusRing" + Visible=true + Group="Appearance" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="BackgroundColor" + Visible=true + Group="Background" + InitialValue="&hFFFFFF" + Type="ColorGroup" + EditorType="ColorGroup" + #tag EndViewProperty + #tag ViewProperty + Name="HasBackgroundColor" + Visible=true + Group="Background" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocus" + Visible=true + Group="Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowTabs" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Backdrop" + Visible=true + Group="Background" + InitialValue="" + Type="Picture" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Enabled" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Height" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="InitialParent" + Visible=false + Group="Position" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockBottom" + Visible=true + Group="Position" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockLeft" + Visible=true + Group="Position" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockRight" + Visible=true + Group="Position" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockTop" + Visible=true + Group="Position" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabIndex" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabPanelIndex" + Visible=false + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabStop" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Transparent" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Visible" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Width" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag EndViewBehavior +End Class +#tag EndClass diff --git a/Project/Views/Palworld/Import/Discovery Views/PalworldFilesDiscoveryView.xojo_window b/Project/Views/Palworld/Import/Discovery Views/PalworldFilesDiscoveryView.xojo_window new file mode 100644 index 000000000..aadb97b17 --- /dev/null +++ b/Project/Views/Palworld/Import/Discovery Views/PalworldFilesDiscoveryView.xojo_window @@ -0,0 +1,670 @@ +#tag DesktopWindow +Begin PalworldDiscoveryView PalworldFilesDiscoveryView + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = False + AllowTabs = True + Backdrop = 0 + BackgroundColor = &cFFFFFF00 + Composited = False + Enabled = True + HasBackgroundColor= False + Height = 142 + Index = -2147483648 + InitialParent = "" + Left = 0 + LockBottom = True + LockLeft = True + LockRight = True + LockTop = True + TabIndex = 0 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 0 + Transparent = True + Visible = True + Width = 600 + Begin UITweaks.ResizedPushButton ActionButton + AllowAutoDeactivate= True + Bold = False + Cancel = False + Caption = "Next" + Default = True + Enabled = False + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 20 + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 500 + LockBottom = False + LockedInPosition= False + LockLeft = False + LockRight = True + LockTop = True + MacButtonStyle = 0 + Scope = 2 + TabIndex = 10 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 102 + Transparent = False + Underline = False + Visible = True + Width = 80 + End + Begin UITweaks.ResizedPushButton CancelButton + AllowAutoDeactivate= True + Bold = False + Cancel = True + Caption = "Cancel" + Default = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 20 + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 408 + LockBottom = False + LockedInPosition= False + LockLeft = False + LockRight = True + LockTop = True + MacButtonStyle = 0 + Scope = 2 + TabIndex = 9 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 102 + Transparent = False + Underline = False + Visible = True + Width = 80 + End + Begin DesktopLabel MessageLabel + AllowAutoDeactivate= True + Bold = True + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 20 + Index = -2147483648 + Italic = False + Left = 20 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + Multiline = False + Scope = 2 + Selectable = False + TabIndex = 0 + TabPanelIndex = 0 + TabStop = True + Text = "Choose your Palworld game files" + TextAlignment = 0 + TextColor = &c000000 + Tooltip = "" + Top = 20 + Transparent = False + Underline = False + Visible = True + Width = 560 + End + Begin UITweaks.ResizedTextField SettingsIniPathField + AllowAutoDeactivate= True + AllowFocusRing = True + AllowSpellChecking= False + AllowTabs = False + BackgroundColor = &cFFFFFF + Bold = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Format = "" + HasBorder = True + Height = 22 + Hint = "" + Index = -2147483648 + Italic = False + Left = 171 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + MaximumCharactersAllowed= 0 + Password = False + ReadOnly = False + Scope = 2 + TabIndex = 2 + TabPanelIndex = 0 + TabStop = True + Text = "" + TextAlignment = 0 + TextColor = &c000000 + Tooltip = "" + Top = 60 + Transparent = False + Underline = False + ValidationMask = "" + Visible = True + Width = 297 + End + Begin UITweaks.ResizedLabel SettingsIniLabel + AllowAutoDeactivate= True + Bold = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 22 + Index = -2147483648 + Italic = False + Left = 20 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Multiline = False + Scope = 2 + Selectable = False + TabIndex = 1 + TabPanelIndex = 0 + TabStop = True + Text = "PalWorldSettings.ini" + TextAlignment = 3 + TextColor = &c000000 + Tooltip = "" + Top = 60 + Transparent = False + Underline = False + Visible = True + Width = 139 + End + Begin UITweaks.ResizedPushButton SettingsIniChooseButton + AllowAutoDeactivate= True + Bold = False + Cancel = False + Caption = "Choose…" + Default = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 20 + Index = -2147483648 + Italic = False + Left = 480 + LockBottom = False + LockedInPosition= False + LockLeft = False + LockRight = True + LockTop = True + MacButtonStyle = 0 + Scope = 2 + TabIndex = 3 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 60 + Transparent = False + Underline = False + Visible = True + Width = 100 + End +End +#tag EndDesktopWindow + +#tag WindowCode + #tag Event + Sub Begin() + Self.SettingsIniLabel.Text = Palworld.ConfigFileSettings + ":" + BeaconUI.SizeToFit(Self.SettingsIniLabel) + + Var FieldsLeft As Integer = Self.SettingsIniLabel.Right + 12 + Var FieldsWidth As Integer = Self.Width - (Self.SettingsIniLabel.Width + Self.SettingsIniChooseButton.Width + 64) + + Var SettingsIniGroup As New ControlGroup(Self.SettingsIniLabel, Self.SettingsIniPathField, Self.SettingsIniChooseButton) + SettingsIniGroup.Top = Self.MessageLabel.Bottom + 20 + Self.SettingsIniPathField.Left = FieldsLeft + Self.SettingsIniPathField.Width = FieldsWidth + Self.SettingsIniChooseButton.Left = Self.SettingsIniPathField.Right + 12 + + Self.ActionButton.Top = SettingsIniGroup.Bottom + 20 + Self.CancelButton.Top = Self.ActionButton.Top + + Self.DesiredHeight = Self.ActionButton.Bottom + 20 + End Sub + #tag EndEvent + + #tag Event + Sub DropObject(obj As DragItem, action As DragItem.Types) + #Pragma Unused Action + Self.HandleDrop(Obj) + End Sub + #tag EndEvent + + #tag Event + Sub Opening() + RaiseEvent Opening + Self.AcceptFileDrop(BeaconFileTypes.IniFile) + Self.SwapButtons() + End Sub + #tag EndEvent + + + #tag Method, Flags = &h0 + Sub AddFile(File As FolderItem) + Self.AddFile(File, ConfigFileType.Other) + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub AddFile(File As FolderItem, ForceType As ConfigFileType) + If ForceType = ConfigFileType.Other Then + ForceType = Self.DetectConfigType(File) + End If + + Select Case ForceType + Case ConfigFileType.SettingsIni + Self.mSettingsIniFile = File + Self.SettingsIniPathField.Text = File.NativePath + Self.SettingsIniPathField.Tooltip = File.NativePath + End Select + Self.ActionButton.Enabled = (Self.mSettingsIniFile Is Nil) = False + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Function DetectConfigType(File As FolderItem) As ConfigFileType + Var Content As String + Try + Content = File.Read + Catch Err As RuntimeException + Return ConfigFileType.Other + End Try + + Content = Content.GuessEncoding("/Script/") + Var SettingsIniPos As Integer = Content.IndexOf(Palworld.HeaderPalworldSettings) + + If SettingsIniPos > -1 Then + Return ConfigFileType.SettingsIni + Else + Select Case File.Name + Case Palworld.ConfigFileSettings + Return ConfigFileType.SettingsIni + Else + Return ConfigFileType.Other + End Select + End If + End Function + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub HandleDrop(Obj As DragItem) + Do + If Obj.FolderItemAvailable Then + Self.AddFile(Obj.FolderItem) + End If + Loop Until Not Obj.NextItem + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Function ReadIniFile(File As FolderItem, Prompt As Boolean = True) As String + Try + #Pragma BreakOnExceptions False + Var Stream As TextInputStream = TextInputStream.Open(File) + #Pragma BreakOnExceptions Default + Var Contents As String = Stream.ReadAll() + Stream.Close + + Contents = Contents.GuessEncoding("/Script/") + + Return Contents + Catch Err As IOException + #Pragma BreakOnExceptions Default + If Prompt = False Or TargetMacOS = False Then + Return "" + End If + + Var Dialog As New OpenFileDialog + Dialog.InitialFolder = File.Parent + Dialog.SuggestedFileName = File.Name + Dialog.PromptText = "Select your " + File.Name + " file if you want to import it too" + Dialog.ActionButtonCaption = "Import" + Dialog.Filter = BeaconFileTypes.IniFile + + Var Selected As FolderItem = Dialog.ShowModal(Self.TrueWindow) + If Selected = Nil Then + Return "" + End If + + Return Self.ReadIniFile(Selected, False) + End Try + End Function + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub ShowFileChooser(DesiredType As ConfigFileType) + Var Filename As String + Select Case DesiredType + Case ConfigFileType.SettingsIni + Filename = Palworld.ConfigFileSettings + Else + Return + End Select + + Var Dialog As New OpenFileDialog + If (Self.mSettingsIniFile Is Nil) = False Then + Dialog.InitialFolder = Self.mSettingsIniFile.Parent + End If + Dialog.SuggestedFileName = Filename + Dialog.Filter = BeaconFileTypes.IniFile + + Var File As FolderItem = Dialog.ShowModal(Self.TrueWindow) + If File Is Nil Then + Return + End If + + Var Type As ConfigFileType = Self.DetectConfigType(File) + If Type <> DesiredType Then + Var Force As Boolean = Self.ShowConfirm("The chosen file may not be a " + Filename + " file.", "Beacon searched in the file, but it does not look like a normal " + Filename + " file. Do you still want to import it?", "Import Anyway", "Cancel") + If Force = False Then + Return + End If + End If + + Self.AddFile(File, DesiredType) + End Sub + #tag EndMethod + + + #tag Hook, Flags = &h0 + Event Opening() + #tag EndHook + + + #tag Property, Flags = &h21 + Private mSettingsIniFile As FolderItem + #tag EndProperty + + #tag Property, Flags = &h21 + Private mSettingUp As Boolean + #tag EndProperty + + + #tag Constant, Name = GameUserSettingsIniIndex, Type = Double, Dynamic = False, Default = \"1", Scope = Private + #tag EndConstant + + #tag Constant, Name = SettingsIniIndex, Type = Double, Dynamic = False, Default = \"2", Scope = Private + #tag EndConstant + + + #tag Enum, Name = ConfigFileType, Flags = &h21 + Other + SettingsIni + #tag EndEnum + + +#tag EndWindowCode + +#tag Events ActionButton + #tag Event + Sub Pressed() + Var Profile As New Palworld.ServerProfile(Local.Identifier, Language.DefaultServerName(Palworld.Identifier)) + If (Self.mSettingsIniFile Is Nil) = False Then + Profile.SettingsIniPath = BookmarkedFolderItem.CreateSaveInfo(Self.mSettingsIniFile) + If Profile.SecondaryName.IsEmpty Then + Profile.SecondaryName = Self.mSettingsIniFile.PartialPath + End If + End If + Self.ShouldFinish(Profile) + End Sub + #tag EndEvent +#tag EndEvents +#tag Events CancelButton + #tag Event + Sub Pressed() + Self.ShouldCancel() + End Sub + #tag EndEvent +#tag EndEvents +#tag Events SettingsIniChooseButton + #tag Event + Sub Pressed() + Self.ShowFileChooser(ConfigFileType.SettingsIni) + End Sub + #tag EndEvent +#tag EndEvents +#tag ViewBehavior + #tag ViewProperty + Name="Composited" + Visible=true + Group="Window Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Tooltip" + Visible=true + Group="Appearance" + InitialValue="" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="AllowAutoDeactivate" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocusRing" + Visible=true + Group="Appearance" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="BackgroundColor" + Visible=true + Group="Background" + InitialValue="&hFFFFFF" + Type="ColorGroup" + EditorType="ColorGroup" + #tag EndViewProperty + #tag ViewProperty + Name="HasBackgroundColor" + Visible=true + Group="Background" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocus" + Visible=true + Group="Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowTabs" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Backdrop" + Visible=true + Group="Background" + InitialValue="" + Type="Picture" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Enabled" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Height" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="InitialParent" + Visible=false + Group="Position" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockBottom" + Visible=true + Group="Position" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockLeft" + Visible=true + Group="Position" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockRight" + Visible=true + Group="Position" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockTop" + Visible=true + Group="Position" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabIndex" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabPanelIndex" + Visible=false + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabStop" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Transparent" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Visible" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Width" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty +#tag EndViewBehavior diff --git a/Project/Views/Palworld/Import/PalworldImportView.xojo_window b/Project/Views/Palworld/Import/PalworldImportView.xojo_window new file mode 100644 index 000000000..5b52f7b54 --- /dev/null +++ b/Project/Views/Palworld/Import/PalworldImportView.xojo_window @@ -0,0 +1,1600 @@ +#tag DesktopWindow +Begin DocumentImportView PalworldImportView + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = False + AllowTabs = True + Backdrop = 0 + BackgroundColor = &cFFFFFF00 + Composite = "False" + Composited = False + DefaultLocation = "2" + DoubleBuffer = "False" + Enabled = True + EraseBackground = "True" + FullScreen = "False" + HasBackgroundColor= False + HasCloseButton = "True" + HasFullScreenButton= "False" + HasMaximizeButton= "True" + HasMinimizeButton= "True" + Height = 480 + ImplicitInstance= "True" + Index = -2147483648 + InitialParent = "" + Left = 0 + LockBottom = False + LockLeft = False + LockRight = False + LockTop = False + MacProcID = "0" + MaximumHeight = "32000" + MaximumWidth = "32000" + MenuBar = "0" + MenuBarVisible = "False" + MinimumHeight = "64" + MinimumWidth = "64" + Resizeable = "True" + TabIndex = 0 + TabPanelIndex = 0 + TabStop = True + Title = "Untitled" + Tooltip = "" + Top = 0 + Transparent = True + Type = "0" + Visible = True + Width = 720 + Begin DesktopPagePanel Views + AllowAutoDeactivate= True + Enabled = True + Height = 480 + Index = -2147483648 + InitialParent = "" + Left = 0 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + PanelCount = 8 + Panels = "" + Scope = 2 + SelectedPanelIndex= 0 + TabIndex = 0 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 0 + Transparent = False + Value = 0 + Visible = True + Width = 720 + Begin FTPDiscoveryView FTPDiscoveryView1 + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = False + AllowTabs = True + Backdrop = 0 + BackgroundColor = &cFFFFFF + Composited = False + Enabled = True + HasBackgroundColor= False + Height = 480 + Index = -2147483648 + InitialParent = "Views" + Left = 0 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + Scope = 2 + TabIndex = 0 + TabPanelIndex = 3 + TabStop = True + Tooltip = "" + Top = 0 + Transparent = True + Visible = True + Width = 720 + End + Begin PalworldFilesDiscoveryView FilesDiscoveryView1 + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = False + AllowTabs = True + Backdrop = 0 + BackgroundColor = &cFFFFFF + Composited = False + Enabled = True + HasBackgroundColor= False + Height = 480 + Index = -2147483648 + InitialParent = "Views" + Left = 0 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + Scope = 2 + TabIndex = 0 + TabPanelIndex = 4 + TabStop = True + Tooltip = "" + Top = 0 + Transparent = True + Visible = True + Width = 720 + End + Begin DesktopLabel StatusMessageLabel + AllowAutoDeactivate= True + Bold = True + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 20 + Index = -2147483648 + InitialParent = "Views" + Italic = False + Left = 20 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + Multiline = False + Scope = 2 + Selectable = False + TabIndex = 0 + TabPanelIndex = 5 + TabStop = True + Text = "Import Status" + TextAlignment = 0 + TextColor = &c00000000 + Tooltip = "" + Top = 20 + Transparent = False + Underline = False + Visible = True + Width = 680 + End + Begin UITweaks.ResizedPushButton StatusCancelButton + AllowAutoDeactivate= True + Bold = False + Cancel = True + Caption = "Cancel" + Default = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 20 + Index = -2147483648 + InitialParent = "Views" + Italic = False + Left = 620 + LockBottom = True + LockedInPosition= False + LockLeft = False + LockRight = True + LockTop = False + MacButtonStyle = 0 + Scope = 2 + TabIndex = 3 + TabPanelIndex = 5 + TabStop = True + Tooltip = "" + Top = 440 + Transparent = False + Underline = False + Visible = True + Width = 80 + End + Begin DesktopLabel OtherDocsMessageLabel + AllowAutoDeactivate= True + Bold = True + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 20 + Index = -2147483648 + InitialParent = "Views" + Italic = False + Left = 20 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + Multiline = False + Scope = 2 + Selectable = False + TabIndex = 0 + TabPanelIndex = 6 + TabStop = True + Text = "Import from Other Projects" + TextAlignment = 0 + TextColor = &c00000000 + Tooltip = "" + Top = 20 + Transparent = False + Underline = False + Visible = True + Width = 680 + End + Begin UITweaks.ResizedPushButton OtherDocsActionButton + AllowAutoDeactivate= True + Bold = False + Cancel = False + Caption = "Continue" + Default = True + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 20 + Index = -2147483648 + InitialParent = "Views" + Italic = False + Left = 620 + LockBottom = True + LockedInPosition= False + LockLeft = False + LockRight = True + LockTop = False + MacButtonStyle = 0 + Scope = 2 + TabIndex = 3 + TabPanelIndex = 6 + TabStop = True + Tooltip = "" + Top = 440 + Transparent = False + Underline = False + Visible = True + Width = 80 + End + Begin UITweaks.ResizedPushButton OtherDocsCancelButton + AllowAutoDeactivate= True + Bold = False + Cancel = True + Caption = "Cancel" + Default = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 20 + Index = -2147483648 + InitialParent = "Views" + Italic = False + Left = 528 + LockBottom = True + LockedInPosition= False + LockLeft = False + LockRight = True + LockTop = False + MacButtonStyle = 0 + Scope = 2 + TabIndex = 2 + TabPanelIndex = 6 + TabStop = True + Tooltip = "" + Top = 440 + Transparent = False + Underline = False + Visible = True + Width = 80 + End + Begin BeaconListbox StatusList + AllowAutoDeactivate= True + AllowAutoHideScrollbars= True + AllowExpandableRows= False + AllowFocusRing = True + AllowInfiniteScroll= False + AllowResizableColumns= False + AllowRowDragging= False + AllowRowReordering= False + Bold = False + ColumnCount = 1 + ColumnWidths = "" + DefaultRowHeight= 40 + DefaultSortColumn= 0 + DefaultSortDirection= 0 + DropIndicatorVisible= False + EditCaption = "Edit" + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + GridLineStyle = 0 + HasBorder = True + HasHeader = False + HasHorizontalScrollbar= False + HasVerticalScrollbar= True + HeadingIndex = -1 + Height = 360 + Index = -2147483648 + InitialParent = "Views" + InitialValue = "" + Italic = False + Left = 20 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + PageSize = 100 + PreferencesKey = "" + RequiresSelection= False + RowSelectionType= 0 + Scope = 2 + TabIndex = 1 + TabPanelIndex = 5 + TabStop = True + Tooltip = "" + Top = 60 + TotalPages = -1 + Transparent = False + TypeaheadColumn = 0 + Underline = False + Visible = True + VisibleRowCount = 0 + Width = 680 + _ScrollOffset = 0 + _ScrollWidth = -1 + End + Begin BeaconListbox OtherDocsList + AllowAutoDeactivate= True + AllowAutoHideScrollbars= True + AllowExpandableRows= False + AllowFocusRing = True + AllowInfiniteScroll= False + AllowResizableColumns= False + AllowRowDragging= False + AllowRowReordering= False + Bold = False + ColumnCount = 2 + ColumnWidths = "26,*" + DefaultRowHeight= 26 + DefaultSortColumn= 0 + DefaultSortDirection= 0 + DropIndicatorVisible= False + EditCaption = "Edit" + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + GridLineStyle = 0 + HasBorder = True + HasHeader = False + HasHorizontalScrollbar= False + HasVerticalScrollbar= True + HeadingIndex = 1 + Height = 360 + Index = -2147483648 + InitialParent = "Views" + InitialValue = "" + Italic = False + Left = 20 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + PageSize = 100 + PreferencesKey = "" + RequiresSelection= False + RowSelectionType= 0 + Scope = 2 + TabIndex = 1 + TabPanelIndex = 6 + TabStop = True + Tooltip = "" + Top = 60 + TotalPages = -1 + Transparent = False + TypeaheadColumn = 0 + Underline = False + Visible = True + VisibleRowCount = 0 + Width = 680 + _ScrollOffset = 0 + _ScrollWidth = -1 + End + Begin DocumentImportSourcePicker SourcePicker + AllowAutoDeactivate= True + AllowedSources = 63 + AllowFocus = False + AllowFocusRing = False + AllowTabs = True + Backdrop = 0 + BackgroundColor = &cFFFFFF + Composited = False + Enabled = True + EnabledSources = 63 + HasBackgroundColor= False + Height = 282 + Index = -2147483648 + InitialParent = "Views" + Left = 0 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + Scope = 2 + TabIndex = 0 + TabPanelIndex = 1 + TabStop = True + Tooltip = "" + Top = 0 + Transparent = True + Visible = True + Width = 720 + End + Begin UITweaks.ResizedPushButton StatusActionButton + AllowAutoDeactivate= True + Bold = False + Cancel = False + Caption = "Import" + Default = False + Enabled = True + FontName = "System" + FontSize = 0.0 + FontUnit = 0 + Height = 20 + Index = -2147483648 + InitialParent = "Views" + Italic = False + Left = 528 + LockBottom = True + LockedInPosition= False + LockLeft = False + LockRight = True + LockTop = False + MacButtonStyle = 0 + Scope = 2 + TabIndex = 4 + TabPanelIndex = 5 + TabStop = True + Tooltip = "" + Top = 440 + Transparent = False + Underline = False + Visible = False + Width = 80 + End + Begin PalworldClipboardDiscoveryView ClipboardDiscoveryView1 + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = False + AllowTabs = True + Backdrop = 0 + BackgroundColor = &cFFFFFF00 + Composited = False + Enabled = True + HasBackgroundColor= False + Height = 480 + Index = -2147483648 + InitialParent = "Views" + Left = 0 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + Scope = 2 + TabIndex = 0 + TabPanelIndex = 8 + TabStop = True + Tooltip = "" + Top = 0 + Transparent = True + Visible = True + Width = 720 + End + Begin MultiSelectDiscoveryView NitradoDiscoveryView1 + AddressColumnLabel= "Address" + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = False + AllowTabs = True + Backdrop = 0 + BackgroundColor = &cFFFFFF + Composited = False + Enabled = True + HasBackgroundColor= False + Height = 480 + Index = -2147483648 + InitialParent = "Views" + Left = 0 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + Scope = 2 + TabIndex = 0 + TabPanelIndex = 2 + TabStop = True + Tooltip = "" + Top = 0 + Transparent = True + Visible = True + Width = 720 + End + Begin MultiSelectDiscoveryView GSADiscoveryView1 + AddressColumnLabel= "Template Id" + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = False + AllowTabs = True + Backdrop = 0 + BackgroundColor = &cFFFFFF + Composited = False + Enabled = True + HasBackgroundColor= False + Height = 480 + Index = -2147483648 + InitialParent = "Views" + Left = 0 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Scope = 2 + TabIndex = 0 + TabPanelIndex = 7 + TabStop = True + Tooltip = "" + Top = 0 + Transparent = True + Visible = True + Width = 720 + End + End + Begin Timer DiscoveryWatcher + Enabled = True + Index = -2147483648 + LockedInPosition= False + Period = 100 + RunMode = 0 + Scope = 2 + TabPanelIndex = 0 + End +End +#tag EndDesktopWindow + +#tag WindowCode + #tag Event + Sub Cleanup() + Self.ClipboardDiscoveryView1.Cleanup + Self.FilesDiscoveryView1.Cleanup + Self.FTPDiscoveryView1.Cleanup + Self.GSADiscoveryView1.Cleanup + Self.NitradoDiscoveryView1.Cleanup + End Sub + #tag EndEvent + + #tag Event + Sub Discover(Profiles() As Beacon.ServerProfile) + Self.mIntegrations.ResizeTo(Profiles.LastIndex) + Self.StatusList.RowCount = Profiles.Count + + For Idx As Integer = Self.mIntegrations.FirstRowIndex To Self.mIntegrations.LastIndex + If (Profiles(Idx) IsA Palworld.ServerProfile) = False Then + Continue + End If + + Var Integration As New Palworld.DiscoverIntegration(Self.mDestinationProject, Profiles(Idx)) + Integration.Begin() + Self.mIntegrations(Idx) = Integration + + Self.StatusList.CellTextAt(Idx, 0) = Profiles(Idx).Name + EndOfLine + "Starting parser…" + Self.StatusList.RowTagAt(Idx) = Integration + Next + + Self.SetThreadPriorities() + Self.DiscoveryWatcher.RunMode = Timer.RunModes.Multiple + Self.Views.SelectedPanelIndex = Self.PageStatus + End Sub + #tag EndEvent + + #tag Event + Sub ImportFile(File As FolderItem) + Self.QuickCancel = True + Self.Views.SelectedPanelIndex = 3 + Self.FilesDiscoveryView1.AddFile(File) + End Sub + #tag EndEvent + + #tag Event + Sub Opening() + RaiseEvent Opening + + Self.SwapButtons + Self.Reset + End Sub + #tag EndEvent + + #tag Event + Sub PullValuesFromProject(Project As Beacon.Project) + If (Project IsA Palworld.Project) = False Then + Return + End If + + Var ArkProject As Palworld.Project = Palworld.Project(Project) + Self.mDestinationProject = ArkProject + Self.FTPDiscoveryView1.PullValuesFromProject(ArkProject) + Self.FilesDiscoveryView1.PullValuesFromProject(ArkProject) + Self.ClipboardDiscoveryView1.PullValuesFromProject(ArkProject) + Self.NitradoDiscoveryView1.PullValuesFromProject(ArkProject) + Self.GSADiscoveryView1.PullValuesFromProject(ArkProject) + End Sub + #tag EndEvent + + #tag Event + Sub Reset() + For Idx As Integer = 0 To Self.mIntegrations.LastIndex + If (Self.mIntegrations(Idx) Is Nil) = False And Not Self.mIntegrations(Idx).Finished Then + Self.mIntegrations(Idx).Cancel + End If + Next + + Self.mIntegrations.ResizeTo(-1) + + If (Self.Views Is Nil) = False Then + If Self.Views.SelectedPanelIndex <> 0 Then + Self.Views.SelectedPanelIndex = 0 + Else + Self.SetPageHeight(Self.SourcePicker.Height) + Self.SourcePicker.ActionButtonEnabled = True + End If + End If + End Sub + #tag EndEvent + + #tag Event + Sub SetOtherProjects(Projects() As Beacon.Project) + Var DestinationProjectId As String + If (Self.mDestinationProject Is Nil) = False Then + DestinationProjectId = Self.mDestinationProject.ProjectId + End If + + Self.mOtherProjects = Projects + + If Projects.Count > 0 Then + Self.SourcePicker.EnabledSources = Self.SourcePicker.EnabledSources Or Self.SourcePicker.SourceOtherProject + Else + Self.SourcePicker.EnabledSources = Self.SourcePicker.EnabledSources And Not Self.SourcePicker.SourceOtherProject + End If + End Sub + #tag EndEvent + + + #tag Method, Flags = &h21 + Private Sub Finish() + Var Projects() As Beacon.Project + For Idx As Integer = Self.mIntegrations.FirstRowIndex To Self.mIntegrations.LastIndex + If (Self.mIntegrations(Idx).Project Is Nil) = False Then + Projects.Add(Self.mIntegrations(Idx).Project) + End If + Next + Self.Finish(Projects) + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub MigrateThread_Run(Sender As Beacon.Thread) + Var SourceProject As Ark.Project = Sender.UserData + Var NewProject As New Palworld.Project + Var ConfigSets() As Beacon.ConfigSet = SourceProject.ConfigSets + For Each ConfigSet As Beacon.ConfigSet In ConfigSets + Var ClonedConfigSet As Beacon.ConfigSet + If ConfigSet.IsBase Then + ClonedConfigSet = NewProject.ConfigSet("Base") + Else + ClonedConfigSet = New Beacon.ConfigSet(ConfigSet.Name) + NewProject.AddConfigSet(ClonedConfigSet) + End If + + For Each Group As Beacon.ConfigGroup In SourceProject.ImplementedConfigs(ConfigSet) + // Skip servers and accounts + Var GroupName As String + Select Case Group.InternalName + Case Ark.Configs.NameCustomConfig + GroupName = Palworld.Configs.NameCustomConfig + Case Ark.Configs.NameGeneralSettings + GroupName = Palworld.Configs.NameGeneralSettings + Case Ark.Configs.NameProjectSettings + GroupName = Palworld.Configs.NameProjectSettings + Else + Continue + End Select + + Try + Var SaveData As Dictionary = Group.SaveData + If SaveData Is Nil Then + Continue + End If + + Var EncryptedData As Dictionary + If SaveData.HasAllKeys("Plain", "Encrypted") Then + EncryptedData = SaveData.Value("Encrypted") + SaveData = SaveData.Value("Plain") + End If + + // JSONify the data + SaveData = Beacon.ParseJSON(Beacon.GenerateJSON(SaveData, False)) + If (EncryptedData Is Nil) = False Then + EncryptedData = Beacon.ParseJSON(Beacon.GenerateJSON(SaveData, False)) + End If + + Var NewGroup As Palworld.ConfigGroup = Palworld.Configs.CreateInstance(GroupName, SaveData, EncryptedData) + If NewGroup Is Nil Then + Continue + End If + + NewProject.AddConfigGroup(NewGroup, ClonedConfigSet) + Catch Err As RuntimeException + End Try + Next + Next + + // Cleanup stuff + NewProject.PruneUnknownContent() + + Var Update As New Dictionary + Update.Value("Event") = "Finished" + Update.Value("Project") = NewProject + Sender.AddUserInterfaceUpdate(Update) + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub MigrateThread_UserInterfaceUpdate(Sender As Beacon.Thread, Updates() As Dictionary) + For Each Update As Dictionary In Updates + Var EventName As String = Update.Lookup("Event", "").StringValue + Select Case EventName + Case "Finished" + Var Project As Palworld.Project = Update.Value("Project") + Self.mSourceProjects.Add(Project) + + Var Idx As Integer = Self.mMigrationThreads.IndexOf(Sender) + If Idx > -1 Then + Self.mMigrationThreads.RemoveAt(Idx) + End If + + If Self.mMigrationThreads.Count = 0 Then + If (Self.mMigrationProgress Is Nil) = False Then + Self.mMigrationProgress.Close + Self.mMigrationProgress = Nil + End If + Self.Finish(Self.mSourceProjects) + End If + End Select + Next + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub SetThreadPriorities() + // Dynamically adjusts thread priority depending on the number that are actively running + + Var ActiveIntegrations() As Palworld.DiscoverIntegration + For Each Integration As Palworld.DiscoverIntegration In Self.mIntegrations + If Integration Is Nil Then + Continue + End If + + If Integration.ThreadState <> Global.Thread.ThreadStates.NotRunning Then + ActiveIntegrations.Add(Integration) + End If + Next + + Var Priority As Integer = If(ActiveIntegrations.Count > 3, Global.Thread.LowestPriority, Global.Thread.NormalPriority) + For Each Integration As Palworld.DiscoverIntegration In ActiveIntegrations + If Integration.ThreadPriority <> Priority Then + Integration.ThreadPriority = Priority + End If + Next + End Sub + #tag EndMethod + + + #tag Hook, Flags = &h0 + Event DocumentsImported(Documents() As Beacon.Project) + #tag EndHook + + #tag Hook, Flags = &h0 + Event Opening() + #tag EndHook + + + #tag Property, Flags = &h21 + Private mDestinationProject As Palworld.Project + #tag EndProperty + + #tag Property, Flags = &h21 + Private mIntegrations() As Palworld.DiscoverIntegration + #tag EndProperty + + #tag Property, Flags = &h21 + Private mMigrationProgress As ProgressWindow + #tag EndProperty + + #tag Property, Flags = &h21 + Private mMigrationThreads() As Beacon.Thread + #tag EndProperty + + #tag Property, Flags = &h21 + Private mOtherProjects() As Beacon.Project + #tag EndProperty + + #tag Property, Flags = &h21 + Private mSourceProjects() As Beacon.Project + #tag EndProperty + + #tag Property, Flags = &h0 + QuickCancel As Boolean + #tag EndProperty + + + #tag Constant, Name = MigrationMessagePlural, Type = String, Dynamic = True, Default = \"Migrating \?1 Projects", Scope = Private + #tag EndConstant + + #tag Constant, Name = MigrationMessageSingular, Type = String, Dynamic = True, Default = \"Migrating \?1 Project", Scope = Private + #tag EndConstant + + #tag Constant, Name = PageClipboard, Type = Double, Dynamic = False, Default = \"7", Scope = Private + #tag EndConstant + + #tag Constant, Name = PageFiles, Type = Double, Dynamic = False, Default = \"3", Scope = Private + #tag EndConstant + + #tag Constant, Name = PageFTP, Type = Double, Dynamic = False, Default = \"2", Scope = Private + #tag EndConstant + + #tag Constant, Name = PageGSA, Type = Double, Dynamic = False, Default = \"6", Scope = Private + #tag EndConstant + + #tag Constant, Name = PageNitrado, Type = Double, Dynamic = False, Default = \"1", Scope = Private + #tag EndConstant + + #tag Constant, Name = PageOtherDocuments, Type = Double, Dynamic = False, Default = \"5", Scope = Private + #tag EndConstant + + #tag Constant, Name = PageSources, Type = Double, Dynamic = False, Default = \"0", Scope = Private + #tag EndConstant + + #tag Constant, Name = PageStatus, Type = Double, Dynamic = False, Default = \"4", Scope = Private + #tag EndConstant + + #tag Constant, Name = StatusPageHeight, Type = Double, Dynamic = False, Default = \"456", Scope = Private + #tag EndConstant + + +#tag EndWindowCode + +#tag Events Views + #tag Event + Sub PanelChanged() + Select Case Me.SelectedPanelIndex + Case Self.PageSources + Self.SetPageHeight(Self.SourcePicker.Height) + Self.SourcePicker.ActionButtonEnabled = True + Case Self.PageNitrado + Self.NitradoDiscoveryView1.Begin + Case Self.PageFTP + Self.FTPDiscoveryView1.Begin + Case Self.PageFiles + Self.FilesDiscoveryView1.Begin + Case Self.PageClipboard + Self.ClipboardDiscoveryView1.Begin + Case Self.PageStatus, Self.PageOtherDocuments + Self.SetPageHeight(Self.StatusPageHeight) + Case Self.PageGSA + Self.GSADiscoveryView1.Begin + End Select + End Sub + #tag EndEvent +#tag EndEvents +#tag Events FTPDiscoveryView1 + #tag Event + Sub ShouldCancel() + If Self.QuickCancel Then + Self.Dismiss + Else + Views.SelectedPanelIndex = 0 + End If + End Sub + #tag EndEvent + #tag Event + Sub Finished(Profiles() As Beacon.ServerProfile) + Self.Discover(Profiles) + End Sub + #tag EndEvent + #tag Event + Sub ShouldResize(NewHeight As Integer) + Self.SetPageHeight(NewHeight) + End Sub + #tag EndEvent + #tag Event + Function GetDestinationProject() As Beacon.Project + Return Self.mDestinationProject + End Function + #tag EndEvent + #tag Event + Function CreateServerProfile(Name As String) As Beacon.ServerProfile + Return New Palworld.ServerProfile(FTP.Identifier, Name) + End Function + #tag EndEvent + #tag Event + Function GameId() As String + Return Palworld.Identifier + End Function + #tag EndEvent + #tag Event + Function Satisfied(Path As String) As Boolean + // Allow the selection of any file, because the user might have renamed them or using a symlink + Return Path.EndsWith("/") = False + End Function + #tag EndEvent + #tag Event + Function Discover(Provider As FTP.HostingProvider, InitialProfile As Beacon.ServerProfile, SenderThread As Beacon.Thread) As Beacon.ServerProfile() + // Do not trap exceptions here. The caller has its own handler so that error messages can reach the user. + + #Pragma Unused SenderThread + + Var Profiles() As Beacon.ServerProfile + Var RootFilenames() As String + RootFilenames = Provider.ListFiles(Self.mDestinationProject, InitialProfile, "/") + If RootFilenames Is Nil Or RootFilenames.Count = 0 Then + Var Err As New UnsupportedOperationException + Err.Message = "The server did not list any files." + Raise Err + End If + + Var IPMatch As New Regex + IPMatch.SearchPattern = "^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}_\d{1,5}$" + + Var PotentialPaths() As String + For Each Filename As String In RootFilenames + If Filename.EndsWith("/") = False Then + Continue + End If + + Filename = Filename.Left(Filename.Length - 1) + If Filename = "Palworld" Or Filename = "arkserver" Then + PotentialPaths.Add("/" + FileName + "/ShooterGame/Saved") + PotentialPaths.Add("/" + Filename + "/ShooterGame/SavedArks") + ElseIf Filename = "ShooterGame" Then + PotentialPaths.Add("/" + FileName + "/Saved") + PotentialPaths.Add("/" + FileName + "/SavedArks") + ElseIf Filename = "Saved" Or Filename = "SavedArks" Then + PotentialPaths.Add("/" + Filename) + ElseIf IPMatch.Search(Filename) <> Nil Then + PotentialPaths.Add("/" + Filename + "/ShooterGame/Saved") + PotentialPaths.Add("/" + Filename + "/ShooterGame/SavedArks") + End If + Next + + For Each Path As String In PotentialPaths + Var Filenames() As String + Try + Filenames = Provider.ListFiles(Self.mDestinationProject, InitialProfile, Path + "/") + Catch Err As RuntimeException + Continue + End Try + Var LogsPath, ConfigPath As String + For Each Filename As String In Filenames + Select Case FileName + Case "Config/" + ConfigPath = Path + "/Config" + Case "Logs/" + LogsPath = Path + "/Logs" + End Select + Next + If ConfigPath.IsEmpty Then + Continue + End If + + Try + Filenames = Provider.ListFiles(Self.mDestinationProject, InitialProfile, ConfigPath + "/") + Catch Err As RuntimeException + Continue + End Try + Var Found As Boolean + For Each Filename As String In Filenames + If Filename.EndsWith("/") = False Then + Continue + End If + + If Filename.EndsWith("Server/") Or Filename.EndsWith("NoEditor/") Then + ConfigPath = ConfigPath + "/" + Filename.Left(Filename.Length - 1) + Found = True + End If + Next + + If Not Found Then + Continue + End If + + Var SettingsIniPath As String = ConfigPath + "/" + Palworld.ConfigFileSettings + Var ProfileId As String = Beacon.UUID.v5(FTP.Identifier + ":" + InitialProfile.HostConfig.Hash + ":" + SettingsIniPath) + Var Profile As New Palworld.ServerProfile(FTP.Identifier, ProfileId, InitialProfile.Name, InitialProfile.Nickname, InitialProfile.SecondaryName) + Profile.HostConfig = InitialProfile.HostConfig + Profile.SettingsIniPath = SettingsIniPath + Profile.LogsPath = LogsPath + Profiles.Add(Profile) + Next + + If Profiles.Count = 0 Then + Var SettingsIniPath As String = Me.ChoosePath(Palworld.ConfigFileSettings, FTPDiscoveryView.ChooserOptionAllowFiles Or FTPDiscoveryView.ChooserOptionStrictNames) + If SettingsIniPath.IsEmpty Then + // Cancelled + Return Profiles + End If + + Var PathComponents() As String = SettingsIniPath.Split("/") + PathComponents.RemoveAt(PathComponents.LastIndex) + + Var LogsPath As String + If PathComponents.Count >= 2 Then + PathComponents.RemoveAt(PathComponents.LastIndex) + PathComponents.RemoveAt(PathComponents.LastIndex) + + Var SavedPath As String = String.FromArray(PathComponents, "/") + Var Children() As String = Provider.ListFiles(Self.mDestinationProject, InitialProfile, SavedPath + "/") + If Children.IndexOf("Logs/") > -1 Then + LogsPath = SavedPath + "/Logs" + Else + LogsPath = Me.ChoosePath("Logs", FTPDiscoveryView.ChooserOptionAllowFolders Or FTPDiscoveryView.ChooserOptionOptional Or FTPDiscoveryView.ChooserOptionStrictNames) + End If + If LogsPath.EndsWith("/") Then + LogsPath = LogsPath.Left(LogsPath.Length -1) + End If + End If + + Var ProfileId As String = Beacon.UUID.v5(FTP.Identifier + ":" + InitialProfile.HostConfig.Hash + ":" + SettingsIniPath) + Var Profile As New Palworld.ServerProfile(FTP.Identifier, ProfileId, InitialProfile.Name, InitialProfile.Nickname, InitialProfile.SecondaryName) + Profile.HostConfig = InitialProfile.HostConfig + Profile.SettingsIniPath = SettingsIniPath + Profile.LogsPath = LogsPath + Profiles.Add(Profile) + End If + + Return Profiles + End Function + #tag EndEvent +#tag EndEvents +#tag Events FilesDiscoveryView1 + #tag Event + Sub ShouldCancel() + If Self.QuickCancel Then + Self.Dismiss + Else + Views.SelectedPanelIndex = 0 + End If + End Sub + #tag EndEvent + #tag Event + Sub Finished(Profiles() As Beacon.ServerProfile) + Self.Discover(Profiles) + End Sub + #tag EndEvent + #tag Event + Sub ShouldResize(NewHeight As Integer) + Self.SetPageHeight(NewHeight) + End Sub + #tag EndEvent + #tag Event + Function GetDestinationProject() As Beacon.Project + Return Self.mDestinationProject + End Function + #tag EndEvent +#tag EndEvents +#tag Events StatusCancelButton + #tag Event + Sub Pressed() + If Self.QuickCancel Then + Self.Close + Else + Self.Reset() + End If + End Sub + #tag EndEvent +#tag EndEvents +#tag Events OtherDocsActionButton + #tag Event + Sub Pressed() + Self.mSourceProjects.ResizeTo(-1) + For I As Integer = 0 To OtherDocsList.RowCount - 1 + If Not OtherDocsList.CellCheckBoxValueAt(I, 0) Then + Continue + End If + + Var SourceProject As Beacon.Project = OtherDocsList.RowTagAt(I) + Select Case SourceProject + Case IsA Palworld.Project + Self.mSourceProjects.Add(Palworld.Project(SourceProject).Clone(App.IdentityManager.CurrentIdentity)) + Case IsA Ark.Project + // We need to export to ini, then import from that, and finally prune it. + Var MigrateThread As New Beacon.Thread + MigrateThread.UserData = SourceProject + MigrateThread.DebugIdentifier = "Palworld Migrator Thread" + AddHandler MigrateThread.Run, WeakAddressOf MigrateThread_Run + AddHandler MigrateThread.UserInterfaceUpdate, WeakAddressOf MigrateThread_UserInterfaceUpdate + MigrateThread.Start + Self.mMigrationThreads.Add(MigrateThread) + End Select + Next + If Self.mMigrationThreads.Count = 0 Then + Self.Finish(Self.mSourceProjects) + Else + Self.mMigrationProgress = New ProgressWindow + Self.mMigrationProgress.Message = Language.ReplacePlaceholders(If(Self.mMigrationThreads.Count = 1, Self.MigrationMessageSingular, Self.MigrationMessagePlural), Language.GameName(Ark.Identifier)) + Self.mMigrationProgress.Progress = Nil + Self.mMigrationProgress.Show(Self) + End If + End Sub + #tag EndEvent +#tag EndEvents +#tag Events OtherDocsCancelButton + #tag Event + Sub Pressed() + Views.SelectedPanelIndex = Self.PageSources + End Sub + #tag EndEvent +#tag EndEvents +#tag Events OtherDocsList + #tag Event + Sub CellAction(row As Integer, column As Integer) + #Pragma Unused Row + + If Column <> 0 Then + Return + End If + + Var Enabled As Boolean + For I As Integer = 0 To Me.RowCount - 1 + If Me.CellCheckBoxValueAt(I, Column) Then + Enabled = True + Exit For I + End If + Next + + OtherDocsActionButton.Enabled = Enabled + End Sub + #tag EndEvent +#tag EndEvents +#tag Events SourcePicker + #tag Event + Sub Cancelled() + Self.Dismiss() + End Sub + #tag EndEvent + #tag Event + Sub ShouldResize(NewHeight As Integer) + Self.SetPageHeight(NewHeight) + End Sub + #tag EndEvent + #tag Event + Sub SourceChosen(Source As Integer) + Select Case Source + Case Me.SourceFTP + Self.Views.SelectedPanelIndex = Self.PageFTP + Case Me.SourceGSA + Self.Views.SelectedPanelIndex = Self.PageGSA + Case Me.SourceFiles + Self.Views.SelectedPanelIndex = Self.PageFiles + Case Me.SourceClipboard + Self.Views.SelectedPanelIndex = Self.PageClipboard + Case Me.SourceNitrado + Self.Views.SelectedPanelIndex = Self.PageNitrado + Case Me.SourceOtherProject + Self.OtherDocsList.RemoveAllRows + Self.OtherDocsList.ColumnTypeAt(0) = DesktopListBox.CellTypes.CheckBox + For Each Project As Beacon.Project In Self.mOtherProjects + Self.OtherDocsList.AddRow("", Project.Title) + Self.OtherDocsList.RowTagAt(Self.OtherDocsList.LastAddedRowIndex) = Project + Next + Self.OtherDocsList.Sort() + Self.OtherDocsActionButton.Enabled = False + + Self.Views.SelectedPanelIndex = Self.PageOtherDocuments + End Select + End Sub + #tag EndEvent + #tag Event + Function CompatibleGameIds() As String() + Return Array(Palworld.Identifier) + End Function + #tag EndEvent +#tag EndEvents +#tag Events StatusActionButton + #tag Event + Sub Pressed() + Self.Finish() + End Sub + #tag EndEvent +#tag EndEvents +#tag Events ClipboardDiscoveryView1 + #tag Event + Sub Finished(Profiles() As Beacon.ServerProfile) + Self.Discover(Profiles) + End Sub + #tag EndEvent + #tag Event + Function GetDestinationProject() As Beacon.Project + Return Self.mDestinationProject + End Function + #tag EndEvent + #tag Event + Sub ShouldCancel() + If Self.QuickCancel Then + Self.Dismiss + Else + Views.SelectedPanelIndex = 0 + End If + End Sub + #tag EndEvent + #tag Event + Sub ShouldResize(NewHeight As Integer) + Self.SetPageHeight(NewHeight) + End Sub + #tag EndEvent +#tag EndEvents +#tag Events NitradoDiscoveryView1 + #tag Event + Sub ShouldCancel() + If Self.QuickCancel Then + Self.Dismiss + Else + Views.SelectedPanelIndex = 0 + End If + End Sub + #tag EndEvent + #tag Event + Sub Finished(Profiles() As Beacon.ServerProfile) + Self.Discover(Profiles) + End Sub + #tag EndEvent + #tag Event + Sub ShouldResize(NewHeight As Integer) + Self.SetPageHeight(NewHeight) + End Sub + #tag EndEvent + #tag Event + Function GetDestinationProject() As Beacon.Project + Return Self.mDestinationProject + End Function + #tag EndEvent + #tag Event + Function GameId() As String + Return Palworld.Identifier + End Function + #tag EndEvent + #tag Event + Function CreateHostingProvider() As Beacon.HostingProvider + Return New Nitrado.HostingProvider + End Function + #tag EndEvent +#tag EndEvents +#tag Events GSADiscoveryView1 + #tag Event + Sub Finished(Profiles() As Beacon.ServerProfile) + Self.Discover(Profiles) + End Sub + #tag EndEvent + #tag Event + Sub ShouldCancel() + If Self.QuickCancel Then + Self.Dismiss + Else + Views.SelectedPanelIndex = 0 + End If + End Sub + #tag EndEvent + #tag Event + Sub ShouldResize(NewHeight As Integer) + Self.SetPageHeight(NewHeight) + End Sub + #tag EndEvent + #tag Event + Function GetDestinationProject() As Beacon.Project + Return Self.mDestinationProject + End Function + #tag EndEvent + #tag Event + Function CreateHostingProvider() As Beacon.HostingProvider + Return New GameServerApp.HostingProvider + End Function + #tag EndEvent + #tag Event + Function GameId() As String + Return Palworld.Identifier + End Function + #tag EndEvent +#tag EndEvents +#tag Events DiscoveryWatcher + #tag Event + Sub Action() + Self.SetThreadPriorities() + + Var AllFinished As Boolean = True + Var ErrorCount, SuccessCount As Integer + For I As Integer = 0 To Self.StatusList.LastRowIndex + Var Integration As Palworld.DiscoverIntegration = Self.StatusList.RowTagAt(I) + AllFinished = AllFinished And Integration.Finished + Self.StatusList.CellTextAt(I, 0) = Integration.Name + EndOfLine + Integration.StatusMessage + + If Integration.Finished Then + If Integration.Project Is Nil Then + ErrorCount = ErrorCount + 1 + Else + SuccessCount = SuccessCount + 1 + End If + End If + Next + + If AllFinished Then + Me.RunMode = Timer.RunModes.Off + If ErrorCount = 0 Then + If Preferences.PlaySoundAfterImport Then + SoundDeploySuccess.Play + End If + Self.Finish() + ElseIf SuccessCount > 0 Then + If Preferences.PlaySoundAfterImport Then + SoundDeployFailed.Play + End If + If Self.ShowConfirm("There were import errors.", "Not all files imported successfully. Do you want to continue importing with the files that did import?", "Continue Import", "Review Errors") Then + Self.Finish() + Else + Self.StatusActionButton.Visible = True + Self.StatusActionButton.Default = True + UITweaks.SwapButtons(Self.StatusActionButton, Self.StatusCancelButton) + End If + Else + If Preferences.PlaySoundAfterImport Then + SoundDeployFailed.Play + End If + Self.ShowAlert("No files imported.", "Beacon was not able to import anything from the selected files.") + End If + End If + End Sub + #tag EndEvent +#tag EndEvents +#tag ViewBehavior + #tag ViewProperty + Name="Composited" + Visible=true + Group="Window Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Tooltip" + Visible=true + Group="Appearance" + InitialValue="" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="AllowAutoDeactivate" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocusRing" + Visible=true + Group="Appearance" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="BackgroundColor" + Visible=true + Group="Background" + InitialValue="&hFFFFFF" + Type="ColorGroup" + EditorType="ColorGroup" + #tag EndViewProperty + #tag ViewProperty + Name="HasBackgroundColor" + Visible=true + Group="Background" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocus" + Visible=true + Group="Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowTabs" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Backdrop" + Visible=true + Group="Background" + InitialValue="" + Type="Picture" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Enabled" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Height" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="InitialParent" + Visible=false + Group="Position" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockBottom" + Visible=true + Group="Position" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockLeft" + Visible=true + Group="Position" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockRight" + Visible=true + Group="Position" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockTop" + Visible=true + Group="Position" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="QuickCancel" + Visible=false + Group="Behavior" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabIndex" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabPanelIndex" + Visible=false + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabStop" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Transparent" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Visible" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Width" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty +#tag EndViewBehavior diff --git a/Project/Views/Palworld/PalworldDocumentEditorView.xojo_window b/Project/Views/Palworld/PalworldDocumentEditorView.xojo_window new file mode 100644 index 000000000..44530afb3 --- /dev/null +++ b/Project/Views/Palworld/PalworldDocumentEditorView.xojo_window @@ -0,0 +1,1651 @@ +#tag DesktopWindow +Begin DocumentEditorView PalworldDocumentEditorView + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = False + AllowTabs = True + Backdrop = 0 + BackgroundColor = &cFFFFFF + Composited = False + Enabled = True + HasBackgroundColor= False + Height = 528 + Index = -2147483648 + InitialParent = "" + Left = 0 + LockBottom = True + LockLeft = True + LockRight = True + LockTop = True + TabIndex = 0 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 0 + Transparent = True + Visible = True + Width = 858 + Begin SourceList ConfigList + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = False + AllowTabs = True + Backdrop = 0 + BackgroundColor = &cFFFFFF00 + Composited = False + Enabled = True + HasBackgroundColor= False + Height = 446 + Index = -2147483648 + InitialParent = "" + Left = 0 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Scope = 2 + SelectedRowIndex= 0 + TabIndex = 0 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 82 + Transparent = True + Visible = True + Width = 230 + End + Begin ConfigSetPicker SetPicker + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = True + AllowTabs = False + Backdrop = 0 + ContentHeight = 0 + Enabled = True + Height = 40 + Index = -2147483648 + Left = 0 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Scope = 2 + ScrollActive = False + ScrollingEnabled= False + ScrollSpeed = 20 + TabIndex = 1 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 41 + Transparent = True + Visible = True + Width = 230 + End + Begin FadedSeparator FadedSeparator2 + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = True + AllowTabs = False + Backdrop = 0 + ContentHeight = 0 + Enabled = True + Height = 1 + Index = -2147483648 + Left = 0 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Scope = 2 + ScrollActive = False + ScrollingEnabled= False + ScrollSpeed = 20 + TabIndex = 2 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 81 + Transparent = True + Visible = True + Width = 230 + End + Begin OmniBar OmniBar1 + Alignment = 0 + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = True + AllowTabs = False + Backdrop = 0 + BackgroundColor = "" + ContentHeight = 0 + Enabled = True + Height = 41 + Index = -2147483648 + Left = 0 + LeftPadding = -1 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + RightPadding = -1 + Scope = 2 + ScrollActive = False + ScrollingEnabled= False + ScrollSpeed = 20 + TabIndex = 4 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 0 + Transparent = True + Visible = True + Width = 858 + End + Begin DesktopPagePanel PagePanel1 + AllowAutoDeactivate= True + Enabled = True + Height = 487 + Index = -2147483648 + Left = 231 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + PanelCount = 2 + Panels = "" + Scope = 2 + SelectedPanelIndex= 0 + TabIndex = 5 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 41 + Transparent = False + Value = 1 + Visible = True + Width = 627 + Begin OmniNoticeBar OmniNoticeBanner + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = True + AllowTabs = False + Backdrop = 0 + ContentHeight = 0 + Enabled = True + Height = 31 + Index = -2147483648 + InitialParent = "PagePanel1" + Left = 231 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + Scope = 2 + ScrollActive = False + ScrollingEnabled= False + ScrollSpeed = 20 + TabIndex = 0 + TabPanelIndex = 2 + TabStop = True + Tooltip = "" + Top = 41 + Transparent = True + Visible = False + Width = 627 + End + Begin LogoFillCanvas LogoFillCanvas1 + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = True + AllowTabs = False + Backdrop = 0 + Caption = "There was an error loading the editor" + ContentHeight = 0 + Enabled = True + Height = 487 + Index = -2147483648 + InitialParent = "PagePanel1" + Left = 231 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + Scope = 2 + ScrollActive = False + ScrollingEnabled= False + ScrollSpeed = 20 + TabIndex = 0 + TabPanelIndex = 1 + TabStop = True + Tooltip = "" + Top = 41 + Transparent = True + Visible = True + Width = 627 + End + End + Begin FadedSeparator SourceSeparator + AllowAutoDeactivate= True + AllowFocus = False + AllowFocusRing = True + AllowTabs = False + Backdrop = 0 + ContentHeight = 0 + Enabled = True + Height = 487 + Index = -2147483648 + Left = 230 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Scope = 2 + ScrollActive = False + ScrollingEnabled= False + ScrollSpeed = 20 + TabIndex = 6 + TabPanelIndex = 0 + TabStop = True + Tooltip = "" + Top = 41 + Transparent = True + Visible = True + Width = 1 + End +End +#tag EndDesktopWindow + +#tag WindowCode + #tag Event + Sub Closing() + If (Self.mImportWindow Is Nil) = False Then + Self.CleanupImportWindow() + Self.mImportWindow.Close + Self.mImportWindow = Nil + End If + End Sub + #tag EndEvent + + #tag Event + Sub EnableMenuItems() + If Self.ReadyToDeploy Then + FileDeploy.Enabled = True + End If + + If Self.ReadyToExport Then + FileExport.Enabled = True + End If + + If Self.Project.ActiveConfigSet.IsBase = False Then + ViewSwitchToBaseConfigSet.Enabled = True + End If + + If Self.CurrentPanel <> Nil Then + Self.CurrentPanel.EnableMenuItems() + End If + End Sub + #tag EndEvent + + #tag Event + Sub Hidden() + Var Panel As PalworldConfigEditor = Self.CurrentPanel + If (Panel Is Nil) = False Then + Panel.SwitchedFrom() + End If + End Sub + #tag EndEvent + + #tag Event + Sub IdentityChanged() + // Simply toggle the menu to force a redraw + Var CurrentConfig As String = Self.CurrentConfigName + Self.CurrentConfigName = "" + Self.CurrentConfigName = CurrentConfig + End Sub + #tag EndEvent + + #tag Event + Sub Opening() + If (Self.Project Is Nil) = False Then + Var ProjectId As String = Self.Project.ProjectId + Var LastConfigName As String = Preferences.ProjectState(ProjectId, "Editor", "") + Var LastConfigSet As Beacon.ConfigSet = Self.Project.FindConfigSet(Preferences.ProjectState(ProjectId, "Config Set", "").StringValue) + If LastConfigName.IsEmpty Or LastConfigSet Is Nil Then + If Self.URL.Type.OneOf(Beacon.ProjectURL.TypeWeb, Beacon.ProjectURL.TypeCommunity) Then + LastConfigName = Palworld.Configs.NameProjectSettings + Else + LastConfigName = Palworld.Configs.NameGeneralSettings + End If + LastConfigSet = Beacon.ConfigSet.BaseConfigSet + End If + Self.ActiveConfigSet = LastConfigSet + Self.CurrentConfigName = LastConfigName + End If + End Sub + #tag EndEvent + + #tag Event + Sub RunScriptAction(Action As Beacon.ScriptAction) + Select Case Action.Action + Case "Deploy" + Var Plan As Beacon.DeployPlan = Beacon.DeployPlan.StopUploadStart + Try + Var PlanString As String = Action.Value("Plan") + If PlanString.IsEmpty = False Then + Plan = CType(Integer.FromString(PlanString, Locale.Raw), Beacon.DeployPlan) + End If + Catch Err As RuntimeException + End Try + + Var Settings As New Beacon.DeploySettings + Settings.Options = UInt64.FromString(Action.Value("Options"), Locale.Raw) + Settings.StopMessage = Action.Value("StopMessage") + Settings.Plan = Plan + Var ProfileIds() As String = Action.Value("Servers").Split(",") + For Each ProfileId As String In ProfileIds + Var Profile As Beacon.ServerProfile = Self.Project.ServerProfile(ProfileId) + If (Profile Is Nil) = False Then + Settings.Servers.Add(Profile) + End If + Next + + Self.BeginDeploy(Settings) + End Select + End Sub + #tag EndEvent + + #tag Event + Sub ShouldSave(CloseWhenFinished As Boolean) + #Pragma Unused CloseWhenFinished + + If Self.mUpdateUITag <> "" Then + CallLater.Cancel(Self.mUpdateUITag) + Self.mUpdateUITag = "" + Self.UpdateUI() + End If + End Sub + #tag EndEvent + + #tag Event + Sub Shown(UserData As Variant = Nil) + Var Panel As PalworldConfigEditor = Self.CurrentPanel + If (Panel Is Nil) = False Then + Panel.SwitchedTo(UserData) + End If + End Sub + #tag EndEvent + + #tag Event + Sub SwitchToEditor(EditorName As String) + Self.CurrentConfigName = EditorName + End Sub + #tag EndEvent + + + #tag MenuHandler + Function FileDeploy() As Boolean Handles FileDeploy.Action + If Self.IsFrontmost = False Then + Return False + End If + + Self.BeginDeploy() + Return True + + End Function + #tag EndMenuHandler + + #tag MenuHandler + Function FileExport() As Boolean Handles FileExport.Action + If Self.IsFrontmost = False Then + Return False + End If + + Self.BeginExport() + Return True + + End Function + #tag EndMenuHandler + + #tag MenuHandler + Function ViewSwitchToBaseConfigSet() As Boolean Handles ViewSwitchToBaseConfigSet.Action + If Self.IsFrontmost = False Then + Return False + End If + + Self.ActiveConfigSet = Beacon.ConfigSet.BaseConfigSet + Return True + End Function + #tag EndMenuHandler + + + #tag Method, Flags = &h0 + Sub ActiveConfigSet(Assigns Value As Beacon.ConfigSet) + Var ConfigName As String = Self.CurrentConfigName + If Value.IsBase = False And Palworld.Configs.SupportsConfigSets(ConfigName) = False Then + // If switching from base and on an editor that won't exist in the desired set, switch to something else + ConfigName = Palworld.Configs.NameGeneralSettings + End If + Self.CurrentConfigName = "" // To unload the current version + + Self.Project.ActiveConfigSet = Value + Self.SetPicker.Refresh + Self.UpdateConfigList + + If (Self.Project Is Nil) = False And Self.Controller.URL.Type <> Beacon.ProjectURL.TypeTransient Then + Preferences.ProjectState(Self.Project.ProjectId, "Config Set") = Value.Name + End If + + Self.CurrentConfigName = ConfigName + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub BeginDeploy() + Self.BeginDeploy(Preferences.NewDeploySettings) + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub BeginDeploy(Settings As Beacon.DeploySettings) + If Self.mDeployWindow <> Nil And Self.mDeployWindow.Value <> Nil And Self.mDeployWindow.Value IsA DeployManager Then + DeployManager(Self.mDeployWindow.Value).BringToFront() + Else + If Self.Project.ReadOnly Then + Self.ShowAlert("This is a read-only project", "Your access to this project does not allow deploy.") + Return + End If + + Self.Autosave() + + If Not Self.ReadyToDeploy Then + Self.ShowAlert("This project is not ready for deploy.", "You must import at least one server into this project to use the deploy feature. Use the Import button in the top left.") + Return + End If + + If Not Self.ContinueWithoutExcludedConfigs() Then + Return + End If + + If Self.mValidator Is Nil And (Self.mValidationResultsDialog Is Nil Or Self.mValidationResultsDialog.Value Is Nil) Then + Var Validator As New Beacon.ProjectValidator + AddHandler Validator.Validating, WeakAddressOf mValidator_Validating + AddHandler Validator.ValidationComplete, WeakAddressOf mValidator_ValidationComplete_Deploy + Validator.StartValidation(Self.Project, Settings) + Self.mValidator = Validator + End If + End If + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub BeginDeploy(PreselectServers() As Beacon.ServerProfile) + Var Settings As Beacon.DeploySettings = Preferences.NewDeploySettings + Settings.Servers = PreselectServers + Self.BeginDeploy(Settings) + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub BeginExport() + If Self.Project.ReadOnly Then + Self.ShowAlert("This is a read-only project", "Your access to this project does not allow export.") + Return + End If + + Self.Autosave() + + If Not Self.ContinueWithoutExcludedConfigs() Then + Return + End If + + If Self.mValidator Is Nil And (Self.mValidationResultsDialog Is Nil Or Self.mValidationResultsDialog.Value Is Nil) Then + Var Validator As New Beacon.ProjectValidator + AddHandler Validator.Validating, WeakAddressOf mValidator_Validating + AddHandler Validator.ValidationComplete, WeakAddressOf mValidator_ValidationComplete_Export + Validator.StartValidation(Self.Project) + Self.mValidator = Validator + End If + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub BeginImport(ForDeployment As Boolean) + If (Self.mImportWindow Is Nil) = False Then + Self.mImportWindow.Show() + Return + End If + + Var ProjectId As String = Self.Project.ProjectId + Var OtherEditors() As DocumentEditorView = Self.EditorsForGameId(Palworld.Identifier) + Var OtherProjects() As Beacon.Project + For Each Editor As DocumentEditorView In OtherEditors + Var OtherProject As Beacon.Project = Editor.Project + If (OtherProject Is Nil) = False And OtherProject.ReadOnly = False And OtherProject.ProjectId <> ProjectId Then + OtherProjects.Add(OtherProject) + End If + Next + + Var ImportView As New PalworldImportView + Var Ref As New DocumentImportWindow(ImportView, Self.Project, OtherProjects) + AddHandler Ref.ProjectsImported, WeakAddressOf mImportWindow_ProjectsImported + AddHandler Ref.Closing, WeakAddressOf mImportWindow_Closing + Ref.UserData = ForDeployment + Ref.Show() + Self.mImportWindow = Ref + + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub CleanupImportWindow() + If Self.mImportWindow Is Nil Then + Return + End If + + Try + RemoveHandler mImportWindow.Closing, WeakAddressOf mImportWindow_Closing + Catch Err As RuntimeException + End Try + + Try + RemoveHandler mImportWindow.ProjectsImported, WeakAddressOf mImportWindow_ProjectsImported + Catch Err As RuntimeException + End Try + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Function ConfirmClose() As Boolean + If Self.Progress <> BeaconSubview.ProgressNone Then + Self.RequestFrontmost() + + Self.ShowAlert(Self.ViewTitle + " cannot be closed right now because it is busy.", "Wait for the progress indicator at the top of the tab to go away before trying to close it.") + Return False + End If + + Return Super.ConfirmClose() + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Constructor(Controller As Beacon.ProjectController) + Self.Panels = New Dictionary + + Super.Constructor(Controller) + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Function ContinueWithoutExcludedConfigs() As Boolean + Var ExcludedConfigs() As Palworld.ConfigGroup = Self.Project.UsesOmniFeaturesWithoutOmni(App.IdentityManager.CurrentIdentity) + If ExcludedConfigs.LastIndex = -1 Then + Return True + End If + + Var HumanNames() As String + For Each Config As Palworld.ConfigGroup In ExcludedConfigs + HumanNames.Add("""" + Language.LabelForConfig(Config) + """") + Next + HumanNames.Sort + + Var Message, Explanation As String + If HumanNames.Count = 1 Then + Message = Self.OmniWarningSingularMessage + Explanation = Language.ReplacePlaceholders(Self.OmniWarningSingularExplanation, HumanNames(0), Language.GameName(Palworld.Identifier)) + Else + Message = Self.OmniWarningPluralMessage + Explanation = Language.ReplacePlaceholders(Self.OmniWarningPluralExplanation, HumanNames.EnglishOxfordList(), Language.GameName(Palworld.Identifier)) + End If + + Return Self.ShowConfirm(Message, Explanation, "Continue", "Cancel") + End Function + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub CopyFromDocuments(SourceProjects As Variant) + Var Projects() As Beacon.Project + Try + Projects = SourceProjects + Catch Err As RuntimeException + End Try + DocumentMergerWindow.Present(Self, Projects, Self.Project, WeakAddressOf MergeCallback) + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub CopyFromDocumentsAndDeploy(SourceProjects As Variant) + Var Projects() As Beacon.Project + Try + Projects = SourceProjects + Catch Err As RuntimeException + End Try + DocumentMergerWindow.Present(Self, Projects, Self.Project, WeakAddressOf MergeAndDeployCallback) + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Sub CurrentConfigName(Assigns Value As String) + If Self.CurrentConfigName = Value Then + Return + End If + + If (Value = Palworld.Configs.NameAccounts Or Value = Palworld.Configs.NameServers Or Value = Palworld.Configs.NameProjectSettings) And Self.ActiveConfigSet.IsBase = False Then + Self.ActiveConfigSet = Beacon.ConfigSet.BaseConfigSet + End If + + Super.CurrentConfigName = Value + + Var NewPanel As PalworldConfigEditor + Var Embed As Boolean + If Value.IsEmpty = False Then + Var CacheKey As String = Self.ActiveConfigSet.ConfigSetId + ":" + Value + + If (Self.Project Is Nil) = False And Self.Controller.URL.Type <> Beacon.ProjectURL.TypeTransient Then + Preferences.ProjectState(Self.Project.ProjectId, "Editor") = Value + End If + + Var HistoryIndex As Integer = Self.mPanelHistory.IndexOf(CacheKey) + If HistoryIndex > 0 Then + Self.mPanelHistory.RemoveAt(HistoryIndex) + End If + Self.mPanelHistory.AddAt(0, CacheKey) + + // Close older panels + If Self.mPanelHistory.LastIndex > 2 Then + For I As Integer = Self.mPanelHistory.LastIndex DownTo 3 + Var PanelTag As String = Self.mPanelHistory(I) + Self.DiscardConfigPanel(PanelTag) + Next + End If + + If Self.Panels.HasKey(CacheKey) Then + NewPanel = Self.Panels.Value(CacheKey) + Else + Select Case Value + Case Palworld.Configs.NameServers + NewPanel = New PalworldServersEditor(Self.Project) + Case Palworld.Configs.NameAccounts + NewPanel = New PalworldAccountsEditor(Self.Project) + Case Palworld.Configs.NameProjectSettings + NewPanel = New PalworldProjectSettingsEditor(Self.Project) + Case Palworld.Configs.NameCustomConfig + NewPanel = New PalworldCustomConfigEditor(Self.Project) + Case Palworld.Configs.NameGeneralSettings + NewPanel = New PalworldGeneralSettingsEditor(Self.Project) + End Select + If NewPanel <> Nil Then + Self.Panels.Value(CacheKey) = NewPanel + Embed = True + End If + End If + End If + + If Self.CurrentPanel = NewPanel Then + Return + End If + + If (Self.CurrentPanel Is Nil) = False Then + Self.CurrentPanel.SwitchedFrom() + Self.CurrentPanel.Visible = False + Self.CurrentPanel = Nil + End If + + Self.CurrentPanel = NewPanel + + If (Self.CurrentPanel Is Nil) = False Then + Var RequiresPurchase As Boolean + If Value.Length > 0 Then + RequiresPurchase = Not Palworld.Configs.ConfigUnlocked(Value, App.IdentityManager.CurrentIdentity) + End If + Var TopOffset As Integer + If RequiresPurchase Then + TopOffset = (Self.OmniNoticeBanner.Top + Self.OmniNoticeBanner.Height) - Self.PagePanel1.Top + End If + If Embed Then + AddHandler Self.CurrentPanel.ContentsChanged, WeakAddressOf Panel_ContentsChanged + If Self.CurrentPanel IsA PalworldServersEditor Then + AddHandler PalworldServersEditor(Self.CurrentPanel).ShouldDeployProfiles, WeakAddressOf ServersEditor_ShouldDeployProfiles + End If + Self.CurrentPanel.EmbedWithinPanel(Self.PagePanel1, 1, 0, TopOffset, Self.PagePanel1.Width, Self.PagePanel1.Height - TopOffset) + Else + Self.CurrentPanel.Top = Self.PagePanel1.Top + TopOffset + Self.CurrentPanel.Height = Self.PagePanel1.Height - TopOffset + End If + Self.OmniNoticeBanner.Visible = RequiresPurchase + Self.CurrentPanel.Visible = True + Self.CurrentPanel.SwitchedTo() + Self.PagePanel1.SelectedPanelIndex = 1 + Else + Self.PagePanel1.SelectedPanelIndex = 0 + End If + + Self.ConfigList.SelectedTag = Value + + Self.UpdateMinimumDimensions() + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Destructor() + If (Self.mValidationResultsDialog Is Nil) = False And (Self.mValidationResultsDialog.Value Is Nil) = False Then + ResolveIssuesDialog(Self.mValidationResultsDialog.Value).Close + End If + + If (Self.mDeployWindow Is Nil) = False And (Self.mDeployWindow.Value Is Nil) = False Then + DeployManager(Self.mDeployWindow.Value).Close + End If + + Super.Destructor() + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub DiscardConfigPanel(CacheKey As String) + // This is indiscriminate and will close the current config if you ask it to. + + If Not Self.Panels.HasKey(CacheKey) Then + Return + End If + + Var Panel As PalworldConfigEditor = Self.Panels.Value(CacheKey) + If Panel IsA PalworldServersEditor Then + RemoveHandler PalworldServersEditor(Panel).ShouldDeployProfiles, WeakAddressOf ServersEditor_ShouldDeployProfiles + End If + RemoveHandler Panel.ContentsChanged, WeakAddressOf Panel_ContentsChanged + Panel.Close + Self.Panels.Remove(CacheKey) + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub MergeAndDeployCallback() + Self.MergeCallback() + Self.BeginDeploy() + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub MergeCallback() + Var Keys() As Variant = Self.Panels.Keys + For Each Key As Variant In Keys + Var Panel As PalworldConfigEditor = Self.Panels.Value(Key) + If Panel <> Nil Then + Panel.ImportFinished() + End If + Next + + Self.Autosave() + Self.UpdateConfigList() + Self.Panel_ContentsChanged(Nil) + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub mImportWindow_Closing(Sender As DocumentImportWindow) + #Pragma Unused Sender + + Self.CleanupImportWindow() + Self.mImportWindow = Nil + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub mImportWindow_ProjectsImported(Sender As DocumentImportWindow, Projects() As Beacon.Project) + #Pragma Unused Sender + + If Sender.UserData.BooleanValue Then + Call CallLater.Schedule(0, WeakAddressOf CopyFromDocumentsAndDeploy, Projects) + Else + Call CallLater.Schedule(0, WeakAddressOf CopyFromDocuments, Projects) + End If + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub mValidationResultsDialog_GoToIssue(Sender As ResolveIssuesDialog, Issue As Beacon.Issue) + Sender.Close + + Var Parts() As String = Issue.Location.Split(Beacon.Issue.Separator) + If Parts.Count < 3 Then + Break + App.Log("Unknown issue path " + Issue.Location) + Return + End If + + Var ConfigSet As String = Parts(0) + Var ConfigName As String = Parts(1) + Self.ActiveConfigSet = Self.Project.FindConfigSet(ConfigSet) + Self.CurrentConfigName = ConfigName + Self.CurrentPanel.GoToIssue(Issue) + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub mValidator_Validating(Sender As Beacon.ProjectValidator) + #Pragma Unused Sender + + Var Progress As New ProgressWindow + Progress.Message = "Checking project for errors…" + Progress.Detail = "Just a moment…" + Progress.Show + Self.mValidatorProgress = Progress + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Function mValidator_ValidationComplete_Common(Sender As Beacon.ProjectValidator, Results As Beacon.ProjectValidationResults, UserData As Variant) As Boolean + #Pragma Unused Sender + #Pragma Unused UserData + + Self.mValidator = Nil + If (Self.mValidatorProgress Is Nil) = False Then + Self.mValidatorProgress.Close + Self.mValidatorProgress = Nil + End If + + If Results.Count > 0 Then + Var Dialog As New ResolveIssuesDialog(Results) + AddHandler Dialog.GoToIssue, WeakAddressOf mValidationResultsDialog_GoToIssue + Dialog.Show(Self) + Self.mValidationResultsDialog = New WeakRef(Dialog) + Return False + End If + + Return True + End Function + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub mValidator_ValidationComplete_Deploy(Sender As Beacon.ProjectValidator, Results As Beacon.ProjectValidationResults, UserData As Variant) + If Self.mValidator_ValidationComplete_Common(Sender, Results, UserData) = False Then + Return + End If + + Var Settings As Beacon.DeploySettings + Try + Settings = UserData + Catch Err As RuntimeException + App.Log(Err, CurrentMethodName, "Casting UserData into PreselectServers") + End Try + Var Win As DeployManager = New DeployManager(Self.Controller, Settings) + Self.mDeployWindow = New WeakRef(Win) + Win.BringToFront() + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub mValidator_ValidationComplete_Export(Sender As Beacon.ProjectValidator, Results As Beacon.ProjectValidationResults, UserData As Variant) + If Self.mValidator_ValidationComplete_Common(Sender, Results, UserData) = False Then + Return + End If + + PalworldExportWindow.Present(Self, Self.Project, Keyboard.AsyncOptionKey And Keyboard.AsyncShiftKey) + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Sub ObservedValueChanged(Source As ObservationKit.Observable, Key As String, OldValue As Variant, NewValue As Variant) + // Part of the ObservationKit.Observer interface. + + Select Case Key + Case "MinimumWidth", "MinimumHeight" + Self.UpdateMinimumDimensions() + Else + Super.ObservedValueChanged(Source, Key, OldValue, NewValue) + End Select + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub Panel_ContentsChanged(Sender As PalworldConfigEditor) + #Pragma Unused Sender + + If Self.Modified <> Self.Project.Modified Then + Self.Modified = Self.Project.Modified + End If + + If Self.mUpdateUITag <> "" Then + CallLater.Cancel(Self.mUpdateUITag) + Self.mUpdateUITag = "" + End If + + Self.mUpdateUITag = CallLater.Schedule(500, WeakAddressOf UpdateUI) + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Function Project() As Palworld.Project + Return Palworld.Project(Super.Project()) + End Function + #tag EndMethod + + #tag Method, Flags = &h21 + Private Function ReadyToDeploy() As Boolean + If Self.Project Is Nil Or Self.Project.ServerProfileCount = 0 Then + Return False + End If + + Var Bound As Integer = Self.Project.ServerProfileCount - 1 + For I As Integer = 0 To Bound + If Self.Project.ServerProfile(I) <> Nil And Self.Project.ServerProfile(I).DeployCapable Then + Return True + End If + Next + End Function + #tag EndMethod + + #tag Method, Flags = &h21 + Private Function ReadyToExport() As Boolean + Return Self.Project <> Nil + End Function + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub RestoreEditor(ConfigName As String) + Var Label As String = Language.LabelForConfig(ConfigName) + If Self.ShowConfirm("Are you sure you want to restore """ + Label + """ to default settings?", "Wherever possible, this will remove the config options from your file completely, restoring settings to Palworld's default values. You cannot undo this action.", "Restore", "Cancel") Then + Var IsSelected As Boolean = Self.CurrentConfigName = ConfigName + + If IsSelected Then + Self.CurrentConfigName = "" + End If + + Self.Project.RemoveConfigGroup(ConfigName) + + Var CacheKey As String = Self.ActiveConfigSet.ConfigSetId + ":" + ConfigName + Self.DiscardConfigPanel(CacheKey) + + If IsSelected Then + Self.CurrentConfigName = ConfigName + End If + + Self.Modified = True + Self.UpdateConfigList() + End If + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub RunTool(ToolId As String) + #Pragma Unused ToolId + + // No project-level tools + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub ServersEditor_ShouldDeployProfiles(Sender As PalworldServersEditor, SelectedProfiles() As Beacon.ServerProfile) + #Pragma Unused Sender + Self.BeginDeploy(SelectedProfiles) + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub UpdateConfigList() + Var Labels(), Tags() As String + + Var ActiveConfigSet As Beacon.ConfigSet = Self.ActiveConfigSet + Var IsBase As Boolean = ActiveConfigSet.IsBase + If IsBase Then + Var PsuedoTags() As String = Array(Palworld.Configs.NameProjectSettings, Palworld.Configs.NameAccounts, Palworld.Configs.NameServers) + // Show everything + For Each Tag As String In PsuedoTags + Labels.Add(Language.LabelForConfig(Tag)) + Tags.Add(Tag) + Next Tag + End If + + Var Names() As String = Palworld.Configs.AllNames + For Each Name As String In Names + Labels.Add(Language.LabelForConfig(Name)) + Tags.Add(Name) + Next + + Labels.SortWith(Tags) + + Var SourceItems() As SourceListItem + For I As Integer = 0 To Labels.LastIndex + Var Item As New SourceListItem(Labels(I), Tags(I)) + Var SupportsConfigSets As Boolean = Palworld.Configs.SupportsConfigSets(Tags(I)) + If IsBase = False And SupportsConfigSets = False Then + Continue + End If + Item.CanDismiss = SupportsConfigSets And Self.Project.HasConfigGroup(Tags(I)) = True And Self.Project.ConfigGroup(Tags(I)).IsImplicit = False + SourceItems.Add(Item) + Next + + Self.ConfigList.ReplaceContents(SourceItems) + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub UpdateMinimumDimensions() + Self.MinimumWidth = If(Self.CurrentPanel <> Nil, Max(Self.CurrentPanel.MinimumWidth, Self.LocalMinWidth), Self.LocalMinWidth) + Self.PagePanel1.Left + Self.MinimumHeight = If(Self.CurrentPanel <> Nil, Max(Self.CurrentPanel.MinimumHeight, Self.LocalMinHeight), Self.LocalMinHeight) + Self.PagePanel1.Top + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub UpdateUI() + Self.ViewTitle = Self.Controller.Name + Self.UpdateConfigList() + End Sub + #tag EndMethod + + + #tag Property, Flags = &h21 + Private CurrentPanel As PalworldConfigEditor + #tag EndProperty + + #tag Property, Flags = &h21 + Private mDeployWindow As WeakRef + #tag EndProperty + + #tag Property, Flags = &h21 + Private mImportWindow As DocumentImportWindow + #tag EndProperty + + #tag Property, Flags = &h21 + Private mPanelHistory() As String + #tag EndProperty + + #tag Property, Flags = &h21 + Private mUpdateUITag As String + #tag EndProperty + + #tag Property, Flags = &h21 + Private mValidationResultsDialog As WeakRef + #tag EndProperty + + #tag Property, Flags = &h21 + Private mValidator As Beacon.ProjectValidator + #tag EndProperty + + #tag Property, Flags = &h21 + Private mValidatorProgress As ProgressWindow + #tag EndProperty + + #tag Property, Flags = &h21 + Private Panels As Dictionary + #tag EndProperty + + + #tag Constant, Name = kConfigGroupClipboardType, Type = String, Dynamic = False, Default = \"com.thezaz.beacon.palworld.configgroup", Scope = Private + #tag EndConstant + + +#tag EndWindowCode + +#tag Events ConfigList + #tag Event + Sub Change() + Var TagVar As Variant + If Me.SelectedRowIndex > -1 Then + TagVar = Me.Item(Me.SelectedRowIndex).Tag + End If + If IsNull(TagVar) = False And (TagVar.Type = Variant.TypeString Or TagVar.Type = Variant.TypeText) Then + Self.CurrentConfigName = TagVar.StringValue + Else + Self.CurrentConfigName = "" + End If + End Sub + #tag EndEvent + #tag Event + Sub Opening() + Self.UpdateConfigList() + End Sub + #tag EndEvent + #tag Event + Function ShouldChange(DesiredIndex As Integer) As Boolean + #Pragma Unused DesiredIndex + + Var CurrentItem As SourceListItem = Me.SelectedItem + If (CurrentItem Is Nil) = False Then + Try + Var GroupName As String = CurrentItem.Tag + CurrentItem.CanDismiss = Self.Project.HasConfigGroup(GroupName) = True And Self.Project.ConfigGroup(GroupName).IsImplicit = False + Catch Err As RuntimeException + End Try + End If + + Return True + End Function + #tag EndEvent + #tag Event + Sub DismissPressed(Item As SourceListItem, ItemIndex As Integer, ItemRect As Rect) + #Pragma Unused ItemIndex + #Pragma Unused ItemRect + + Self.RestoreEditor(Item.Tag) + End Sub + #tag EndEvent + #tag Event + Sub ContextualClick(MouseX As Integer, MouseY As Integer, ItemIndex As Integer, ItemRect As Rect) + Const RestoreTag = "b4d7f3d8-17f2-425f-8ab8-9032d558b29d" + Const CopyTag = "a0b7a0ee-518a-4ee8-a33c-5c8e46ba570f" + Const PasteTag = "31f1decc-7706-4baf-af11-f4d4fdde799d" + Const HelpTag = "f3766fd7-7483-446f-8fa9-47dd0dd09209" + + Var Base As New DesktopMenuItem + Var ReadOnly As Boolean = Self.Project.ReadOnly + Var Item As SourceListItem + Var ConfigName As String + Var Config As Palworld.ConfigGroup + If ItemIndex > -1 Then + Item = Me.Item(ItemIndex) + ConfigName = Item.Tag + Config = Self.Project.ConfigGroup(ConfigName, False) + + Var HelpItem As New DesktopMenuItem(Item.Caption + " Help", HelpTag) + HelpItem.Enabled = True + Base.AddMenu(HelpItem) + Base.AddMenu(New DesktopMenuItem(DesktopMenuItem.TextSeparator)) + End If + + Var CopyItem As New DesktopMenuItem("Copy", CopyTag) + CopyItem.Enabled = ReadOnly = False And (ItemIndex > -1) And (Config Is Nil) = False And Config.IsImplicit = False + Base.AddMenu(CopyItem) + Var Board As New Clipboard + Var PasteItem As New DesktopMenuItem("Paste", PasteTag) + PasteItem.Enabled = Board.RawDataAvailable(Self.kConfigGroupClipboardType) And (ItemIndex = -1 Or Board.RawData(Self.kConfigGroupClipboardType).IndexOf("""GroupName"":""" + ConfigName + """") > -1) + Base.AddMenu(PasteItem) + + If ItemIndex > -1 Then + Base.AddMenu(New DesktopMenuItem(DesktopMenuItem.TextSeparator)) + Var Tools() As Palworld.ProjectTool = Palworld.Configs.AllTools + For Each Tool As Palworld.ProjectTool In Tools + If Tool.IsRelevantForGroup(ConfigName) Then + Base.AddMenu(New DesktopMenuItem(Tool.Caption, Tool.ToolId)) + End If + Next + If Base.Count > 0 Then + Base.AddMenu(New DesktopMenuItem(DesktopMenuItem.TextSeparator)) + End If + Base.AddMenu(New DesktopMenuItem("Restore """ + Item.Caption + """ to Default", RestoreTag)) + End If + + Var Position As Point = Me.GlobalPosition + Var Choice As DesktopMenuItem + If ItemRect Is Nil Then + Choice = Base.PopUp(Position.X + MouseX, Position.Y + MouseY) + Else + Choice = Base.PopUp(Position.X + ItemRect.Left, Position.Y + ItemRect.Bottom) + End If + If Choice Is Nil Then + Return + End If + + Select Case Choice.Tag + Case HelpTag + Var HelpPath As String = Beacon.ConfigHelpPath(ConfigName) + Var HelpUrl As String = Beacon.HelpUrl(HelpPath) + + Var Component As HelpComponent = App.MainWindow.Help(False) + If Component Is Nil Then + System.GotoURL(HelpUrl) + Return + End If + App.MainWindow.ShowHelp() + Component.LoadURL(HelpUrl) + Case RestoreTag + Self.RestoreEditor(ConfigName) + Case CopyTag + If (Config Is Nil) = False And ReadOnly = False Then + Var SaveData As New Dictionary + SaveData.Value("GroupName") = ConfigName + SaveData.Value("SaveData") = Config.SaveData() + Var JSON As String = Beacon.GenerateJSON(SaveData, False) + Board.RawData(Self.kConfigGroupClipboardType) = JSON + End If + Case PasteTag + Try + Var Parsed As Dictionary = Beacon.ParseJSON(Board.RawData(Self.kConfigGroupClipboardType)) + Var NewConfigName As String = Parsed.Value("GroupName") + Var NewConfigData As Dictionary = Parsed.Value("SaveData") + Var EncryptedSaveData As Dictionary = Nil // The encrypted data will be inside NewConfigData. Nil tells the logic to get the data there. + Var NewConfig As Palworld.ConfigGroup = Palworld.Configs.CreateInstance(NewConfigName, NewConfigData, EncryptedSaveData) + Self.Project.AddConfigGroup(NewConfig) + Self.UpdateConfigList() + Self.Modified = Self.Project.Modified + + If Me.SelectedRowIndex = ItemIndex Or (ItemIndex = -1 And Me.SelectedRowIndex > -1 And Me.Item(Me.SelectedRowIndex).Tag = NewConfigName) Then + // Refresh + Self.CurrentConfigName = "" + Self.CurrentConfigName = NewConfigName + End If + Catch Err As RuntimeException + App.Log(Err, CurrentMethodName, "Working with pasted data") + App.Log("Pasted data: " + EncodeBase64(Board.RawData(Self.kConfigGroupClipboardType), 0)) + Self.ShowAlert("Beacon was unable to complete the paste.", "The error was '" + Err.Message + "'. More data may be available in the log files.") + End Try + Else + If Me.SelectedRowIndex <> ItemIndex Then + Me.SelectedRowIndex = ItemIndex + End If + + Call Self.CurrentPanel.RunTool(Choice.Tag) + Self.UpdateConfigList() + End Select + End Sub + #tag EndEvent +#tag EndEvents +#tag Events SetPicker + #tag Event + Function GetProject() As Beacon.Project + Return Self.Project + End Function + #tag EndEvent + #tag Event + Sub ManageConfigSets() + Self.ManageConfigSets() + End Sub + #tag EndEvent + #tag Event + Sub NewConfigSet() + Self.NewConfigSet() + End Sub + #tag EndEvent + #tag Event + Sub Changed(Set As Beacon.ConfigSet) + Self.ActiveConfigSet = Set + End Sub + #tag EndEvent +#tag EndEvents +#tag Events OmniBar1 + #tag Event + Sub Opening() + Me.Append(OmniBarItem.CreateTitle("GameName", Palworld.FullName)) + Me.Append(OmniBarItem.CreateSeparator) + + Me.Append(OmniBarItem.CreateButton("ImportButton", "Import", IconToolbarImport, "Import config files.")) + Me.Append(OmniBarItem.CreateSpace()) + Me.Append(OmniBarItem.CreateButton("ExportButton", "Export", IconToolbarExport, "Save new config file.")) + Me.Append(OmniBarItem.CreateButton("DeployButton", "Deploy", IconToolbarDeploy, "Make config changes live.")) + + Me.Append(OmniBarItem.CreateSpace()) + Me.Append(OmniBarItem.CreateButton("ShareButton", "Share", IconToolbarShare, "Share this project with other users.")) + Me.Append(OmniBarItem.CreateSpace()) + + Me.Append(OmniBarItem.CreateSeparator()) + Me.Append(OmniBarItem.CreateSpace()) + Me.Append(OmniBarItem.CreateButton("ToolsButton", "Tools", IconToolbarTools, "Use convenience tools for this project.")) + End Sub + #tag EndEvent + #tag Event + Sub ItemPressed(Item As OmniBarItem, ItemRect As Rect) + Select Case Item.Name + Case "ImportButton" + Self.BeginImport(False) + Case "ExportButton" + Self.BeginExport() + Case "ShareButton" + If Self.URL.Type.OneOf(Beacon.ProjectURL.TypeCloud, Beacon.ProjectURL.TypeShared) Then + SharingDialog.Present(Self, Self.Project) + Self.Modified = Self.Project.Modified + ElseIf Self.URL.Type = Beacon.ProjectURL.TypeLocal Then + Self.ShowAlert("Project sharing is only available to cloud projects", "Use ""Save As…"" under the file menu to save a new copy of this project to the cloud if you would like to use Beacon's sharing features.") + Else + Self.ShowAlert("Project sharing is only available to cloud projects", "If you would like to use Beacon's sharing features, first save your project using ""Save"" under the file menu.") + End If + Case "DeployButton" + Self.BeginDeploy() + Case "ToolsButton" + Var Tools() As Palworld.ProjectTool = Palworld.Configs.AllTools + Var GroupNames() As String + Var GroupedTools As New Dictionary + For Each Tool As Palworld.ProjectTool In Tools + Var GroupName As String = Tool.FirstGroup + If GroupNames.IndexOf(GroupName) = -1 Then + GroupNames.Add(GroupName) + End If + + Var Siblings() As Palworld.ProjectTool + If GroupedTools.HasKey(GroupName) Then + Siblings = GroupedTools.Value(GroupName) + Siblings.Add(Tool) + Else + Siblings.Add(Tool) + GroupedTools.Value(GroupName) = Siblings + End If + Next + GroupNames.Sort + + Var Base As New DesktopMenuItem + For Each GroupName As String In GroupNames + If GroupName.IsEmpty = False Then + If Base.Count > 0 Then + Base.AddMenu(New DesktopMenuItem(DesktopMenuItem.TextSeparator)) + End If + + Var Header As New DesktopMenuItem(Language.LabelForConfig(GroupName)) + Header.Enabled = False + Base.AddMenu(Header) + End If + + Var GroupTools() As Palworld.ProjectTool = GroupedTools.Value(GroupName) + Var ToolNames() As String + For Each Tool As Palworld.ProjectTool In GroupTools + ToolNames.Add(Tool.Caption) + Next + ToolNames.SortWith(GroupTools) + + For Each Tool As Palworld.ProjectTool In GroupTools + Base.AddMenu(New DesktopMenuItem(Tool.Caption, Tool)) + Next + Next + + Var Position As Point = Me.GlobalPosition + Var Choice As DesktopMenuItem = Base.PopUp(Position.X + ItemRect.Left, Position.Y + ItemRect.Bottom) + If Choice Is Nil Then + Return + End If + + Var Tool As Palworld.ProjectTool = Choice.Tag + If Tool.IsGlobal Then + Self.RunTool(Tool.ToolId) + Else + If Tool.IsRelevantForGroup(Self.CurrentConfigName) = False Then + Self.CurrentConfigName = Tool.FirstGroup + End If + Call Self.CurrentPanel.RunTool(Tool.ToolId) + End If + Self.UpdateConfigList() + End Select + End Sub + #tag EndEvent +#tag EndEvents +#tag Events OmniNoticeBanner + #tag Event + Sub Pressed() + System.GotoURL(Beacon.WebURL("/omni#Palworld")) + End Sub + #tag EndEvent + #tag Event + Function GameName() As String + Return Language.GameName(Palworld.Identifier) + End Function + #tag EndEvent +#tag EndEvents +#tag ViewBehavior + #tag ViewProperty + Name="Modified" + Visible=false + Group="Behavior" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="MinimumWidth" + Visible=false + Group="Behavior" + InitialValue="" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="MinimumHeight" + Visible=false + Group="Behavior" + InitialValue="" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Progress" + Visible=false + Group="Behavior" + InitialValue="" + Type="Double" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="ViewTitle" + Visible=true + Group="Behavior" + InitialValue="Untitled" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="ViewIcon" + Visible=false + Group="Behavior" + InitialValue="" + Type="Picture" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="IsFrontmost" + Visible=false + Group="Behavior" + InitialValue="" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Width" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Height" + Visible=true + Group="Size" + InitialValue="300" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="InitialParent" + Visible=false + Group="Position" + InitialValue="" + Type="String" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockLeft" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockTop" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockRight" + Visible=true + Group="Position" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="LockBottom" + Visible=true + Group="Position" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabIndex" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabPanelIndex" + Visible=false + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="TabStop" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowAutoDeactivate" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Enabled" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Tooltip" + Visible=true + Group="Appearance" + InitialValue="" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocusRing" + Visible=true + Group="Appearance" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Visible" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="BackgroundColor" + Visible=true + Group="Background" + InitialValue="&hFFFFFF" + Type="ColorGroup" + EditorType="ColorGroup" + #tag EndViewProperty + #tag ViewProperty + Name="Backdrop" + Visible=true + Group="Background" + InitialValue="" + Type="Picture" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="HasBackgroundColor" + Visible=true + Group="Background" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowFocus" + Visible=true + Group="Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="AllowTabs" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Transparent" + Visible=true + Group="Behavior" + InitialValue="True" + Type="Boolean" + EditorType="" + #tag EndViewProperty + #tag ViewProperty + Name="Composited" + Visible=true + Group="Window Behavior" + InitialValue="False" + Type="Boolean" + EditorType="" + #tag EndViewProperty +#tag EndViewBehavior diff --git a/Project/Views/Support Ticket/DebugWindow.xojo_window b/Project/Views/Support Ticket/DebugWindow.xojo_window index d15321470..715a21e84 100644 --- a/Project/Views/Support Ticket/DebugWindow.xojo_window +++ b/Project/Views/Support Ticket/DebugWindow.xojo_window @@ -649,6 +649,9 @@ End If License.IsFlagged(SDTD.OmniFlag)Then GameNames.Add(Language.GameName(SDTD.Identifier)) End If + If License.IsFlagged(Palworld.OmniFlag) Then + GameNames.Add(Language.GameName(Palworld.Identifier)) + End If If License.IsFlagged(Beacon.OmniLicense.CuratorFlag) Then GameNames.Add("Curator Access") End If diff --git a/Publisher/EditorWindow.xojo_window b/Publisher/EditorWindow.xojo_window index bbd695e8f..78ec44b71 100644 --- a/Publisher/EditorWindow.xojo_window +++ b/Publisher/EditorWindow.xojo_window @@ -471,12 +471,15 @@ Begin DesktopWindow EditorWindow Width = 80 End Begin Thread HashThread + DebugIdentifier = "" Index = -2147483648 LockedInPosition= False Priority = 1 Scope = 2 StackSize = 0 TabPanelIndex = 0 + ThreadID = 0 + ThreadState = 0 End End #tag EndDesktopWindow @@ -709,7 +712,16 @@ End End If InsertData.Value("delta_version") = DeltaVersion InsertData.Value("published") = "'" + DateTime.Now.SQLDateTime + "'" - InsertData.Value("supported_games") = "'{Ark,ArkSA}'" + + Var SupportedGames() As String = Array("Ark") + If Self.mBuildNumber >= 20000000 Then + SupportedGames.Add("ArkSA") + End If + If Self.mBuildNumber >= 20100000 Then + SupportedGames.Add("Palworld") + End If + InsertData.Value("supported_games") = "'{" + String.FromArray(SupportedGames, ",") + "}'" + Statements.Add(Self.DictionaryToInsertSQL("updates", InsertData)) For Each DownloadObj As Download In Self.mDownloads diff --git a/Website/api/slack/commands/createcodes.php b/Website/api/slack/commands/createcodes.php index 88b00d7cd..b3fe61e87 100644 --- a/Website/api/slack/commands/createcodes.php +++ b/Website/api/slack/commands/createcodes.php @@ -33,6 +33,10 @@ case 'arksa': $productId = BeaconShop::GetProductByTag('USD', 'ArkSA', 'Base'); break; +case 'palworld': +case 'minimal': + $productId = BeaconShop::GetProductByTag('USD', 'BeaconMinimal', 'Base'); + break; default: PostReply('Unknown game_id value.'); return; diff --git a/Website/api/slack/commands/grant.php b/Website/api/slack/commands/grant.php index 9296b2cf8..8dfcc0f09 100644 --- a/Website/api/slack/commands/grant.php +++ b/Website/api/slack/commands/grant.php @@ -30,6 +30,10 @@ case 'curator': $product = BeaconShop::GetProductByTag('USD', 'Curator', 'Base'); break; +case 'minimal': +case 'palworld': + $product = BeaconShop::GetProductByTag('USD', 'BeaconMinimal', 'Base'); + break; default: PostReply('Unknown game_id value.'); return; diff --git a/Website/api/v4/classes/Palworld/ConfigOption.php b/Website/api/v4/classes/Palworld/ConfigOption.php new file mode 100644 index 000000000..b9014be87 --- /dev/null +++ b/Website/api/v4/classes/Palworld/ConfigOption.php @@ -0,0 +1,170 @@ +nativeEditorVersion = $row->Field('native_editor_version'); + $this->file = $row->Field('file'); + $this->header = $row->Field('header'); + $this->struct = $row->Field('struct'); + $this->key = $row->Field('key'); + $this->valueType = $row->Field('value_type'); + $this->maxAllowed = is_null($row->Field('max_allowed')) ? null : intval($row->Field('max_allowed')); + $this->description = trim($row->Field('description')); + $this->defaultValue = $row->Field('default_value'); + $this->nitradoPath = $row->Field('nitrado_path'); + $this->nitradoFormat = $row->Field('nitrado_format'); + $this->nitradoDeployStyle = $row->Field('nitrado_deploy_style'); + $this->uiGroup = $row->Field('ui_group'); + $this->constraints = is_null($row->Field('constraints')) ? null : json_decode($row->Field('constraints'), true); + $this->customSort = $row->Field('custom_sort'); + } + + protected static function CustomVariablePrefix(): string { + return 'configOption'; + } + + public static function BuildDatabaseSchema(): DatabaseSchema { + $schema = parent::BuildDatabaseSchema(); + $schema->SetTable('ini_options'); + $schema->AddColumns([ + new DatabaseObjectProperty('nativeEditorVersion', ['columnName' => 'native_editor_version']), + new DatabaseObjectProperty('file'), + new DatabaseObjectProperty('header'), + new DatabaseObjectProperty('struct'), + new DatabaseObjectProperty('key'), + new DatabaseObjectProperty('valueType', ['columnName' => 'value_type']), + new DatabaseObjectProperty('maxAllowed', ['columnName' => 'max_allowed']), + new DatabaseObjectProperty('description'), + new DatabaseObjectProperty('defaultValue', ['columnName' => 'default_value']), + new DatabaseObjectProperty('nitradoPath', ['columnName' => 'nitrado_path']), + new DatabaseObjectProperty('nitradoFormat', ['columnName' => 'nitrado_format']), + new DatabaseObjectProperty('nitradoDeployStyle', ['columnName' => 'nitrado_deploy_style']), + new DatabaseObjectProperty('uiGroup', ['columnName' => 'ui_group']), + new DatabaseObjectProperty('constraints'), + new DatabaseObjectProperty('customSort', ['columnName' => 'custom_sort']) + ]); + return $schema; + } + + protected static function BuildSearchParameters(DatabaseSearchParameters $parameters, array $filters, bool $isNested): void { + parent::BuildSearchParameters($parameters, $filters, $isNested); + + $schema = static::DatabaseSchema(); + $parameters->AddFromFilter($schema, $filters, 'file'); + $parameters->AddFromFilter($schema, $filters, 'header'); + $parameters->AddFromFilter($schema, $filters, 'struct'); + $parameters->AddFromFilter($schema, $filters, 'key'); + } + + public function jsonSerialize(): mixed { + $json = parent::jsonSerialize(); + unset($json['configOptionGroup']); + $json['nativeEditorVersion'] = $this->nativeEditorVersion; + $json['file'] = $this->file; + $json['header'] = $this->header; + $json['struct'] = $this->struct; + $json['key'] = $this->key; + $json['valueType'] = $this->valueType; + $json['maxAllowed'] = $this->maxAllowed; + $json['description'] = $this->description; + $json['defaultValue'] = $this->defaultValue; + if (is_null($this->nitradoPath) == false && is_null($this->nitradoFormat) == false && is_null($this->nitradoDeployStyle) == false) { + $json['nitradoEquivalent'] = [ + 'path' => $this->nitradoPath, + 'format' => $this->nitradoFormat, + 'deployStyle' => $this->nitradoDeployStyle + ]; + } + $json['uiGroup'] = $this->uiGroup; + $json['customSort'] = $this->customSort; + $json['constraints'] = $this->constraints; + return $json; + } + + public function NativeEditorInVersion(): ?int { + return $this->nativeEditorVersion; + } + + public function ConfigFileName(): string { + return $this->file; + } + + public function SectionHeader(): string { + return $this->header; + } + + public function StructName(): ?string { + return $this->struct; + } + + public function KeyName(): string { + return $this->key; + } + + public function ValueType(): string { + return $this->valueType; + } + + public function MaxAllowed(): ?int { + return $this->maxAllowed; + } + + public function Description(): ?string { + return $this->description; + } + + public function DefaultValue(): mixed { + return $this->defaultValue; + } + + public function UIGroup(): ?string { + return $this->uiGroup; + } + + public function CustomSort(): ?string { + return $this->customSort; + } + + public function Constraints(): ?array { + return $this->constraints; + } +} + +?> diff --git a/Website/api/v4/classes/Palworld/GameVariable.php b/Website/api/v4/classes/Palworld/GameVariable.php new file mode 100644 index 000000000..7311b63b5 --- /dev/null +++ b/Website/api/v4/classes/Palworld/GameVariable.php @@ -0,0 +1,50 @@ +key = $row->Field('key'); + $this->value = $row->Field('value'); + $this->lastUpdate = round($row->Field('last_update')); + } + + public static function BuildDatabaseSchema(): DatabaseSchema { + return new DatabaseSchema('palworld', 'game_variables', [ + new DatabaseObjectProperty('key', ['primaryKey' => true]), + new DatabaseObjectProperty('value'), + new DatabaseObjectProperty('lastUpdate', ['columnName' => 'last_update', 'accessor' => 'EXTRACT(EPOCH FROM %%TABLE%%.%%COLUMN%%)', 'setter' => 'TO_TIMESTAMP(%%PLACEHOLDER%%)']) + ]); + } + + protected static function BuildSearchParameters(DatabaseSearchParameters $parameters, array $filters, bool $isNested): void { + $schema = static::DatabaseSchema(); + $parameters->orderBy = $schema->Accessor('key'); + $parameters->allowAll = true; + $parameters->AddFromFilter($schema, $filters, 'lastUpdate', '>'); + } + + public function jsonSerialize(): mixed { + return [ + 'key' => $this->key, + 'value' => $this->value, + 'lastUpdate' => $this->lastUpdate + ]; + } + + public function Key(): string { + return $this->key; + } + + public function Value(): string { + return $this->value; + } +} + +?> diff --git a/Website/api/v4/classes/Palworld/GenericObject.php b/Website/api/v4/classes/Palworld/GenericObject.php new file mode 100644 index 000000000..b71d2f5eb --- /dev/null +++ b/Website/api/v4/classes/Palworld/GenericObject.php @@ -0,0 +1,398 @@ +Field('tags'), 1, -1); + if (strlen($tags) > 0) { + $tags = explode(',', $tags); + } else { + $tags = []; + } + asort($tags); + + $this->objectId = $row->Field('object_id'); + $this->objectGroup = $row->Field('object_group'); + $this->label = $row->Field('label'); + $this->alternateLabel = $row->Field('alternate_label'); + $this->minVersion = intval($row->Field('min_version')); + $this->contentPackId = $row->Field('content_pack_id'); + $this->contentPackName = $row->Field('content_pack_name'); + $this->contentPackMarketplace = $row->Field('content_pack_marketplace'); + $this->contentPackMarketplaceId = $row->Field('content_pack_marketplace_id'); + $this->tags = array_values($tags); + $this->lastUpdate = round($row->Field('last_update')); + } + + protected static function CustomVariablePrefix(): string { + return 'object'; + } + + public static function BuildDatabaseSchema(): DatabaseSchema { + $prefix = static::CustomVariablePrefix(); + return new DatabaseSchema('palworld', 'objects', [ + new DatabaseObjectProperty($prefix . 'Id', ['primaryKey' => true, 'columnName' => 'object_id']), + new DatabaseObjectProperty($prefix . 'Group', ['accessor' => 'palworld.table_to_group(SUBSTRING(%%TABLE%%.tableoid::regclass::TEXT, 7))', 'columnName' => 'object_group', 'editable' => DatabaseObjectProperty::kEditableNever]), + new DatabaseObjectProperty('label', ['editable' => DatabaseObjectProperty::kEditableAlways]), + new DatabaseObjectProperty('alternateLabel', ['columnName' => 'alternate_label', 'required' => false, 'editable' => DatabaseObjectProperty::kEditableAlways]), + new DatabaseObjectProperty('tags', ['required' => false, 'editable' => DatabaseObjectProperty::kEditableAlways]), + new DatabaseObjectProperty('minVersion', ['accessor' => 'GREATEST(%%TABLE%%.min_version, content_packs.min_version)', 'setter' => '%%PLACEHOLDER%%', 'columnName' => 'min_version', 'required' => false]), + new DatabaseObjectProperty('contentPackId', ['accessor' => 'content_packs.content_pack_id', 'setter' => '%%PLACEHOLDER%%', 'columnName' => 'content_pack_id']), + new DatabaseObjectProperty('contentPackName', ['accessor' => 'content_packs.name', 'columnName' => 'content_pack_name', 'editable' => DatabaseObjectProperty::kEditableNever]), + new DatabaseObjectProperty('contentPackMarketplace', ['accessor' => 'content_packs.marketplace', 'columnName' => 'content_pack_marketplace', 'editable' => DatabaseObjectProperty::kEditableNever]), + new DatabaseObjectProperty('contentPackMarketplaceId', ['accessor' => 'content_packs.marketplace_id', 'columnName' => 'content_pack_marketplace_id', 'editable' => DatabaseObjectProperty::kEditableNever]), + new DatabaseObjectProperty('lastUpdate', ['columnName' => 'last_update', 'accessor' => 'EXTRACT(EPOCH FROM %%TABLE%%.%%COLUMN%%)', 'setter' => 'TO_TIMESTAMP(%%PLACEHOLDER%%)', 'editable' => DatabaseObjectProperty::kEditableNever]) + ], [ + 'INNER JOIN public.content_packs ON (%%TABLE%%.content_pack_id = content_packs.content_pack_id)' + ]); + } + + protected static function BuildSearchParameters(DatabaseSearchParameters $parameters, array $filters, bool $isNested): void { + $schema = static::DatabaseSchema(); + $parameters->orderBy = $schema->Accessor('label'); + $parameters->allowAll = true; + $parameters->AddFromFilter($schema, $filters, 'lastUpdate', '>'); + $parameters->AddFromFilter($schema, $filters, 'contentPackMarketplace', '='); + $prefix = static::CustomVariablePrefix(); + + if (isset($filters['contentPackId'])) { + if (is_array($filters['contentPackId'])) { + $packs = $filters['contentPackId']; + } else { + $packs = explode(',', $filters['contentPackId']); + } + $packIds = []; + foreach ($packs as $pack) { + if (is_string($pack) && BeaconCommon::IsUUID($pack)) { + $packIds[] = $pack; + } + } + + $clauses = []; + if (count($packIds) > 1) { + $clauses[] = $schema->Accessor('contentPackId') . ' = ANY(' . $schema->Setter('contentPackId', $parameters->placeholder++) . ')'; + $parameters->values[] = '{' . implode(',', $packIds) . '}'; + } else if (count($packIds) === 1) { + $clauses[] = $schema->Comparison('contentPackId', '=', $parameters->placeholder++); + $parameters->values[] = $packIds[array_key_first($packIds)]; + } + if (count($clauses) > 0) { + $parameters->clauses[] = '(' . implode(' OR ', $clauses) . ')'; + } + } + + if (isset($filters['contentPackMarketplaceId'])) { + if (is_array($filters['contentPackMarketplaceId'])) { + $packs = $filters['contentPackMarketplaceId']; + } else { + $marketplaceId = str_replace('\\,', '73f2d6ad-0070-44df-8d9a-c8838da050d2', $filters['contentPackMarketplaceId']); + $packs = explode(',', $marketplaceId); + } + for ($idx = 0; $idx < count($packs); $idx++) { + $packs[$idx] = str_replace(['73f2d6ad-0070-44df-8d9a-c8838da050d2', '{', '}'], ['\\,', '\\{', '\\}'], $packs[$idx]); + } + + $clauses = []; + if (count($packs) > 1) { + $clauses[] = $schema->Accessor('contentPackMarketplaceId') . ' = ANY(' . $schema->Setter('contentPackMarketplaceId', $parameters->placeholder++) . ')'; + $parameters->values[] = '{' . implode(',', $packs) . '}'; + } else if (count($packs) === 1) { + $clauses[] = $schema->Comparison('contentPackMarketplaceId', '=', $parameters->placeholder++); + $parameters->values[] = $packs[array_key_first($packs)]; + } + if (count($clauses) > 0) { + $parameters->clauses[] = '(' . implode(' OR ', $clauses) . ')'; + } + } + + if (isset($filters[$prefix . 'Group'])) { + $filterKey = $prefix . 'Group'; + $filterValue = $filters[$filterKey]; + $groups = []; + if (str_contains($filterValue, ',')) { + $groups = explode(',', $filterValue); + } else { + $groups = [$filterValue]; + } + + for ($idx = 0; $idx < count($groups); $idx++) { + $groups[$idx] = trim($groups[$idx]); + } + + if (count($groups) > 1) { + $parameters->clauses[] = $schema->Accessor($filterKey) . ' = ANY(' . $schema->Setter($filterKey, $parameters->placeholder++) . ')'; + $parameters->values[] = '{' . implode(',', $groups) . '}'; + } else if (count($groups) === 1) { + $group = $groups[array_key_first($groups)]; + $parameters->clauses[] = $schema->Comparison($filterKey, '=', $parameters->placeholder++); + $parameters->values[] = $group; + } + } + + if (isset($filters['tag'])) { + $parameters->clauses[] = $schema->Setter('tags', $parameters->placeholder++) . ' = ANY(' . $schema->Accessor('tags') . ')'; + $parameters->values[] = $filters['tag']; + } + + if (isset($filters['tags'])) { + $tags = explode(',', $filters['tags']); + foreach ($tags as $tag) { + $parameters->clauses[] = $schema->Setter('tags', $parameters->placeholder++) . ' = ANY(' . $schema->Accessor('tags') . ')'; + $parameters->values[] = $tag; + } + } + + if (isset($filters['label'])) { + if (str_contains($filters['label'], '%')) { + $parameters->clauses[] = $schema->Accessor('label') . ' LIKE ' . $schema->Setter('label', $parameters->placeholder++); + } else { + $parameters->clauses[] = $schema->Comparison('label', '=', $parameters->placeholder++); + } + $parameters->values[] = $filters['label']; + } + + if (isset($filters['alternateLabel'])) { + if (str_contains($filters['alternateLabel'], '%')) { + $parameters->clauses[] = $schema->Accessor('alternateLabel') . ' LIKE ' . $schema->Setter('alternateLabel', $parameters->placeholder++); + } else { + $parameters->clauses[] = $schema->Comparison('alternateLabel', '=', $parameters->placeholder++); + } + $parameters->values[] = $filters['alternateLabel']; + } + } + + public function GetPermissionsForUser(User $user): int { + return DatabaseObjectAuthorizer::GetPermissionsForUser(className: '\BeaconAPI\v4\ContentPack', objectId: $this->contentPackId, user: $user); + } + + public static function GetNewObjectPermissionsForUser(User $user, ?array $newObjectProperties): int { + if (is_null($newObjectProperties) || isset($newObjectProperties['contentPackId']) === false) { + return static::kPermissionRead; + } + + return DatabaseObjectAuthorizer::GetPermissionsForUser(className: '\BeaconAPI\v4\ContentPack', objectId: $newObjectProperties['contentPackId'], user: $user, options: DatabaseObjectAuthorizer::kOptionMustExist); + } + + public static function LastUpdate(int $min_version = -1): DateTime { + $database = BeaconCommon::Database(); + $schema = static::SchemaName(); + $table = static::TableName(); + + if ($min_version == -1) { + $min_version = BeaconCommon::MinVersion(); + } + + $results = $database->Query('SELECT MAX(last_update) AS most_recent_change FROM ' . $schema . '.' . $table . ' WHERE min_version <= $1;', $min_version); + if ($results->Field('most_recent_change') !== null) { + $change_time = new DateTime($results->Field('most_recent_change')); + } else { + $change_time = new DateTime('2000-01-01'); + } + + if ($table == self::TableName()) { + $results = $database->Query('SELECT MAX(action_time) AS most_recent_delete FROM ' . $schema . '.deletions WHERE min_version <= $1;', $min_version); + } else { + $results = $database->Query('SELECT MAX(action_time) AS most_recent_delete FROM ' . $schema . '.deletions WHERE min_version <= $1 AND from_table = $2;', $min_version, $table); + } + if ($results->Field('most_recent_delete') !== null) { + $delete_time = new DateTime($results->Field('most_recent_delete')); + } else { + $delete_time = new DateTime('2000-01-01'); + } + return ($change_time >= $delete_time) ? $change_time : $delete_time; + } + + public static function Deletions(int $min_version = -1, DateTime $since = null): array { + if ($since === null) { + $since = new DateTime('2000-01-01'); + } + + if ($min_version == -1) { + $min_version = BeaconCommon::MinVersion(); + } + + $database = BeaconCommon::Database(); + $columns = 'object_id, palworld.table_to_group(from_table) AS from_table, label, min_version, action_time, tag'; + $mySchema = self::DatabaseSchema(); + $classSchema = static::DatabaseSchema(); + $schema = $classSchema->Schema(); + $table = $classSchema->Table(); + + if ($schema === $mySchema->Schema() && $table === $mySchema->Table()) { + $results = $database->Query("SELECT {$columns} FROM {$schema}.deletions WHERE min_version <= $1 AND action_time > $2;", $min_version, $since->format('Y-m-d H:i:sO')); + } else { + $results = $database->Query("SELECT {$columns} FROM {$schema}.deletions WHERE min_version <= $1 AND action_time > $2 AND from_table = $3;", $min_version, $since->format('Y-m-d H:i:sO'), $table); + } + $arr = []; + while (!$results->EOF()) { + $arr[] = [ + 'objectId' => $results->Field('object_id'), + 'minVersion' => $results->Field('min_version'), + 'group' => $results->Field('from_table'), + 'label' => $results->Field('label'), + 'tag' => $results->Field('tag') + ]; + $results->MoveNext(); + } + return $arr; + } + + public function jsonSerialize(): mixed { + $prefix = static::CustomVariablePrefix(); + return [ + $prefix . 'Id' => $this->objectId, + $prefix . 'Group' => $this->objectGroup, + 'label' => $this->label, + 'alternateLabel' => $this->alternateLabel, + 'contentPackId' => $this->contentPackId, + 'contentPackName' => $this->contentPackName, + 'contentPackMarketplace' => $this->contentPackMarketplace, + 'contentPackMarketplaceId' => $this->contentPackMarketplaceId, + 'tags' => $this->tags, + 'minVersion' => $this->minVersion, + 'lastUpdate' => $this->lastUpdate + ]; + } + + public function PrimaryKey(): string { + return $this->objectId; + } + + public function UUID(): string { + return $this->objectId; + } + + public function ObjectId(): string { + return $this->objectId; + } + + public function ObjectGroup(): string { + return $this->objectGroup; + } + + public function Label(): string { + return $this->label; + } + + public function SetLabel(string $label): void { + $this->label = $label; + } + + public function AlternateLabel(): ?string { + return $this->alternateLabel; + } + + public function SetAlternateLabel(?string $alternateLabel): void { + $this->alternateLabel = $alternateLabel; + } + + public function MinVersion(): int { + return $this->minVersion; + } + + public function ContentPackId(): string { + return $this->contentPackId; + } + + public function ContentPackName(): string { + return $this->contentPackName; + } + + public function ContentPackMarketplace(): string { + return $this->contentPackMarketplace; + } + + public function ContentPackMarketplaceId(): string { + return $this->contentPackMarketplaceId; + } + + public static function NormalizeTag(string $tag): string { + $tag = strtolower($tag); + $tag = preg_replace('/[^\w]/', '', $tag); + return $tag; + } + + public function Tags(): array { + return $this->tags; + } + + public function AddTag(string $tag): void { + $tag = self::NormalizeTag($tag); + if (!in_array($tag, $this->tags)) { + $this->tags[] = $tag; + } + } + + public function RemoveTag(string $tag): void { + $tag = self::NormalizeTag($tag); + if (in_array($tag, $this->tags)) { + $arr = array(); + foreach ($this->tags as $current_tag) { + if ($current_tag !== $tag) { + $arr[] = $current_tag; + } + } + $this->tags = $arr; + } + } + + public function IsTagged(string $tag): bool { + return in_array(self::NormalizeTag($tag), $this->tags); + } + + public static function DeleteObjects(string $object_id, string $user_id): bool { + $database = BeaconCommon::Database(); + $escaped_schema = $database->EscapeIdentifier(static::SchemaName()); + $escaped_table = $database->EscapeIdentifier(static::TableName()); + + $database->BeginTransaction(); + $results = $database->Query('SELECT content_packs.user_id, ' . $escaped_table . '.object_id FROM ' . $escaped_schema . '.' . $escaped_table . ' INNER JOIN public.content_packs ON (' . $escaped_table . '.content_pack_id = content_packs.content_pack_id) WHERE ' . $escaped_table . '.object_id = ANY($1) FOR UPDATE OF ' . $escaped_table . ';', '{' . $object_id . '}'); + $objects = array(); + while (!$results->EOF()) { + if ($results->Field('user_id') !== $user_id) { + $database->Rollback(); + return false; + } + $objects[] = $results->Field('object_id'); + $results->MoveNext(); + } + if (count($objects) == 0) { + $database->Rollback(); + return true; + } + $database->Query('DELETE FROM ' . $escaped_schema . '.' . $escaped_table . ' WHERE object_id = ANY($1);', '{' . implode(',', $objects) . '}'); + $database->Commit(); + return true; + } + + public function GetPropertyValue(string $propertyName): mixed { + $prefix = static::CustomVariablePrefix(); + switch ($propertyName) { + case $prefix . 'Id': + return $this->objectId; + case $prefix . 'Group': + return $this->objectGroup; + default: + return parent::GetPropertyValue($propertyName); + } + } +} + +?> diff --git a/Website/api/v4/classes/Palworld/Project.php b/Website/api/v4/classes/Palworld/Project.php new file mode 100644 index 000000000..84d597923 --- /dev/null +++ b/Website/api/v4/classes/Palworld/Project.php @@ -0,0 +1,92 @@ +gameSpecific)) { + $contentPacks = $this->gameSpecific['contentPacks']; + if ($asArray) { + return $contentPacks; + } else { + return implode(',', $contentPacks); + } + } else { + if ($asArray) { + return []; + } else { + return ''; + } + } + } + + public function ImplementedConfigs(bool $asArray): array|string { + if (array_key_exists('included_editors', $this->gameSpecific)) { + $editors = $this->gameSpecific['included_editors']; + if ($asArray) { + return $editors; + } else { + return implode(',', $editors); + } + } else { + if ($asArray) { + return []; + } else { + return ''; + } + } + } + + protected static function AddColumnValues(array $project, array &$row_values): bool { + if (parent::AddColumnValues($project, $row_values) === false) { + return false; + } + + $database = BeaconCommon::Database(); + + $content_pack_ids = []; + if (isset($project['ModSelections'])) { + $console_safe = true; + foreach ($project['ModSelections'] as $content_pack_id => $content_pack_enabled) { + if ($content_pack_enabled) { + $rows = $database->Query('SELECT content_pack_id, console_safe FROM palworld.content_packs WHERE confirmed = TRUE AND content_pack_id = $1;', $content_pack_id); + if ($rows->RecordCount() === 1) { + $content_pack_ids[] = $content_pack_id; + $console_safe = $console_safe && $rows->Field('console_safe'); + } + } + } + } elseif (isset($project['ConsoleModsOnly'])) { + $console_mods_only = $project['ConsoleModsOnly']; + $rows = $database->Query('SELECT content_pack_id FROM palworld.content_packs WHERE confirmed = TRUE AND console_safe = TRUE AND default_enabled = TRUE;'); + while (!$rows->EOF()) { + $content_pack_ids[] = $rows->Field('content_pack_id'); + $rows->MoveNext(); + } + } else { + $console_safe = false; + } + + $editor_names = []; + foreach ($project['EditorNames'] as $editor_name) { + if ($editor_name === 'Metadata') { + continue; + } + + $editor_names[] = $editor_name; + } + $project['EditorNames'] = $editor_names; + + $row_values['game_specific'] = json_encode([ + 'contentPacks' => $content_pack_ids, + 'included_editors' => $project['EditorNames'] + ]); + $row_values['console_safe'] = $console_safe; + + return true; + } +} + +?> diff --git a/Website/api/v4/classes/Project.php b/Website/api/v4/classes/Project.php index 5f590fbc4..e360b96bd 100644 --- a/Website/api/v4/classes/Project.php +++ b/Website/api/v4/classes/Project.php @@ -86,6 +86,9 @@ protected static function NewInstance(BeaconRecordSet $rows): Project { case 'ArkSA': return new ArkSA\Project($rows); break; + case 'Palworld': + return new Palworld\Project($rows); + break; default: throw new Exception('Unknown game ' . $gameId); } @@ -589,6 +592,7 @@ public static function Save(User $user, array $manifest): ?static { break; case '7DaysToDie': + case 'Palworld': break; default: throw new Exception('Unknown game ' . $gameId . '.', 400); diff --git a/Website/api/v4/includes/builddeltas.php b/Website/api/v4/includes/builddeltas.php index d9d6eb5f3..da8092533 100644 --- a/Website/api/v4/includes/builddeltas.php +++ b/Website/api/v4/includes/builddeltas.php @@ -4,6 +4,7 @@ use BeaconAPI\v4\Ark; use BeaconAPI\v4\SDTD; use BeaconAPI\v4\ArkSA; +use BeaconAPI\v4\Palworld; $root = "/v{$version}"; if (BeaconCommon::InProduction() == false) { @@ -41,6 +42,9 @@ case 'ArkSA': BuildArkSAContentPackFile($completeArchive, null, $pack); break; + case 'Palworld': + BuildPalworldContentPackFile($completeArchive, null, $pack); + break; } } @@ -73,6 +77,9 @@ case 'ArkSA': BuildArKSAContentPackFile($deltaArchive, $since, $pack); break; + case 'Palworld': + BuildPalworldContentPackFile($deltaArchive, $since, $pack); + break; } $rows->MoveNext(); @@ -132,6 +139,17 @@ function BuildArkSAContentPackFile(Archiver $archive, ?DateTime $since, ContentP ]); } +function BuildPalworldContentPackFile(Archiver $archive, ?DateTime $since, ContentPack $contentPack): void { + BuildFile([ + 'archive' => $archive, + 'class' => 'Palworld/ContentPack', + 'since' => $since, + 'Palworld' => [ + 'contentPack' => $contentPack + ] + ]); +} + function BuildFile(array $settings): void { global $lastDatabaseUpdate, $database, $root, $version; @@ -217,6 +235,12 @@ function BuildFile(array $settings): void { 'gameVariables' => ArkSA\GameVariable::Search($filters, true) ]; + $payloads[] = [ + 'gameId' => 'Palworld', + 'contentPacks' => ContentPack::Search([...$filters, 'gameId' => 'Palworld'], true), + 'gameVariables' => Palworld\GameVariable::Search($filters, true) + ]; + $localName = 'Main.json'; break; case 'Ark/Mod': @@ -262,6 +286,17 @@ function BuildFile(array $settings): void { 'spawnPoints' => ArkSA\SpawnPoint::Search($filters, true) ]; + $localName = "{$pack->ContentPackId()}.json"; + break; + case 'Palworld/ContentPack': + $pack = $settings['Palworld']['contentPack']; + $filters['contentPackId'] = $pack->ContentPackId(); + + $payloads[] = [ + 'gameId' => 'Palworld', + 'configOptions' => Palworld\ConfigOption::Search($filters, true) + ]; + $localName = "{$pack->ContentPackId()}.json"; break; default: diff --git a/Website/api/v4/index.php b/Website/api/v4/index.php index 908afc579..02db5b32a 100644 --- a/Website/api/v4/index.php +++ b/Website/api/v4/index.php @@ -182,6 +182,8 @@ DatabaseObjectManager::RegisterRoutes('BeaconAPI\v4\Sentinel\PlayerNote', 'sentinel/playerNotes', 'playerNoteId'); DatabaseObjectManager::RegisterRoutes('BeaconAPI\v4\Sentinel\Service', 'sentinel/services', 'serviceId'); DatabaseObjectManager::RegisterRoutes('BeaconAPI\v4\Sentinel\ServiceGroup', 'sentinel/serviceGroups', 'serviceGroupId'); +DatabaseObjectManager::RegisterRoutes('BeaconAPI\v4\Palworld\ConfigOption', 'palworld/configOptions', 'configOptionId'); +DatabaseObjectManager::RegisterRoutes('BeaconAPI\v4\Palworld\GameVariable', 'palworld/gameVariables', 'key'); DatabaseObjectManager::RegisterRoutes('BeaconAPI\v4\SDTD\ConfigOption', '7dtd/configOptions', 'configOptionId'); diff --git a/Website/framework/classes/BeaconCommon.php b/Website/framework/classes/BeaconCommon.php index 5c0282ea8..d1c540887 100644 --- a/Website/framework/classes/BeaconCommon.php +++ b/Website/framework/classes/BeaconCommon.php @@ -1040,9 +1040,25 @@ public static function StandardizeGameId(string $gameId): string { return 'ArkSA'; case 'sdtd': return '7DaysToDie'; + case 'palworld': + return 'Palworld'; } return ''; } + + public static function GameIdToName(string $gameId): string { + switch (strtolower(trim($gameId))) { + case 'ark': + return 'Ark: Survival Evolved'; + case 'arksa': + return 'Ark: Survival Ascended'; + case 'sdtd': + return '7 Days To Die'; + case 'palworld': + return 'Palworld (Early Access)'; + } + return $gameId; + } } ?> diff --git a/Website/src/js/checkout.js b/Website/src/js/checkout.js index 04997081d..0bb326790 100644 --- a/Website/src/js/checkout.js +++ b/Website/src/js/checkout.js @@ -2,6 +2,7 @@ import { BeaconDialog } from "./classes/BeaconDialog.js"; import { BeaconWebRequest } from "./classes/BeaconWebRequest.js"; +import { BeaconPagePanel } from "./classes/BeaconPagePanel.js"; import { getCurrencyFormatter, formatPrices, formatDate, epochToDate, randomUUID } from "./common.js"; const StatusOwns = 'owns'; // User has a license for the item @@ -20,6 +21,21 @@ document.addEventListener('beaconRunCheckout', ({checkoutProperties}) => { const Products = checkoutProperties.products; const ProductIds = checkoutProperties.productIds; const formatCurrency = getCurrencyFormatter(checkoutProperties.currencyCode); + const GamesList = checkoutProperties.games; + const OtherGamesList = GamesList.filter(item => item.gameId !== 'Ark' && item.gameId !== 'ArkSA'); + + BeaconPagePanel.init(); + const gamesPanel = BeaconPagePanel.pagePanels['panel-games']; + if (gamesPanel) { + gamesPanel.element.addEventListener('panelSwitched', () => { + if (gamesPanel.currentPageTitle) { + document.title = `Beacon Omni for ${gamesPanel.currentPageTitle}`; + } else { + document.title = 'Beacon Omni'; + } + history.pushState({}, '', `/omni#${gamesPanel.currentPageName}`); + }); + } class BeaconCartItem { #id = null; @@ -112,10 +128,37 @@ document.addEventListener('beaconRunCheckout', ({checkoutProperties}) => { } get arkSAYears() { - return Math.min(this.getQuantity(Products?.ArkSA?.Base?.ProductId) + this.getQuantity(Products?.ArkSA?.Upgrade?.ProductId) + this.getQuantity(Products?.ArkSA?.Renewal?.ProductId), 10); + return Math.min(this.getQuantity(Products?.ArkSA?.Base?.ProductId) + this.getQuantity(Products?.ArkSA?.Upgrade?.ProductId) + this.getQuantity(Products?.ArkSA?.Renewal?.ProductId), MaxRenewalCount); + } + + hasGame(gameId) { + if (gameId === 'ArkSA') { + return this.hasArkSA; + } + + return this.getQuantity(Products?.[gameId]?.Base?.ProductId) > 0 || this.getQuantity(Products?.[gameId]?.Renewal?.ProductId) > 0; } - build(targetCart, isGift, withArk, arkSAYears) { + yearsForGame(gameId) { + if (gameId === 'ArkSA') { + return this.arkSAYears; + } + + return Math.min(this.getQuantity(Products?.[gameId]?.Base?.ProductId) + this.getQuantity(Products?.[gameId]?.Renewal?.ProductId), MaxRenewalCount); + } + + yearsForOtherGames() { + const otherGameYears = {}; + for (const game of OtherGamesList) { + const years = this.yearsForGame(game.gameId); + if (years > 0) { + otherGameYears[game.gameId] = this.yearsForGame(game.gameId); + } + } + return otherGameYears; + } + + build(targetCart, isGift, withArk, arkSAYears, otherGameYears) { this.reset(); this.isGift = isGift; @@ -150,11 +193,29 @@ document.addEventListener('beaconRunCheckout', ({checkoutProperties}) => { } } } + + if (otherGameYears) { + for (const game of OtherGamesList) { + const years = Math.min((otherGameYears[game.gameId] ?? 0), MaxRenewalCount); + if (years === 0) { + continue; + } + + if (isGift === false && targetCart.findLicense(Products[game.gameId].Base.ProductId) !== null) { + this.setQuantity(Products[game.gameId].Renewal.ProductId, years); + } else { + this.setQuantity(Products[game.gameId].Base.ProductId, 1); + if (years > 1) { + this.setQuantity(Products[game.gameId].Renewal.ProductId, years - 1); + } + } + } + } } rebuild(targetCart) { const oldFingerprint = this.fingerprint; - this.build(targetCart, this.isGift, this.hasArk, this.arkSAYears); + this.build(targetCart, this.isGift, this.hasArk, this.arkSAYears, this.yearsForOtherGames()); return this.fingerprint !== oldFingerprint; } @@ -169,7 +230,12 @@ document.addEventListener('beaconRunCheckout', ({checkoutProperties}) => { const includeArk = this.hasArk || otherLineItem.hasArk; const arkSAYears = this.arkSAYears + otherLineItem.arkSAYears; - this.build(cart, this.isGift, includeArk, arkSAYears); + const otherGameYears = this.yearsForOtherGames(); + const additionalGameYears = otherLineItem.yearsForOtherGames(); + for (const [gameId, years] of Object.entries(additionalGameYears)) { + otherGameYears[gameId] = (otherGameYears[gameId] ?? 0) + years; + } + this.build(cart, this.isGift, includeArk, arkSAYears, otherGameYears); } get total() { @@ -444,15 +510,25 @@ document.addEventListener('beaconRunCheckout', ({checkoutProperties}) => { const buyButton = document.getElementById('buy-button'); const landingButton = document.getElementById('cart-back-button'); - const arkOnlyMode = Object.keys(ProductIds).length === 1; const goToCart = () => { + document.title = 'Buy Beacon Omni'; history.pushState({}, '', '/omni#checkout'); dispatchEvent(new PopStateEvent('popstate', {})); }; const goToLanding = () => { - history.pushState({}, '', '/omni'); + if (gamesPanel) { + if (gamesPanel.currentPageTitle) { + document.title = `Beacon Omni for ${gamesPanel.currentPageTitle}`; + } else { + document.title = 'Beacon Omni'; + } + history.pushState({}, '', `/omni#${gamesPanel.currentPageName}`); + } else { + document.title = 'Beacon Omni'; + history.pushState({}, '', '/omni'); + } dispatchEvent(new PopStateEvent('popstate', {})); }; @@ -531,30 +607,42 @@ document.addEventListener('beaconRunCheckout', ({checkoutProperties}) => { const wizard = { cartView: null, editCartItem: null, - cancelButton: document.getElementById('checkout-wizard-cancel'), actionButton: document.getElementById('checkout-wizard-action'), - giftCheck: document.getElementById('checkout-wizard-gift-check'), - arkSACheck: document.getElementById('checkout-wizard-arksa-check'), arkCheck: document.getElementById('checkout-wizard-ark-check'), - arkPriceField: document.getElementById('checkout-wizard-ark-price'), - arkSAPriceField: document.getElementById('checkout-wizard-arksa-full-price'), - arkSAUpgradePriceField: document.getElementById('checkout-wizard-arksa-discount-price'), - arkSAStatusField: document.getElementById('checkout-wizard-status-arksa'), - arkStatusField: document.getElementById('checkout-wizard-status-ark'), - arkSADurationGroup: document.getElementById('checkout-wizard-arksa-duration-group'), - arkSADurationField: document.getElementById('checkout-wizard-arksa-duration-field'), - arkSADurationUpButton: document.getElementById('checkout-wizard-arksa-yearup-button'), - arkSADurationDownButton: document.getElementById('checkout-wizard-arksa-yeardown-button'), - arkSAPromoField: document.getElementById('checkout-wizard-promo-arksa'), arkOnlyCheck: document.getElementById('storefront-ark-check'), + arkOnlyGiftDownButton: document.getElementById('storefront-ark-gift-decrease'), arkOnlyGiftField: document.getElementById('storefront-ark-gift-field'), - arkOnlyOwnedField: document.getElementById('storefront-ark-owned'), arkOnlyGiftQuantityGroup: document.getElementById('storefront-ark-gift-group'), arkOnlyGiftUpButton: document.getElementById('storefront-ark-gift-increase'), - arkOnlyGiftDownButton: document.getElementById('storefront-ark-gift-decrease'), + arkOnlyOwnedField: document.getElementById('storefront-ark-owned'), + arkPriceField: document.getElementById('checkout-wizard-ark-price'), + arkSACheck: document.getElementById('checkout-wizard-arksa-check'), + arkSADurationDownButton: document.getElementById('checkout-wizard-arksa-yeardown-button'), + arkSADurationField: document.getElementById('checkout-wizard-arksa-duration-field'), + arkSADurationGroup: document.getElementById('checkout-wizard-arksa-duration-group'), + arkSADurationUpButton: document.getElementById('checkout-wizard-arksa-yearup-button'), + arkSAPriceField: document.getElementById('checkout-wizard-arksa-full-price'), + arkSAPromoField: document.getElementById('checkout-wizard-promo-arksa'), + arkSAStatusField: document.getElementById('checkout-wizard-status-arksa'), + arkSAUpgradePriceField: document.getElementById('checkout-wizard-arksa-discount-price'), + arkStatusField: document.getElementById('checkout-wizard-status-ark'), + giftCheck: document.getElementById('checkout-wizard-gift-check'), + cancelButton: document.getElementById('checkout-wizard-cancel'), init: function(cartView) { this.cartView = cartView; + for (const game of OtherGamesList) { + const gameId = game.gameId; + const lowerGameId = gameId.toLowerCase(); + this[`${lowerGameId}Check`] = document.getElementById(`checkout-wizard-${lowerGameId}-check`); + this[`${lowerGameId}DurationDownButton`] = document.getElementById(`checkout-wizard-${lowerGameId}-yeardown-button`); + this[`${lowerGameId}DurationField`] = document.getElementById(`checkout-wizard-${lowerGameId}-duration-field`); + this[`${lowerGameId}DurationGroup`] = document.getElementById(`checkout-wizard-${lowerGameId}-duration-group`); + this[`${lowerGameId}DurationUpButton`] = document.getElementById(`checkout-wizard-${lowerGameId}-yearup-button`); + this[`${lowerGameId}PriceField`] = document.getElementById(`checkout-wizard-${lowerGameId}-price`); + this[`${lowerGameId}StatusField`] = document.getElementById(`checkout-wizard-status-${lowerGameId}`); + } + if (this.cancelButton) { this.cancelButton.addEventListener('click', (ev) => { ev.preventDefault(); @@ -569,8 +657,18 @@ document.addEventListener('beaconRunCheckout', ({checkoutProperties}) => { const isGift = this.giftCheck.checked; const includeArk = this.arkCheck && this.arkCheck.disabled === false && this.arkCheck.checked; const includeArkSA = this.arkSACheck && this.arkSACheck.disabled === false && this.arkSACheck.checked; + let includeOtherGames = false; + const otherGameYears = {}; + for (const game of OtherGamesList) { + const gameId = game.gameId; + const lowerGameId = gameId.toLowerCase(); + if (this[`${lowerGameId}Check`] && this[`${lowerGameId}Check`].disabled === false && this[`${lowerGameId}Check`].checked) { + includeOtherGames = true; + otherGameYears[gameId] = parseInt(this[`${lowerGameId}DurationField`].value) || 1; + } + } - if ((includeArk || includeArkSA) === false) { + if ((includeArk || includeArkSA || includeOtherGames) === false) { return; } @@ -584,7 +682,7 @@ document.addEventListener('beaconRunCheckout', ({checkoutProperties}) => { lineItem.isGift = isGift; } - lineItem.build(cart, isGift, includeArk, arkSAYears); + lineItem.build(cart, isGift, includeArk, arkSAYears, otherGameYears); if (Boolean(this.editCartItem) === false) { const personalCartItem = cart.personalCartItem; @@ -652,6 +750,52 @@ document.addEventListener('beaconRunCheckout', ({checkoutProperties}) => { }); } + for (const game of OtherGamesList) { + const lowerGameId = game.gameId.toLowerCase(); + const checkbox = this[`${lowerGameId}Check`]; + const durationField = this[`${lowerGameId}DurationField`]; + const durationUpButton = this[`${lowerGameId}DurationUpButton`]; + const durationDownButton = this[`${lowerGameId}DurationDownButton`]; + const durationGroup = this[`${lowerGameId}DurationGroup`]; + + if (checkbox && durationField && durationUpButton && durationDownButton && durationGroup) { + checkbox.addEventListener('change', () => { + this.update(); + }); + + durationField.addEventListener('input', () => { + this.update(); + }); + + const nudgeGamesDuration = (amount) => { + const originalValue = parseInt(durationField.value); + let newValue = originalValue + amount; + if (newValue > MaxRenewalCount || newValue < 1) { + durationGroup.classList.add('shake'); + setTimeout(() => { + durationGroup.classList.remove('shake'); + }, 400); + newValue = Math.max(Math.min(newValue, MaxRenewalCount), 1); + } + if (originalValue !== newValue) { + durationField.value = newValue; + checkbox.checked = true; + this.update(); + } + }; + + durationUpButton.addEventListener('click', (ev) => { + ev.preventDefault(); + nudgeGamesDuration(1); + }); + + durationDownButton.addEventListener('click', (ev) => { + ev.preventDefault(); + nudgeGamesDuration(-1); + }); + } + } + if (this.arkOnlyCheck) { this.arkOnlyCheck.addEventListener('change', (ev) => { ev.preventDefault(); @@ -734,12 +878,42 @@ document.addEventListener('beaconRunCheckout', ({checkoutProperties}) => { }); } }, - present: function(editCartItem = null) { + filter: function(gameIds) { + const cells = document.querySelectorAll('.checkout-wizard-list-game'); + let firstCell = true; + for (const cell of cells) { + const cellGameId = cell.getAttribute('beacon-game-id'); + const visible = gameIds.length === 0 || gameIds.includes(cellGameId) || ((cellGameId === 'Ark' || cellGameId === 'ArkSA') && (gameIds.includes('Ark') || gameIds.includes('ArkSA'))); + if (visible) { + cell.classList.remove('hidden'); + if (firstCell) { + firstCell = false; + cell.classList.remove('separated'); + } else { + cell.classList.add('separated'); + } + } else { + cell.classList.add('hidden'); + } + } + }, + present: function(editCartItem = null, filterGameIds = [], activateGameId = null) { + this.filter(filterGameIds); + if (filterGameIds.length > 0 && filterGameIds.includes(activateGameId) === false) { + activateGameId = null; + } + this.editCartItem = editCartItem; this.arkCheck.disabled = false; // update() will disable them if (this.arkSACheck) { this.arkSACheck.disabled = false; } + for (const game of OtherGamesList) { + const lowerGameId = game.gameId.toLowerCase(); + if (this[`${lowerGameId}Check`]) { + this[`${lowerGameId}Check`].disabled = false; + } + } if (editCartItem) { this.giftCheck.checked = editCartItem.isGift; @@ -748,12 +922,26 @@ document.addEventListener('beaconRunCheckout', ({checkoutProperties}) => { if (this.arkSACheck) { this.arkSACheck.checked = editCartItem.hasArkSA; } + for (const game of OtherGamesList) { + const gameId = game.gameId; + const lowerGameId = gameId.toLowerCase(); + if (this[`${lowerGameId}Check`]) { + this[`${lowerGameId}Check`].checked = editCartItem.hasGame(gameId); + } + } } else { this.giftCheck.checked = false; this.giftCheck.disabled = false; - this.arkCheck.checked = false; + this.arkCheck.checked = (activateGameId === 'Ark'); if (this.arkSACheck) { - this.arkSACheck.checked = false; + this.arkSACheck.checked = (activateGameId === 'ArkSA'); + } + for (const game of OtherGamesList) { + const gameId = game.gameId; + const lowerGameId = gameId.toLowerCase(); + if (this[`${lowerGameId}Check`]) { + this[`${lowerGameId}Check`].checked = (activateGameId === gameId); + } } } @@ -765,6 +953,17 @@ document.addEventListener('beaconRunCheckout', ({checkoutProperties}) => { } } + for (const game of OtherGamesList) { + const gameId = game.gameId; + const lowerGameId = gameId.toLowerCase(); + const durationField = this[`${lowerGameId}DurationField`]; + if (editCartItem) { + durationField.value = editCartItem.yearsForGame(gameId); + } else { + durationField.value = '1'; + } + } + this.cancelButton.innerText = 'Cancel'; this.update(); @@ -775,6 +974,9 @@ document.addEventListener('beaconRunCheckout', ({checkoutProperties}) => { Ark: StatusNone, ArkSA: StatusNone, }; + for (const game of OtherGamesList) { + gameStatus[game.gameId] = StatusNone; + } const personalCartItem = cart.personalCartItem; const isEditing = Boolean(this.editCartItem); @@ -782,6 +984,10 @@ document.addEventListener('beaconRunCheckout', ({checkoutProperties}) => { if (isGift) { gameStatus.Ark = this.arkCheck && this.arkCheck.disabled === false && this.arkCheck.checked ? StatusBuying : StatusNone; gameStatus.ArkSA = this.arkSACheck && this.arkSACheck.disabled === false && this.arkSACheck.checked ? StatusBuying : StatusNone; + for (const game of OtherGamesList) { + const lowerGameId = game.gameId.toLowerCase(); + gameStatus[gameId] = (this[`${lowerGameId}Check`] && this[`${lowerGameId}Check`].disabled === false && this[`${lowerGameId}Check`].checked) ? StatusBuying : StatusNone; + } } else { if (cart.arkLicense) { gameStatus.Ark = StatusOwns; @@ -802,6 +1008,21 @@ document.addEventListener('beaconRunCheckout', ({checkoutProperties}) => { } else { gameStatus.ArkSA = StatusNone; } + + for (const game of OtherGamesList) { + const gameId = game.gameId; + const lowerGameId = gameId.toLowerCase(); + const license = cart.findLicense(Products[gameId]?.Base?.ProductId); + if (license) { + gameStatus[gameId] = StatusOwns; + } else if (personalCartItem && (isEditing === false || personalCartItem.id !== this.editCartItem.id) && personalCartItem.hasGame(gameId)) { + gameStatus[gameId] = StatusInCart; + } else if (this[`${lowerGameId}Check`] && this[`${lowerGameId}Check`].disabled === false && this[`${lowerGameId}Check`].checked) { + gameStatus[gameId] = StatusBuying; + } else { + gameStatus[gameId] = StatusNone; + } + } } return gameStatus; @@ -862,12 +1083,12 @@ document.addEventListener('beaconRunCheckout', ({checkoutProperties}) => { arkSAEffectivePrice = Products.ArkSA.Upgrade.Price + (Products.ArkSA.Renewal.Price * arkSAAdditionalYears); const discountLanguage = gameStatus.Ark === StatusOwns ? 'because you own' : 'when bundled with'; - this.arkSAStatusField.innerText = `Includes first year of app updates. Additional years cost ${formatCurrency(Products.ArkSA.Renewal.Price)} each.`; + this.arkSAStatusField.innerText = `Includes 1 year of app updates. Additional years cost ${formatCurrency(Products.ArkSA.Renewal.Price)} each.`; this.arkSAPromoField.innerText = `${discount}% off first year ${discountLanguage} ${Products.Ark.Base.Name}`; this.arkSAPromoField.classList.remove('hidden'); } else { // Show normal - this.arkSAStatusField.innerText = `Includes first year of app updates. Additional years cost ${formatCurrency(Products.ArkSA.Renewal.Price)} each.`; + this.arkSAStatusField.innerText = `Includes 1 year of app updates. Additional years cost ${formatCurrency(Products.ArkSA.Renewal.Price)} each.`; this.arkSAPromoField.innerText = `${discount}% off first year when bundled with ${Products.Ark.Base.Name}`; this.arkSAPromoField.classList.remove('hidden'); @@ -898,6 +1119,76 @@ document.addEventListener('beaconRunCheckout', ({checkoutProperties}) => { this.arkCheck.disabled = false; } + for (const game of OtherGamesList) { + const gameId = game.gameId; + const lowerGameId = gameId.toLowerCase(); + const checkbox = this[`${lowerGameId}Check`]; + const durationField = this[`${lowerGameId}DurationField`]; + const statusField = this[`${lowerGameId}StatusField`]; + const priceField = this[`${lowerGameId}PriceField`]; + + if (!checkbox) { + continue; + } + + let basePrice = Products[gameId]?.Base?.Price; + const isLifetime = Products[gameId]?.Base?.PlanLengthSeconds === null; + + const desiredYears = Math.min(Math.max(parseInt(durationField.value) || 1, 1), MaxRenewalCount); + if (parseInt(durationField.value) !== desiredYears && document.activeElement !== durationField) { + durationField.value = desiredYears; + } + const additionalYears = Math.max(desiredYears - 1, 0); + + if (isLifetime) { + if (gameStatus[gameId] === StatusOwns) { + statusField.innerHTML = `You already own ${Products[gameId]?.Base?.Name}.`; + checkbox.disabled = true; + checkbox.checked = false; + } else if (gameStatus[gameId] === StatusInCart) { + statusField.innerText = `${Products[gameId]?.Base?.Name} is already in your cart.`; + checkbox.disabled = true; + checkbox.checked = false; + } else { + statusField.innerText = 'Includes lifetime app updates.'; + checkbox.disabled = false; + } + } else { + if (gameStatus[gameId] === StatusOwns) { + // Show as renewal + const license = cart.findLicense(Products[gameId]?.Base?.ProductId); + const now = Math.floor(Date.now() / 1000); + const currentExpiration = license.expiresEpoch; + const currentExpirationDisplay = formatDate(epochToDate(currentExpiration), false); + const startEpoch = (now > currentExpiration) ? ((Math.floor(now / 86400) * 86400) + 86400) : currentExpiration; + const newExpiration = startEpoch + (Products[gameId]?.Renewal?.PlanLengthSeconds * desiredYears); + const newExpirationDisplay = formatDate(epochToDate(newExpiration), false); + + let statusHtml; + if (now > currentExpiration) { + statusHtml = `Renew your update plan
Expired on ${currentExpirationDisplay}
New expiration: ${newExpirationDisplay}`; + } else { + statusHtml = `Extend your update plan
Expires on ${currentExpirationDisplay}
New expiration: ${newExpirationDisplay}`; + } + statusField.innerHTML = statusHtml; + } else if (gameStatus[gameId] === StatusInCart) { + // Show as renewal + basePrice = Products[gameId]?.Renewal?.Price * desiredYears; + statusField.innerText = `Additional renewal years for ${Products[gameId]?.Base?.Name} in your cart.`; + } else { + // Show normal + statusField.innerText = `Includes ${Products[gameId]?.Renewal?.PlanLength} of app updates. Additional years cost ${formatCurrency(Products[gameId]?.Renewal?.Price)} each.`; + basePrice = Products[gameId]?.Base?.Price + (Products[gameId]?.Renewal?.Price * additionalYears); + } + } + + priceField.innerText = formatCurrency(basePrice); + + if (checkbox.disabled === false && checkbox.checked === true) { + total = total + basePrice; + } + } + const addToCart = (this.editCartItem) ? 'Edit' : 'Add to Cart'; if (total > 0) { this.actionButton.disabled = false; @@ -1001,32 +1292,7 @@ document.addEventListener('beaconRunCheckout', ({checkoutProperties}) => { this.update(); }, update: function() { - if (arkOnlyMode) { - this.emailField.innerText = cart.email; - this.changeEmailButton.disabled = Boolean(checkoutProperties.forceEmail); - this.changeEmailButton.innerText = cart.email ? 'Change Email' : 'Set Email'; - this.changeEmailButton.classList.remove('hidden'); - this.addMoreButton.classList.add('hidden'); - - if (cart.arkLicense) { - this.wizard.arkOnlyOwnedField.classList.remove('hidden'); - this.wizard.arkOnlyCheck.parentElement.classList.add('hidden'); - } else { - this.wizard.arkOnlyOwnedField.classList.add('hidden'); - this.wizard.arkOnlyCheck.parentElement.classList.remove('hidden'); - } - - const personalCartItem = cart.personalCartItem; - this.wizard.arkOnlyCheck.checked = personalCartItem && personalCartItem.hasArk; - - if (document.activeElement !== this.wizard.arkOnlyGiftField) { - const giftItems = cart.items.filter((cartItem) => { - return cartItem.isGift === true && cartItem.hasArk; - }); - - this.wizard.arkOnlyGiftField.value = giftItems.length; - } - } else if (cart.count > 0) { + if (cart.count > 0) { this.emailField.innerText = cart.email; this.changeEmailButton.disabled = Boolean(checkoutProperties.forceEmail); this.changeEmailButton.innerText = cart.email ? 'Change Email' : 'Set Email'; @@ -1176,8 +1442,15 @@ document.addEventListener('beaconRunCheckout', ({checkoutProperties}) => { cartView.init(wizard); const setViewMode = (animated = true) => { + const pageName = window.location.hash.substring(1); + if (gamesPanel && gamesPanel.hasPage(pageName)) { + storeViewManager.switchView('page-landing', animated); + gamesPanel.switchPage(pageName); + return; + } + window.scrollTo(window.scrollX, 0); - if (window.location.hash === '#checkout') { + if (pageName === 'checkout') { storeViewManager.switchView('page-cart', animated); } else { storeViewManager.back(animated); @@ -1187,7 +1460,7 @@ document.addEventListener('beaconRunCheckout', ({checkoutProperties}) => { buyButton.addEventListener('click', (ev) => { ev.preventDefault(); - if (arkOnlyMode || cart.count > 0) { + if (cart.count > 0) { goToCart(); return; } @@ -1197,6 +1470,28 @@ document.addEventListener('beaconRunCheckout', ({checkoutProperties}) => { }); }); + for (const game of GamesList) { + const gameId = game.gameId; + const gameBuyButton = document.getElementById(`checkout-buy-${gameId.toLowerCase()}-button`); + if (!gameBuyButton) { + continue; + } + + gameBuyButton.addEventListener('click', (ev) => { + ev.preventDefault(); + + const wizardFunction = () => { + wizard.present(null, [gameId], gameId); + }; + + if (cart.count > 0) { + wizardFunction(); + } else { + emailDialog.present(true, wizardFunction); + } + }); + } + landingButton.addEventListener('click', () => { goToLanding(); }); diff --git a/Website/src/js/classes/BeaconPagePanel.js b/Website/src/js/classes/BeaconPagePanel.js index 9b4a71e85..dfa3b5c15 100644 --- a/Website/src/js/classes/BeaconPagePanel.js +++ b/Website/src/js/classes/BeaconPagePanel.js @@ -1,13 +1,14 @@ export class BeaconPagePanel { static pagePanels = {}; - + element = null; pageMap = {}; currentPageName = null; - + currentPageTitle = null; + constructor(element) { this.element = element; - + const pages = element.querySelectorAll('div.page-panel-page'); for (const page of pages) { const pageName = page.getAttribute('page'); @@ -16,25 +17,25 @@ export class BeaconPagePanel { this.currentPageName = pageName; } } - + const links = element.querySelectorAll('div.page-panel-nav a'); for (const link of links) { const pageName = link.getAttribute('page'); if (this.pageMap[pageName]) { this.pageMap[pageName].link = link; - + link.addEventListener('click', (ev) => { ev.preventDefault(); this.switchPage(ev.target.getAttribute('page')); }); } } - + const ev = new Event('panelCreated'); ev.panel = this; this.element.dispatchEvent(ev); } - + switchPage(newPageName) { const oldPageName = this.currentPageName; if (oldPageName === newPageName) { @@ -43,18 +44,27 @@ export class BeaconPagePanel { if (!this.pageMap[newPageName]) { return; } - + this.pageMap[oldPageName].classList.remove('page-panel-visible'); this.pageMap[oldPageName].link.parentElement.classList.remove('page-panel-active'); this.pageMap[newPageName].classList.add('page-panel-visible'); this.pageMap[newPageName].link.parentElement.classList.add('page-panel-active'); this.currentPageName = newPageName; - + this.currentPageTitle = this.pageMap[newPageName].link.innerText; + const ev = new Event('panelSwitched'); ev.panel = this; this.element.dispatchEvent(ev); } - + + hasPage(pageName) { + if (Object.hasOwn) { + return Object.hasOwn(this.pageMap, pageName); + } else { + return Object.prototype.hasOwnProperty.call(this.pageMap, pageName); + } + } + static init() { const panels = document.querySelectorAll('div.page-panel'); for (const panel of panels) { diff --git a/Website/src/scss/_theme.scss b/Website/src/scss/_theme.scss index c5307edf4..fe9053223 100644 --- a/Website/src/scss/_theme.scss +++ b/Website/src/scss/_theme.scss @@ -2080,6 +2080,10 @@ ul.no-markings, ol.no-markings { text-align: center; padding: 16px; + &+& { + margin-top: 1rem; + } + &.notice-warning { border-color: color.mix($text-red, $background-color, 35%); color: $text-red; @@ -2461,12 +2465,14 @@ input.no-stepper { /* !Store */ #checkout-wizard-list { - & > div { + & > .checkout-wizard-list-game { display: flex; align-items: center; - &+div { + &.separated { margin-top: 20px; + padding-top: 20px; + border-top: 1px solid $separator-color; } .checkout-wizard-checkbox-cell { @@ -2722,6 +2728,24 @@ input.no-stepper { animation: shake-keyframes 0.4s linear 1; } +.omni-game-header { + display: flex; + align-items: center; + margin-bottom: 1.5rem; + + .omni-game-header-title { + font-weight: 600; + font-size: 1.5rem; + margin-right: 1.5rem; + flex: 1 1 auto; + } + + .omni-game-header-button { + flex: 0 0 auto; + align-self: flex-start; + } +} + @keyframes shake-keyframes { 0% { transform: translate(10px); @@ -2797,6 +2821,14 @@ body.no-navigation { } } +.license-name { + color: $text-accent-color; +} + +.game-name { + color: $text-blue; +} + @media (min-width: $breakpoint_desktop) { div.double_column div.column { display: table-cell; diff --git a/Website/src/scss/omni.scss b/Website/src/scss/omni.scss index f070836d5..9db2935e9 100644 --- a/Website/src/scss/omni.scss +++ b/Website/src/scss/omni.scss @@ -48,7 +48,7 @@ background-size: 100%; float: left; background-color: rgba(255, 255, 255, 0.05); - + img { display: block; } @@ -66,16 +66,16 @@ .signin_flex { display: flex; - + &+& { margin-top: 1rem; } - + .signin_flex-images { flex-grow: 0; flex-shrink: 0; flex-basis: calc(50% - 0.5rem); - + img { display: block; border-width: 0.5px; @@ -85,27 +85,27 @@ height: auto; } } - + .signin_flex-text { padding-left: 1rem; - + *:first-child { margin-top: 0; } - + *:last-child { margin-bottom: 0; } - + h4 { margin-top: 1.5rem; margin-bottom: 0.15rem; - + &+p { margin-top: 0.15rem; } } - + p { margin-top: 1rem; margin-bottom: 1rem; @@ -118,7 +118,7 @@ .signin_flex-images { flex-basis: 0; width: unset; - + img { width: unset; height: unset; @@ -135,7 +135,7 @@ padding: 12px; border-radius: 6px; text-align: center; - + @media (prefers-color-scheme: dark) { background-color: rgba(255, 255, 255, 0.02); border-color: rgba(255, 255, 255, 0.1); @@ -155,7 +155,7 @@ #checking_subtext { font-size: smaller; color: rgba(0, 0, 0, 0.8); - + @media (prefers-color-scheme: dark) { color: rgba(255, 255, 255, 0.8); } @@ -196,7 +196,7 @@ table.generic .bullet-column { width: 0px; white-space: nowrap; - + @media (min-width: 635px) { width: 100px; } @@ -241,51 +241,51 @@ td.bullet-column { margin-top: 15px; margin-bottom: 5px; justify-content: center; - + img { margin: 5px; float: left; display: block; height: 22px; } - + &.usd { .usd { display: block; } - + .eur { display: none; } - + .gbp { display: none; } } - + &.eur { .usd { display: none; } - + .eur { display: block; } - + .gbp { display: none; } } - + &.gbp { .usd { display: none; } - + .eur { display: none; } - + .gbp { display: block; } @@ -299,7 +299,7 @@ td.bullet-column { #checkout_currency_cell { text-align: center; font-size: small; - + a { display: inline-block; padding: 2px 6px; @@ -308,7 +308,7 @@ td.bullet-column { margin-left: 0.2em; margin-right: 0.2em; color: #084FD1; - + &.chosen { background-color: #084FD1; color: white; @@ -316,10 +316,32 @@ td.bullet-column { } } +.omni-hero { + margin-bottom: 1.5rem; + + img { + width: auto; + height: auto; + max-width: 100%; + max-height: 140px; + margin-left: auto; + margin-right: auto; + display: block; + } +} + +#panel-games { + margin-top: 2rem; +} + +.omni-feature-table { + margin-top: 1rem; +} + @media (prefers-color-scheme: dark) { #checkout_currency_cell a { color: #3486FE; - + &.chosen { background-color: #3486FE; } diff --git a/Website/src/scss/theme-beacon-dark.scss b/Website/src/scss/theme-beacon-dark.scss index 1e1a13e74..6b73b9d94 100644 --- a/Website/src/scss/theme-beacon-dark.scss +++ b/Website/src/scss/theme-beacon-dark.scss @@ -11,3 +11,11 @@ .accented-foreground { filter: invert(22%) sepia(68%) saturate(2401%) hue-rotate(271deg) brightness(88%) contrast(105%); } + +.dark-only { + display: unset; +} + +.light-only { + display: none; +} diff --git a/Website/src/scss/theme-beacon.scss b/Website/src/scss/theme-beacon.scss index b39d135ad..2fb214e4d 100644 --- a/Website/src/scss/theme-beacon.scss +++ b/Website/src/scss/theme-beacon.scss @@ -5,3 +5,11 @@ .accented-foreground { filter: invert(22%) sepia(68%) saturate(2401%) hue-rotate(271deg) brightness(88%) contrast(105%); } + +.dark-only { + display: none; +} + +.light-only { + display: unset; +} diff --git a/Website/src/scss/theme-purple.scss b/Website/src/scss/theme-purple.scss index 1657788f9..45e2cbe4c 100644 --- a/Website/src/scss/theme-purple.scss +++ b/Website/src/scss/theme-purple.scss @@ -9,3 +9,11 @@ #header_logo, .white-on-dark, .accented-foreground { filter: brightness(0) invert(1); } + +.dark-only { + display: unset; +} + +.light-only { + display: none; +} diff --git a/Website/www/assets/css/omni.css b/Website/www/assets/css/omni.css index e4d7f93a3..45f046e23 100644 --- a/Website/www/assets/css/omni.css +++ b/Website/www/assets/css/omni.css @@ -1 +1 @@ -#img_signin_auth{background-image:url(/omni/welcome/auth.png);width:150px;height:118px}#img_signin_import{background-image:url(/omni/welcome/import.png);width:150px;height:59px}#img_signin_password{background-image:url(/omni/welcome/password.png);width:150px;height:59px;clear:left;margin-top:6px}.omni-section{width:95%;margin-left:auto;margin-right:auto;margin-top:3em;margin-bottom:3em;box-sizing:border-box}.action-link+.action-link{margin-left:12px}.signin_step+.signin_step{margin-top:1rem;border-top-width:1px;border-top-style:solid;padding-top:1rem}.signin_text{padding-left:170px}.img_signin{border-width:1px;border-style:solid;background-size:100%;float:left;background-color:rgba(255,255,255,.05)}.img_signin img{display:block}#img_signin_enable{clear:left;margin-top:6px}#img_signin_fields{clear:left;margin-top:6px}.signin_flex{display:flex}.signin_flex+.signin_flex{margin-top:1rem}.signin_flex .signin_flex-images{flex-grow:0;flex-shrink:0;flex-basis:calc(50% - .5rem)}.signin_flex .signin_flex-images img{display:block;border-width:.5px;border-style:solid;box-sizing:border-box;width:100%;height:auto}.signin_flex .signin_flex-text{padding-left:1rem}.signin_flex .signin_flex-text *:first-child{margin-top:0}.signin_flex .signin_flex-text *:last-child{margin-bottom:0}.signin_flex .signin_flex-text h4{margin-top:1.5rem;margin-bottom:.15rem}.signin_flex .signin_flex-text h4+p{margin-top:.15rem}.signin_flex .signin_flex-text p{margin-top:1rem;margin-bottom:1rem}@media only screen and (min-width: 544px){.signin_flex .signin_flex-images{flex-basis:0;width:unset}.signin_flex .signin_flex-images img{width:unset;height:unset}}#checking_container{background-color:rgba(0,0,0,.02);border:1px solid rgba(0,0,0,.1);padding:12px;border-radius:6px;text-align:center}@media(prefers-color-scheme: dark){#checking_container{background-color:rgba(255,255,255,.02);border-color:rgba(255,255,255,.1)}}#checking_spinner{vertical-align:middle;margin-right:12px}#checking_text{line-height:1.5em;font-weight:bold}#checking_subtext{font-size:smaller;color:rgba(0,0,0,.8)}@media(prefers-color-scheme: dark){#checking_subtext{color:rgba(255,255,255,.8)}}#purchase_confirmed{display:none}#purchase_unknown{display:none}#purchase_delayed{margin-top:30px;display:none}#signin_instructions{display:none;margin-top:30px}.welcome_content{width:100%;margin-left:auto;margin-right:auto;max-width:500px;box-sizing:border-box}.push{clear:both;overflow:hidden;height:0px}table.generic .bullet-column{width:0px;white-space:nowrap}@media(min-width: 635px){table.generic .bullet-column{width:100px}}table.generic .storefront-quantity-column{min-width:125px;max-width:150px}td.bullet-column{color:green}#cart_back_paragraph{float:left}.price_column{width:115px;text-align:right}.quantity_column{width:75px;text-align:center}#email_section{border-width:1px;border-style:solid;clear:both;padding:20px;margin:20px auto;border-radius:6px;max-width:400px}#checkout_methods_cell{display:flex;flex-wrap:wrap;margin-top:15px;margin-bottom:5px;justify-content:center}#checkout_methods_cell img{margin:5px;float:left;display:block;height:22px}#checkout_methods_cell.usd .usd{display:block}#checkout_methods_cell.usd .eur{display:none}#checkout_methods_cell.usd .gbp{display:none}#checkout_methods_cell.eur .usd{display:none}#checkout_methods_cell.eur .eur{display:block}#checkout_methods_cell.eur .gbp{display:none}#checkout_methods_cell.gbp .usd{display:none}#checkout_methods_cell.gbp .eur{display:none}#checkout_methods_cell.gbp .gbp{display:block}#checkout_button_cell{text-align:center}#checkout_currency_cell{text-align:center;font-size:small}#checkout_currency_cell a{display:inline-block;padding:2px 6px;border-radius:4px;text-decoration:none;margin-left:.2em;margin-right:.2em;color:#084fd1}#checkout_currency_cell a.chosen{background-color:#084fd1;color:#fff}@media(prefers-color-scheme: dark){#checkout_currency_cell a{color:#3486fe}#checkout_currency_cell a.chosen{background-color:#3486fe}} +#img_signin_auth{background-image:url(/omni/welcome/auth.png);width:150px;height:118px}#img_signin_import{background-image:url(/omni/welcome/import.png);width:150px;height:59px}#img_signin_password{background-image:url(/omni/welcome/password.png);width:150px;height:59px;clear:left;margin-top:6px}.omni-section{width:95%;margin-left:auto;margin-right:auto;margin-top:3em;margin-bottom:3em;box-sizing:border-box}.action-link+.action-link{margin-left:12px}.signin_step+.signin_step{margin-top:1rem;border-top-width:1px;border-top-style:solid;padding-top:1rem}.signin_text{padding-left:170px}.img_signin{border-width:1px;border-style:solid;background-size:100%;float:left;background-color:rgba(255,255,255,.05)}.img_signin img{display:block}#img_signin_enable{clear:left;margin-top:6px}#img_signin_fields{clear:left;margin-top:6px}.signin_flex{display:flex}.signin_flex+.signin_flex{margin-top:1rem}.signin_flex .signin_flex-images{flex-grow:0;flex-shrink:0;flex-basis:calc(50% - .5rem)}.signin_flex .signin_flex-images img{display:block;border-width:.5px;border-style:solid;box-sizing:border-box;width:100%;height:auto}.signin_flex .signin_flex-text{padding-left:1rem}.signin_flex .signin_flex-text *:first-child{margin-top:0}.signin_flex .signin_flex-text *:last-child{margin-bottom:0}.signin_flex .signin_flex-text h4{margin-top:1.5rem;margin-bottom:.15rem}.signin_flex .signin_flex-text h4+p{margin-top:.15rem}.signin_flex .signin_flex-text p{margin-top:1rem;margin-bottom:1rem}@media only screen and (min-width: 544px){.signin_flex .signin_flex-images{flex-basis:0;width:unset}.signin_flex .signin_flex-images img{width:unset;height:unset}}#checking_container{background-color:rgba(0,0,0,.02);border:1px solid rgba(0,0,0,.1);padding:12px;border-radius:6px;text-align:center}@media(prefers-color-scheme: dark){#checking_container{background-color:rgba(255,255,255,.02);border-color:rgba(255,255,255,.1)}}#checking_spinner{vertical-align:middle;margin-right:12px}#checking_text{line-height:1.5em;font-weight:bold}#checking_subtext{font-size:smaller;color:rgba(0,0,0,.8)}@media(prefers-color-scheme: dark){#checking_subtext{color:rgba(255,255,255,.8)}}#purchase_confirmed{display:none}#purchase_unknown{display:none}#purchase_delayed{margin-top:30px;display:none}#signin_instructions{display:none;margin-top:30px}.welcome_content{width:100%;margin-left:auto;margin-right:auto;max-width:500px;box-sizing:border-box}.push{clear:both;overflow:hidden;height:0px}table.generic .bullet-column{width:0px;white-space:nowrap}@media(min-width: 635px){table.generic .bullet-column{width:100px}}table.generic .storefront-quantity-column{min-width:125px;max-width:150px}td.bullet-column{color:green}#cart_back_paragraph{float:left}.price_column{width:115px;text-align:right}.quantity_column{width:75px;text-align:center}#email_section{border-width:1px;border-style:solid;clear:both;padding:20px;margin:20px auto;border-radius:6px;max-width:400px}#checkout_methods_cell{display:flex;flex-wrap:wrap;margin-top:15px;margin-bottom:5px;justify-content:center}#checkout_methods_cell img{margin:5px;float:left;display:block;height:22px}#checkout_methods_cell.usd .usd{display:block}#checkout_methods_cell.usd .eur{display:none}#checkout_methods_cell.usd .gbp{display:none}#checkout_methods_cell.eur .usd{display:none}#checkout_methods_cell.eur .eur{display:block}#checkout_methods_cell.eur .gbp{display:none}#checkout_methods_cell.gbp .usd{display:none}#checkout_methods_cell.gbp .eur{display:none}#checkout_methods_cell.gbp .gbp{display:block}#checkout_button_cell{text-align:center}#checkout_currency_cell{text-align:center;font-size:small}#checkout_currency_cell a{display:inline-block;padding:2px 6px;border-radius:4px;text-decoration:none;margin-left:.2em;margin-right:.2em;color:#084fd1}#checkout_currency_cell a.chosen{background-color:#084fd1;color:#fff}.omni-hero{margin-bottom:1.5rem}.omni-hero img{width:auto;height:auto;max-width:100%;max-height:140px;margin-left:auto;margin-right:auto;display:block}#panel-games{margin-top:2rem}.omni-feature-table{margin-top:1rem}@media(prefers-color-scheme: dark){#checkout_currency_cell a{color:#3486fe}#checkout_currency_cell a.chosen{background-color:#3486fe}} diff --git a/Website/www/assets/css/theme-beacon-dark.css b/Website/www/assets/css/theme-beacon-dark.css index f1a84eba8..44bffee78 100644 --- a/Website/www/assets/css/theme-beacon-dark.css +++ b/Website/www/assets/css/theme-beacon-dark.css @@ -1 +1 @@ -/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}button,html [type=button],[type=reset],[type=submit]{-webkit-appearance:button}button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}template{display:none}[hidden]{display:none}@font-face{font-family:"Source Code Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcecodepro/4c416e66-c769-51a5-a337-ad832ad2e74a.woff) format("woff");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:"Source Code Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcecodepro/0842acdd-1516-53e8-a07d-ded3b64aba16.woff) format("woff");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:"Source Code Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcecodepro/7c7d8d1f-3387-5d0f-9110-4b3a57111e3a.woff) format("woff");unicode-range:U+1F00-1FFF}@font-face{font-family:"Source Code Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcecodepro/f13cc6a0-67e3-5260-b676-7f411573d880.woff) format("woff");unicode-range:U+0370-03FF}@font-face{font-family:"Source Code Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcecodepro/8a252637-447c-5992-bbab-0f6d8488e53e.woff) format("woff");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:"Source Code Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcecodepro/6e6237d0-47c2-532a-8c66-b8a1f411afbb.woff) format("woff");unicode-range:U+0100-02AF,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Source Code Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcecodepro/fb7509bc-2466-5fd4-a06a-4e9769262bed.woff) format("woff");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:"Source Sans Pro";font-style:italic;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/cd80694f-b33d-508d-907e-7544db677936.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:"Source Sans Pro";font-style:italic;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/78f748da-0e7b-5feb-aeae-a6b3b7a72fbb.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:"Source Sans Pro";font-style:italic;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/0f3938f3-e96b-588f-af3a-ba8b244f5c62.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:"Source Sans Pro";font-style:italic;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/b95003c5-6216-5e4c-a68b-40a22643520d.woff2) format("woff2");unicode-range:U+0370-03FF}@font-face{font-family:"Source Sans Pro";font-style:italic;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/83549da6-17a0-5f7e-afb7-bfd2913cebc8.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:"Source Sans Pro";font-style:italic;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/bae9ad58-d401-5d8d-8885-e653187e8945.woff2) format("woff2");unicode-range:U+0100-02AF,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Source Sans Pro";font-style:italic;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/56c68f48-8586-5f86-a831-5341d8f8893e.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:300;font-display:swap;src:url(/assets/fonts/sourcesanspro/049831a8-098c-5e17-b9b7-3d4d147220b8.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:300;font-display:swap;src:url(/assets/fonts/sourcesanspro/099262aa-4d57-59f2-95f5-89baa13aeb31.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:300;font-display:swap;src:url(/assets/fonts/sourcesanspro/b4c545b6-4511-5a9f-8754-22b7acf2d1cf.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:300;font-display:swap;src:url(/assets/fonts/sourcesanspro/90df22a0-015c-52a7-9cbf-ddd09ade4fbd.woff2) format("woff2");unicode-range:U+0370-03FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:300;font-display:swap;src:url(/assets/fonts/sourcesanspro/2134ded7-88ee-5959-bb8d-8be588893e60.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:300;font-display:swap;src:url(/assets/fonts/sourcesanspro/ba0a93ec-d1e4-5657-808d-e3c06cac07d3.woff2) format("woff2");unicode-range:U+0100-02AF,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:300;font-display:swap;src:url(/assets/fonts/sourcesanspro/611734bb-b7fa-5cd8-be7e-0185b98d7968.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/03db3c69-1325-596b-a3e5-1ab821511b04.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/36c309c3-ea5a-583d-808b-4001bca13238.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/b6fd8ba7-a279-5d7d-9bcb-f85971b2980f.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/7c2623bd-f50f-5fc6-b037-d2cf4948a4a4.woff2) format("woff2");unicode-range:U+0370-03FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/a8b3a121-1cae-5965-8e16-0aa443bda51d.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/16115b0a-c537-59b3-b8af-70ec055da4b2.woff2) format("woff2");unicode-range:U+0100-02AF,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/9a0511ad-4231-545f-8ef4-f35d35fbb60c.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/sourcesanspro/bdde82bb-f944-551f-836a-4c05e0f1573f.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/sourcesanspro/d26025f7-d2e5-54d4-aa4d-2e25b1c8c4cf.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/sourcesanspro/15f136cb-e12c-5694-887e-1dc9ed9259a4.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/sourcesanspro/87121b63-28d6-5847-8a68-1218b8ba3738.woff2) format("woff2");unicode-range:U+0370-03FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/sourcesanspro/ba62d068-2dab-5fb6-9e0e-b2ce6830c777.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/sourcesanspro/a1fa33d8-a2d4-5fdb-988a-69860d54fcef.woff2) format("woff2");unicode-range:U+0100-02AF,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/sourcesanspro/fdc908e0-7ba0-5e1b-be7c-7b60913a7e89.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sourcesanspro/6921b0ca-4eb6-5888-896b-f5e043beb61e.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sourcesanspro/9b3d0111-ea94-5ed8-85ee-c4f8c0838f54.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sourcesanspro/6bcf4d01-711e-5b49-a4ae-4b72c5a950c6.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sourcesanspro/1eafcd61-b347-5326-aa01-28314ba55db8.woff2) format("woff2");unicode-range:U+0370-03FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sourcesanspro/6c0dcb70-dff7-591a-a43d-3d94def35579.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sourcesanspro/eb85d155-2a94-58f6-b344-3c0a4747ee4d.woff2) format("woff2");unicode-range:U+0100-02AF,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sourcesanspro/b58ffa91-9940-5088-8866-d6f8bce9c45d.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}.w-0{width:0%}.w-10{width:10%}.w-20{width:20%}.w-25{width:25%}.w-30{width:30%}.w-40{width:40%}.w-50{width:50%}.w-60{width:60%}.w-70{width:70%}.w-75{width:75%}.w-80{width:80%}.w-90{width:90%}.w-100{width:100%}.m-0{margin:0px !important}.m-1{margin:.25rem !important}.m-2{margin:.5rem !important}.m-3{margin:1rem !important}.m-4{margin:1.5rem !important}.m-5{margin:3rem !important}.my-0{margin-top:0px !important;margin-bottom:0px !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-5{margin-top:3rem !important;margin-bottom:3rem !important}.mx-0{margin-left:0px !important;margin-right:0px !important}.mx-1{margin-left:.25rem !important;margin-right:.25rem !important}.mx-2{margin-left:.5rem !important;margin-right:.5rem !important}.mx-3{margin-left:1rem !important;margin-right:1rem !important}.mx-4{margin-left:1.5rem !important;margin-right:1.5rem !important}.mx-5{margin-left:3rem !important;margin-right:3rem !important}.mt-0{margin-top:0px !important}.mt-1{margin-top:.25rem !important}.mt-2{margin-top:.5rem !important}.mt-3{margin-top:1rem !important}.mt-4{margin-top:1.5rem !important}.mt-5{margin-top:3rem !important}.ml-0{margin-left:0px !important}.ml-1{margin-left:.25rem !important}.ml-2{margin-left:.5rem !important}.ml-3{margin-left:1rem !important}.ml-4{margin-left:1.5rem !important}.ml-5{margin-left:3rem !important}.mr-0{margin-right:0px !important}.mr-1{margin-right:.25rem !important}.mr-2{margin-right:.5rem !important}.mr-3{margin-right:1rem !important}.mr-4{margin-right:1.5rem !important}.mr-5{margin-right:3rem !important}.mb-0{margin-bottom:0px !important}.mb-1{margin-bottom:.25rem !important}.mb-2{margin-bottom:.5rem !important}.mb-3{margin-bottom:1rem !important}.mb-4{margin-bottom:1.5rem !important}.mb-5{margin-bottom:3rem !important}.p-0{padding:0px !important}.p-1{padding:.25rem !important}.p-2{padding:.5rem !important}.p-3{padding:1rem !important}.p-4{padding:1.5rem !important}.p-5{padding:3rem !important}.py-0{padding-top:0px !important;padding-bottom:0px !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-5{padding-top:3rem !important;padding-bottom:3rem !important}.px-0{padding-left:0px !important;padding-right:0px !important}.px-1{padding-left:.25rem !important;padding-right:.25rem !important}.px-2{padding-left:.5rem !important;padding-right:.5rem !important}.px-3{padding-left:1rem !important;padding-right:1rem !important}.px-4{padding-left:1.5rem !important;padding-right:1.5rem !important}.px-5{padding-left:3rem !important;padding-right:3rem !important}.pt-0{padding-top:0px !important}.pt-1{padding-top:.25rem !important}.pt-2{padding-top:.5rem !important}.pt-3{padding-top:1rem !important}.pt-4{padding-top:1.5rem !important}.pt-5{padding-top:3rem !important}.pl-0{padding-left:0px !important}.pl-1{padding-left:.25rem !important}.pl-2{padding-left:.5rem !important}.pl-3{padding-left:1rem !important}.pl-4{padding-left:1.5rem !important}.pl-5{padding-left:3rem !important}.pr-0{padding-right:0px !important}.pr-1{padding-right:.25rem !important}.pr-2{padding-right:.5rem !important}.pr-3{padding-right:1rem !important}.pr-4{padding-right:1.5rem !important}.pr-5{padding-right:3rem !important}.pb-0{padding-bottom:0px !important}.pb-1{padding-bottom:.25rem !important}.pb-2{padding-bottom:.5rem !important}.pb-3{padding-bottom:1rem !important}.pb-4{padding-bottom:1.5rem !important}.pb-5{padding-bottom:3rem !important}html{font-family:"Source Sans Pro",sans-serif;font-weight:400;font-style:normal;font-size:14px;color:#fff;background-color:#262626;font-kerning:normal}*:first-child{margin-top:0px}*:last-child{margin-bottom:0px}b,strong,.bold{font-weight:700}i,em,.italic{font-style:italic}.uppercase{text-transform:uppercase}.lowercase{text-transform:lowercase}p,h1,h2,h3{margin-bottom:15px;margin-top:15px}code,.code{font-family:"Source Code Pro",monospace;font-weight:400;font-style:normal;border-width:1px;border-style:solid;border-radius:.4rem;padding:.2rem .5rem;font-size:.8rem;line-height:2rem;background-color:rgba(255,255,255,.05);border-color:rgba(255,255,255,.1)}pre{border-width:1px;border-style:solid;padding:.6rem;border-radius:.4rem;overflow:auto;margin:10px;display:block;font-size:.8rem;background-color:rgba(255,255,255,.05);border-color:rgba(255,255,255,.1)}pre code,pre .code{background:none;padding:0px;border-radius:0px;border:none}a{word-break:break-word;color:#599ae2}a.username-suggestion{font-style:italic}.source-code-font{font-family:"Source Code Pro",monospace;font-weight:400;font-style:normal}.break-code{word-break:break-word}.break-code code{display:revert;word-break:break-all;border:none;padding:0px;background:none}.platform_tag,.tag{border-radius:.25em;line-height:1;font-weight:700;display:inline-block;padding:.35em .65em;font-size:.75em}.platform_tag+.platform_tag,.platform_tag+.tag,.tag+.platform_tag,.tag+.tag{margin-left:6px}.platform_tag.left-space,.tag.left-space{margin-left:1em}.platform_tag.right-space,.tag.right-space{margin-right:1em}.platform_tag.xbox,.tag.xbox{background-color:#16a916;color:#262626}.platform_tag.playstation,.tag.playstation{background-color:#458bff;color:#262626}.platform_tag.pc,.tag.pc{background-color:#da8b10;color:#333}.platform_tag.nintendo,.tag.nintendo{background-color:#ff4d5b;color:#262626}.platform_tag.red,.tag.red{background-color:#e77681;color:#262626}.platform_tag.grey,.tag.grey{background-color:#868e96;color:#262626}.platform_tag.blue,.tag.blue{background-color:#408cfd;color:#262626}.platform_tag.green,.tag.green{background-color:#21b26f;color:#333}.platform_tag.yellow,.tag.yellow{background-color:#ffc107;color:#212529}.platform_tag.cyan,.tag.cyan{background-color:#0dcaf0;color:#212529}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.text-red{color:#ff534a}.text-green{color:#28cd41}.text-blue{color:#3395ff}.text-purple{color:#c37de6}.text-yellow{color:#fc0}.larger{font-size:larger}.smaller{font-size:smaller}.mini{font-size:small}.redacted{text-decoration:line-through}.nowrap{white-space:nowrap}.push{clear:both;height:0px;max-height:0px;overflow:hidden}.pagebody{min-width:320px;width:auto;max-width:1200px;margin-left:auto;margin-right:auto;padding-left:max(20px,env(safe-area-inset-left, 20px));padding-right:max(20px,env(safe-area-inset-right, 20px));box-sizing:border-box}.reduced-width{max-width:800px;margin-left:auto;margin-right:auto}.indent{margin-left:40px}.hidden{display:none !important}.invisible{opacity:0}div.small_section{max-width:400px;margin-left:auto;margin-right:auto}div.medium_section{width:100%;max-width:600px;margin-left:auto;margin-right:auto}.option_group{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-ms-flex-wrap:wrap;flex-wrap:wrap}.button-group{display:inline-flex;align-items:center;flex-wrap:wrap;gap:1rem}.button-group *{margin:0px}.double-group{display:flex;align-items:center;flex-wrap:wrap;gap:1rem}.double-group div{margin:0px}.double-group div:nth-child(1){flex:1 1 auto;text-align:left}.double-group div:nth-child(2){flex:1 1 auto;text-align:right}.tripe-group{display:flex;align-items:center;flex-wrap:wrap;gap:1rem}.tripe-group div{margin:0px}.tripe-group div:nth-child(1){flex:0 1 auto;text-align:left}.tripe-group div:nth-child(2){flex:1 1 auto;text-align:center}.tripe-group div:nth-child(3){flex:0 1 auto;text-align:right}h1{font-size:16.8px}h2{font-size:15.4px}h3{font-size:14px}h1,h2,h3{font-weight:600}h1 .subtitle,h2 .subtitle,h3 .subtitle{font-weight:300;font-size:.8rem;line-height:.8rem}.user-suffix{font-weight:300;color:#939393}.text-lighter{color:#939393}span.object_type{font-weight:300;font-size:smaller;float:right;color:rgba(255,255,255,.35)}.inset-note{border-width:1px;border-style:solid;margin-left:30px;margin-right:30px;padding:15px;border-color:#474747}blockquote{border-left-width:3px;border-left-style:solid;margin-left:0px;margin-right:0px;padding-left:30px;padding-top:10px;padding-bottom:10px;padding-right:10px;border-left-color:#6c6c6c;color:#d5d5d5;background-color:#2d2d2d}blockquote p:first-child{margin-top:0px !important}blockquote p:last-child{margin-bottom:0px !important}div.visual-group{padding:20px;border-radius:10px;box-shadow:0px 2px 4px rgba(0,0,0,.15);background-color:#313131}div.visual-group h1,div.visual-group h2,div.visual-group h3{margin-bottom:20px}div.visual-group+div.visual-group{margin-top:20px}table.generic{border-collapse:collapse;width:100%;border-width:1px;border-style:solid}table.generic tbody>tr:nth-child(even){background-color:#2b252c}table.generic tbody>tr:nth-child(odd){background-color:#262626}table.generic.no-row-colors tbody tr{background-color:rgba(0,0,0,0)}table.generic td,table.generic th{border-width:1px;border-style:solid;padding:6pt;border-color:#474747}table.generic thead,table.generic tr.header{background-color:#9c0fb0;color:#d9d9d9}table.generic thead td,table.generic thead th,table.generic tr.header td,table.generic tr.header th{text-align:left;font-weight:400;border-color:#9c0fb0;color:inherit}table.generic ul{list-style:none;padding-left:0px}table.generic ul span.crafting_quantity{display:inline-block;min-width:40px;text-align:right}table.generic ul ul{padding-left:40px}table.generic div.row-details{display:flex;flex-wrap:wrap;flex-direction:row;justify-content:start;font-size:10pt;border-top-style:solid;border-top-width:1px;margin-top:8pt;margin-bottom:-2pt;padding-top:2pt;word-break:break-word;border-color:#474747;color:#939393}table.generic div.row-details span.detail{margin:4pt}table.generic div.row-details a{word-break:normal}table.generic .low-priority{display:none}table.generic .min-width{width:1px;white-space:nowrap}table.generic.auto-width{width:auto}#header_wrapper{border-bottom-width:1px;border-bottom-style:solid;position:-webkit-sticky;position:-moz-sticky;position:-ms-sticky;position:-o-sticky;position:sticky;top:0px;left:0px;right:0px;z-index:99;padding-top:max(10px,env(safe-area-inset-top, 10px));padding-bottom:10px;background-color:#262626;border-color:rgba(255,255,255,.1)}#header{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start;-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch}#header_logo_cell{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1;-ms-flex-preferred-size:100%;flex-basis:100%;text-align:center}#header_logo{height:60px;vertical-align:top}#header_links_cell{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2;-ms-flex-preferred-size:100%;flex-basis:100%;overflow:hidden}#header_links_cell ul{list-style-type:none;margin:10px 0px 0px 0px;padding:0;overflow:hidden;display:-webkit-box;display:-ms-flexbox;display:flex;height:100%;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}#header_links_cell ul li{-webkit-box-flex:0;-ms-flex:0 0 0px;flex:0 0 0;text-align:center;white-space:nowrap;vertical-align:center}#header_links_cell ul li+li{margin-left:2px}#header_links_cell ul li a{display:block;text-align:center;text-decoration:none;padding:.2em .5em;border-radius:1em;border-width:1px;border-style:solid;border-color:rgba(0,0,0,0);-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s;-webkit-transition-timing-function:ease-out;-o-transition-timing-function:ease-out;transition-timing-function:ease-out;-webkit-transition-property:border-color,color,background-color,border-radius;-o-transition-property:border-color,color,background-color,border-radius;transition-property:border-color,color,background-color,border-radius;color:rgba(255,255,255,.6)}#header_links_cell ul li a:hover{border-color:rgba(255,255,255,.4)}#header_links_cell ul li a:active{background-color:rgba(255,255,255,.2)}@media(min-width: 340px){#header_links_cell ul li a{padding-left:.75em;padding-right:.75em}}.accent-color{color:#9c0fb0;fill:#9c0fb0}.separator-color{border-color:#474747}:target:before{content:"";display:block;height:150px;margin:-150px 0 0}#content_wrapper{margin-top:20px;line-height:1.7rem}#footer{font-size:.8em;text-align:center;margin-top:40px;margin-bottom:max(20px,env(safe-area-inset-bottom, 20px))}#footer a.external_logo{margin-left:6px;margin-right:6px}#footer a.external_logo img{border:none}button,input[type=button],input[type=submit],input[type=reset],a.button{-webkit-appearance:none;appearance:none;border-radius:.25rem;border:1px solid rgba(0,0,0,0);box-sizing:border-box;cursor:pointer;display:inline-block;font-family:inherit;font-size:1rem;padding:.375rem .75rem;text-align:center;text-decoration:none;-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s;-webkit-transition-property:background-color,color,opacity;-o-transition-property:background-color,color,opacity;transition-property:background-color,color,opacity;-webkit-transition-timing-function:ease-out;-o-transition-timing-function:ease-out;transition-timing-function:ease-out;vertical-align:middle;white-space:nowrap;line-height:1.5em;font-weight:400;background-color:#5c636a;color:#fff}button.small,input[type=button].small,input[type=submit].small,input[type=reset].small,a.button.small{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}button.large,input[type=button].large,input[type=submit].large,input[type=reset].large,a.button.large{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}button+button,button+input[type=button],button+input[type=submit],button+input[type=reset],button+a.button,input[type=button]+button,input[type=button]+input[type=button],input[type=button]+input[type=submit],input[type=button]+input[type=reset],input[type=button]+a.button,input[type=submit]+button,input[type=submit]+input[type=button],input[type=submit]+input[type=submit],input[type=submit]+input[type=reset],input[type=submit]+a.button,input[type=reset]+button,input[type=reset]+input[type=button],input[type=reset]+input[type=submit],input[type=reset]+input[type=reset],input[type=reset]+a.button,a.button+button,a.button+input[type=button],a.button+input[type=submit],a.button+input[type=reset],a.button+a.button{margin-left:12px}button:disabled,input[type=button]:disabled,input[type=submit]:disabled,input[type=reset]:disabled,a.button:disabled{cursor:default;opacity:.4}button .spinner:after,input[type=button] .spinner:after,input[type=submit] .spinner:after,input[type=reset] .spinner:after,a.button .spinner:after{border-color:#fff rgba(0,0,0,0) #fff rgba(0,0,0,0)}button:not(:disabled):active,input[type=button]:not(:disabled):active,input[type=submit]:not(:disabled):active,input[type=reset]:not(:disabled):active,a.button:not(:disabled):active{background-color:#2e3235;color:gray}button:not(:disabled):active .spinner:after,input[type=button]:not(:disabled):active .spinner:after,input[type=submit]:not(:disabled):active .spinner:after,input[type=reset]:not(:disabled):active .spinner:after,a.button:not(:disabled):active .spinner:after{border-color:gray rgba(0,0,0,0) gray rgba(0,0,0,0)}button.blue,input[type=button].blue,input[type=submit].blue,input[type=reset].blue,a.button.blue{background-color:#0d6efd;color:#fff}button.blue .spinner:after,input[type=button].blue .spinner:after,input[type=submit].blue .spinner:after,input[type=reset].blue .spinner:after,a.button.blue .spinner:after{border-color:#fff rgba(0,0,0,0) #fff rgba(0,0,0,0)}button.blue:not(:disabled):active,input[type=button].blue:not(:disabled):active,input[type=submit].blue:not(:disabled):active,input[type=reset].blue:not(:disabled):active,a.button.blue:not(:disabled):active{background-color:#07377f;color:gray}button.blue:not(:disabled):active .spinner:after,input[type=button].blue:not(:disabled):active .spinner:after,input[type=submit].blue:not(:disabled):active .spinner:after,input[type=reset].blue:not(:disabled):active .spinner:after,a.button.blue:not(:disabled):active .spinner:after{border-color:gray rgba(0,0,0,0) gray rgba(0,0,0,0)}button.green,input[type=button].green,input[type=submit].green,input[type=reset].green,a.button.green{background-color:#157347;color:#fff}button.green .spinner:after,input[type=button].green .spinner:after,input[type=submit].green .spinner:after,input[type=reset].green .spinner:after,a.button.green .spinner:after{border-color:#fff rgba(0,0,0,0) #fff rgba(0,0,0,0)}button.green:not(:disabled):active,input[type=button].green:not(:disabled):active,input[type=submit].green:not(:disabled):active,input[type=reset].green:not(:disabled):active,a.button.green:not(:disabled):active{background-color:#0b3a24;color:gray}button.green:not(:disabled):active .spinner:after,input[type=button].green:not(:disabled):active .spinner:after,input[type=submit].green:not(:disabled):active .spinner:after,input[type=reset].green:not(:disabled):active .spinner:after,a.button.green:not(:disabled):active .spinner:after{border-color:gray rgba(0,0,0,0) gray rgba(0,0,0,0)}button.red,input[type=button].red,input[type=submit].red,input[type=reset].red,a.button.red{background-color:#bb2d3b;color:#fff}button.red .spinner:after,input[type=button].red .spinner:after,input[type=submit].red .spinner:after,input[type=reset].red .spinner:after,a.button.red .spinner:after{border-color:#fff rgba(0,0,0,0) #fff rgba(0,0,0,0)}button.red:not(:disabled):active,input[type=button].red:not(:disabled):active,input[type=submit].red:not(:disabled):active,input[type=reset].red:not(:disabled):active,a.button.red:not(:disabled):active{background-color:#5e171e;color:gray}button.red:not(:disabled):active .spinner:after,input[type=button].red:not(:disabled):active .spinner:after,input[type=submit].red:not(:disabled):active .spinner:after,input[type=reset].red:not(:disabled):active .spinner:after,a.button.red:not(:disabled):active .spinner:after{border-color:gray rgba(0,0,0,0) gray rgba(0,0,0,0)}button.yellow,input[type=button].yellow,input[type=submit].yellow,input[type=reset].yellow,a.button.yellow{background-color:#ffc107;color:#000}button.yellow .spinner:after,input[type=button].yellow .spinner:after,input[type=submit].yellow .spinner:after,input[type=reset].yellow .spinner:after,a.button.yellow .spinner:after{border-color:#000 rgba(0,0,0,0) #000 rgba(0,0,0,0)}button.yellow:not(:disabled):active,input[type=button].yellow:not(:disabled):active,input[type=submit].yellow:not(:disabled):active,input[type=reset].yellow:not(:disabled):active,a.button.yellow:not(:disabled):active{background-color:#806104;color:#000}button.yellow:not(:disabled):active .spinner:after,input[type=button].yellow:not(:disabled):active .spinner:after,input[type=submit].yellow:not(:disabled):active .spinner:after,input[type=reset].yellow:not(:disabled):active .spinner:after,a.button.yellow:not(:disabled):active .spinner:after{border-color:#000 rgba(0,0,0,0) #000 rgba(0,0,0,0)}button .spinner,input[type=button] .spinner,input[type=submit] .spinner,input[type=reset] .spinner,a.button .spinner{display:none}button.working .caption,input[type=button].working .caption,input[type=submit].working .caption,input[type=reset].working .caption,a.button.working .caption{display:none}button.working .spinner,input[type=button].working .spinner,input[type=submit].working .spinner,input[type=reset].working .spinner,a.button.working .spinner{display:revert}input[type=submit],button.default,a.button.default{background-color:#9c0fb0;color:#d9d9d9}input[type=submit] .spinner:after,button.default .spinner:after,a.button.default .spinner:after{border-color:#d9d9d9 rgba(0,0,0,0) #d9d9d9 rgba(0,0,0,0)}input[type=submit]:not(:disabled):active,button.default:not(:disabled):active,a.button.default:not(:disabled):active{background-color:#4e0858;color:#6d6d6d}input[type=submit]:not(:disabled):active .spinner:after,button.default:not(:disabled):active .spinner:after,a.button.default:not(:disabled):active .spinner:after{border-color:#6d6d6d rgba(0,0,0,0) #6d6d6d rgba(0,0,0,0)}.text-field,input[type=text],input[type=password],input[type=email],input[type=url],input[type=tel],input[type=search],input[type=number],textarea{-webkit-appearance:none;appearance:none;border-radius:.25rem;border:1px solid #7d7d7d;box-sizing:border-box;font-family:inherit;font-size:1rem;margin:0px;padding:.375rem .75rem;-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s;-webkit-transition-property:background-color,color,border-color,box-shadow;-o-transition-property:background-color,color,border-color,box-shadow;transition-property:background-color,color,border-color,box-shadow;-webkit-transition-timing-function:ease-in-out;-o-transition-timing-function:ease-in-out;transition-timing-function:ease-in-out;width:100%;line-height:1rem;background-clip:padding-box;background-color:#262626;color:#fff}.text-field::placeholder,input[type=text]::placeholder,input[type=password]::placeholder,input[type=email]::placeholder,input[type=url]::placeholder,input[type=tel]::placeholder,input[type=search]::placeholder,input[type=number]::placeholder,textarea::placeholder{line-height:1rem;vertical-align:bottom;padding-top:.3rem;color:#fff;opacity:.6}.text-field:focus,input[type=text]:focus,input[type=password]:focus,input[type=email]:focus,input[type=url]:focus,input[type=tel]:focus,input[type=search]:focus,input[type=number]:focus,textarea:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.text-field.invalid,input[type=text].invalid,input[type=password].invalid,input[type=email].invalid,input[type=url].invalid,input[type=tel].invalid,input[type=search].invalid,input[type=number].invalid,textarea.invalid{border-color:#ff534a}textarea{line-height:1.5}textarea::placeholder{padding-top:0px;line-height:1.5}.floating-label{position:relative;margin-top:16px;margin-bottom:16px}.floating-label>.text-field{padding:1rem .75rem;height:calc(3.5rem + 2px);line-height:1.25rem;box-sizing:border-box}.floating-label>.text-field::placeholder{color:rgba(0,0,0,0) !important}.floating-label>.text-field:focus,.floating-label>.text-field:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.floating-label>.text-field:focus~label,.floating-label>.text-field:not(:placeholder-shown)~label{transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.floating-label>.text-field:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.floating-label>.text-field:-webkit-autofill~label{transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.floating-label>div.select>select{padding:1.625rem 2.5rem .625rem .75rem;height:calc(3.5rem + 2px);line-height:1.25rem;box-sizing:border-box}.floating-label>label{position:absolute;top:0;left:0;height:100%;padding:1rem .75rem;pointer-events:none;border:1px solid rgba(0,0,0,0);transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out;box-sizing:border-box;opacity:.6}@media(prefers-reduced-motion: reduce){.floating-label>label{transition:none}}.floating-label>div.select+label{transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem);color:#fff}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%;margin-top:16px;margin-bottom:16px}.input-group>.text-field{position:relative;flex:1 1 auto;width:1%;min-width:0}.input-group>.text-field:focus{z-index:3}.input-group button{border:1px solid #7d7d7d;background-color:#2d2d2d;color:#fff}.input-group button .spinner:after{border-color:#fff rgba(0,0,0,0) #fff rgba(0,0,0,0)}.input-group button:not(:disabled):active{background-color:#171717;color:gray}.input-group button:not(:disabled):active .spinner:after{border-color:gray rgba(0,0,0,0) gray rgba(0,0,0,0)}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5rem;text-align:center;white-space:nowrap;border:1px solid #7d7d7d;box-sizing:border-box;border-radius:.25rem;background-color:#2d2d2d}.input-group-sm{margin-top:12px;margin-bottom:12px}.input-group-sm>.text-field,.input-group-sm>.input-group-text{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.input-group-lg{margin-top:20px;margin-bottom:20px}.input-group-lg>.text-field,.input-group-lg>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.input-group>:not(:first-child){margin-left:-1px;border-top-left-radius:0;border-bottom-left-radius:0}.input-group>:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.field-pair label{padding-left:6px;line-height:1.6rem;color:#bebebe}label.invalid,span.invalid,p.invalid,div.invalid{color:#ff534a}label.checkbox input[type=checkbox]{display:none}label.checkbox span{display:inline-block;vertical-align:middle;margin:6px;position:relative;border-radius:1rem;-webkit-transition-property:background-color,border-color;-o-transition-property:background-color,border-color;transition-property:background-color,border-color;-webkit-transition-timing-function:ease-out;-o-transition-timing-function:ease-out;transition-timing-function:ease-out;-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s;border-width:2px;border-style:solid;background-color:rgba(156,15,176,0);border-color:#9c0fb0}label.checkbox span:after{background-color:#9c0fb0}label.checkbox :checked:active+span{background-color:#490752;border-color:#490752}label.checkbox :checked:active+span:after{background-color:#a6a6a6}label.checkbox :checked+span{background-color:#9c0fb0}label.checkbox :checked+span:after{background-color:#d9d9d9}label.checkbox :active+span{background-color:#000;border-color:#490752}label.checkbox :active:after{background-color:#490752}label.checkbox :disabled+span{opacity:.4}label.checkbox span{width:42px;height:20px}label.checkbox span:after{position:absolute;top:3px;left:3px;width:14px;height:14px;border-radius:14px;display:block;content:"";-webkit-transition-property:left,background-color;-o-transition-property:left,background-color;transition-property:left,background-color;-webkit-transition-timing-function:ease-out;-o-transition-timing-function:ease-out;transition-timing-function:ease-out;-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s}label.checkbox :checked+span:after{left:26px}.input-radio{display:block;min-height:1.5rem;padding-left:1.4em;box-sizing:border-box;margin:0px}.input-radio input{-webkit-appearance:none;appearance:none;border:1px solid #939393;background-color:#262626;width:1em;height:1em;border-radius:50%;vertical-align:top;transition-duration:.15s;transition-property:border-color,border-width;transition-timing-function:ease-in-out;margin-top:.25em;margin-left:-1.4em;float:left;box-sizing:border-box;color-adjust:exact}.input-radio input:checked{border-width:.3em;border-color:#9c0fb0}.input-radio label{display:inline-block;box-sizing:border-box}.input-radio+.input-radio{margin-left:16px}div.select{position:relative;display:inline-block}div.select select{-webkit-appearance:none;-moz-appearance:none;appearance:none;min-height:22px;border:none;border-radius:.25rem;display:inline-block;font-family:inherit;font-size:1rem;text-decoration:none;-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s;-webkit-transition-property:background-color;-o-transition-property:background-color;transition-property:background-color;-webkit-transition-timing-function:ease-out;-o-transition-timing-function:ease-out;transition-timing-function:ease-out;vertical-align:middle;white-space:nowrap;padding:.375rem 2.5rem .375rem .75rem;line-height:1.5em}div.select select:focus{font-size:1rem}div.select span:after{position:absolute;top:calc(50% - .5em);right:.75rem;content:"▾";font-size:1rem;pointer-events:none;line-height:.9em}div.select select{background-color:#9c0fb0;color:#d9d9d9}div.select select:not(:disabled):active{background-color:#4e0858;color:#6d6d6d}div.select span:after{color:#d9d9d9}div.select.gray select{background-color:#5c636a;color:#fff}div.select.gray select:not(:disabled):active{background-color:#2e3235;color:gray}div.select.gray span:after{color:#fff}div.select.blue select{background-color:#0d6efd;color:#fff}div.select.blue select:not(:disabled):active{background-color:#07377f;color:gray}div.select.blue span:after{color:#fff}div.select.green select{background-color:#157347;color:#fff}div.select.green select:not(:disabled):active{background-color:#0b3a24;color:gray}div.select.green span:after{color:#fff}div.select.red select{background-color:#bb2d3b;color:#fff}div.select.red select:not(:disabled):active{background-color:#5e171e;color:gray}div.select.red span:after{color:#fff}div.select.yellow select{background-color:#ffc107;color:#000}div.select.yellow select:not(:disabled):active{background-color:#806104;color:#000}div.select.yellow span:after{color:#000}div.select.small select{border-radius:.2rem;font-size:.875rem;padding:.25rem 1.875rem .25rem .5rem}div.select.small span:after{top:.25rem;right:.5rem;font-size:.875rem}div.select.large select{border-radius:.3rem;font-size:1.25rem;padding:.5rem 3.25rem .5rem 1rem}div.select.large span:after{top:.25rem;right:1rem;font-size:1.25rem}#explore_container{display:none;z-index:1000;position:fixed;top:0px;bottom:0px;left:0px;right:0px;-webkit-tap-highlight-color:rgba(0,0,0,0)}#explore_popover{position:absolute;width:260px;top:20px;left:20px;box-shadow:0px 5px 5px rgba(0,0,0,.05);border-radius:4px;padding:9px;border:1px solid #8f8f8f;-webkit-tap-highlight-color:rgba(0,0,0,0);background-color:#262626;border-color:#474747}@media(min-width: 350px){#explore_popover{width:310px}}#explore_popover #explore_search_field{margin-bottom:9px}#explore_popover #explore_results{display:none}#explore_popover #explore_results ul{max-height:250px;overflow-y:auto}#explore_popover #explore_results_empty{display:none;text-align:center}#explore_popover #explore_results_buttons{display:-webkit-box;display:-ms-flexbox;display:flex;margin-top:9px;width:100%}#explore_popover #explore_results_buttons #explore_results_left_button{-webkit-box-flex:1;-ms-flex:1 0 0px;flex:1 0 0;text-align:left}#explore_popover #explore_results_buttons #explore_results_right_button{-webkit-box-flex:1;-ms-flex:1 0 0px;flex:1 0 0;text-align:right}#explore_popover ul{list-style-type:none;margin:0px;padding:0px;overflow:hidden}#explore_popover ul li a{text-decoration:none;border-radius:4px;padding:4px 6px;-webkit-transition-property:color,background-color;-o-transition-property:color,background-color;transition-property:color,background-color;-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s;-webkit-transition-timing-function:ease-out;-o-transition-timing-function:ease-out;transition-timing-function:ease-out;display:block;color:#fff}#explore_popover ul li a .result_preview{font-size:10pt;color:rgba(255,255,255,.5)}#explore_popover ul li a .result_type{font-size:9pt;margin-left:12px;float:right;border-width:1px;border-style:solid;padding:2px 4px;border-radius:4px;color:rgba(255,255,255,.4);border-color:rgba(255,255,255,.2)}#explore_popover ul li a:hover{color:#d9d9d9;background-color:#9c0fb0}#explore_popover ul li a:hover .result_preview{color:#d9d9d9}#explore_popover ul li a:hover .result_type{color:#d9d9d9;border-color:#d9d9d9}#explore_popover ul li+li{margin-top:4px}#menu_explore_link.expanded{border-radius:4px 4px 0px 0px;background-color:#474747;color:#fff}div.double_column,div.triple_column{display:table;width:100%;border-spacing:30px;border-collapse:separate}div.double_column div.column,div.triple_column div.column{display:table-row;width:100%;vertical-align:top}div.results span.result_type{font-weight:500;margin:.8em;color:rgba(255,255,255,.35)}div.results div.result{border:1px solid #e4e4e4;padding:.8em;border-radius:.5em;border-color:rgba(255,255,255,.15)}div.results div.result+div.result{margin-top:.8em}div.results div.result span.summary{font-size:.9em;color:rgba(255,255,255,.65)}#overlay{display:none;position:fixed;top:0px;bottom:0px;left:0px;right:0px;transition-duration:.25s;transition-timing-function:ease-out;transition-property:background-color,-webkit-backdrop-filter,backdrop-filter;z-index:2000;background-color:rgba(0,0,0,0);-webkit-tap-highlight-color:rgba(0,0,0,0);background-color:rgba(0,0,0,0)}@supports(-webkit-backdrop-filter: blur(0px)){#overlay{-webkit-backdrop-filter:blur(0px);background-color:rgba(0,0,0,0)}}@supports(backdrop-filter: blur(0px)){#overlay{backdrop-filter:blur(0px);background-color:rgba(0,0,0,0)}}#overlay.exist{display:block}#overlay.visible{background-color:rgba(0,0,0,.8)}@supports(-webkit-backdrop-filter: blur(15px)){#overlay.visible{-webkit-backdrop-filter:blur(15px);background-color:rgba(0,0,0,.3)}}@supports(backdrop-filter: blur(15px)){#overlay.visible{backdrop-filter:blur(15px);background-color:rgba(0,0,0,.3)}}#dialog{display:none;position:fixed;top:30%;left:0px;right:0px;z-index:2001;opacity:0;transition-duration:.25s;transition-timing-function:ease-out;transition-property:opacity,filter;filter:blur(30px);background-color:#262626}#dialog.exist{display:block}#dialog.visible{opacity:1;filter:none}#dialog p{margin:20px}#dialog_inner{width:300px;margin-left:auto;margin-right:auto}#dialog_message{font-weight:600;text-align:left}#dialog_explanation{text-align:left;font-size:smaller}#dialog_buttons{text-align:center}.modal{display:none;opacity:1;transition-duration:.25s;transition-timing-function:ease-out;transition-property:opacity,top;z-index:2001;position:fixed;top:100%;left:0px;width:100%;min-width:320px;height:100%;padding:0px;box-shadow:0px 4px 16px rgba(0,0,0,.45);box-sizing:border-box;line-height:1.5rem;background-color:#262626}.modal.exist{display:block}.modal.visible{top:0%}.modal .modal-content{display:flex;flex-direction:column;box-sizing:border-box;max-height:100vh}.modal .modal-content .title-bar{font-weight:600;flex:0 0 auto;box-sizing:border-box;margin-top:max(env(safe-area-inset-top, 20px),20px);margin-left:max(env(safe-area-inset-left, 20px),20px);margin-right:max(env(safe-area-inset-right, 20px),20px);margin-bottom:20px}.modal .modal-content .content{flex:1 1 auto;overflow:auto;box-sizing:border-box;padding-top:20px;padding-left:max(env(safe-area-inset-left, 20px),20px);padding-right:max(env(safe-area-inset-right, 20px),20px);padding-bottom:20px;border-top:1px solid #474747;border-bottom:1px solid #474747}.modal .modal-content .button-bar{display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:1rem;flex:0 0 auto;box-sizing:border-box;margin-top:20px;margin-left:max(env(safe-area-inset-left, 20px),20px);margin-right:max(env(safe-area-inset-right, 20px),20px);margin-bottom:max(env(safe-area-inset-bottom, 20px),20px)}.modal .modal-content .button-bar .left{text-align:left;flex:1 1 auto}.modal .modal-content .button-bar .middle{text-align:center;flex:1 1 auto}.modal .modal-content .button-bar .right{text-align:right;flex:1 1 auto}.spinner{display:inline-block;width:30px;height:30px}.spinner.large{width:65px;height:65px}.spinner.large:after{width:52px;height:52px;border-width:4.875px}.spinner:after{width:24px;height:24px;border-width:2.25px}.spinner.small{width:20px;height:20px}.spinner.small:after{width:16px;height:16px;border-width:1.5px}.spinner:after{content:" ";display:block;border-radius:50%;border-style:solid;animation:spinner-keyframes 1.2s linear infinite;border-color:#9c0fb0 rgba(0,0,0,0) #9c0fb0 rgba(0,0,0,0)}.spinner.hidden{display:none}@keyframes spinner-keyframes{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}#account_toolbar_menu div a{color:#9c0fb0;background-color:none}#account_toolbar_menu div.active{background-color:#9c0fb0}#account_toolbar_menu div.active a{color:#d9d9d9}#knowledge_wrapper{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:column-reverse;-ms-flex-direction:column-reverse;flex-direction:column-reverse;-ms-flex-wrap:nowrap;flex-wrap:nowrap}#knowledge_wrapper #knowledge_main>*{margin-top:.5rem;margin-bottom:.5rem}#knowledge_wrapper #knowledge_main>*:first-child{margin-top:0px}#knowledge_wrapper #knowledge_main>*:last-child{margin-bottom:0px}#knowledge_wrapper #knowledge_main h2,#knowledge_wrapper #knowledge_main h3{border-bottom-width:1px;border-bottom-style:solid;padding-bottom:.3rem;border-color:#373737}#knowledge_wrapper #knowledge_main code{padding:0px 5px;border-radius:4px;line-height:1.4rem}#knowledge_wrapper #knowledge_main pre code{padding:0px}#knowledge_wrapper #knowledge_main .affected_ini_keys{border-top-width:1px;border-top-style:solid;font-size:.8em;padding:15px;margin-top:20px;font-weight:300;background-color:#2a2a2a;text-shadow:0px 1px 0px #262626;border-top-color:#343434}#knowledge_wrapper #knowledge_contents{border-top-width:1px;border-top-style:solid;margin-top:30px;padding-top:30px;font-size:.9em;border-color:#474747}#knowledge_wrapper #knowledge_contents #knowledge_search_block{text-align:center;margin-bottom:30px}#knowledge_wrapper #knowledge_contents #knowledge_version{text-align:center;margin-bottom:20px;font-size:11pt;font-weight:600;color:#939393}#knowledge_wrapper #knowledge_contents p{margin-top:0px;margin-bottom:0px;padding-left:1em;text-indent:-1em;font-size:1.1em;font-weight:500}#knowledge_wrapper #knowledge_contents ul{margin-top:4px;list-style:none;padding-left:0px}#knowledge_wrapper #knowledge_contents ul+p{margin-top:12px}#knowledge_wrapper #knowledge_contents ul li{padding-left:10px;padding-top:4px;padding-bottom:4px;line-height:1.4rem}#knowledge_wrapper #knowledge_contents ul li.current{padding-left:7px;border-left-width:3px;border-left-style:solid;color:#9c0fb0;border-left-color:#9c0fb0}#knowledge_wrapper #knowledge_contents ul li.current::before{color:#676767}#knowledge_wrapper #knowledge_contents ul li+li{border-top-width:1px;border-top-style:solid;border-top-color:#303030}#knowledge_wrapper #knowledge_contents ul li:first-child{padding-top:0px}#knowledge_wrapper #knowledge_contents ul li:last-child{padding-bottom:0px}#knowledge_wrapper #knowledge_contents ul li a{text-decoration:none}#knowledge_wrapper #knowledge_contents ul li a:hover{text-decoration:underline}p.help-summary{border-bottom-width:1px;border-bottom-style:dotted;font-size:1.1em;padding:0px 0px 10px 0px;border-bottom-color:#474747}ul.object_list{list-style:none;padding-left:15px}ul.object_list li+li{margin-top:10px}ul.no-markings,ol.no-markings{list-style:none}.notice-block{border-width:1px;border-style:solid;text-align:center;padding:16px}.notice-block.notice-warning{border-color:#723633;color:#ff534a;background-color:#2d2727}.notice-block.notice-caution{border-color:#726019;color:#fc0;background-color:#2d2b25}.notice-block.notice-info{border-color:#2b4d72;color:#3395ff;background-color:#26292d}.subsection{border-width:1px;border-style:solid;padding:16px;border-color:rgba(255,255,255,.1)}.downloads-table .row{padding:10px;display:flex;align-items:center;border-width:1px;border-style:solid;border-top-width:0px;border-color:#474747}.downloads-table .row:first-child{border-top-left-radius:10px;border-top-right-radius:10px;border-top-width:1px;background-color:#9c0fb0;color:#d9d9d9;border-color:#9c0fb0}.downloads-table .row:last-child{border-bottom-left-radius:10px;border-bottom-right-radius:10px}.downloads-table .row .label{flex:1 1}.downloads-table .row .button{flex:0 0 80px;margin-left:10px}.downloads-table .row .header{line-height:1rem}.downloads-table .row .games{opacity:.7;font-size:small}.downloads-table+.downloads-table{margin-top:20px}.mini-only{display:revert}.mobile-only{display:none}.desktop-only{display:none}.small-only{display:revert}.not-mini{display:none}.large-only{display:none}.embedded_youtube_video{position:relative;padding-bottom:56.25%;padding-top:30px;height:0;overflow:hidden}.embedded_youtube_video iframe,.embedded_youtube_video object,.embedded_youtube_video embed{position:absolute;top:0;left:0;width:100%;height:100%}div.page-panel{display:flex;flex-direction:column;gap:20px}@supports not (selector(:first-child)){div.page-panel>*{margin:10px}}div.page-panel div.page-panel-nav ul{display:flex;flex-direction:row;margin:0px;padding:0px;gap:10px;justify-content:center;align-items:stretch;flex-wrap:wrap}@supports not (selector(:first-child)){div.page-panel div.page-panel-nav ul>*{margin:5px}}div.page-panel div.page-panel-nav ul li{list-style:none}div.page-panel div.page-panel-nav ul li a{text-decoration:none;display:inline-block;padding:.375rem .75rem;border-radius:.25rem;white-space:nowrap;background-color:none;color:#dc4ff0;line-height:1.5em;font-size:1rem}div.page-panel div.page-panel-nav ul li.page-panel-active a{background-color:#9c0fb0;color:#d9d9d9}div.page-panel div.page-panel-page{display:none}div.page-panel div.page-panel-visible{display:block}div.page-panel div.page-panel-footer{display:none}div.flex-grid{display:flex;flex-wrap:wrap;flex-direction:row;justify-content:flex-start;align-items:flex-start;align-content:flex-start}div.flex-grid div.flex-grid-item{flex:0 0 auto}img.avatar{border-radius:10px;box-shadow:0px 2px 4px 0px rgba(0,0,0,.25);display:block}.pagination-controls{display:flex;gap:10px;justify-content:center;margin-top:20px;flex-wrap:wrap}.pagination-controls .pagination-button{display:block;padding:3px 6px;border:1px solid #000;border-radius:6px;text-align:center;white-space:nowrap;border-color:#313131;background-color:#2a2a2a;text-decoration:none}.pagination-controls .pagination-current{background-color:#9c0fb0;color:#d9d9d9;border-color:#9c0fb0}.pagination-controls .pagination-placeholder{display:block}.pagination-controls .pagination-text,.pagination-controls .pagination-placeholder{min-width:50px}.pagination-controls a.pagination-button:hover{background-color:#599ae2;color:#d9d9d9;border-color:#599ae2}@media(min-width: 400px){#header_logo{height:80px}}@media(min-width: 635px){html{font-size:16px}h1{font-size:19.2px}h2{font-size:17.6px}h3{font-size:16px}#header_wrapper{padding-top:20px;padding-bottom:20px}#header{-ms-flex-wrap:nowrap;flex-wrap:nowrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center}#header_logo_cell{-ms-flex-preferred-size:80px;flex-basis:80px}#header_links_cell{height:auto}#header_links_cell ul{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;margin:0px}#header_links_cell li{-webkit-box-flex:0;-ms-flex:0 0 0px;flex:0 0 0}:target:before{height:140px;margin:-140px 0 0}.mini-only{display:none}.mobile-only{display:revert}.not-mini{display:revert}}div.comment-block{text-align:center;font-size:small;margin:20px auto;max-width:400px;border-width:1px;border-style:solid;border-radius:4pt;padding:10pt;display:flex;justify-content:center;align-items:center;background-color:rgba(255,255,255,.02);border-color:rgba(255,255,255,.1);color:rgba(255,255,255,.7)}div.comment-block div.icon{margin-right:10pt}div.comment-block div.icon img{margin:0px;display:block}#mode_tabs div.selected,#mode_view,#mode_customizations{background-color:#3c3c3c}#mode_tabs_new,#mode_tabs_paste,#mode_tabs_upload{background-color:#313131}#browse_results div.properties-text{color:rgba(255,255,255,.5);border-color:rgba(255,255,255,.1)}input.no-stepper::-webkit-outer-spin-button,input.no-stepper::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}input.no-stepper[type=number]{-moz-appearance:textfield}#checkout-wizard-list>div{display:flex;align-items:center}#checkout-wizard-list>div+div{margin-top:20px}#checkout-wizard-list>div .checkout-wizard-checkbox-cell{flex:0 1 auto}#checkout-wizard-list>div .checkout-wizard-description-cell{flex:1 1 auto;padding-left:10px;padding-right:10px}#checkout-wizard-list>div .checkout-wizard-description-cell>div>label{font-weight:bold}#checkout-wizard-list>div .checkout-wizard-description-cell .checkout-wizard-status{font-size:.8em;opacity:.8}#checkout-wizard-list>div .checkout-wizard-description-cell .checkout-wizard-promo{font-weight:bold;color:#28cd41;font-size:.9em}#checkout-wizard-list>div .checkout-wizard-description-cell div.field-with-label{display:inline-flex;align-items:center;gap:12px}#checkout-wizard-list>div .checkout-wizard-description-cell div.field-with-label>div:nth-child(1){flex:0 0 auto;text-align:right}#checkout-wizard-list>div .checkout-wizard-description-cell div.field-with-label>div:nth-child(2){flex:0 0 60px}#checkout-wizard-list>div .checkout-wizard-description-cell div.field-with-label>div:nth-child(3){flex:0 0 auto}#checkout-wizard-list>div .checkout-wizard-price-cell{flex:0 1 auto}#checkout-wizard-list>div .checkout-wizard-price-cell .checkout-wizard-discounted{text-decoration:line-through}#storefront .storefront-cart-section{box-sizing:border-box;max-width:800px;margin-left:auto;margin-right:auto}#storefront #storefront-cart-asanotice{margin-bottom:1rem}#storefront #storefront-cart-header{display:flex;align-items:center;border-bottom:1px solid #474747;padding-bottom:1rem;margin-bottom:1rem}#storefront #storefront-cart-header div:nth-child(1){text-align:left;flex:0 1 auto}#storefront #storefront-cart-header div:nth-child(2){text-align:right;margin-left:1rem;margin-right:1rem;flex:1 1 auto;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}#storefront #storefront-cart-header div:nth-child(3){text-align:right;flex:0 1 auto}#storefront>div{webkit-transition-property:opacity;-o-transition-property:opacity;transition-property:opacity;-webkit-transition-timing-function:linear;-o-transition-timing-function:linear;transition-timing-function:linear;-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s}#storefront #storefront-cart{margin-top:1rem;margin-bottom:1rem}#storefront #storefront-cart.empty{display:flex;flex-direction:column;min-height:300px}#storefront #storefront-cart.empty div:nth-child(1){flex:1 1 30%}#storefront #storefront-cart.empty div:nth-child(2){flex:0 0 auto;text-align:center}#storefront #storefront-cart.empty div:nth-child(3){flex:1 1 70%}#storefront #storefront-cart .bundle{border:1px solid #474747;border-radius:.25rem;margin:1rem;padding:1rem}#storefront #storefront-cart .bundle.gift{border-color:skyblue;background-color:#30373a;color:#c3e7f5}#storefront #storefront-cart .bundle .bundle-product{display:flex;align-items:center;gap:1rem}#storefront #storefront-cart .bundle .bundle-product div:nth-child(1){flex:0 0 5%;text-align:center}#storefront #storefront-cart .bundle .bundle-product div:nth-child(2){flex:1 1 auto}#storefront #storefront-cart .bundle .bundle-product div:nth-child(3){flex:0 0 20%;text-align:right}#storefront #storefront-cart .bundle>div:not(:last-child){margin-bottom:.25rem}#storefront #storefront-cart .bundle .gift{font-size:.75em;font-weight:600;color:skyblue}#storefront #storefront-cart .bundle .actions{margin-top:1rem}#storefront #storefront-cart-footer{padding-top:1rem;border-top:1px solid #474747;margin-top:1rem}#storefront #storefront-cart-footer .storefront-cart-totals{margin-bottom:1rem}#storefront #storefront-cart-footer .storefront-cart-totals .storefront-cart-total-row{display:flex;flex-wrap:wrap;justify-content:flex-end}#storefront #storefront-cart-footer .storefront-cart-totals .storefront-cart-total-row div:nth-child(1){text-align:right;flex:0 0 auto}#storefront #storefront-cart-footer .storefront-cart-totals .storefront-cart-total-row div:nth-child(2){text-align:right;flex:0 0 120px}#storefront #storefront-cart-footer .storefront-cart-totals .storefront-cart-total-row+.storefront-cart-total-row{margin-top:.5rem}#storefront #storefront-cart-footer .storefront-cart-paymethods{display:flex;gap:.4rem;align-items:center;justify-content:center;margin-top:2rem;flex-wrap:wrap}#storefront #storefront-cart-footer .storefront-cart-paymethods img{width:2.5rem;margin:0px;display:block}#storefront #storefront-cart-footer .storefront-cart-notice{text-align:center;margin-top:2rem;font-size:.8rem}#storefront #storefront-cart-footer .storefront-button-row{margin-top:2rem}#storefront #storefront-cart-footer .storefront-refund-notice{max-width:600px;font-size:.8rem;margin-top:2rem;margin-left:auto;margin-right:auto}.shake{animation:shake-keyframes .4s linear 1}@keyframes shake-keyframes{0%{transform:translate(10px)}20%{transform:translate(-10px)}40%{transform:translate(5px)}80%{transform:translate(-5px)}100%{transform:translate(0px)}}.header-with-subtitle{margin-bottom:1rem}.header-with-subtitle h1,.header-with-subtitle h2,.header-with-subtitle h3,.header-with-subtitle h4,.header-with-subtitle h5,.header-with-subtitle h6{margin-bottom:0px;margin-top:0px}.header-with-subtitle .subtitle{font-size:.8rem;line-height:1rem;display:block}.beacon-engram-mod-name{font-size:14px;color:rgba(255,255,255,.5)}body.no-navigation #header_wrapper,body.no-navigation #footer{display:none}body.no-navigation #content_wrapper{margin-top:max(20px,env(safe-area-inset-top, 20px));margin-bottom:max(20px,env(safe-area-inset-bottom, 20px))}.breadcrumbs{border-radius:.2rem;display:flex;gap:.5rem;background-color:#2a2a2a;font-size:14px;padding:.25rem .5rem;flex-wrap:wrap}.breadcrumbs .breadcrumb{display:inline-block;white-space:nowrap}.breadcrumbs .divider{display:inline-block;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%236a6a6a' class='bi bi-chevron-right' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3E%3C/svg%3E");background-position:center;background-repeat:no-repeat;background-size:16px 16px;width:8px}@media(min-width: 840px){div.double_column div.column{display:table-cell;width:50%}div.triple_column div.column{display:table-cell;width:33%}#knowledge_wrapper{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}#knowledge_wrapper #knowledge_main{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;padding-left:20px;width:980px}#knowledge_wrapper #knowledge_contents{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;width:220px;border-right-width:1px;border-right-style:solid;border-top:none;padding-right:20px;padding-top:0px;margin-top:0px}table.generic div.row-details{display:none}table.generic .low-priority{display:table-cell}.mini-only{display:none}.desktop-only{display:revert}.small-only{display:none}.large-only{display:revert}div.page-panel{flex-direction:row}div.page-panel div.page-panel-nav{flex:1 1 0}div.page-panel div.page-panel-nav ul{flex-direction:column}div.page-panel div.page-panel-nav ul li{text-align:right}div.page-panel div.page-panel-pages{flex:2 2 600px}div.page-panel div.page-panel-footer{flex:1 1 0;display:block}.modal{opacity:0;top:35%;left:50%;width:600px;margin-left:-300px;margin-right:auto;transform:translateY(-50%);border-radius:.25rem;margin-left:-300px;margin-right:auto;height:auto}.modal.visible{top:40%;opacity:1}.modal.centered{top:45%}.modal.centered.visible{top:50%}.modal .modal-content .title-bar{margin:0px;padding:22px 22px 0px 22px}.modal .modal-content .content{border:none;padding:22px}.modal .modal-content .button-bar{margin:0px;padding:0px 22px 22px 22px}.modal.scrolled{border-radius:0px}.modal.scrolled .modal-content .title-bar{padding:15px 22px 15px 22px;border-bottom:1px solid #474747}.modal.scrolled .modal-content .content{padding:14px 22px}.modal.scrolled .modal-content .button-bar{padding:15px 22px 15px 22px;border-top:1px solid #474747}}@media(min-width: 840px)and (max-width: 640px){.modal{left:20px;width:auto;right:20px;margin:0px;top:45%}.modal.visible{top:50%}}@media print{#header,#header_wrapper,#footer{display:none}#content_wrapper{margin-top:max(20px,env(safe-area-inset-top, 20px));margin-bottom:max(20px,env(safe-area-inset-bottom, 20px))}table.generic{border-color:#d9d9d9}table.generic td,table.generic th{border-color:inherit;background-color:#fff}table.generic thead,table.generic tr.header{background-color:#fff;color:#000}table.generic thead td,table.generic thead th,table.generic tr.header td,table.generic tr.header th{border-color:#000;color:inherit;font-weight:600}}#header_logo,.white-on-dark{filter:brightness(0) invert(1)}.accented-foreground{filter:invert(22%) sepia(68%) saturate(2401%) hue-rotate(271deg) brightness(88%) contrast(105%)} +/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}button,html [type=button],[type=reset],[type=submit]{-webkit-appearance:button}button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}template{display:none}[hidden]{display:none}@font-face{font-family:"Source Code Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcecodepro/4c416e66-c769-51a5-a337-ad832ad2e74a.woff) format("woff");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:"Source Code Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcecodepro/0842acdd-1516-53e8-a07d-ded3b64aba16.woff) format("woff");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:"Source Code Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcecodepro/7c7d8d1f-3387-5d0f-9110-4b3a57111e3a.woff) format("woff");unicode-range:U+1F00-1FFF}@font-face{font-family:"Source Code Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcecodepro/f13cc6a0-67e3-5260-b676-7f411573d880.woff) format("woff");unicode-range:U+0370-03FF}@font-face{font-family:"Source Code Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcecodepro/8a252637-447c-5992-bbab-0f6d8488e53e.woff) format("woff");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:"Source Code Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcecodepro/6e6237d0-47c2-532a-8c66-b8a1f411afbb.woff) format("woff");unicode-range:U+0100-02AF,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Source Code Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcecodepro/fb7509bc-2466-5fd4-a06a-4e9769262bed.woff) format("woff");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:"Source Sans Pro";font-style:italic;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/cd80694f-b33d-508d-907e-7544db677936.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:"Source Sans Pro";font-style:italic;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/78f748da-0e7b-5feb-aeae-a6b3b7a72fbb.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:"Source Sans Pro";font-style:italic;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/0f3938f3-e96b-588f-af3a-ba8b244f5c62.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:"Source Sans Pro";font-style:italic;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/b95003c5-6216-5e4c-a68b-40a22643520d.woff2) format("woff2");unicode-range:U+0370-03FF}@font-face{font-family:"Source Sans Pro";font-style:italic;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/83549da6-17a0-5f7e-afb7-bfd2913cebc8.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:"Source Sans Pro";font-style:italic;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/bae9ad58-d401-5d8d-8885-e653187e8945.woff2) format("woff2");unicode-range:U+0100-02AF,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Source Sans Pro";font-style:italic;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/56c68f48-8586-5f86-a831-5341d8f8893e.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:300;font-display:swap;src:url(/assets/fonts/sourcesanspro/049831a8-098c-5e17-b9b7-3d4d147220b8.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:300;font-display:swap;src:url(/assets/fonts/sourcesanspro/099262aa-4d57-59f2-95f5-89baa13aeb31.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:300;font-display:swap;src:url(/assets/fonts/sourcesanspro/b4c545b6-4511-5a9f-8754-22b7acf2d1cf.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:300;font-display:swap;src:url(/assets/fonts/sourcesanspro/90df22a0-015c-52a7-9cbf-ddd09ade4fbd.woff2) format("woff2");unicode-range:U+0370-03FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:300;font-display:swap;src:url(/assets/fonts/sourcesanspro/2134ded7-88ee-5959-bb8d-8be588893e60.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:300;font-display:swap;src:url(/assets/fonts/sourcesanspro/ba0a93ec-d1e4-5657-808d-e3c06cac07d3.woff2) format("woff2");unicode-range:U+0100-02AF,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:300;font-display:swap;src:url(/assets/fonts/sourcesanspro/611734bb-b7fa-5cd8-be7e-0185b98d7968.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/03db3c69-1325-596b-a3e5-1ab821511b04.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/36c309c3-ea5a-583d-808b-4001bca13238.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/b6fd8ba7-a279-5d7d-9bcb-f85971b2980f.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/7c2623bd-f50f-5fc6-b037-d2cf4948a4a4.woff2) format("woff2");unicode-range:U+0370-03FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/a8b3a121-1cae-5965-8e16-0aa443bda51d.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/16115b0a-c537-59b3-b8af-70ec055da4b2.woff2) format("woff2");unicode-range:U+0100-02AF,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/9a0511ad-4231-545f-8ef4-f35d35fbb60c.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/sourcesanspro/bdde82bb-f944-551f-836a-4c05e0f1573f.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/sourcesanspro/d26025f7-d2e5-54d4-aa4d-2e25b1c8c4cf.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/sourcesanspro/15f136cb-e12c-5694-887e-1dc9ed9259a4.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/sourcesanspro/87121b63-28d6-5847-8a68-1218b8ba3738.woff2) format("woff2");unicode-range:U+0370-03FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/sourcesanspro/ba62d068-2dab-5fb6-9e0e-b2ce6830c777.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/sourcesanspro/a1fa33d8-a2d4-5fdb-988a-69860d54fcef.woff2) format("woff2");unicode-range:U+0100-02AF,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/sourcesanspro/fdc908e0-7ba0-5e1b-be7c-7b60913a7e89.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sourcesanspro/6921b0ca-4eb6-5888-896b-f5e043beb61e.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sourcesanspro/9b3d0111-ea94-5ed8-85ee-c4f8c0838f54.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sourcesanspro/6bcf4d01-711e-5b49-a4ae-4b72c5a950c6.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sourcesanspro/1eafcd61-b347-5326-aa01-28314ba55db8.woff2) format("woff2");unicode-range:U+0370-03FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sourcesanspro/6c0dcb70-dff7-591a-a43d-3d94def35579.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sourcesanspro/eb85d155-2a94-58f6-b344-3c0a4747ee4d.woff2) format("woff2");unicode-range:U+0100-02AF,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sourcesanspro/b58ffa91-9940-5088-8866-d6f8bce9c45d.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}.w-0{width:0%}.w-10{width:10%}.w-20{width:20%}.w-25{width:25%}.w-30{width:30%}.w-40{width:40%}.w-50{width:50%}.w-60{width:60%}.w-70{width:70%}.w-75{width:75%}.w-80{width:80%}.w-90{width:90%}.w-100{width:100%}.m-0{margin:0px !important}.m-1{margin:.25rem !important}.m-2{margin:.5rem !important}.m-3{margin:1rem !important}.m-4{margin:1.5rem !important}.m-5{margin:3rem !important}.my-0{margin-top:0px !important;margin-bottom:0px !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-5{margin-top:3rem !important;margin-bottom:3rem !important}.mx-0{margin-left:0px !important;margin-right:0px !important}.mx-1{margin-left:.25rem !important;margin-right:.25rem !important}.mx-2{margin-left:.5rem !important;margin-right:.5rem !important}.mx-3{margin-left:1rem !important;margin-right:1rem !important}.mx-4{margin-left:1.5rem !important;margin-right:1.5rem !important}.mx-5{margin-left:3rem !important;margin-right:3rem !important}.mt-0{margin-top:0px !important}.mt-1{margin-top:.25rem !important}.mt-2{margin-top:.5rem !important}.mt-3{margin-top:1rem !important}.mt-4{margin-top:1.5rem !important}.mt-5{margin-top:3rem !important}.ml-0{margin-left:0px !important}.ml-1{margin-left:.25rem !important}.ml-2{margin-left:.5rem !important}.ml-3{margin-left:1rem !important}.ml-4{margin-left:1.5rem !important}.ml-5{margin-left:3rem !important}.mr-0{margin-right:0px !important}.mr-1{margin-right:.25rem !important}.mr-2{margin-right:.5rem !important}.mr-3{margin-right:1rem !important}.mr-4{margin-right:1.5rem !important}.mr-5{margin-right:3rem !important}.mb-0{margin-bottom:0px !important}.mb-1{margin-bottom:.25rem !important}.mb-2{margin-bottom:.5rem !important}.mb-3{margin-bottom:1rem !important}.mb-4{margin-bottom:1.5rem !important}.mb-5{margin-bottom:3rem !important}.p-0{padding:0px !important}.p-1{padding:.25rem !important}.p-2{padding:.5rem !important}.p-3{padding:1rem !important}.p-4{padding:1.5rem !important}.p-5{padding:3rem !important}.py-0{padding-top:0px !important;padding-bottom:0px !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-5{padding-top:3rem !important;padding-bottom:3rem !important}.px-0{padding-left:0px !important;padding-right:0px !important}.px-1{padding-left:.25rem !important;padding-right:.25rem !important}.px-2{padding-left:.5rem !important;padding-right:.5rem !important}.px-3{padding-left:1rem !important;padding-right:1rem !important}.px-4{padding-left:1.5rem !important;padding-right:1.5rem !important}.px-5{padding-left:3rem !important;padding-right:3rem !important}.pt-0{padding-top:0px !important}.pt-1{padding-top:.25rem !important}.pt-2{padding-top:.5rem !important}.pt-3{padding-top:1rem !important}.pt-4{padding-top:1.5rem !important}.pt-5{padding-top:3rem !important}.pl-0{padding-left:0px !important}.pl-1{padding-left:.25rem !important}.pl-2{padding-left:.5rem !important}.pl-3{padding-left:1rem !important}.pl-4{padding-left:1.5rem !important}.pl-5{padding-left:3rem !important}.pr-0{padding-right:0px !important}.pr-1{padding-right:.25rem !important}.pr-2{padding-right:.5rem !important}.pr-3{padding-right:1rem !important}.pr-4{padding-right:1.5rem !important}.pr-5{padding-right:3rem !important}.pb-0{padding-bottom:0px !important}.pb-1{padding-bottom:.25rem !important}.pb-2{padding-bottom:.5rem !important}.pb-3{padding-bottom:1rem !important}.pb-4{padding-bottom:1.5rem !important}.pb-5{padding-bottom:3rem !important}html{font-family:"Source Sans Pro",sans-serif;font-weight:400;font-style:normal;font-size:14px;color:#fff;background-color:#262626;font-kerning:normal}*:first-child{margin-top:0px}*:last-child{margin-bottom:0px}b,strong,.bold{font-weight:700}i,em,.italic{font-style:italic}.uppercase{text-transform:uppercase}.lowercase{text-transform:lowercase}p,h1,h2,h3{margin-bottom:15px;margin-top:15px}code,.code{font-family:"Source Code Pro",monospace;font-weight:400;font-style:normal;border-width:1px;border-style:solid;border-radius:.4rem;padding:.2rem .5rem;font-size:.8rem;line-height:2rem;background-color:rgba(255,255,255,.05);border-color:rgba(255,255,255,.1)}pre{border-width:1px;border-style:solid;padding:.6rem;border-radius:.4rem;overflow:auto;margin:10px;display:block;font-size:.8rem;background-color:rgba(255,255,255,.05);border-color:rgba(255,255,255,.1)}pre code,pre .code{background:none;padding:0px;border-radius:0px;border:none}a{word-break:break-word;color:#599ae2}a.username-suggestion{font-style:italic}.source-code-font{font-family:"Source Code Pro",monospace;font-weight:400;font-style:normal}.break-code{word-break:break-word}.break-code code{display:revert;word-break:break-all;border:none;padding:0px;background:none}.platform_tag,.tag{border-radius:.25em;line-height:1;font-weight:700;display:inline-block;padding:.35em .65em;font-size:.75em}.platform_tag+.platform_tag,.platform_tag+.tag,.tag+.platform_tag,.tag+.tag{margin-left:6px}.platform_tag.left-space,.tag.left-space{margin-left:1em}.platform_tag.right-space,.tag.right-space{margin-right:1em}.platform_tag.xbox,.tag.xbox{background-color:#16a916;color:#262626}.platform_tag.playstation,.tag.playstation{background-color:#458bff;color:#262626}.platform_tag.pc,.tag.pc{background-color:#da8b10;color:#333}.platform_tag.nintendo,.tag.nintendo{background-color:#ff4d5b;color:#262626}.platform_tag.red,.tag.red{background-color:#e77681;color:#262626}.platform_tag.grey,.tag.grey{background-color:#868e96;color:#262626}.platform_tag.blue,.tag.blue{background-color:#408cfd;color:#262626}.platform_tag.green,.tag.green{background-color:#21b26f;color:#333}.platform_tag.yellow,.tag.yellow{background-color:#ffc107;color:#212529}.platform_tag.cyan,.tag.cyan{background-color:#0dcaf0;color:#212529}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.text-red{color:#ff534a}.text-green{color:#28cd41}.text-blue{color:#3395ff}.text-purple{color:#c37de6}.text-yellow{color:#fc0}.larger{font-size:larger}.smaller{font-size:smaller}.mini{font-size:small}.redacted{text-decoration:line-through}.nowrap{white-space:nowrap}.push{clear:both;height:0px;max-height:0px;overflow:hidden}.pagebody{min-width:320px;width:auto;max-width:1200px;margin-left:auto;margin-right:auto;padding-left:max(20px,env(safe-area-inset-left, 20px));padding-right:max(20px,env(safe-area-inset-right, 20px));box-sizing:border-box}.reduced-width{max-width:800px;margin-left:auto;margin-right:auto}.indent{margin-left:40px}.hidden{display:none !important}.invisible{opacity:0}div.small_section{max-width:400px;margin-left:auto;margin-right:auto}div.medium_section{width:100%;max-width:600px;margin-left:auto;margin-right:auto}.option_group{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-ms-flex-wrap:wrap;flex-wrap:wrap}.button-group{display:inline-flex;align-items:center;flex-wrap:wrap;gap:1rem}.button-group *{margin:0px}.double-group{display:flex;align-items:center;flex-wrap:wrap;gap:1rem}.double-group div{margin:0px}.double-group div:nth-child(1){flex:1 1 auto;text-align:left}.double-group div:nth-child(2){flex:1 1 auto;text-align:right}.tripe-group{display:flex;align-items:center;flex-wrap:wrap;gap:1rem}.tripe-group div{margin:0px}.tripe-group div:nth-child(1){flex:0 1 auto;text-align:left}.tripe-group div:nth-child(2){flex:1 1 auto;text-align:center}.tripe-group div:nth-child(3){flex:0 1 auto;text-align:right}h1{font-size:16.8px}h2{font-size:15.4px}h3{font-size:14px}h1,h2,h3{font-weight:600}h1 .subtitle,h2 .subtitle,h3 .subtitle{font-weight:300;font-size:.8rem;line-height:.8rem}.user-suffix{font-weight:300;color:#939393}.text-lighter{color:#939393}span.object_type{font-weight:300;font-size:smaller;float:right;color:rgba(255,255,255,.35)}.inset-note{border-width:1px;border-style:solid;margin-left:30px;margin-right:30px;padding:15px;border-color:#474747}blockquote{border-left-width:3px;border-left-style:solid;margin-left:0px;margin-right:0px;padding-left:30px;padding-top:10px;padding-bottom:10px;padding-right:10px;border-left-color:#6c6c6c;color:#d5d5d5;background-color:#2d2d2d}blockquote p:first-child{margin-top:0px !important}blockquote p:last-child{margin-bottom:0px !important}div.visual-group{padding:20px;border-radius:10px;box-shadow:0px 2px 4px rgba(0,0,0,.15);background-color:#313131}div.visual-group h1,div.visual-group h2,div.visual-group h3{margin-bottom:20px}div.visual-group+div.visual-group{margin-top:20px}table.generic{border-collapse:collapse;width:100%;border-width:1px;border-style:solid}table.generic tbody>tr:nth-child(even){background-color:#2b252c}table.generic tbody>tr:nth-child(odd){background-color:#262626}table.generic.no-row-colors tbody tr{background-color:rgba(0,0,0,0)}table.generic td,table.generic th{border-width:1px;border-style:solid;padding:6pt;border-color:#474747}table.generic thead,table.generic tr.header{background-color:#9c0fb0;color:#d9d9d9}table.generic thead td,table.generic thead th,table.generic tr.header td,table.generic tr.header th{text-align:left;font-weight:400;border-color:#9c0fb0;color:inherit}table.generic ul{list-style:none;padding-left:0px}table.generic ul span.crafting_quantity{display:inline-block;min-width:40px;text-align:right}table.generic ul ul{padding-left:40px}table.generic div.row-details{display:flex;flex-wrap:wrap;flex-direction:row;justify-content:start;font-size:10pt;border-top-style:solid;border-top-width:1px;margin-top:8pt;margin-bottom:-2pt;padding-top:2pt;word-break:break-word;border-color:#474747;color:#939393}table.generic div.row-details span.detail{margin:4pt}table.generic div.row-details a{word-break:normal}table.generic .low-priority{display:none}table.generic .min-width{width:1px;white-space:nowrap}table.generic.auto-width{width:auto}#header_wrapper{border-bottom-width:1px;border-bottom-style:solid;position:-webkit-sticky;position:-moz-sticky;position:-ms-sticky;position:-o-sticky;position:sticky;top:0px;left:0px;right:0px;z-index:99;padding-top:max(10px,env(safe-area-inset-top, 10px));padding-bottom:10px;background-color:#262626;border-color:rgba(255,255,255,.1)}#header{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start;-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch}#header_logo_cell{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1;-ms-flex-preferred-size:100%;flex-basis:100%;text-align:center}#header_logo{height:60px;vertical-align:top}#header_links_cell{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2;-ms-flex-preferred-size:100%;flex-basis:100%;overflow:hidden}#header_links_cell ul{list-style-type:none;margin:10px 0px 0px 0px;padding:0;overflow:hidden;display:-webkit-box;display:-ms-flexbox;display:flex;height:100%;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}#header_links_cell ul li{-webkit-box-flex:0;-ms-flex:0 0 0px;flex:0 0 0;text-align:center;white-space:nowrap;vertical-align:center}#header_links_cell ul li+li{margin-left:2px}#header_links_cell ul li a{display:block;text-align:center;text-decoration:none;padding:.2em .5em;border-radius:1em;border-width:1px;border-style:solid;border-color:rgba(0,0,0,0);-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s;-webkit-transition-timing-function:ease-out;-o-transition-timing-function:ease-out;transition-timing-function:ease-out;-webkit-transition-property:border-color,color,background-color,border-radius;-o-transition-property:border-color,color,background-color,border-radius;transition-property:border-color,color,background-color,border-radius;color:rgba(255,255,255,.6)}#header_links_cell ul li a:hover{border-color:rgba(255,255,255,.4)}#header_links_cell ul li a:active{background-color:rgba(255,255,255,.2)}@media(min-width: 340px){#header_links_cell ul li a{padding-left:.75em;padding-right:.75em}}.accent-color{color:#9c0fb0;fill:#9c0fb0}.separator-color{border-color:#474747}:target:before{content:"";display:block;height:150px;margin:-150px 0 0}#content_wrapper{margin-top:20px;line-height:1.7rem}#footer{font-size:.8em;text-align:center;margin-top:40px;margin-bottom:max(20px,env(safe-area-inset-bottom, 20px))}#footer a.external_logo{margin-left:6px;margin-right:6px}#footer a.external_logo img{border:none}button,input[type=button],input[type=submit],input[type=reset],a.button{-webkit-appearance:none;appearance:none;border-radius:.25rem;border:1px solid rgba(0,0,0,0);box-sizing:border-box;cursor:pointer;display:inline-block;font-family:inherit;font-size:1rem;padding:.375rem .75rem;text-align:center;text-decoration:none;-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s;-webkit-transition-property:background-color,color,opacity;-o-transition-property:background-color,color,opacity;transition-property:background-color,color,opacity;-webkit-transition-timing-function:ease-out;-o-transition-timing-function:ease-out;transition-timing-function:ease-out;vertical-align:middle;white-space:nowrap;line-height:1.5em;font-weight:400;background-color:#5c636a;color:#fff}button.small,input[type=button].small,input[type=submit].small,input[type=reset].small,a.button.small{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}button.large,input[type=button].large,input[type=submit].large,input[type=reset].large,a.button.large{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}button+button,button+input[type=button],button+input[type=submit],button+input[type=reset],button+a.button,input[type=button]+button,input[type=button]+input[type=button],input[type=button]+input[type=submit],input[type=button]+input[type=reset],input[type=button]+a.button,input[type=submit]+button,input[type=submit]+input[type=button],input[type=submit]+input[type=submit],input[type=submit]+input[type=reset],input[type=submit]+a.button,input[type=reset]+button,input[type=reset]+input[type=button],input[type=reset]+input[type=submit],input[type=reset]+input[type=reset],input[type=reset]+a.button,a.button+button,a.button+input[type=button],a.button+input[type=submit],a.button+input[type=reset],a.button+a.button{margin-left:12px}button:disabled,input[type=button]:disabled,input[type=submit]:disabled,input[type=reset]:disabled,a.button:disabled{cursor:default;opacity:.4}button .spinner:after,input[type=button] .spinner:after,input[type=submit] .spinner:after,input[type=reset] .spinner:after,a.button .spinner:after{border-color:#fff rgba(0,0,0,0) #fff rgba(0,0,0,0)}button:not(:disabled):active,input[type=button]:not(:disabled):active,input[type=submit]:not(:disabled):active,input[type=reset]:not(:disabled):active,a.button:not(:disabled):active{background-color:#2e3235;color:gray}button:not(:disabled):active .spinner:after,input[type=button]:not(:disabled):active .spinner:after,input[type=submit]:not(:disabled):active .spinner:after,input[type=reset]:not(:disabled):active .spinner:after,a.button:not(:disabled):active .spinner:after{border-color:gray rgba(0,0,0,0) gray rgba(0,0,0,0)}button.blue,input[type=button].blue,input[type=submit].blue,input[type=reset].blue,a.button.blue{background-color:#0d6efd;color:#fff}button.blue .spinner:after,input[type=button].blue .spinner:after,input[type=submit].blue .spinner:after,input[type=reset].blue .spinner:after,a.button.blue .spinner:after{border-color:#fff rgba(0,0,0,0) #fff rgba(0,0,0,0)}button.blue:not(:disabled):active,input[type=button].blue:not(:disabled):active,input[type=submit].blue:not(:disabled):active,input[type=reset].blue:not(:disabled):active,a.button.blue:not(:disabled):active{background-color:#07377f;color:gray}button.blue:not(:disabled):active .spinner:after,input[type=button].blue:not(:disabled):active .spinner:after,input[type=submit].blue:not(:disabled):active .spinner:after,input[type=reset].blue:not(:disabled):active .spinner:after,a.button.blue:not(:disabled):active .spinner:after{border-color:gray rgba(0,0,0,0) gray rgba(0,0,0,0)}button.green,input[type=button].green,input[type=submit].green,input[type=reset].green,a.button.green{background-color:#157347;color:#fff}button.green .spinner:after,input[type=button].green .spinner:after,input[type=submit].green .spinner:after,input[type=reset].green .spinner:after,a.button.green .spinner:after{border-color:#fff rgba(0,0,0,0) #fff rgba(0,0,0,0)}button.green:not(:disabled):active,input[type=button].green:not(:disabled):active,input[type=submit].green:not(:disabled):active,input[type=reset].green:not(:disabled):active,a.button.green:not(:disabled):active{background-color:#0b3a24;color:gray}button.green:not(:disabled):active .spinner:after,input[type=button].green:not(:disabled):active .spinner:after,input[type=submit].green:not(:disabled):active .spinner:after,input[type=reset].green:not(:disabled):active .spinner:after,a.button.green:not(:disabled):active .spinner:after{border-color:gray rgba(0,0,0,0) gray rgba(0,0,0,0)}button.red,input[type=button].red,input[type=submit].red,input[type=reset].red,a.button.red{background-color:#bb2d3b;color:#fff}button.red .spinner:after,input[type=button].red .spinner:after,input[type=submit].red .spinner:after,input[type=reset].red .spinner:after,a.button.red .spinner:after{border-color:#fff rgba(0,0,0,0) #fff rgba(0,0,0,0)}button.red:not(:disabled):active,input[type=button].red:not(:disabled):active,input[type=submit].red:not(:disabled):active,input[type=reset].red:not(:disabled):active,a.button.red:not(:disabled):active{background-color:#5e171e;color:gray}button.red:not(:disabled):active .spinner:after,input[type=button].red:not(:disabled):active .spinner:after,input[type=submit].red:not(:disabled):active .spinner:after,input[type=reset].red:not(:disabled):active .spinner:after,a.button.red:not(:disabled):active .spinner:after{border-color:gray rgba(0,0,0,0) gray rgba(0,0,0,0)}button.yellow,input[type=button].yellow,input[type=submit].yellow,input[type=reset].yellow,a.button.yellow{background-color:#ffc107;color:#000}button.yellow .spinner:after,input[type=button].yellow .spinner:after,input[type=submit].yellow .spinner:after,input[type=reset].yellow .spinner:after,a.button.yellow .spinner:after{border-color:#000 rgba(0,0,0,0) #000 rgba(0,0,0,0)}button.yellow:not(:disabled):active,input[type=button].yellow:not(:disabled):active,input[type=submit].yellow:not(:disabled):active,input[type=reset].yellow:not(:disabled):active,a.button.yellow:not(:disabled):active{background-color:#806104;color:#000}button.yellow:not(:disabled):active .spinner:after,input[type=button].yellow:not(:disabled):active .spinner:after,input[type=submit].yellow:not(:disabled):active .spinner:after,input[type=reset].yellow:not(:disabled):active .spinner:after,a.button.yellow:not(:disabled):active .spinner:after{border-color:#000 rgba(0,0,0,0) #000 rgba(0,0,0,0)}button .spinner,input[type=button] .spinner,input[type=submit] .spinner,input[type=reset] .spinner,a.button .spinner{display:none}button.working .caption,input[type=button].working .caption,input[type=submit].working .caption,input[type=reset].working .caption,a.button.working .caption{display:none}button.working .spinner,input[type=button].working .spinner,input[type=submit].working .spinner,input[type=reset].working .spinner,a.button.working .spinner{display:revert}input[type=submit],button.default,a.button.default{background-color:#9c0fb0;color:#d9d9d9}input[type=submit] .spinner:after,button.default .spinner:after,a.button.default .spinner:after{border-color:#d9d9d9 rgba(0,0,0,0) #d9d9d9 rgba(0,0,0,0)}input[type=submit]:not(:disabled):active,button.default:not(:disabled):active,a.button.default:not(:disabled):active{background-color:#4e0858;color:#6d6d6d}input[type=submit]:not(:disabled):active .spinner:after,button.default:not(:disabled):active .spinner:after,a.button.default:not(:disabled):active .spinner:after{border-color:#6d6d6d rgba(0,0,0,0) #6d6d6d rgba(0,0,0,0)}.text-field,input[type=text],input[type=password],input[type=email],input[type=url],input[type=tel],input[type=search],input[type=number],textarea{-webkit-appearance:none;appearance:none;border-radius:.25rem;border:1px solid #7d7d7d;box-sizing:border-box;font-family:inherit;font-size:1rem;margin:0px;padding:.375rem .75rem;-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s;-webkit-transition-property:background-color,color,border-color,box-shadow;-o-transition-property:background-color,color,border-color,box-shadow;transition-property:background-color,color,border-color,box-shadow;-webkit-transition-timing-function:ease-in-out;-o-transition-timing-function:ease-in-out;transition-timing-function:ease-in-out;width:100%;line-height:1rem;background-clip:padding-box;background-color:#262626;color:#fff}.text-field::placeholder,input[type=text]::placeholder,input[type=password]::placeholder,input[type=email]::placeholder,input[type=url]::placeholder,input[type=tel]::placeholder,input[type=search]::placeholder,input[type=number]::placeholder,textarea::placeholder{line-height:1rem;vertical-align:bottom;padding-top:.3rem;color:#fff;opacity:.6}.text-field:focus,input[type=text]:focus,input[type=password]:focus,input[type=email]:focus,input[type=url]:focus,input[type=tel]:focus,input[type=search]:focus,input[type=number]:focus,textarea:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.text-field.invalid,input[type=text].invalid,input[type=password].invalid,input[type=email].invalid,input[type=url].invalid,input[type=tel].invalid,input[type=search].invalid,input[type=number].invalid,textarea.invalid{border-color:#ff534a}textarea{line-height:1.5}textarea::placeholder{padding-top:0px;line-height:1.5}.floating-label{position:relative;margin-top:16px;margin-bottom:16px}.floating-label>.text-field{padding:1rem .75rem;height:calc(3.5rem + 2px);line-height:1.25rem;box-sizing:border-box}.floating-label>.text-field::placeholder{color:rgba(0,0,0,0) !important}.floating-label>.text-field:focus,.floating-label>.text-field:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.floating-label>.text-field:focus~label,.floating-label>.text-field:not(:placeholder-shown)~label{transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.floating-label>.text-field:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.floating-label>.text-field:-webkit-autofill~label{transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.floating-label>div.select>select{padding:1.625rem 2.5rem .625rem .75rem;height:calc(3.5rem + 2px);line-height:1.25rem;box-sizing:border-box}.floating-label>label{position:absolute;top:0;left:0;height:100%;padding:1rem .75rem;pointer-events:none;border:1px solid rgba(0,0,0,0);transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out;box-sizing:border-box;opacity:.6}@media(prefers-reduced-motion: reduce){.floating-label>label{transition:none}}.floating-label>div.select+label{transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem);color:#fff}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%;margin-top:16px;margin-bottom:16px}.input-group>.text-field{position:relative;flex:1 1 auto;width:1%;min-width:0}.input-group>.text-field:focus{z-index:3}.input-group button{border:1px solid #7d7d7d;background-color:#2d2d2d;color:#fff}.input-group button .spinner:after{border-color:#fff rgba(0,0,0,0) #fff rgba(0,0,0,0)}.input-group button:not(:disabled):active{background-color:#171717;color:gray}.input-group button:not(:disabled):active .spinner:after{border-color:gray rgba(0,0,0,0) gray rgba(0,0,0,0)}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5rem;text-align:center;white-space:nowrap;border:1px solid #7d7d7d;box-sizing:border-box;border-radius:.25rem;background-color:#2d2d2d}.input-group-sm{margin-top:12px;margin-bottom:12px}.input-group-sm>.text-field,.input-group-sm>.input-group-text{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.input-group-lg{margin-top:20px;margin-bottom:20px}.input-group-lg>.text-field,.input-group-lg>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.input-group>:not(:first-child){margin-left:-1px;border-top-left-radius:0;border-bottom-left-radius:0}.input-group>:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.field-pair label{padding-left:6px;line-height:1.6rem;color:#bebebe}label.invalid,span.invalid,p.invalid,div.invalid{color:#ff534a}label.checkbox input[type=checkbox]{display:none}label.checkbox span{display:inline-block;vertical-align:middle;margin:6px;position:relative;border-radius:1rem;-webkit-transition-property:background-color,border-color;-o-transition-property:background-color,border-color;transition-property:background-color,border-color;-webkit-transition-timing-function:ease-out;-o-transition-timing-function:ease-out;transition-timing-function:ease-out;-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s;border-width:2px;border-style:solid;background-color:rgba(156,15,176,0);border-color:#9c0fb0}label.checkbox span:after{background-color:#9c0fb0}label.checkbox :checked:active+span{background-color:#490752;border-color:#490752}label.checkbox :checked:active+span:after{background-color:#a6a6a6}label.checkbox :checked+span{background-color:#9c0fb0}label.checkbox :checked+span:after{background-color:#d9d9d9}label.checkbox :active+span{background-color:#000;border-color:#490752}label.checkbox :active:after{background-color:#490752}label.checkbox :disabled+span{opacity:.4}label.checkbox span{width:42px;height:20px}label.checkbox span:after{position:absolute;top:3px;left:3px;width:14px;height:14px;border-radius:14px;display:block;content:"";-webkit-transition-property:left,background-color;-o-transition-property:left,background-color;transition-property:left,background-color;-webkit-transition-timing-function:ease-out;-o-transition-timing-function:ease-out;transition-timing-function:ease-out;-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s}label.checkbox :checked+span:after{left:26px}.input-radio{display:block;min-height:1.5rem;padding-left:1.4em;box-sizing:border-box;margin:0px}.input-radio input{-webkit-appearance:none;appearance:none;border:1px solid #939393;background-color:#262626;width:1em;height:1em;border-radius:50%;vertical-align:top;transition-duration:.15s;transition-property:border-color,border-width;transition-timing-function:ease-in-out;margin-top:.25em;margin-left:-1.4em;float:left;box-sizing:border-box;color-adjust:exact}.input-radio input:checked{border-width:.3em;border-color:#9c0fb0}.input-radio label{display:inline-block;box-sizing:border-box}.input-radio+.input-radio{margin-left:16px}div.select{position:relative;display:inline-block}div.select select{-webkit-appearance:none;-moz-appearance:none;appearance:none;min-height:22px;border:none;border-radius:.25rem;display:inline-block;font-family:inherit;font-size:1rem;text-decoration:none;-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s;-webkit-transition-property:background-color;-o-transition-property:background-color;transition-property:background-color;-webkit-transition-timing-function:ease-out;-o-transition-timing-function:ease-out;transition-timing-function:ease-out;vertical-align:middle;white-space:nowrap;padding:.375rem 2.5rem .375rem .75rem;line-height:1.5em}div.select select:focus{font-size:1rem}div.select span:after{position:absolute;top:calc(50% - .5em);right:.75rem;content:"▾";font-size:1rem;pointer-events:none;line-height:.9em}div.select select{background-color:#9c0fb0;color:#d9d9d9}div.select select:not(:disabled):active{background-color:#4e0858;color:#6d6d6d}div.select span:after{color:#d9d9d9}div.select.gray select{background-color:#5c636a;color:#fff}div.select.gray select:not(:disabled):active{background-color:#2e3235;color:gray}div.select.gray span:after{color:#fff}div.select.blue select{background-color:#0d6efd;color:#fff}div.select.blue select:not(:disabled):active{background-color:#07377f;color:gray}div.select.blue span:after{color:#fff}div.select.green select{background-color:#157347;color:#fff}div.select.green select:not(:disabled):active{background-color:#0b3a24;color:gray}div.select.green span:after{color:#fff}div.select.red select{background-color:#bb2d3b;color:#fff}div.select.red select:not(:disabled):active{background-color:#5e171e;color:gray}div.select.red span:after{color:#fff}div.select.yellow select{background-color:#ffc107;color:#000}div.select.yellow select:not(:disabled):active{background-color:#806104;color:#000}div.select.yellow span:after{color:#000}div.select.small select{border-radius:.2rem;font-size:.875rem;padding:.25rem 1.875rem .25rem .5rem}div.select.small span:after{top:.25rem;right:.5rem;font-size:.875rem}div.select.large select{border-radius:.3rem;font-size:1.25rem;padding:.5rem 3.25rem .5rem 1rem}div.select.large span:after{top:.25rem;right:1rem;font-size:1.25rem}#explore_container{display:none;z-index:1000;position:fixed;top:0px;bottom:0px;left:0px;right:0px;-webkit-tap-highlight-color:rgba(0,0,0,0)}#explore_popover{position:absolute;width:260px;top:20px;left:20px;box-shadow:0px 5px 5px rgba(0,0,0,.05);border-radius:4px;padding:9px;border:1px solid #8f8f8f;-webkit-tap-highlight-color:rgba(0,0,0,0);background-color:#262626;border-color:#474747}@media(min-width: 350px){#explore_popover{width:310px}}#explore_popover #explore_search_field{margin-bottom:9px}#explore_popover #explore_results{display:none}#explore_popover #explore_results ul{max-height:250px;overflow-y:auto}#explore_popover #explore_results_empty{display:none;text-align:center}#explore_popover #explore_results_buttons{display:-webkit-box;display:-ms-flexbox;display:flex;margin-top:9px;width:100%}#explore_popover #explore_results_buttons #explore_results_left_button{-webkit-box-flex:1;-ms-flex:1 0 0px;flex:1 0 0;text-align:left}#explore_popover #explore_results_buttons #explore_results_right_button{-webkit-box-flex:1;-ms-flex:1 0 0px;flex:1 0 0;text-align:right}#explore_popover ul{list-style-type:none;margin:0px;padding:0px;overflow:hidden}#explore_popover ul li a{text-decoration:none;border-radius:4px;padding:4px 6px;-webkit-transition-property:color,background-color;-o-transition-property:color,background-color;transition-property:color,background-color;-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s;-webkit-transition-timing-function:ease-out;-o-transition-timing-function:ease-out;transition-timing-function:ease-out;display:block;color:#fff}#explore_popover ul li a .result_preview{font-size:10pt;color:rgba(255,255,255,.5)}#explore_popover ul li a .result_type{font-size:9pt;margin-left:12px;float:right;border-width:1px;border-style:solid;padding:2px 4px;border-radius:4px;color:rgba(255,255,255,.4);border-color:rgba(255,255,255,.2)}#explore_popover ul li a:hover{color:#d9d9d9;background-color:#9c0fb0}#explore_popover ul li a:hover .result_preview{color:#d9d9d9}#explore_popover ul li a:hover .result_type{color:#d9d9d9;border-color:#d9d9d9}#explore_popover ul li+li{margin-top:4px}#menu_explore_link.expanded{border-radius:4px 4px 0px 0px;background-color:#474747;color:#fff}div.double_column,div.triple_column{display:table;width:100%;border-spacing:30px;border-collapse:separate}div.double_column div.column,div.triple_column div.column{display:table-row;width:100%;vertical-align:top}div.results span.result_type{font-weight:500;margin:.8em;color:rgba(255,255,255,.35)}div.results div.result{border:1px solid #e4e4e4;padding:.8em;border-radius:.5em;border-color:rgba(255,255,255,.15)}div.results div.result+div.result{margin-top:.8em}div.results div.result span.summary{font-size:.9em;color:rgba(255,255,255,.65)}#overlay{display:none;position:fixed;top:0px;bottom:0px;left:0px;right:0px;transition-duration:.25s;transition-timing-function:ease-out;transition-property:background-color,-webkit-backdrop-filter,backdrop-filter;z-index:2000;background-color:rgba(0,0,0,0);-webkit-tap-highlight-color:rgba(0,0,0,0);background-color:rgba(0,0,0,0)}@supports(-webkit-backdrop-filter: blur(0px)){#overlay{-webkit-backdrop-filter:blur(0px);background-color:rgba(0,0,0,0)}}@supports(backdrop-filter: blur(0px)){#overlay{backdrop-filter:blur(0px);background-color:rgba(0,0,0,0)}}#overlay.exist{display:block}#overlay.visible{background-color:rgba(0,0,0,.8)}@supports(-webkit-backdrop-filter: blur(15px)){#overlay.visible{-webkit-backdrop-filter:blur(15px);background-color:rgba(0,0,0,.3)}}@supports(backdrop-filter: blur(15px)){#overlay.visible{backdrop-filter:blur(15px);background-color:rgba(0,0,0,.3)}}#dialog{display:none;position:fixed;top:30%;left:0px;right:0px;z-index:2001;opacity:0;transition-duration:.25s;transition-timing-function:ease-out;transition-property:opacity,filter;filter:blur(30px);background-color:#262626}#dialog.exist{display:block}#dialog.visible{opacity:1;filter:none}#dialog p{margin:20px}#dialog_inner{width:300px;margin-left:auto;margin-right:auto}#dialog_message{font-weight:600;text-align:left}#dialog_explanation{text-align:left;font-size:smaller}#dialog_buttons{text-align:center}.modal{display:none;opacity:1;transition-duration:.25s;transition-timing-function:ease-out;transition-property:opacity,top;z-index:2001;position:fixed;top:100%;left:0px;width:100%;min-width:320px;height:100%;padding:0px;box-shadow:0px 4px 16px rgba(0,0,0,.45);box-sizing:border-box;line-height:1.5rem;background-color:#262626}.modal.exist{display:block}.modal.visible{top:0%}.modal .modal-content{display:flex;flex-direction:column;box-sizing:border-box;max-height:100vh}.modal .modal-content .title-bar{font-weight:600;flex:0 0 auto;box-sizing:border-box;margin-top:max(env(safe-area-inset-top, 20px),20px);margin-left:max(env(safe-area-inset-left, 20px),20px);margin-right:max(env(safe-area-inset-right, 20px),20px);margin-bottom:20px}.modal .modal-content .content{flex:1 1 auto;overflow:auto;box-sizing:border-box;padding-top:20px;padding-left:max(env(safe-area-inset-left, 20px),20px);padding-right:max(env(safe-area-inset-right, 20px),20px);padding-bottom:20px;border-top:1px solid #474747;border-bottom:1px solid #474747}.modal .modal-content .button-bar{display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:1rem;flex:0 0 auto;box-sizing:border-box;margin-top:20px;margin-left:max(env(safe-area-inset-left, 20px),20px);margin-right:max(env(safe-area-inset-right, 20px),20px);margin-bottom:max(env(safe-area-inset-bottom, 20px),20px)}.modal .modal-content .button-bar .left{text-align:left;flex:1 1 auto}.modal .modal-content .button-bar .middle{text-align:center;flex:1 1 auto}.modal .modal-content .button-bar .right{text-align:right;flex:1 1 auto}.spinner{display:inline-block;width:30px;height:30px}.spinner.large{width:65px;height:65px}.spinner.large:after{width:52px;height:52px;border-width:4.875px}.spinner:after{width:24px;height:24px;border-width:2.25px}.spinner.small{width:20px;height:20px}.spinner.small:after{width:16px;height:16px;border-width:1.5px}.spinner:after{content:" ";display:block;border-radius:50%;border-style:solid;animation:spinner-keyframes 1.2s linear infinite;border-color:#9c0fb0 rgba(0,0,0,0) #9c0fb0 rgba(0,0,0,0)}.spinner.hidden{display:none}@keyframes spinner-keyframes{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}#account_toolbar_menu div a{color:#9c0fb0;background-color:none}#account_toolbar_menu div.active{background-color:#9c0fb0}#account_toolbar_menu div.active a{color:#d9d9d9}#knowledge_wrapper{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:column-reverse;-ms-flex-direction:column-reverse;flex-direction:column-reverse;-ms-flex-wrap:nowrap;flex-wrap:nowrap}#knowledge_wrapper #knowledge_main>*{margin-top:.5rem;margin-bottom:.5rem}#knowledge_wrapper #knowledge_main>*:first-child{margin-top:0px}#knowledge_wrapper #knowledge_main>*:last-child{margin-bottom:0px}#knowledge_wrapper #knowledge_main h2,#knowledge_wrapper #knowledge_main h3{border-bottom-width:1px;border-bottom-style:solid;padding-bottom:.3rem;border-color:#373737}#knowledge_wrapper #knowledge_main code{padding:0px 5px;border-radius:4px;line-height:1.4rem}#knowledge_wrapper #knowledge_main pre code{padding:0px}#knowledge_wrapper #knowledge_main .affected_ini_keys{border-top-width:1px;border-top-style:solid;font-size:.8em;padding:15px;margin-top:20px;font-weight:300;background-color:#2a2a2a;text-shadow:0px 1px 0px #262626;border-top-color:#343434}#knowledge_wrapper #knowledge_contents{border-top-width:1px;border-top-style:solid;margin-top:30px;padding-top:30px;font-size:.9em;border-color:#474747}#knowledge_wrapper #knowledge_contents #knowledge_search_block{text-align:center;margin-bottom:30px}#knowledge_wrapper #knowledge_contents #knowledge_version{text-align:center;margin-bottom:20px;font-size:11pt;font-weight:600;color:#939393}#knowledge_wrapper #knowledge_contents p{margin-top:0px;margin-bottom:0px;padding-left:1em;text-indent:-1em;font-size:1.1em;font-weight:500}#knowledge_wrapper #knowledge_contents ul{margin-top:4px;list-style:none;padding-left:0px}#knowledge_wrapper #knowledge_contents ul+p{margin-top:12px}#knowledge_wrapper #knowledge_contents ul li{padding-left:10px;padding-top:4px;padding-bottom:4px;line-height:1.4rem}#knowledge_wrapper #knowledge_contents ul li.current{padding-left:7px;border-left-width:3px;border-left-style:solid;color:#9c0fb0;border-left-color:#9c0fb0}#knowledge_wrapper #knowledge_contents ul li.current::before{color:#676767}#knowledge_wrapper #knowledge_contents ul li+li{border-top-width:1px;border-top-style:solid;border-top-color:#303030}#knowledge_wrapper #knowledge_contents ul li:first-child{padding-top:0px}#knowledge_wrapper #knowledge_contents ul li:last-child{padding-bottom:0px}#knowledge_wrapper #knowledge_contents ul li a{text-decoration:none}#knowledge_wrapper #knowledge_contents ul li a:hover{text-decoration:underline}p.help-summary{border-bottom-width:1px;border-bottom-style:dotted;font-size:1.1em;padding:0px 0px 10px 0px;border-bottom-color:#474747}ul.object_list{list-style:none;padding-left:15px}ul.object_list li+li{margin-top:10px}ul.no-markings,ol.no-markings{list-style:none}.notice-block{border-width:1px;border-style:solid;text-align:center;padding:16px}.notice-block+.notice-block{margin-top:1rem}.notice-block.notice-warning{border-color:#723633;color:#ff534a;background-color:#2d2727}.notice-block.notice-caution{border-color:#726019;color:#fc0;background-color:#2d2b25}.notice-block.notice-info{border-color:#2b4d72;color:#3395ff;background-color:#26292d}.subsection{border-width:1px;border-style:solid;padding:16px;border-color:rgba(255,255,255,.1)}.downloads-table .row{padding:10px;display:flex;align-items:center;border-width:1px;border-style:solid;border-top-width:0px;border-color:#474747}.downloads-table .row:first-child{border-top-left-radius:10px;border-top-right-radius:10px;border-top-width:1px;background-color:#9c0fb0;color:#d9d9d9;border-color:#9c0fb0}.downloads-table .row:last-child{border-bottom-left-radius:10px;border-bottom-right-radius:10px}.downloads-table .row .label{flex:1 1}.downloads-table .row .button{flex:0 0 80px;margin-left:10px}.downloads-table .row .header{line-height:1rem}.downloads-table .row .games{opacity:.7;font-size:small}.downloads-table+.downloads-table{margin-top:20px}.mini-only{display:revert}.mobile-only{display:none}.desktop-only{display:none}.small-only{display:revert}.not-mini{display:none}.large-only{display:none}.embedded_youtube_video{position:relative;padding-bottom:56.25%;padding-top:30px;height:0;overflow:hidden}.embedded_youtube_video iframe,.embedded_youtube_video object,.embedded_youtube_video embed{position:absolute;top:0;left:0;width:100%;height:100%}div.page-panel{display:flex;flex-direction:column;gap:20px}@supports not (selector(:first-child)){div.page-panel>*{margin:10px}}div.page-panel div.page-panel-nav ul{display:flex;flex-direction:row;margin:0px;padding:0px;gap:10px;justify-content:center;align-items:stretch;flex-wrap:wrap}@supports not (selector(:first-child)){div.page-panel div.page-panel-nav ul>*{margin:5px}}div.page-panel div.page-panel-nav ul li{list-style:none}div.page-panel div.page-panel-nav ul li a{text-decoration:none;display:inline-block;padding:.375rem .75rem;border-radius:.25rem;white-space:nowrap;background-color:none;color:#dc4ff0;line-height:1.5em;font-size:1rem}div.page-panel div.page-panel-nav ul li.page-panel-active a{background-color:#9c0fb0;color:#d9d9d9}div.page-panel div.page-panel-page{display:none}div.page-panel div.page-panel-visible{display:block}div.page-panel div.page-panel-footer{display:none}div.flex-grid{display:flex;flex-wrap:wrap;flex-direction:row;justify-content:flex-start;align-items:flex-start;align-content:flex-start}div.flex-grid div.flex-grid-item{flex:0 0 auto}img.avatar{border-radius:10px;box-shadow:0px 2px 4px 0px rgba(0,0,0,.25);display:block}.pagination-controls{display:flex;gap:10px;justify-content:center;margin-top:20px;flex-wrap:wrap}.pagination-controls .pagination-button{display:block;padding:3px 6px;border:1px solid #000;border-radius:6px;text-align:center;white-space:nowrap;border-color:#313131;background-color:#2a2a2a;text-decoration:none}.pagination-controls .pagination-current{background-color:#9c0fb0;color:#d9d9d9;border-color:#9c0fb0}.pagination-controls .pagination-placeholder{display:block}.pagination-controls .pagination-text,.pagination-controls .pagination-placeholder{min-width:50px}.pagination-controls a.pagination-button:hover{background-color:#599ae2;color:#d9d9d9;border-color:#599ae2}@media(min-width: 400px){#header_logo{height:80px}}@media(min-width: 635px){html{font-size:16px}h1{font-size:19.2px}h2{font-size:17.6px}h3{font-size:16px}#header_wrapper{padding-top:20px;padding-bottom:20px}#header{-ms-flex-wrap:nowrap;flex-wrap:nowrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center}#header_logo_cell{-ms-flex-preferred-size:80px;flex-basis:80px}#header_links_cell{height:auto}#header_links_cell ul{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;margin:0px}#header_links_cell li{-webkit-box-flex:0;-ms-flex:0 0 0px;flex:0 0 0}:target:before{height:140px;margin:-140px 0 0}.mini-only{display:none}.mobile-only{display:revert}.not-mini{display:revert}}div.comment-block{text-align:center;font-size:small;margin:20px auto;max-width:400px;border-width:1px;border-style:solid;border-radius:4pt;padding:10pt;display:flex;justify-content:center;align-items:center;background-color:rgba(255,255,255,.02);border-color:rgba(255,255,255,.1);color:rgba(255,255,255,.7)}div.comment-block div.icon{margin-right:10pt}div.comment-block div.icon img{margin:0px;display:block}#mode_tabs div.selected,#mode_view,#mode_customizations{background-color:#3c3c3c}#mode_tabs_new,#mode_tabs_paste,#mode_tabs_upload{background-color:#313131}#browse_results div.properties-text{color:rgba(255,255,255,.5);border-color:rgba(255,255,255,.1)}input.no-stepper::-webkit-outer-spin-button,input.no-stepper::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}input.no-stepper[type=number]{-moz-appearance:textfield}#checkout-wizard-list>.checkout-wizard-list-game{display:flex;align-items:center}#checkout-wizard-list>.checkout-wizard-list-game.separated{margin-top:20px;padding-top:20px;border-top:1px solid #474747}#checkout-wizard-list>.checkout-wizard-list-game .checkout-wizard-checkbox-cell{flex:0 1 auto}#checkout-wizard-list>.checkout-wizard-list-game .checkout-wizard-description-cell{flex:1 1 auto;padding-left:10px;padding-right:10px}#checkout-wizard-list>.checkout-wizard-list-game .checkout-wizard-description-cell>div>label{font-weight:bold}#checkout-wizard-list>.checkout-wizard-list-game .checkout-wizard-description-cell .checkout-wizard-status{font-size:.8em;opacity:.8}#checkout-wizard-list>.checkout-wizard-list-game .checkout-wizard-description-cell .checkout-wizard-promo{font-weight:bold;color:#28cd41;font-size:.9em}#checkout-wizard-list>.checkout-wizard-list-game .checkout-wizard-description-cell div.field-with-label{display:inline-flex;align-items:center;gap:12px}#checkout-wizard-list>.checkout-wizard-list-game .checkout-wizard-description-cell div.field-with-label>div:nth-child(1){flex:0 0 auto;text-align:right}#checkout-wizard-list>.checkout-wizard-list-game .checkout-wizard-description-cell div.field-with-label>div:nth-child(2){flex:0 0 60px}#checkout-wizard-list>.checkout-wizard-list-game .checkout-wizard-description-cell div.field-with-label>div:nth-child(3){flex:0 0 auto}#checkout-wizard-list>.checkout-wizard-list-game .checkout-wizard-price-cell{flex:0 1 auto}#checkout-wizard-list>.checkout-wizard-list-game .checkout-wizard-price-cell .checkout-wizard-discounted{text-decoration:line-through}#storefront .storefront-cart-section{box-sizing:border-box;max-width:800px;margin-left:auto;margin-right:auto}#storefront #storefront-cart-asanotice{margin-bottom:1rem}#storefront #storefront-cart-header{display:flex;align-items:center;border-bottom:1px solid #474747;padding-bottom:1rem;margin-bottom:1rem}#storefront #storefront-cart-header div:nth-child(1){text-align:left;flex:0 1 auto}#storefront #storefront-cart-header div:nth-child(2){text-align:right;margin-left:1rem;margin-right:1rem;flex:1 1 auto;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}#storefront #storefront-cart-header div:nth-child(3){text-align:right;flex:0 1 auto}#storefront>div{webkit-transition-property:opacity;-o-transition-property:opacity;transition-property:opacity;-webkit-transition-timing-function:linear;-o-transition-timing-function:linear;transition-timing-function:linear;-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s}#storefront #storefront-cart{margin-top:1rem;margin-bottom:1rem}#storefront #storefront-cart.empty{display:flex;flex-direction:column;min-height:300px}#storefront #storefront-cart.empty div:nth-child(1){flex:1 1 30%}#storefront #storefront-cart.empty div:nth-child(2){flex:0 0 auto;text-align:center}#storefront #storefront-cart.empty div:nth-child(3){flex:1 1 70%}#storefront #storefront-cart .bundle{border:1px solid #474747;border-radius:.25rem;margin:1rem;padding:1rem}#storefront #storefront-cart .bundle.gift{border-color:skyblue;background-color:#30373a;color:#c3e7f5}#storefront #storefront-cart .bundle .bundle-product{display:flex;align-items:center;gap:1rem}#storefront #storefront-cart .bundle .bundle-product div:nth-child(1){flex:0 0 5%;text-align:center}#storefront #storefront-cart .bundle .bundle-product div:nth-child(2){flex:1 1 auto}#storefront #storefront-cart .bundle .bundle-product div:nth-child(3){flex:0 0 20%;text-align:right}#storefront #storefront-cart .bundle>div:not(:last-child){margin-bottom:.25rem}#storefront #storefront-cart .bundle .gift{font-size:.75em;font-weight:600;color:skyblue}#storefront #storefront-cart .bundle .actions{margin-top:1rem}#storefront #storefront-cart-footer{padding-top:1rem;border-top:1px solid #474747;margin-top:1rem}#storefront #storefront-cart-footer .storefront-cart-totals{margin-bottom:1rem}#storefront #storefront-cart-footer .storefront-cart-totals .storefront-cart-total-row{display:flex;flex-wrap:wrap;justify-content:flex-end}#storefront #storefront-cart-footer .storefront-cart-totals .storefront-cart-total-row div:nth-child(1){text-align:right;flex:0 0 auto}#storefront #storefront-cart-footer .storefront-cart-totals .storefront-cart-total-row div:nth-child(2){text-align:right;flex:0 0 120px}#storefront #storefront-cart-footer .storefront-cart-totals .storefront-cart-total-row+.storefront-cart-total-row{margin-top:.5rem}#storefront #storefront-cart-footer .storefront-cart-paymethods{display:flex;gap:.4rem;align-items:center;justify-content:center;margin-top:2rem;flex-wrap:wrap}#storefront #storefront-cart-footer .storefront-cart-paymethods img{width:2.5rem;margin:0px;display:block}#storefront #storefront-cart-footer .storefront-cart-notice{text-align:center;margin-top:2rem;font-size:.8rem}#storefront #storefront-cart-footer .storefront-button-row{margin-top:2rem}#storefront #storefront-cart-footer .storefront-refund-notice{max-width:600px;font-size:.8rem;margin-top:2rem;margin-left:auto;margin-right:auto}.shake{animation:shake-keyframes .4s linear 1}.omni-game-header{display:flex;align-items:center;margin-bottom:1.5rem}.omni-game-header .omni-game-header-title{font-weight:600;font-size:1.5rem;margin-right:1.5rem;flex:1 1 auto}.omni-game-header .omni-game-header-button{flex:0 0 auto;align-self:flex-start}@keyframes shake-keyframes{0%{transform:translate(10px)}20%{transform:translate(-10px)}40%{transform:translate(5px)}80%{transform:translate(-5px)}100%{transform:translate(0px)}}.header-with-subtitle{margin-bottom:1rem}.header-with-subtitle h1,.header-with-subtitle h2,.header-with-subtitle h3,.header-with-subtitle h4,.header-with-subtitle h5,.header-with-subtitle h6{margin-bottom:0px;margin-top:0px}.header-with-subtitle .subtitle{font-size:.8rem;line-height:1rem;display:block}.beacon-engram-mod-name{font-size:14px;color:rgba(255,255,255,.5)}body.no-navigation #header_wrapper,body.no-navigation #footer{display:none}body.no-navigation #content_wrapper{margin-top:max(20px,env(safe-area-inset-top, 20px));margin-bottom:max(20px,env(safe-area-inset-bottom, 20px))}.breadcrumbs{border-radius:.2rem;display:flex;gap:.5rem;background-color:#2a2a2a;font-size:14px;padding:.25rem .5rem;flex-wrap:wrap}.breadcrumbs .breadcrumb{display:inline-block;white-space:nowrap}.breadcrumbs .divider{display:inline-block;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%236a6a6a' class='bi bi-chevron-right' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3E%3C/svg%3E");background-position:center;background-repeat:no-repeat;background-size:16px 16px;width:8px}.license-name{color:#dc4ff0}.game-name{color:#3395ff}@media(min-width: 840px){div.double_column div.column{display:table-cell;width:50%}div.triple_column div.column{display:table-cell;width:33%}#knowledge_wrapper{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}#knowledge_wrapper #knowledge_main{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;padding-left:20px;width:980px}#knowledge_wrapper #knowledge_contents{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;width:220px;border-right-width:1px;border-right-style:solid;border-top:none;padding-right:20px;padding-top:0px;margin-top:0px}table.generic div.row-details{display:none}table.generic .low-priority{display:table-cell}.mini-only{display:none}.desktop-only{display:revert}.small-only{display:none}.large-only{display:revert}div.page-panel{flex-direction:row}div.page-panel div.page-panel-nav{flex:1 1 0}div.page-panel div.page-panel-nav ul{flex-direction:column}div.page-panel div.page-panel-nav ul li{text-align:right}div.page-panel div.page-panel-pages{flex:2 2 600px}div.page-panel div.page-panel-footer{flex:1 1 0;display:block}.modal{opacity:0;top:35%;left:50%;width:600px;margin-left:-300px;margin-right:auto;transform:translateY(-50%);border-radius:.25rem;margin-left:-300px;margin-right:auto;height:auto}.modal.visible{top:40%;opacity:1}.modal.centered{top:45%}.modal.centered.visible{top:50%}.modal .modal-content .title-bar{margin:0px;padding:22px 22px 0px 22px}.modal .modal-content .content{border:none;padding:22px}.modal .modal-content .button-bar{margin:0px;padding:0px 22px 22px 22px}.modal.scrolled{border-radius:0px}.modal.scrolled .modal-content .title-bar{padding:15px 22px 15px 22px;border-bottom:1px solid #474747}.modal.scrolled .modal-content .content{padding:14px 22px}.modal.scrolled .modal-content .button-bar{padding:15px 22px 15px 22px;border-top:1px solid #474747}}@media(min-width: 840px)and (max-width: 640px){.modal{left:20px;width:auto;right:20px;margin:0px;top:45%}.modal.visible{top:50%}}@media print{#header,#header_wrapper,#footer{display:none}#content_wrapper{margin-top:max(20px,env(safe-area-inset-top, 20px));margin-bottom:max(20px,env(safe-area-inset-bottom, 20px))}table.generic{border-color:#d9d9d9}table.generic td,table.generic th{border-color:inherit;background-color:#fff}table.generic thead,table.generic tr.header{background-color:#fff;color:#000}table.generic thead td,table.generic thead th,table.generic tr.header td,table.generic tr.header th{border-color:#000;color:inherit;font-weight:600}}#header_logo,.white-on-dark{filter:brightness(0) invert(1)}.accented-foreground{filter:invert(22%) sepia(68%) saturate(2401%) hue-rotate(271deg) brightness(88%) contrast(105%)}.dark-only{display:unset}.light-only{display:none} diff --git a/Website/www/assets/css/theme-beacon.css b/Website/www/assets/css/theme-beacon.css index ef15f3932..a14ae5942 100644 --- a/Website/www/assets/css/theme-beacon.css +++ b/Website/www/assets/css/theme-beacon.css @@ -1 +1 @@ -/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}button,html [type=button],[type=reset],[type=submit]{-webkit-appearance:button}button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}template{display:none}[hidden]{display:none}@font-face{font-family:"Source Code Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcecodepro/4c416e66-c769-51a5-a337-ad832ad2e74a.woff) format("woff");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:"Source Code Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcecodepro/0842acdd-1516-53e8-a07d-ded3b64aba16.woff) format("woff");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:"Source Code Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcecodepro/7c7d8d1f-3387-5d0f-9110-4b3a57111e3a.woff) format("woff");unicode-range:U+1F00-1FFF}@font-face{font-family:"Source Code Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcecodepro/f13cc6a0-67e3-5260-b676-7f411573d880.woff) format("woff");unicode-range:U+0370-03FF}@font-face{font-family:"Source Code Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcecodepro/8a252637-447c-5992-bbab-0f6d8488e53e.woff) format("woff");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:"Source Code Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcecodepro/6e6237d0-47c2-532a-8c66-b8a1f411afbb.woff) format("woff");unicode-range:U+0100-02AF,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Source Code Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcecodepro/fb7509bc-2466-5fd4-a06a-4e9769262bed.woff) format("woff");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:"Source Sans Pro";font-style:italic;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/cd80694f-b33d-508d-907e-7544db677936.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:"Source Sans Pro";font-style:italic;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/78f748da-0e7b-5feb-aeae-a6b3b7a72fbb.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:"Source Sans Pro";font-style:italic;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/0f3938f3-e96b-588f-af3a-ba8b244f5c62.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:"Source Sans Pro";font-style:italic;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/b95003c5-6216-5e4c-a68b-40a22643520d.woff2) format("woff2");unicode-range:U+0370-03FF}@font-face{font-family:"Source Sans Pro";font-style:italic;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/83549da6-17a0-5f7e-afb7-bfd2913cebc8.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:"Source Sans Pro";font-style:italic;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/bae9ad58-d401-5d8d-8885-e653187e8945.woff2) format("woff2");unicode-range:U+0100-02AF,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Source Sans Pro";font-style:italic;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/56c68f48-8586-5f86-a831-5341d8f8893e.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:300;font-display:swap;src:url(/assets/fonts/sourcesanspro/049831a8-098c-5e17-b9b7-3d4d147220b8.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:300;font-display:swap;src:url(/assets/fonts/sourcesanspro/099262aa-4d57-59f2-95f5-89baa13aeb31.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:300;font-display:swap;src:url(/assets/fonts/sourcesanspro/b4c545b6-4511-5a9f-8754-22b7acf2d1cf.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:300;font-display:swap;src:url(/assets/fonts/sourcesanspro/90df22a0-015c-52a7-9cbf-ddd09ade4fbd.woff2) format("woff2");unicode-range:U+0370-03FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:300;font-display:swap;src:url(/assets/fonts/sourcesanspro/2134ded7-88ee-5959-bb8d-8be588893e60.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:300;font-display:swap;src:url(/assets/fonts/sourcesanspro/ba0a93ec-d1e4-5657-808d-e3c06cac07d3.woff2) format("woff2");unicode-range:U+0100-02AF,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:300;font-display:swap;src:url(/assets/fonts/sourcesanspro/611734bb-b7fa-5cd8-be7e-0185b98d7968.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/03db3c69-1325-596b-a3e5-1ab821511b04.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/36c309c3-ea5a-583d-808b-4001bca13238.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/b6fd8ba7-a279-5d7d-9bcb-f85971b2980f.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/7c2623bd-f50f-5fc6-b037-d2cf4948a4a4.woff2) format("woff2");unicode-range:U+0370-03FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/a8b3a121-1cae-5965-8e16-0aa443bda51d.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/16115b0a-c537-59b3-b8af-70ec055da4b2.woff2) format("woff2");unicode-range:U+0100-02AF,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/9a0511ad-4231-545f-8ef4-f35d35fbb60c.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/sourcesanspro/bdde82bb-f944-551f-836a-4c05e0f1573f.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/sourcesanspro/d26025f7-d2e5-54d4-aa4d-2e25b1c8c4cf.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/sourcesanspro/15f136cb-e12c-5694-887e-1dc9ed9259a4.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/sourcesanspro/87121b63-28d6-5847-8a68-1218b8ba3738.woff2) format("woff2");unicode-range:U+0370-03FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/sourcesanspro/ba62d068-2dab-5fb6-9e0e-b2ce6830c777.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/sourcesanspro/a1fa33d8-a2d4-5fdb-988a-69860d54fcef.woff2) format("woff2");unicode-range:U+0100-02AF,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/sourcesanspro/fdc908e0-7ba0-5e1b-be7c-7b60913a7e89.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sourcesanspro/6921b0ca-4eb6-5888-896b-f5e043beb61e.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sourcesanspro/9b3d0111-ea94-5ed8-85ee-c4f8c0838f54.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sourcesanspro/6bcf4d01-711e-5b49-a4ae-4b72c5a950c6.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sourcesanspro/1eafcd61-b347-5326-aa01-28314ba55db8.woff2) format("woff2");unicode-range:U+0370-03FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sourcesanspro/6c0dcb70-dff7-591a-a43d-3d94def35579.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sourcesanspro/eb85d155-2a94-58f6-b344-3c0a4747ee4d.woff2) format("woff2");unicode-range:U+0100-02AF,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sourcesanspro/b58ffa91-9940-5088-8866-d6f8bce9c45d.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}.w-0{width:0%}.w-10{width:10%}.w-20{width:20%}.w-25{width:25%}.w-30{width:30%}.w-40{width:40%}.w-50{width:50%}.w-60{width:60%}.w-70{width:70%}.w-75{width:75%}.w-80{width:80%}.w-90{width:90%}.w-100{width:100%}.m-0{margin:0px !important}.m-1{margin:.25rem !important}.m-2{margin:.5rem !important}.m-3{margin:1rem !important}.m-4{margin:1.5rem !important}.m-5{margin:3rem !important}.my-0{margin-top:0px !important;margin-bottom:0px !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-5{margin-top:3rem !important;margin-bottom:3rem !important}.mx-0{margin-left:0px !important;margin-right:0px !important}.mx-1{margin-left:.25rem !important;margin-right:.25rem !important}.mx-2{margin-left:.5rem !important;margin-right:.5rem !important}.mx-3{margin-left:1rem !important;margin-right:1rem !important}.mx-4{margin-left:1.5rem !important;margin-right:1.5rem !important}.mx-5{margin-left:3rem !important;margin-right:3rem !important}.mt-0{margin-top:0px !important}.mt-1{margin-top:.25rem !important}.mt-2{margin-top:.5rem !important}.mt-3{margin-top:1rem !important}.mt-4{margin-top:1.5rem !important}.mt-5{margin-top:3rem !important}.ml-0{margin-left:0px !important}.ml-1{margin-left:.25rem !important}.ml-2{margin-left:.5rem !important}.ml-3{margin-left:1rem !important}.ml-4{margin-left:1.5rem !important}.ml-5{margin-left:3rem !important}.mr-0{margin-right:0px !important}.mr-1{margin-right:.25rem !important}.mr-2{margin-right:.5rem !important}.mr-3{margin-right:1rem !important}.mr-4{margin-right:1.5rem !important}.mr-5{margin-right:3rem !important}.mb-0{margin-bottom:0px !important}.mb-1{margin-bottom:.25rem !important}.mb-2{margin-bottom:.5rem !important}.mb-3{margin-bottom:1rem !important}.mb-4{margin-bottom:1.5rem !important}.mb-5{margin-bottom:3rem !important}.p-0{padding:0px !important}.p-1{padding:.25rem !important}.p-2{padding:.5rem !important}.p-3{padding:1rem !important}.p-4{padding:1.5rem !important}.p-5{padding:3rem !important}.py-0{padding-top:0px !important;padding-bottom:0px !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-5{padding-top:3rem !important;padding-bottom:3rem !important}.px-0{padding-left:0px !important;padding-right:0px !important}.px-1{padding-left:.25rem !important;padding-right:.25rem !important}.px-2{padding-left:.5rem !important;padding-right:.5rem !important}.px-3{padding-left:1rem !important;padding-right:1rem !important}.px-4{padding-left:1.5rem !important;padding-right:1.5rem !important}.px-5{padding-left:3rem !important;padding-right:3rem !important}.pt-0{padding-top:0px !important}.pt-1{padding-top:.25rem !important}.pt-2{padding-top:.5rem !important}.pt-3{padding-top:1rem !important}.pt-4{padding-top:1.5rem !important}.pt-5{padding-top:3rem !important}.pl-0{padding-left:0px !important}.pl-1{padding-left:.25rem !important}.pl-2{padding-left:.5rem !important}.pl-3{padding-left:1rem !important}.pl-4{padding-left:1.5rem !important}.pl-5{padding-left:3rem !important}.pr-0{padding-right:0px !important}.pr-1{padding-right:.25rem !important}.pr-2{padding-right:.5rem !important}.pr-3{padding-right:1rem !important}.pr-4{padding-right:1.5rem !important}.pr-5{padding-right:3rem !important}.pb-0{padding-bottom:0px !important}.pb-1{padding-bottom:.25rem !important}.pb-2{padding-bottom:.5rem !important}.pb-3{padding-bottom:1rem !important}.pb-4{padding-bottom:1.5rem !important}.pb-5{padding-bottom:3rem !important}html{font-family:"Source Sans Pro",sans-serif;font-weight:400;font-style:normal;font-size:14px;color:#414141;background-color:#fff;font-kerning:normal}*:first-child{margin-top:0px}*:last-child{margin-bottom:0px}b,strong,.bold{font-weight:700}i,em,.italic{font-style:italic}.uppercase{text-transform:uppercase}.lowercase{text-transform:lowercase}p,h1,h2,h3{margin-bottom:15px;margin-top:15px}code,.code{font-family:"Source Code Pro",monospace;font-weight:400;font-style:normal;border-width:1px;border-style:solid;border-radius:.4rem;padding:.2rem .5rem;font-size:.8rem;line-height:2rem;background-color:rgba(65,65,65,.05);border-color:rgba(65,65,65,.1)}pre{border-width:1px;border-style:solid;padding:.6rem;border-radius:.4rem;overflow:auto;margin:10px;display:block;font-size:.8rem;background-color:rgba(65,65,65,.05);border-color:rgba(65,65,65,.1)}pre code,pre .code{background:none;padding:0px;border-radius:0px;border:none}a{word-break:break-word;color:#2472cb}a.username-suggestion{font-style:italic}.source-code-font{font-family:"Source Code Pro",monospace;font-weight:400;font-style:normal}.break-code{word-break:break-word}.break-code code{display:revert;word-break:break-all;border:none;padding:0px;background:none}.platform_tag,.tag{border-radius:.25em;line-height:1;font-weight:700;display:inline-block;padding:.35em .65em;font-size:.75em}.platform_tag+.platform_tag,.platform_tag+.tag,.tag+.platform_tag,.tag+.tag{margin-left:6px}.platform_tag.left-space,.tag.left-space{margin-left:1em}.platform_tag.right-space,.tag.right-space{margin-right:1em}.platform_tag.xbox,.tag.xbox{background-color:#107c10;color:#fff}.platform_tag.playstation,.tag.playstation{background-color:#003791;color:#fff}.platform_tag.pc,.tag.pc{background-color:#925e0b;color:#fff}.platform_tag.nintendo,.tag.nintendo{background-color:#e60012;color:#fff}.platform_tag.red,.tag.red{background-color:#dc3545;color:#fff}.platform_tag.grey,.tag.grey{background-color:#6c757d;color:#fff}.platform_tag.blue,.tag.blue{background-color:#0d6efd;color:#fff}.platform_tag.green,.tag.green{background-color:#198754;color:#fff}.platform_tag.yellow,.tag.yellow{background-color:#876500;color:#eff1f3}.platform_tag.cyan,.tag.cyan{background-color:#08798f;color:#fefefe}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.text-red{color:#e30c00}.text-green{color:#177826}.text-blue{color:#006ee6}.text-purple{color:#a53dda}.text-yellow{color:#806600}.larger{font-size:larger}.smaller{font-size:smaller}.mini{font-size:small}.redacted{text-decoration:line-through}.nowrap{white-space:nowrap}.push{clear:both;height:0px;max-height:0px;overflow:hidden}.pagebody{min-width:320px;width:auto;max-width:1200px;margin-left:auto;margin-right:auto;padding-left:max(20px,env(safe-area-inset-left, 20px));padding-right:max(20px,env(safe-area-inset-right, 20px));box-sizing:border-box}.reduced-width{max-width:800px;margin-left:auto;margin-right:auto}.indent{margin-left:40px}.hidden{display:none !important}.invisible{opacity:0}div.small_section{max-width:400px;margin-left:auto;margin-right:auto}div.medium_section{width:100%;max-width:600px;margin-left:auto;margin-right:auto}.option_group{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-ms-flex-wrap:wrap;flex-wrap:wrap}.button-group{display:inline-flex;align-items:center;flex-wrap:wrap;gap:1rem}.button-group *{margin:0px}.double-group{display:flex;align-items:center;flex-wrap:wrap;gap:1rem}.double-group div{margin:0px}.double-group div:nth-child(1){flex:1 1 auto;text-align:left}.double-group div:nth-child(2){flex:1 1 auto;text-align:right}.tripe-group{display:flex;align-items:center;flex-wrap:wrap;gap:1rem}.tripe-group div{margin:0px}.tripe-group div:nth-child(1){flex:0 1 auto;text-align:left}.tripe-group div:nth-child(2){flex:1 1 auto;text-align:center}.tripe-group div:nth-child(3){flex:0 1 auto;text-align:right}h1{font-size:16.8px}h2{font-size:15.4px}h3{font-size:14px}h1,h2,h3{font-weight:600}h1 .subtitle,h2 .subtitle,h3 .subtitle{font-weight:300;font-size:.8rem;line-height:.8rem}.user-suffix{font-weight:300;color:#a0a0a0}.text-lighter{color:#a0a0a0}span.object_type{font-weight:300;font-size:smaller;float:right;color:rgba(65,65,65,.35)}.inset-note{border-width:1px;border-style:solid;margin-left:30px;margin-right:30px;padding:15px;border-color:#e3e3e3}blockquote{border-left-width:3px;border-left-style:solid;margin-left:0px;margin-right:0px;padding-left:30px;padding-top:10px;padding-bottom:10px;padding-right:10px;border-left-color:#c2c2c2;color:#666;background-color:#f9f9f9}blockquote p:first-child{margin-top:0px !important}blockquote p:last-child{margin-bottom:0px !important}div.visual-group{padding:20px;border-radius:10px;box-shadow:0px 2px 4px rgba(0,0,0,.15);background-color:#f6f6f6}div.visual-group h1,div.visual-group h2,div.visual-group h3{margin-bottom:20px}div.visual-group+div.visual-group{margin-top:20px}table.generic{border-collapse:collapse;width:100%;border-width:1px;border-style:solid}table.generic tbody>tr:nth-child(even){background-color:#fbf5fc}table.generic tbody>tr:nth-child(odd){background-color:#fff}table.generic.no-row-colors tbody tr{background-color:rgba(0,0,0,0)}table.generic td,table.generic th{border-width:1px;border-style:solid;padding:6pt;border-color:#e3e3e3}table.generic thead,table.generic tr.header{background-color:#9c0fb0;color:#fff}table.generic thead td,table.generic thead th,table.generic tr.header td,table.generic tr.header th{text-align:left;font-weight:400;border-color:#9c0fb0;color:inherit}table.generic ul{list-style:none;padding-left:0px}table.generic ul span.crafting_quantity{display:inline-block;min-width:40px;text-align:right}table.generic ul ul{padding-left:40px}table.generic div.row-details{display:flex;flex-wrap:wrap;flex-direction:row;justify-content:start;font-size:10pt;border-top-style:solid;border-top-width:1px;margin-top:8pt;margin-bottom:-2pt;padding-top:2pt;word-break:break-word;border-color:#e3e3e3;color:#a0a0a0}table.generic div.row-details span.detail{margin:4pt}table.generic div.row-details a{word-break:normal}table.generic .low-priority{display:none}table.generic .min-width{width:1px;white-space:nowrap}table.generic.auto-width{width:auto}#header_wrapper{border-bottom-width:1px;border-bottom-style:solid;position:-webkit-sticky;position:-moz-sticky;position:-ms-sticky;position:-o-sticky;position:sticky;top:0px;left:0px;right:0px;z-index:99;padding-top:max(10px,env(safe-area-inset-top, 10px));padding-bottom:10px;background-color:#fff;border-color:rgba(65,65,65,.1)}#header{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start;-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch}#header_logo_cell{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1;-ms-flex-preferred-size:100%;flex-basis:100%;text-align:center}#header_logo{height:60px;vertical-align:top}#header_links_cell{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2;-ms-flex-preferred-size:100%;flex-basis:100%;overflow:hidden}#header_links_cell ul{list-style-type:none;margin:10px 0px 0px 0px;padding:0;overflow:hidden;display:-webkit-box;display:-ms-flexbox;display:flex;height:100%;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}#header_links_cell ul li{-webkit-box-flex:0;-ms-flex:0 0 0px;flex:0 0 0;text-align:center;white-space:nowrap;vertical-align:center}#header_links_cell ul li+li{margin-left:2px}#header_links_cell ul li a{display:block;text-align:center;text-decoration:none;padding:.2em .5em;border-radius:1em;border-width:1px;border-style:solid;border-color:rgba(0,0,0,0);-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s;-webkit-transition-timing-function:ease-out;-o-transition-timing-function:ease-out;transition-timing-function:ease-out;-webkit-transition-property:border-color,color,background-color,border-radius;-o-transition-property:border-color,color,background-color,border-radius;transition-property:border-color,color,background-color,border-radius;color:rgba(65,65,65,.6)}#header_links_cell ul li a:hover{border-color:rgba(65,65,65,.4)}#header_links_cell ul li a:active{background-color:rgba(65,65,65,.2)}@media(min-width: 340px){#header_links_cell ul li a{padding-left:.75em;padding-right:.75em}}.accent-color{color:#9c0fb0;fill:#9c0fb0}.separator-color{border-color:#e3e3e3}:target:before{content:"";display:block;height:150px;margin:-150px 0 0}#content_wrapper{margin-top:20px;line-height:1.7rem}#footer{font-size:.8em;text-align:center;margin-top:40px;margin-bottom:max(20px,env(safe-area-inset-bottom, 20px))}#footer a.external_logo{margin-left:6px;margin-right:6px}#footer a.external_logo img{border:none}button,input[type=button],input[type=submit],input[type=reset],a.button{-webkit-appearance:none;appearance:none;border-radius:.25rem;border:1px solid rgba(0,0,0,0);box-sizing:border-box;cursor:pointer;display:inline-block;font-family:inherit;font-size:1rem;padding:.375rem .75rem;text-align:center;text-decoration:none;-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s;-webkit-transition-property:background-color,color,opacity;-o-transition-property:background-color,color,opacity;transition-property:background-color,color,opacity;-webkit-transition-timing-function:ease-out;-o-transition-timing-function:ease-out;transition-timing-function:ease-out;vertical-align:middle;white-space:nowrap;line-height:1.5em;font-weight:400;background-color:#5c636a;color:#fff}button.small,input[type=button].small,input[type=submit].small,input[type=reset].small,a.button.small{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}button.large,input[type=button].large,input[type=submit].large,input[type=reset].large,a.button.large{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}button+button,button+input[type=button],button+input[type=submit],button+input[type=reset],button+a.button,input[type=button]+button,input[type=button]+input[type=button],input[type=button]+input[type=submit],input[type=button]+input[type=reset],input[type=button]+a.button,input[type=submit]+button,input[type=submit]+input[type=button],input[type=submit]+input[type=submit],input[type=submit]+input[type=reset],input[type=submit]+a.button,input[type=reset]+button,input[type=reset]+input[type=button],input[type=reset]+input[type=submit],input[type=reset]+input[type=reset],input[type=reset]+a.button,a.button+button,a.button+input[type=button],a.button+input[type=submit],a.button+input[type=reset],a.button+a.button{margin-left:12px}button:disabled,input[type=button]:disabled,input[type=submit]:disabled,input[type=reset]:disabled,a.button:disabled{cursor:default;opacity:.4}button .spinner:after,input[type=button] .spinner:after,input[type=submit] .spinner:after,input[type=reset] .spinner:after,a.button .spinner:after{border-color:#fff rgba(0,0,0,0) #fff rgba(0,0,0,0)}button:not(:disabled):active,input[type=button]:not(:disabled):active,input[type=submit]:not(:disabled):active,input[type=reset]:not(:disabled):active,a.button:not(:disabled):active{background-color:#2e3235;color:gray}button:not(:disabled):active .spinner:after,input[type=button]:not(:disabled):active .spinner:after,input[type=submit]:not(:disabled):active .spinner:after,input[type=reset]:not(:disabled):active .spinner:after,a.button:not(:disabled):active .spinner:after{border-color:gray rgba(0,0,0,0) gray rgba(0,0,0,0)}button.blue,input[type=button].blue,input[type=submit].blue,input[type=reset].blue,a.button.blue{background-color:#0d6efd;color:#fff}button.blue .spinner:after,input[type=button].blue .spinner:after,input[type=submit].blue .spinner:after,input[type=reset].blue .spinner:after,a.button.blue .spinner:after{border-color:#fff rgba(0,0,0,0) #fff rgba(0,0,0,0)}button.blue:not(:disabled):active,input[type=button].blue:not(:disabled):active,input[type=submit].blue:not(:disabled):active,input[type=reset].blue:not(:disabled):active,a.button.blue:not(:disabled):active{background-color:#07377f;color:gray}button.blue:not(:disabled):active .spinner:after,input[type=button].blue:not(:disabled):active .spinner:after,input[type=submit].blue:not(:disabled):active .spinner:after,input[type=reset].blue:not(:disabled):active .spinner:after,a.button.blue:not(:disabled):active .spinner:after{border-color:gray rgba(0,0,0,0) gray rgba(0,0,0,0)}button.green,input[type=button].green,input[type=submit].green,input[type=reset].green,a.button.green{background-color:#157347;color:#fff}button.green .spinner:after,input[type=button].green .spinner:after,input[type=submit].green .spinner:after,input[type=reset].green .spinner:after,a.button.green .spinner:after{border-color:#fff rgba(0,0,0,0) #fff rgba(0,0,0,0)}button.green:not(:disabled):active,input[type=button].green:not(:disabled):active,input[type=submit].green:not(:disabled):active,input[type=reset].green:not(:disabled):active,a.button.green:not(:disabled):active{background-color:#0b3a24;color:gray}button.green:not(:disabled):active .spinner:after,input[type=button].green:not(:disabled):active .spinner:after,input[type=submit].green:not(:disabled):active .spinner:after,input[type=reset].green:not(:disabled):active .spinner:after,a.button.green:not(:disabled):active .spinner:after{border-color:gray rgba(0,0,0,0) gray rgba(0,0,0,0)}button.red,input[type=button].red,input[type=submit].red,input[type=reset].red,a.button.red{background-color:#bb2d3b;color:#fff}button.red .spinner:after,input[type=button].red .spinner:after,input[type=submit].red .spinner:after,input[type=reset].red .spinner:after,a.button.red .spinner:after{border-color:#fff rgba(0,0,0,0) #fff rgba(0,0,0,0)}button.red:not(:disabled):active,input[type=button].red:not(:disabled):active,input[type=submit].red:not(:disabled):active,input[type=reset].red:not(:disabled):active,a.button.red:not(:disabled):active{background-color:#5e171e;color:gray}button.red:not(:disabled):active .spinner:after,input[type=button].red:not(:disabled):active .spinner:after,input[type=submit].red:not(:disabled):active .spinner:after,input[type=reset].red:not(:disabled):active .spinner:after,a.button.red:not(:disabled):active .spinner:after{border-color:gray rgba(0,0,0,0) gray rgba(0,0,0,0)}button.yellow,input[type=button].yellow,input[type=submit].yellow,input[type=reset].yellow,a.button.yellow{background-color:#ffc107;color:#000}button.yellow .spinner:after,input[type=button].yellow .spinner:after,input[type=submit].yellow .spinner:after,input[type=reset].yellow .spinner:after,a.button.yellow .spinner:after{border-color:#000 rgba(0,0,0,0) #000 rgba(0,0,0,0)}button.yellow:not(:disabled):active,input[type=button].yellow:not(:disabled):active,input[type=submit].yellow:not(:disabled):active,input[type=reset].yellow:not(:disabled):active,a.button.yellow:not(:disabled):active{background-color:#806104;color:#000}button.yellow:not(:disabled):active .spinner:after,input[type=button].yellow:not(:disabled):active .spinner:after,input[type=submit].yellow:not(:disabled):active .spinner:after,input[type=reset].yellow:not(:disabled):active .spinner:after,a.button.yellow:not(:disabled):active .spinner:after{border-color:#000 rgba(0,0,0,0) #000 rgba(0,0,0,0)}button .spinner,input[type=button] .spinner,input[type=submit] .spinner,input[type=reset] .spinner,a.button .spinner{display:none}button.working .caption,input[type=button].working .caption,input[type=submit].working .caption,input[type=reset].working .caption,a.button.working .caption{display:none}button.working .spinner,input[type=button].working .spinner,input[type=submit].working .spinner,input[type=reset].working .spinner,a.button.working .spinner{display:revert}input[type=submit],button.default,a.button.default{background-color:#9c0fb0;color:#fff}input[type=submit] .spinner:after,button.default .spinner:after,a.button.default .spinner:after{border-color:#fff rgba(0,0,0,0) #fff rgba(0,0,0,0)}input[type=submit]:not(:disabled):active,button.default:not(:disabled):active,a.button.default:not(:disabled):active{background-color:#4e0858;color:gray}input[type=submit]:not(:disabled):active .spinner:after,button.default:not(:disabled):active .spinner:after,a.button.default:not(:disabled):active .spinner:after{border-color:gray rgba(0,0,0,0) gray rgba(0,0,0,0)}.text-field,input[type=text],input[type=password],input[type=email],input[type=url],input[type=tel],input[type=search],input[type=number],textarea{-webkit-appearance:none;appearance:none;border-radius:.25rem;border:1px solid #b3b3b3;box-sizing:border-box;font-family:inherit;font-size:1rem;margin:0px;padding:.375rem .75rem;-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s;-webkit-transition-property:background-color,color,border-color,box-shadow;-o-transition-property:background-color,color,border-color,box-shadow;transition-property:background-color,color,border-color,box-shadow;-webkit-transition-timing-function:ease-in-out;-o-transition-timing-function:ease-in-out;transition-timing-function:ease-in-out;width:100%;line-height:1rem;background-clip:padding-box;background-color:#fff;color:#414141}.text-field::placeholder,input[type=text]::placeholder,input[type=password]::placeholder,input[type=email]::placeholder,input[type=url]::placeholder,input[type=tel]::placeholder,input[type=search]::placeholder,input[type=number]::placeholder,textarea::placeholder{line-height:1rem;vertical-align:bottom;padding-top:.3rem;color:#414141;opacity:.6}.text-field:focus,input[type=text]:focus,input[type=password]:focus,input[type=email]:focus,input[type=url]:focus,input[type=tel]:focus,input[type=search]:focus,input[type=number]:focus,textarea:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.text-field.invalid,input[type=text].invalid,input[type=password].invalid,input[type=email].invalid,input[type=url].invalid,input[type=tel].invalid,input[type=search].invalid,input[type=number].invalid,textarea.invalid{border-color:#e30c00}textarea{line-height:1.5}textarea::placeholder{padding-top:0px;line-height:1.5}.floating-label{position:relative;margin-top:16px;margin-bottom:16px}.floating-label>.text-field{padding:1rem .75rem;height:calc(3.5rem + 2px);line-height:1.25rem;box-sizing:border-box}.floating-label>.text-field::placeholder{color:rgba(0,0,0,0) !important}.floating-label>.text-field:focus,.floating-label>.text-field:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.floating-label>.text-field:focus~label,.floating-label>.text-field:not(:placeholder-shown)~label{transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.floating-label>.text-field:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.floating-label>.text-field:-webkit-autofill~label{transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.floating-label>div.select>select{padding:1.625rem 2.5rem .625rem .75rem;height:calc(3.5rem + 2px);line-height:1.25rem;box-sizing:border-box}.floating-label>label{position:absolute;top:0;left:0;height:100%;padding:1rem .75rem;pointer-events:none;border:1px solid rgba(0,0,0,0);transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out;box-sizing:border-box;opacity:.6}@media(prefers-reduced-motion: reduce){.floating-label>label{transition:none}}.floating-label>div.select+label{transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem);color:#fff}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%;margin-top:16px;margin-bottom:16px}.input-group>.text-field{position:relative;flex:1 1 auto;width:1%;min-width:0}.input-group>.text-field:focus{z-index:3}.input-group button{border:1px solid #b3b3b3;background-color:#f9f9f9;color:#414141}.input-group button .spinner:after{border-color:#414141 rgba(0,0,0,0) #414141 rgba(0,0,0,0)}.input-group button:not(:disabled):active{background-color:#7d7d7d;color:#212121}.input-group button:not(:disabled):active .spinner:after{border-color:#212121 rgba(0,0,0,0) #212121 rgba(0,0,0,0)}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5rem;text-align:center;white-space:nowrap;border:1px solid #b3b3b3;box-sizing:border-box;border-radius:.25rem;background-color:#f9f9f9}.input-group-sm{margin-top:12px;margin-bottom:12px}.input-group-sm>.text-field,.input-group-sm>.input-group-text{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.input-group-lg{margin-top:20px;margin-bottom:20px}.input-group-lg>.text-field,.input-group-lg>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.input-group>:not(:first-child){margin-left:-1px;border-top-left-radius:0;border-bottom-left-radius:0}.input-group>:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.field-pair label{padding-left:6px;line-height:1.6rem;color:#7a7a7a}label.invalid,span.invalid,p.invalid,div.invalid{color:#e30c00}label.checkbox input[type=checkbox]{display:none}label.checkbox span{display:inline-block;vertical-align:middle;margin:6px;position:relative;border-radius:1rem;-webkit-transition-property:background-color,border-color;-o-transition-property:background-color,border-color;transition-property:background-color,border-color;-webkit-transition-timing-function:ease-out;-o-transition-timing-function:ease-out;transition-timing-function:ease-out;-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s;border-width:2px;border-style:solid;background-color:rgba(156,15,176,0);border-color:#9c0fb0}label.checkbox span:after{background-color:#9c0fb0}label.checkbox :checked:active+span{background-color:#490752;border-color:#490752}label.checkbox :checked:active+span:after{background-color:#ccc}label.checkbox :checked+span{background-color:#9c0fb0}label.checkbox :checked+span:after{background-color:#fff}label.checkbox :active+span{background-color:#ccc;border-color:#490752}label.checkbox :active:after{background-color:#490752}label.checkbox :disabled+span{opacity:.4}label.checkbox span{width:42px;height:20px}label.checkbox span:after{position:absolute;top:3px;left:3px;width:14px;height:14px;border-radius:14px;display:block;content:"";-webkit-transition-property:left,background-color;-o-transition-property:left,background-color;transition-property:left,background-color;-webkit-transition-timing-function:ease-out;-o-transition-timing-function:ease-out;transition-timing-function:ease-out;-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s}label.checkbox :checked+span:after{left:26px}.input-radio{display:block;min-height:1.5rem;padding-left:1.4em;box-sizing:border-box;margin:0px}.input-radio input{-webkit-appearance:none;appearance:none;border:1px solid #a0a0a0;background-color:#fff;width:1em;height:1em;border-radius:50%;vertical-align:top;transition-duration:.15s;transition-property:border-color,border-width;transition-timing-function:ease-in-out;margin-top:.25em;margin-left:-1.4em;float:left;box-sizing:border-box;color-adjust:exact}.input-radio input:checked{border-width:.3em;border-color:#9c0fb0}.input-radio label{display:inline-block;box-sizing:border-box}.input-radio+.input-radio{margin-left:16px}div.select{position:relative;display:inline-block}div.select select{-webkit-appearance:none;-moz-appearance:none;appearance:none;min-height:22px;border:none;border-radius:.25rem;display:inline-block;font-family:inherit;font-size:1rem;text-decoration:none;-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s;-webkit-transition-property:background-color;-o-transition-property:background-color;transition-property:background-color;-webkit-transition-timing-function:ease-out;-o-transition-timing-function:ease-out;transition-timing-function:ease-out;vertical-align:middle;white-space:nowrap;padding:.375rem 2.5rem .375rem .75rem;line-height:1.5em}div.select select:focus{font-size:1rem}div.select span:after{position:absolute;top:calc(50% - .5em);right:.75rem;content:"▾";font-size:1rem;pointer-events:none;line-height:.9em}div.select select{background-color:#9c0fb0;color:#fff}div.select select:not(:disabled):active{background-color:#4e0858;color:gray}div.select span:after{color:#fff}div.select.gray select{background-color:#5c636a;color:#fff}div.select.gray select:not(:disabled):active{background-color:#2e3235;color:gray}div.select.gray span:after{color:#fff}div.select.blue select{background-color:#0d6efd;color:#fff}div.select.blue select:not(:disabled):active{background-color:#07377f;color:gray}div.select.blue span:after{color:#fff}div.select.green select{background-color:#157347;color:#fff}div.select.green select:not(:disabled):active{background-color:#0b3a24;color:gray}div.select.green span:after{color:#fff}div.select.red select{background-color:#bb2d3b;color:#fff}div.select.red select:not(:disabled):active{background-color:#5e171e;color:gray}div.select.red span:after{color:#fff}div.select.yellow select{background-color:#ffc107;color:#000}div.select.yellow select:not(:disabled):active{background-color:#806104;color:#000}div.select.yellow span:after{color:#000}div.select.small select{border-radius:.2rem;font-size:.875rem;padding:.25rem 1.875rem .25rem .5rem}div.select.small span:after{top:.25rem;right:.5rem;font-size:.875rem}div.select.large select{border-radius:.3rem;font-size:1.25rem;padding:.5rem 3.25rem .5rem 1rem}div.select.large span:after{top:.25rem;right:1rem;font-size:1.25rem}#explore_container{display:none;z-index:1000;position:fixed;top:0px;bottom:0px;left:0px;right:0px;-webkit-tap-highlight-color:rgba(0,0,0,0)}#explore_popover{position:absolute;width:260px;top:20px;left:20px;box-shadow:0px 5px 5px rgba(0,0,0,.05);border-radius:4px;padding:9px;border:1px solid #8f8f8f;-webkit-tap-highlight-color:rgba(0,0,0,0);background-color:#fff;border-color:#e3e3e3}@media(min-width: 350px){#explore_popover{width:310px}}#explore_popover #explore_search_field{margin-bottom:9px}#explore_popover #explore_results{display:none}#explore_popover #explore_results ul{max-height:250px;overflow-y:auto}#explore_popover #explore_results_empty{display:none;text-align:center}#explore_popover #explore_results_buttons{display:-webkit-box;display:-ms-flexbox;display:flex;margin-top:9px;width:100%}#explore_popover #explore_results_buttons #explore_results_left_button{-webkit-box-flex:1;-ms-flex:1 0 0px;flex:1 0 0;text-align:left}#explore_popover #explore_results_buttons #explore_results_right_button{-webkit-box-flex:1;-ms-flex:1 0 0px;flex:1 0 0;text-align:right}#explore_popover ul{list-style-type:none;margin:0px;padding:0px;overflow:hidden}#explore_popover ul li a{text-decoration:none;border-radius:4px;padding:4px 6px;-webkit-transition-property:color,background-color;-o-transition-property:color,background-color;transition-property:color,background-color;-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s;-webkit-transition-timing-function:ease-out;-o-transition-timing-function:ease-out;transition-timing-function:ease-out;display:block;color:#414141}#explore_popover ul li a .result_preview{font-size:10pt;color:rgba(65,65,65,.5)}#explore_popover ul li a .result_type{font-size:9pt;margin-left:12px;float:right;border-width:1px;border-style:solid;padding:2px 4px;border-radius:4px;color:rgba(65,65,65,.4);border-color:rgba(65,65,65,.2)}#explore_popover ul li a:hover{color:#fff;background-color:#9c0fb0}#explore_popover ul li a:hover .result_preview{color:#fff}#explore_popover ul li a:hover .result_type{color:#fff;border-color:#fff}#explore_popover ul li+li{margin-top:4px}#menu_explore_link.expanded{border-radius:4px 4px 0px 0px;background-color:#e3e3e3;color:#414141}div.double_column,div.triple_column{display:table;width:100%;border-spacing:30px;border-collapse:separate}div.double_column div.column,div.triple_column div.column{display:table-row;width:100%;vertical-align:top}div.results span.result_type{font-weight:500;margin:.8em;color:rgba(65,65,65,.35)}div.results div.result{border:1px solid #e4e4e4;padding:.8em;border-radius:.5em;border-color:rgba(65,65,65,.15)}div.results div.result+div.result{margin-top:.8em}div.results div.result span.summary{font-size:.9em;color:rgba(65,65,65,.65)}#overlay{display:none;position:fixed;top:0px;bottom:0px;left:0px;right:0px;transition-duration:.25s;transition-timing-function:ease-out;transition-property:background-color,-webkit-backdrop-filter,backdrop-filter;z-index:2000;background-color:rgba(0,0,0,0);-webkit-tap-highlight-color:rgba(0,0,0,0);background-color:rgba(0,0,0,0)}@supports(-webkit-backdrop-filter: blur(0px)){#overlay{-webkit-backdrop-filter:blur(0px);background-color:rgba(0,0,0,0)}}@supports(backdrop-filter: blur(0px)){#overlay{backdrop-filter:blur(0px);background-color:rgba(0,0,0,0)}}#overlay.exist{display:block}#overlay.visible{background-color:rgba(0,0,0,.8)}@supports(-webkit-backdrop-filter: blur(15px)){#overlay.visible{-webkit-backdrop-filter:blur(15px);background-color:rgba(0,0,0,.3)}}@supports(backdrop-filter: blur(15px)){#overlay.visible{backdrop-filter:blur(15px);background-color:rgba(0,0,0,.3)}}#dialog{display:none;position:fixed;top:30%;left:0px;right:0px;z-index:2001;opacity:0;transition-duration:.25s;transition-timing-function:ease-out;transition-property:opacity,filter;filter:blur(30px);background-color:#fff}#dialog.exist{display:block}#dialog.visible{opacity:1;filter:none}#dialog p{margin:20px}#dialog_inner{width:300px;margin-left:auto;margin-right:auto}#dialog_message{font-weight:600;text-align:left}#dialog_explanation{text-align:left;font-size:smaller}#dialog_buttons{text-align:center}.modal{display:none;opacity:1;transition-duration:.25s;transition-timing-function:ease-out;transition-property:opacity,top;z-index:2001;position:fixed;top:100%;left:0px;width:100%;min-width:320px;height:100%;padding:0px;box-shadow:0px 4px 16px rgba(0,0,0,.45);box-sizing:border-box;line-height:1.5rem;background-color:#fff}.modal.exist{display:block}.modal.visible{top:0%}.modal .modal-content{display:flex;flex-direction:column;box-sizing:border-box;max-height:100vh}.modal .modal-content .title-bar{font-weight:600;flex:0 0 auto;box-sizing:border-box;margin-top:max(env(safe-area-inset-top, 20px),20px);margin-left:max(env(safe-area-inset-left, 20px),20px);margin-right:max(env(safe-area-inset-right, 20px),20px);margin-bottom:20px}.modal .modal-content .content{flex:1 1 auto;overflow:auto;box-sizing:border-box;padding-top:20px;padding-left:max(env(safe-area-inset-left, 20px),20px);padding-right:max(env(safe-area-inset-right, 20px),20px);padding-bottom:20px;border-top:1px solid #e3e3e3;border-bottom:1px solid #e3e3e3}.modal .modal-content .button-bar{display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:1rem;flex:0 0 auto;box-sizing:border-box;margin-top:20px;margin-left:max(env(safe-area-inset-left, 20px),20px);margin-right:max(env(safe-area-inset-right, 20px),20px);margin-bottom:max(env(safe-area-inset-bottom, 20px),20px)}.modal .modal-content .button-bar .left{text-align:left;flex:1 1 auto}.modal .modal-content .button-bar .middle{text-align:center;flex:1 1 auto}.modal .modal-content .button-bar .right{text-align:right;flex:1 1 auto}.spinner{display:inline-block;width:30px;height:30px}.spinner.large{width:65px;height:65px}.spinner.large:after{width:52px;height:52px;border-width:4.875px}.spinner:after{width:24px;height:24px;border-width:2.25px}.spinner.small{width:20px;height:20px}.spinner.small:after{width:16px;height:16px;border-width:1.5px}.spinner:after{content:" ";display:block;border-radius:50%;border-style:solid;animation:spinner-keyframes 1.2s linear infinite;border-color:#9c0fb0 rgba(0,0,0,0) #9c0fb0 rgba(0,0,0,0)}.spinner.hidden{display:none}@keyframes spinner-keyframes{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}#account_toolbar_menu div a{color:#9c0fb0;background-color:none}#account_toolbar_menu div.active{background-color:#9c0fb0}#account_toolbar_menu div.active a{color:#fff}#knowledge_wrapper{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:column-reverse;-ms-flex-direction:column-reverse;flex-direction:column-reverse;-ms-flex-wrap:nowrap;flex-wrap:nowrap}#knowledge_wrapper #knowledge_main>*{margin-top:.5rem;margin-bottom:.5rem}#knowledge_wrapper #knowledge_main>*:first-child{margin-top:0px}#knowledge_wrapper #knowledge_main>*:last-child{margin-bottom:0px}#knowledge_wrapper #knowledge_main h2,#knowledge_wrapper #knowledge_main h3{border-bottom-width:1px;border-bottom-style:solid;padding-bottom:.3rem;border-color:#f1f1f1}#knowledge_wrapper #knowledge_main code{padding:0px 5px;border-radius:4px;line-height:1.4rem}#knowledge_wrapper #knowledge_main pre code{padding:0px}#knowledge_wrapper #knowledge_main .affected_ini_keys{border-top-width:1px;border-top-style:solid;font-size:.8em;padding:15px;margin-top:20px;font-weight:300;background-color:#fbfbfb;text-shadow:0px 1px 0px #fff;border-top-color:#f3f3f3}#knowledge_wrapper #knowledge_contents{border-top-width:1px;border-top-style:solid;margin-top:30px;padding-top:30px;font-size:.9em;border-color:#e3e3e3}#knowledge_wrapper #knowledge_contents #knowledge_search_block{text-align:center;margin-bottom:30px}#knowledge_wrapper #knowledge_contents #knowledge_version{text-align:center;margin-bottom:20px;font-size:11pt;font-weight:600;color:#a0a0a0}#knowledge_wrapper #knowledge_contents p{margin-top:0px;margin-bottom:0px;padding-left:1em;text-indent:-1em;font-size:1.1em;font-weight:500}#knowledge_wrapper #knowledge_contents ul{margin-top:4px;list-style:none;padding-left:0px}#knowledge_wrapper #knowledge_contents ul+p{margin-top:12px}#knowledge_wrapper #knowledge_contents ul li{padding-left:10px;padding-top:4px;padding-bottom:4px;line-height:1.4rem}#knowledge_wrapper #knowledge_contents ul li.current{padding-left:7px;border-left-width:3px;border-left-style:solid;color:#9c0fb0;border-left-color:#9c0fb0}#knowledge_wrapper #knowledge_contents ul li.current::before{color:#c6c6c6}#knowledge_wrapper #knowledge_contents ul li+li{border-top-width:1px;border-top-style:solid;border-top-color:#f7f7f7}#knowledge_wrapper #knowledge_contents ul li:first-child{padding-top:0px}#knowledge_wrapper #knowledge_contents ul li:last-child{padding-bottom:0px}#knowledge_wrapper #knowledge_contents ul li a{text-decoration:none}#knowledge_wrapper #knowledge_contents ul li a:hover{text-decoration:underline}p.help-summary{border-bottom-width:1px;border-bottom-style:dotted;font-size:1.1em;padding:0px 0px 10px 0px;border-bottom-color:#e3e3e3}ul.object_list{list-style:none;padding-left:15px}ul.object_list li+li{margin-top:10px}ul.no-markings,ol.no-markings{list-style:none}.notice-block{border-width:1px;border-style:solid;text-align:center;padding:16px}.notice-block.notice-warning{border-color:#f5aaa6;color:#e30c00;background-color:#fef8f7}.notice-block.notice-caution{border-color:#d3c9a6;color:#806600;background-color:#fbfaf7}.notice-block.notice-info{border-color:#a6ccf6;color:#006ee6;background-color:#f7fbfe}.subsection{border-width:1px;border-style:solid;padding:16px;border-color:rgba(65,65,65,.1)}.downloads-table .row{padding:10px;display:flex;align-items:center;border-width:1px;border-style:solid;border-top-width:0px;border-color:#e3e3e3}.downloads-table .row:first-child{border-top-left-radius:10px;border-top-right-radius:10px;border-top-width:1px;background-color:#9c0fb0;color:#fff;border-color:#9c0fb0}.downloads-table .row:last-child{border-bottom-left-radius:10px;border-bottom-right-radius:10px}.downloads-table .row .label{flex:1 1}.downloads-table .row .button{flex:0 0 80px;margin-left:10px}.downloads-table .row .header{line-height:1rem}.downloads-table .row .games{opacity:.7;font-size:small}.downloads-table+.downloads-table{margin-top:20px}.mini-only{display:revert}.mobile-only{display:none}.desktop-only{display:none}.small-only{display:revert}.not-mini{display:none}.large-only{display:none}.embedded_youtube_video{position:relative;padding-bottom:56.25%;padding-top:30px;height:0;overflow:hidden}.embedded_youtube_video iframe,.embedded_youtube_video object,.embedded_youtube_video embed{position:absolute;top:0;left:0;width:100%;height:100%}div.page-panel{display:flex;flex-direction:column;gap:20px}@supports not (selector(:first-child)){div.page-panel>*{margin:10px}}div.page-panel div.page-panel-nav ul{display:flex;flex-direction:row;margin:0px;padding:0px;gap:10px;justify-content:center;align-items:stretch;flex-wrap:wrap}@supports not (selector(:first-child)){div.page-panel div.page-panel-nav ul>*{margin:5px}}div.page-panel div.page-panel-nav ul li{list-style:none}div.page-panel div.page-panel-nav ul li a{text-decoration:none;display:inline-block;padding:.375rem .75rem;border-radius:.25rem;white-space:nowrap;background-color:none;color:#9c0fb0;line-height:1.5em;font-size:1rem}div.page-panel div.page-panel-nav ul li.page-panel-active a{background-color:#9c0fb0;color:#fff}div.page-panel div.page-panel-page{display:none}div.page-panel div.page-panel-visible{display:block}div.page-panel div.page-panel-footer{display:none}div.flex-grid{display:flex;flex-wrap:wrap;flex-direction:row;justify-content:flex-start;align-items:flex-start;align-content:flex-start}div.flex-grid div.flex-grid-item{flex:0 0 auto}img.avatar{border-radius:10px;box-shadow:0px 2px 4px 0px rgba(0,0,0,.25);display:block}.pagination-controls{display:flex;gap:10px;justify-content:center;margin-top:20px;flex-wrap:wrap}.pagination-controls .pagination-button{display:block;padding:3px 6px;border:1px solid #000;border-radius:6px;text-align:center;white-space:nowrap;border-color:#f6f6f6;background-color:#fbfbfb;text-decoration:none}.pagination-controls .pagination-current{background-color:#9c0fb0;color:#fff;border-color:#9c0fb0}.pagination-controls .pagination-placeholder{display:block}.pagination-controls .pagination-text,.pagination-controls .pagination-placeholder{min-width:50px}.pagination-controls a.pagination-button:hover{background-color:#2472cb;color:#fff;border-color:#2472cb}@media(min-width: 400px){#header_logo{height:80px}}@media(min-width: 635px){html{font-size:16px}h1{font-size:19.2px}h2{font-size:17.6px}h3{font-size:16px}#header_wrapper{padding-top:20px;padding-bottom:20px}#header{-ms-flex-wrap:nowrap;flex-wrap:nowrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center}#header_logo_cell{-ms-flex-preferred-size:80px;flex-basis:80px}#header_links_cell{height:auto}#header_links_cell ul{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;margin:0px}#header_links_cell li{-webkit-box-flex:0;-ms-flex:0 0 0px;flex:0 0 0}:target:before{height:140px;margin:-140px 0 0}.mini-only{display:none}.mobile-only{display:revert}.not-mini{display:revert}}div.comment-block{text-align:center;font-size:small;margin:20px auto;max-width:400px;border-width:1px;border-style:solid;border-radius:4pt;padding:10pt;display:flex;justify-content:center;align-items:center;background-color:rgba(65,65,65,.02);border-color:rgba(65,65,65,.1);color:rgba(65,65,65,.7)}div.comment-block div.icon{margin-right:10pt}div.comment-block div.icon img{margin:0px;display:block}#mode_tabs div.selected,#mode_view,#mode_customizations{background-color:#ececec}#mode_tabs_new,#mode_tabs_paste,#mode_tabs_upload{background-color:#f6f6f6}#browse_results div.properties-text{color:rgba(65,65,65,.5);border-color:rgba(65,65,65,.1)}input.no-stepper::-webkit-outer-spin-button,input.no-stepper::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}input.no-stepper[type=number]{-moz-appearance:textfield}#checkout-wizard-list>div{display:flex;align-items:center}#checkout-wizard-list>div+div{margin-top:20px}#checkout-wizard-list>div .checkout-wizard-checkbox-cell{flex:0 1 auto}#checkout-wizard-list>div .checkout-wizard-description-cell{flex:1 1 auto;padding-left:10px;padding-right:10px}#checkout-wizard-list>div .checkout-wizard-description-cell>div>label{font-weight:bold}#checkout-wizard-list>div .checkout-wizard-description-cell .checkout-wizard-status{font-size:.8em;opacity:.8}#checkout-wizard-list>div .checkout-wizard-description-cell .checkout-wizard-promo{font-weight:bold;color:#177826;font-size:.9em}#checkout-wizard-list>div .checkout-wizard-description-cell div.field-with-label{display:inline-flex;align-items:center;gap:12px}#checkout-wizard-list>div .checkout-wizard-description-cell div.field-with-label>div:nth-child(1){flex:0 0 auto;text-align:right}#checkout-wizard-list>div .checkout-wizard-description-cell div.field-with-label>div:nth-child(2){flex:0 0 60px}#checkout-wizard-list>div .checkout-wizard-description-cell div.field-with-label>div:nth-child(3){flex:0 0 auto}#checkout-wizard-list>div .checkout-wizard-price-cell{flex:0 1 auto}#checkout-wizard-list>div .checkout-wizard-price-cell .checkout-wizard-discounted{text-decoration:line-through}#storefront .storefront-cart-section{box-sizing:border-box;max-width:800px;margin-left:auto;margin-right:auto}#storefront #storefront-cart-asanotice{margin-bottom:1rem}#storefront #storefront-cart-header{display:flex;align-items:center;border-bottom:1px solid #e3e3e3;padding-bottom:1rem;margin-bottom:1rem}#storefront #storefront-cart-header div:nth-child(1){text-align:left;flex:0 1 auto}#storefront #storefront-cart-header div:nth-child(2){text-align:right;margin-left:1rem;margin-right:1rem;flex:1 1 auto;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}#storefront #storefront-cart-header div:nth-child(3){text-align:right;flex:0 1 auto}#storefront>div{webkit-transition-property:opacity;-o-transition-property:opacity;transition-property:opacity;-webkit-transition-timing-function:linear;-o-transition-timing-function:linear;transition-timing-function:linear;-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s}#storefront #storefront-cart{margin-top:1rem;margin-bottom:1rem}#storefront #storefront-cart.empty{display:flex;flex-direction:column;min-height:300px}#storefront #storefront-cart.empty div:nth-child(1){flex:1 1 30%}#storefront #storefront-cart.empty div:nth-child(2){flex:0 0 auto;text-align:center}#storefront #storefront-cart.empty div:nth-child(3){flex:1 1 70%}#storefront #storefront-cart .bundle{border:1px solid #e3e3e3;border-radius:.25rem;margin:1rem;padding:1rem}#storefront #storefront-cart .bundle.gift{border-color:skyblue;background-color:#f3fafd;color:#506c77}#storefront #storefront-cart .bundle .bundle-product{display:flex;align-items:center;gap:1rem}#storefront #storefront-cart .bundle .bundle-product div:nth-child(1){flex:0 0 5%;text-align:center}#storefront #storefront-cart .bundle .bundle-product div:nth-child(2){flex:1 1 auto}#storefront #storefront-cart .bundle .bundle-product div:nth-child(3){flex:0 0 20%;text-align:right}#storefront #storefront-cart .bundle>div:not(:last-child){margin-bottom:.25rem}#storefront #storefront-cart .bundle .gift{font-size:.75em;font-weight:600;color:#186c8e}#storefront #storefront-cart .bundle .actions{margin-top:1rem}#storefront #storefront-cart-footer{padding-top:1rem;border-top:1px solid #e3e3e3;margin-top:1rem}#storefront #storefront-cart-footer .storefront-cart-totals{margin-bottom:1rem}#storefront #storefront-cart-footer .storefront-cart-totals .storefront-cart-total-row{display:flex;flex-wrap:wrap;justify-content:flex-end}#storefront #storefront-cart-footer .storefront-cart-totals .storefront-cart-total-row div:nth-child(1){text-align:right;flex:0 0 auto}#storefront #storefront-cart-footer .storefront-cart-totals .storefront-cart-total-row div:nth-child(2){text-align:right;flex:0 0 120px}#storefront #storefront-cart-footer .storefront-cart-totals .storefront-cart-total-row+.storefront-cart-total-row{margin-top:.5rem}#storefront #storefront-cart-footer .storefront-cart-paymethods{display:flex;gap:.4rem;align-items:center;justify-content:center;margin-top:2rem;flex-wrap:wrap}#storefront #storefront-cart-footer .storefront-cart-paymethods img{width:2.5rem;margin:0px;display:block}#storefront #storefront-cart-footer .storefront-cart-notice{text-align:center;margin-top:2rem;font-size:.8rem}#storefront #storefront-cart-footer .storefront-button-row{margin-top:2rem}#storefront #storefront-cart-footer .storefront-refund-notice{max-width:600px;font-size:.8rem;margin-top:2rem;margin-left:auto;margin-right:auto}.shake{animation:shake-keyframes .4s linear 1}@keyframes shake-keyframes{0%{transform:translate(10px)}20%{transform:translate(-10px)}40%{transform:translate(5px)}80%{transform:translate(-5px)}100%{transform:translate(0px)}}.header-with-subtitle{margin-bottom:1rem}.header-with-subtitle h1,.header-with-subtitle h2,.header-with-subtitle h3,.header-with-subtitle h4,.header-with-subtitle h5,.header-with-subtitle h6{margin-bottom:0px;margin-top:0px}.header-with-subtitle .subtitle{font-size:.8rem;line-height:1rem;display:block}.beacon-engram-mod-name{font-size:14px;color:rgba(65,65,65,.5)}body.no-navigation #header_wrapper,body.no-navigation #footer{display:none}body.no-navigation #content_wrapper{margin-top:max(20px,env(safe-area-inset-top, 20px));margin-bottom:max(20px,env(safe-area-inset-bottom, 20px))}.breadcrumbs{border-radius:.2rem;display:flex;gap:.5rem;background-color:#fbfbfb;font-size:14px;padding:.25rem .5rem;flex-wrap:wrap}.breadcrumbs .breadcrumb{display:inline-block;white-space:nowrap}.breadcrumbs .divider{display:inline-block;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23c3c3c3' class='bi bi-chevron-right' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3E%3C/svg%3E");background-position:center;background-repeat:no-repeat;background-size:16px 16px;width:8px}@media(min-width: 840px){div.double_column div.column{display:table-cell;width:50%}div.triple_column div.column{display:table-cell;width:33%}#knowledge_wrapper{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}#knowledge_wrapper #knowledge_main{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;padding-left:20px;width:980px}#knowledge_wrapper #knowledge_contents{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;width:220px;border-right-width:1px;border-right-style:solid;border-top:none;padding-right:20px;padding-top:0px;margin-top:0px}table.generic div.row-details{display:none}table.generic .low-priority{display:table-cell}.mini-only{display:none}.desktop-only{display:revert}.small-only{display:none}.large-only{display:revert}div.page-panel{flex-direction:row}div.page-panel div.page-panel-nav{flex:1 1 0}div.page-panel div.page-panel-nav ul{flex-direction:column}div.page-panel div.page-panel-nav ul li{text-align:right}div.page-panel div.page-panel-pages{flex:2 2 600px}div.page-panel div.page-panel-footer{flex:1 1 0;display:block}.modal{opacity:0;top:35%;left:50%;width:600px;margin-left:-300px;margin-right:auto;transform:translateY(-50%);border-radius:.25rem;margin-left:-300px;margin-right:auto;height:auto}.modal.visible{top:40%;opacity:1}.modal.centered{top:45%}.modal.centered.visible{top:50%}.modal .modal-content .title-bar{margin:0px;padding:22px 22px 0px 22px}.modal .modal-content .content{border:none;padding:22px}.modal .modal-content .button-bar{margin:0px;padding:0px 22px 22px 22px}.modal.scrolled{border-radius:0px}.modal.scrolled .modal-content .title-bar{padding:15px 22px 15px 22px;border-bottom:1px solid #e3e3e3}.modal.scrolled .modal-content .content{padding:14px 22px}.modal.scrolled .modal-content .button-bar{padding:15px 22px 15px 22px;border-top:1px solid #e3e3e3}}@media(min-width: 840px)and (max-width: 640px){.modal{left:20px;width:auto;right:20px;margin:0px;top:45%}.modal.visible{top:50%}}@media print{#header,#header_wrapper,#footer{display:none}#content_wrapper{margin-top:max(20px,env(safe-area-inset-top, 20px));margin-bottom:max(20px,env(safe-area-inset-bottom, 20px))}table.generic{border-color:#d9d9d9}table.generic td,table.generic th{border-color:inherit;background-color:#fff}table.generic thead,table.generic tr.header{background-color:#fff;color:#000}table.generic thead td,table.generic thead th,table.generic tr.header td,table.generic tr.header th{border-color:#000;color:inherit;font-weight:600}}.accented-foreground{filter:invert(22%) sepia(68%) saturate(2401%) hue-rotate(271deg) brightness(88%) contrast(105%)} +/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}button,html [type=button],[type=reset],[type=submit]{-webkit-appearance:button}button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}template{display:none}[hidden]{display:none}@font-face{font-family:"Source Code Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcecodepro/4c416e66-c769-51a5-a337-ad832ad2e74a.woff) format("woff");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:"Source Code Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcecodepro/0842acdd-1516-53e8-a07d-ded3b64aba16.woff) format("woff");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:"Source Code Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcecodepro/7c7d8d1f-3387-5d0f-9110-4b3a57111e3a.woff) format("woff");unicode-range:U+1F00-1FFF}@font-face{font-family:"Source Code Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcecodepro/f13cc6a0-67e3-5260-b676-7f411573d880.woff) format("woff");unicode-range:U+0370-03FF}@font-face{font-family:"Source Code Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcecodepro/8a252637-447c-5992-bbab-0f6d8488e53e.woff) format("woff");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:"Source Code Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcecodepro/6e6237d0-47c2-532a-8c66-b8a1f411afbb.woff) format("woff");unicode-range:U+0100-02AF,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Source Code Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcecodepro/fb7509bc-2466-5fd4-a06a-4e9769262bed.woff) format("woff");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:"Source Sans Pro";font-style:italic;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/cd80694f-b33d-508d-907e-7544db677936.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:"Source Sans Pro";font-style:italic;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/78f748da-0e7b-5feb-aeae-a6b3b7a72fbb.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:"Source Sans Pro";font-style:italic;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/0f3938f3-e96b-588f-af3a-ba8b244f5c62.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:"Source Sans Pro";font-style:italic;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/b95003c5-6216-5e4c-a68b-40a22643520d.woff2) format("woff2");unicode-range:U+0370-03FF}@font-face{font-family:"Source Sans Pro";font-style:italic;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/83549da6-17a0-5f7e-afb7-bfd2913cebc8.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:"Source Sans Pro";font-style:italic;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/bae9ad58-d401-5d8d-8885-e653187e8945.woff2) format("woff2");unicode-range:U+0100-02AF,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Source Sans Pro";font-style:italic;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/56c68f48-8586-5f86-a831-5341d8f8893e.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:300;font-display:swap;src:url(/assets/fonts/sourcesanspro/049831a8-098c-5e17-b9b7-3d4d147220b8.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:300;font-display:swap;src:url(/assets/fonts/sourcesanspro/099262aa-4d57-59f2-95f5-89baa13aeb31.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:300;font-display:swap;src:url(/assets/fonts/sourcesanspro/b4c545b6-4511-5a9f-8754-22b7acf2d1cf.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:300;font-display:swap;src:url(/assets/fonts/sourcesanspro/90df22a0-015c-52a7-9cbf-ddd09ade4fbd.woff2) format("woff2");unicode-range:U+0370-03FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:300;font-display:swap;src:url(/assets/fonts/sourcesanspro/2134ded7-88ee-5959-bb8d-8be588893e60.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:300;font-display:swap;src:url(/assets/fonts/sourcesanspro/ba0a93ec-d1e4-5657-808d-e3c06cac07d3.woff2) format("woff2");unicode-range:U+0100-02AF,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:300;font-display:swap;src:url(/assets/fonts/sourcesanspro/611734bb-b7fa-5cd8-be7e-0185b98d7968.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/03db3c69-1325-596b-a3e5-1ab821511b04.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/36c309c3-ea5a-583d-808b-4001bca13238.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/b6fd8ba7-a279-5d7d-9bcb-f85971b2980f.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/7c2623bd-f50f-5fc6-b037-d2cf4948a4a4.woff2) format("woff2");unicode-range:U+0370-03FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/a8b3a121-1cae-5965-8e16-0aa443bda51d.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/16115b0a-c537-59b3-b8af-70ec055da4b2.woff2) format("woff2");unicode-range:U+0100-02AF,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/9a0511ad-4231-545f-8ef4-f35d35fbb60c.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/sourcesanspro/bdde82bb-f944-551f-836a-4c05e0f1573f.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/sourcesanspro/d26025f7-d2e5-54d4-aa4d-2e25b1c8c4cf.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/sourcesanspro/15f136cb-e12c-5694-887e-1dc9ed9259a4.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/sourcesanspro/87121b63-28d6-5847-8a68-1218b8ba3738.woff2) format("woff2");unicode-range:U+0370-03FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/sourcesanspro/ba62d068-2dab-5fb6-9e0e-b2ce6830c777.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/sourcesanspro/a1fa33d8-a2d4-5fdb-988a-69860d54fcef.woff2) format("woff2");unicode-range:U+0100-02AF,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/sourcesanspro/fdc908e0-7ba0-5e1b-be7c-7b60913a7e89.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sourcesanspro/6921b0ca-4eb6-5888-896b-f5e043beb61e.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sourcesanspro/9b3d0111-ea94-5ed8-85ee-c4f8c0838f54.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sourcesanspro/6bcf4d01-711e-5b49-a4ae-4b72c5a950c6.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sourcesanspro/1eafcd61-b347-5326-aa01-28314ba55db8.woff2) format("woff2");unicode-range:U+0370-03FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sourcesanspro/6c0dcb70-dff7-591a-a43d-3d94def35579.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sourcesanspro/eb85d155-2a94-58f6-b344-3c0a4747ee4d.woff2) format("woff2");unicode-range:U+0100-02AF,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sourcesanspro/b58ffa91-9940-5088-8866-d6f8bce9c45d.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}.w-0{width:0%}.w-10{width:10%}.w-20{width:20%}.w-25{width:25%}.w-30{width:30%}.w-40{width:40%}.w-50{width:50%}.w-60{width:60%}.w-70{width:70%}.w-75{width:75%}.w-80{width:80%}.w-90{width:90%}.w-100{width:100%}.m-0{margin:0px !important}.m-1{margin:.25rem !important}.m-2{margin:.5rem !important}.m-3{margin:1rem !important}.m-4{margin:1.5rem !important}.m-5{margin:3rem !important}.my-0{margin-top:0px !important;margin-bottom:0px !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-5{margin-top:3rem !important;margin-bottom:3rem !important}.mx-0{margin-left:0px !important;margin-right:0px !important}.mx-1{margin-left:.25rem !important;margin-right:.25rem !important}.mx-2{margin-left:.5rem !important;margin-right:.5rem !important}.mx-3{margin-left:1rem !important;margin-right:1rem !important}.mx-4{margin-left:1.5rem !important;margin-right:1.5rem !important}.mx-5{margin-left:3rem !important;margin-right:3rem !important}.mt-0{margin-top:0px !important}.mt-1{margin-top:.25rem !important}.mt-2{margin-top:.5rem !important}.mt-3{margin-top:1rem !important}.mt-4{margin-top:1.5rem !important}.mt-5{margin-top:3rem !important}.ml-0{margin-left:0px !important}.ml-1{margin-left:.25rem !important}.ml-2{margin-left:.5rem !important}.ml-3{margin-left:1rem !important}.ml-4{margin-left:1.5rem !important}.ml-5{margin-left:3rem !important}.mr-0{margin-right:0px !important}.mr-1{margin-right:.25rem !important}.mr-2{margin-right:.5rem !important}.mr-3{margin-right:1rem !important}.mr-4{margin-right:1.5rem !important}.mr-5{margin-right:3rem !important}.mb-0{margin-bottom:0px !important}.mb-1{margin-bottom:.25rem !important}.mb-2{margin-bottom:.5rem !important}.mb-3{margin-bottom:1rem !important}.mb-4{margin-bottom:1.5rem !important}.mb-5{margin-bottom:3rem !important}.p-0{padding:0px !important}.p-1{padding:.25rem !important}.p-2{padding:.5rem !important}.p-3{padding:1rem !important}.p-4{padding:1.5rem !important}.p-5{padding:3rem !important}.py-0{padding-top:0px !important;padding-bottom:0px !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-5{padding-top:3rem !important;padding-bottom:3rem !important}.px-0{padding-left:0px !important;padding-right:0px !important}.px-1{padding-left:.25rem !important;padding-right:.25rem !important}.px-2{padding-left:.5rem !important;padding-right:.5rem !important}.px-3{padding-left:1rem !important;padding-right:1rem !important}.px-4{padding-left:1.5rem !important;padding-right:1.5rem !important}.px-5{padding-left:3rem !important;padding-right:3rem !important}.pt-0{padding-top:0px !important}.pt-1{padding-top:.25rem !important}.pt-2{padding-top:.5rem !important}.pt-3{padding-top:1rem !important}.pt-4{padding-top:1.5rem !important}.pt-5{padding-top:3rem !important}.pl-0{padding-left:0px !important}.pl-1{padding-left:.25rem !important}.pl-2{padding-left:.5rem !important}.pl-3{padding-left:1rem !important}.pl-4{padding-left:1.5rem !important}.pl-5{padding-left:3rem !important}.pr-0{padding-right:0px !important}.pr-1{padding-right:.25rem !important}.pr-2{padding-right:.5rem !important}.pr-3{padding-right:1rem !important}.pr-4{padding-right:1.5rem !important}.pr-5{padding-right:3rem !important}.pb-0{padding-bottom:0px !important}.pb-1{padding-bottom:.25rem !important}.pb-2{padding-bottom:.5rem !important}.pb-3{padding-bottom:1rem !important}.pb-4{padding-bottom:1.5rem !important}.pb-5{padding-bottom:3rem !important}html{font-family:"Source Sans Pro",sans-serif;font-weight:400;font-style:normal;font-size:14px;color:#414141;background-color:#fff;font-kerning:normal}*:first-child{margin-top:0px}*:last-child{margin-bottom:0px}b,strong,.bold{font-weight:700}i,em,.italic{font-style:italic}.uppercase{text-transform:uppercase}.lowercase{text-transform:lowercase}p,h1,h2,h3{margin-bottom:15px;margin-top:15px}code,.code{font-family:"Source Code Pro",monospace;font-weight:400;font-style:normal;border-width:1px;border-style:solid;border-radius:.4rem;padding:.2rem .5rem;font-size:.8rem;line-height:2rem;background-color:rgba(65,65,65,.05);border-color:rgba(65,65,65,.1)}pre{border-width:1px;border-style:solid;padding:.6rem;border-radius:.4rem;overflow:auto;margin:10px;display:block;font-size:.8rem;background-color:rgba(65,65,65,.05);border-color:rgba(65,65,65,.1)}pre code,pre .code{background:none;padding:0px;border-radius:0px;border:none}a{word-break:break-word;color:#2472cb}a.username-suggestion{font-style:italic}.source-code-font{font-family:"Source Code Pro",monospace;font-weight:400;font-style:normal}.break-code{word-break:break-word}.break-code code{display:revert;word-break:break-all;border:none;padding:0px;background:none}.platform_tag,.tag{border-radius:.25em;line-height:1;font-weight:700;display:inline-block;padding:.35em .65em;font-size:.75em}.platform_tag+.platform_tag,.platform_tag+.tag,.tag+.platform_tag,.tag+.tag{margin-left:6px}.platform_tag.left-space,.tag.left-space{margin-left:1em}.platform_tag.right-space,.tag.right-space{margin-right:1em}.platform_tag.xbox,.tag.xbox{background-color:#107c10;color:#fff}.platform_tag.playstation,.tag.playstation{background-color:#003791;color:#fff}.platform_tag.pc,.tag.pc{background-color:#925e0b;color:#fff}.platform_tag.nintendo,.tag.nintendo{background-color:#e60012;color:#fff}.platform_tag.red,.tag.red{background-color:#dc3545;color:#fff}.platform_tag.grey,.tag.grey{background-color:#6c757d;color:#fff}.platform_tag.blue,.tag.blue{background-color:#0d6efd;color:#fff}.platform_tag.green,.tag.green{background-color:#198754;color:#fff}.platform_tag.yellow,.tag.yellow{background-color:#876500;color:#eff1f3}.platform_tag.cyan,.tag.cyan{background-color:#08798f;color:#fefefe}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.text-red{color:#e30c00}.text-green{color:#177826}.text-blue{color:#006ee6}.text-purple{color:#a53dda}.text-yellow{color:#806600}.larger{font-size:larger}.smaller{font-size:smaller}.mini{font-size:small}.redacted{text-decoration:line-through}.nowrap{white-space:nowrap}.push{clear:both;height:0px;max-height:0px;overflow:hidden}.pagebody{min-width:320px;width:auto;max-width:1200px;margin-left:auto;margin-right:auto;padding-left:max(20px,env(safe-area-inset-left, 20px));padding-right:max(20px,env(safe-area-inset-right, 20px));box-sizing:border-box}.reduced-width{max-width:800px;margin-left:auto;margin-right:auto}.indent{margin-left:40px}.hidden{display:none !important}.invisible{opacity:0}div.small_section{max-width:400px;margin-left:auto;margin-right:auto}div.medium_section{width:100%;max-width:600px;margin-left:auto;margin-right:auto}.option_group{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-ms-flex-wrap:wrap;flex-wrap:wrap}.button-group{display:inline-flex;align-items:center;flex-wrap:wrap;gap:1rem}.button-group *{margin:0px}.double-group{display:flex;align-items:center;flex-wrap:wrap;gap:1rem}.double-group div{margin:0px}.double-group div:nth-child(1){flex:1 1 auto;text-align:left}.double-group div:nth-child(2){flex:1 1 auto;text-align:right}.tripe-group{display:flex;align-items:center;flex-wrap:wrap;gap:1rem}.tripe-group div{margin:0px}.tripe-group div:nth-child(1){flex:0 1 auto;text-align:left}.tripe-group div:nth-child(2){flex:1 1 auto;text-align:center}.tripe-group div:nth-child(3){flex:0 1 auto;text-align:right}h1{font-size:16.8px}h2{font-size:15.4px}h3{font-size:14px}h1,h2,h3{font-weight:600}h1 .subtitle,h2 .subtitle,h3 .subtitle{font-weight:300;font-size:.8rem;line-height:.8rem}.user-suffix{font-weight:300;color:#a0a0a0}.text-lighter{color:#a0a0a0}span.object_type{font-weight:300;font-size:smaller;float:right;color:rgba(65,65,65,.35)}.inset-note{border-width:1px;border-style:solid;margin-left:30px;margin-right:30px;padding:15px;border-color:#e3e3e3}blockquote{border-left-width:3px;border-left-style:solid;margin-left:0px;margin-right:0px;padding-left:30px;padding-top:10px;padding-bottom:10px;padding-right:10px;border-left-color:#c2c2c2;color:#666;background-color:#f9f9f9}blockquote p:first-child{margin-top:0px !important}blockquote p:last-child{margin-bottom:0px !important}div.visual-group{padding:20px;border-radius:10px;box-shadow:0px 2px 4px rgba(0,0,0,.15);background-color:#f6f6f6}div.visual-group h1,div.visual-group h2,div.visual-group h3{margin-bottom:20px}div.visual-group+div.visual-group{margin-top:20px}table.generic{border-collapse:collapse;width:100%;border-width:1px;border-style:solid}table.generic tbody>tr:nth-child(even){background-color:#fbf5fc}table.generic tbody>tr:nth-child(odd){background-color:#fff}table.generic.no-row-colors tbody tr{background-color:rgba(0,0,0,0)}table.generic td,table.generic th{border-width:1px;border-style:solid;padding:6pt;border-color:#e3e3e3}table.generic thead,table.generic tr.header{background-color:#9c0fb0;color:#fff}table.generic thead td,table.generic thead th,table.generic tr.header td,table.generic tr.header th{text-align:left;font-weight:400;border-color:#9c0fb0;color:inherit}table.generic ul{list-style:none;padding-left:0px}table.generic ul span.crafting_quantity{display:inline-block;min-width:40px;text-align:right}table.generic ul ul{padding-left:40px}table.generic div.row-details{display:flex;flex-wrap:wrap;flex-direction:row;justify-content:start;font-size:10pt;border-top-style:solid;border-top-width:1px;margin-top:8pt;margin-bottom:-2pt;padding-top:2pt;word-break:break-word;border-color:#e3e3e3;color:#a0a0a0}table.generic div.row-details span.detail{margin:4pt}table.generic div.row-details a{word-break:normal}table.generic .low-priority{display:none}table.generic .min-width{width:1px;white-space:nowrap}table.generic.auto-width{width:auto}#header_wrapper{border-bottom-width:1px;border-bottom-style:solid;position:-webkit-sticky;position:-moz-sticky;position:-ms-sticky;position:-o-sticky;position:sticky;top:0px;left:0px;right:0px;z-index:99;padding-top:max(10px,env(safe-area-inset-top, 10px));padding-bottom:10px;background-color:#fff;border-color:rgba(65,65,65,.1)}#header{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start;-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch}#header_logo_cell{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1;-ms-flex-preferred-size:100%;flex-basis:100%;text-align:center}#header_logo{height:60px;vertical-align:top}#header_links_cell{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2;-ms-flex-preferred-size:100%;flex-basis:100%;overflow:hidden}#header_links_cell ul{list-style-type:none;margin:10px 0px 0px 0px;padding:0;overflow:hidden;display:-webkit-box;display:-ms-flexbox;display:flex;height:100%;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}#header_links_cell ul li{-webkit-box-flex:0;-ms-flex:0 0 0px;flex:0 0 0;text-align:center;white-space:nowrap;vertical-align:center}#header_links_cell ul li+li{margin-left:2px}#header_links_cell ul li a{display:block;text-align:center;text-decoration:none;padding:.2em .5em;border-radius:1em;border-width:1px;border-style:solid;border-color:rgba(0,0,0,0);-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s;-webkit-transition-timing-function:ease-out;-o-transition-timing-function:ease-out;transition-timing-function:ease-out;-webkit-transition-property:border-color,color,background-color,border-radius;-o-transition-property:border-color,color,background-color,border-radius;transition-property:border-color,color,background-color,border-radius;color:rgba(65,65,65,.6)}#header_links_cell ul li a:hover{border-color:rgba(65,65,65,.4)}#header_links_cell ul li a:active{background-color:rgba(65,65,65,.2)}@media(min-width: 340px){#header_links_cell ul li a{padding-left:.75em;padding-right:.75em}}.accent-color{color:#9c0fb0;fill:#9c0fb0}.separator-color{border-color:#e3e3e3}:target:before{content:"";display:block;height:150px;margin:-150px 0 0}#content_wrapper{margin-top:20px;line-height:1.7rem}#footer{font-size:.8em;text-align:center;margin-top:40px;margin-bottom:max(20px,env(safe-area-inset-bottom, 20px))}#footer a.external_logo{margin-left:6px;margin-right:6px}#footer a.external_logo img{border:none}button,input[type=button],input[type=submit],input[type=reset],a.button{-webkit-appearance:none;appearance:none;border-radius:.25rem;border:1px solid rgba(0,0,0,0);box-sizing:border-box;cursor:pointer;display:inline-block;font-family:inherit;font-size:1rem;padding:.375rem .75rem;text-align:center;text-decoration:none;-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s;-webkit-transition-property:background-color,color,opacity;-o-transition-property:background-color,color,opacity;transition-property:background-color,color,opacity;-webkit-transition-timing-function:ease-out;-o-transition-timing-function:ease-out;transition-timing-function:ease-out;vertical-align:middle;white-space:nowrap;line-height:1.5em;font-weight:400;background-color:#5c636a;color:#fff}button.small,input[type=button].small,input[type=submit].small,input[type=reset].small,a.button.small{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}button.large,input[type=button].large,input[type=submit].large,input[type=reset].large,a.button.large{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}button+button,button+input[type=button],button+input[type=submit],button+input[type=reset],button+a.button,input[type=button]+button,input[type=button]+input[type=button],input[type=button]+input[type=submit],input[type=button]+input[type=reset],input[type=button]+a.button,input[type=submit]+button,input[type=submit]+input[type=button],input[type=submit]+input[type=submit],input[type=submit]+input[type=reset],input[type=submit]+a.button,input[type=reset]+button,input[type=reset]+input[type=button],input[type=reset]+input[type=submit],input[type=reset]+input[type=reset],input[type=reset]+a.button,a.button+button,a.button+input[type=button],a.button+input[type=submit],a.button+input[type=reset],a.button+a.button{margin-left:12px}button:disabled,input[type=button]:disabled,input[type=submit]:disabled,input[type=reset]:disabled,a.button:disabled{cursor:default;opacity:.4}button .spinner:after,input[type=button] .spinner:after,input[type=submit] .spinner:after,input[type=reset] .spinner:after,a.button .spinner:after{border-color:#fff rgba(0,0,0,0) #fff rgba(0,0,0,0)}button:not(:disabled):active,input[type=button]:not(:disabled):active,input[type=submit]:not(:disabled):active,input[type=reset]:not(:disabled):active,a.button:not(:disabled):active{background-color:#2e3235;color:gray}button:not(:disabled):active .spinner:after,input[type=button]:not(:disabled):active .spinner:after,input[type=submit]:not(:disabled):active .spinner:after,input[type=reset]:not(:disabled):active .spinner:after,a.button:not(:disabled):active .spinner:after{border-color:gray rgba(0,0,0,0) gray rgba(0,0,0,0)}button.blue,input[type=button].blue,input[type=submit].blue,input[type=reset].blue,a.button.blue{background-color:#0d6efd;color:#fff}button.blue .spinner:after,input[type=button].blue .spinner:after,input[type=submit].blue .spinner:after,input[type=reset].blue .spinner:after,a.button.blue .spinner:after{border-color:#fff rgba(0,0,0,0) #fff rgba(0,0,0,0)}button.blue:not(:disabled):active,input[type=button].blue:not(:disabled):active,input[type=submit].blue:not(:disabled):active,input[type=reset].blue:not(:disabled):active,a.button.blue:not(:disabled):active{background-color:#07377f;color:gray}button.blue:not(:disabled):active .spinner:after,input[type=button].blue:not(:disabled):active .spinner:after,input[type=submit].blue:not(:disabled):active .spinner:after,input[type=reset].blue:not(:disabled):active .spinner:after,a.button.blue:not(:disabled):active .spinner:after{border-color:gray rgba(0,0,0,0) gray rgba(0,0,0,0)}button.green,input[type=button].green,input[type=submit].green,input[type=reset].green,a.button.green{background-color:#157347;color:#fff}button.green .spinner:after,input[type=button].green .spinner:after,input[type=submit].green .spinner:after,input[type=reset].green .spinner:after,a.button.green .spinner:after{border-color:#fff rgba(0,0,0,0) #fff rgba(0,0,0,0)}button.green:not(:disabled):active,input[type=button].green:not(:disabled):active,input[type=submit].green:not(:disabled):active,input[type=reset].green:not(:disabled):active,a.button.green:not(:disabled):active{background-color:#0b3a24;color:gray}button.green:not(:disabled):active .spinner:after,input[type=button].green:not(:disabled):active .spinner:after,input[type=submit].green:not(:disabled):active .spinner:after,input[type=reset].green:not(:disabled):active .spinner:after,a.button.green:not(:disabled):active .spinner:after{border-color:gray rgba(0,0,0,0) gray rgba(0,0,0,0)}button.red,input[type=button].red,input[type=submit].red,input[type=reset].red,a.button.red{background-color:#bb2d3b;color:#fff}button.red .spinner:after,input[type=button].red .spinner:after,input[type=submit].red .spinner:after,input[type=reset].red .spinner:after,a.button.red .spinner:after{border-color:#fff rgba(0,0,0,0) #fff rgba(0,0,0,0)}button.red:not(:disabled):active,input[type=button].red:not(:disabled):active,input[type=submit].red:not(:disabled):active,input[type=reset].red:not(:disabled):active,a.button.red:not(:disabled):active{background-color:#5e171e;color:gray}button.red:not(:disabled):active .spinner:after,input[type=button].red:not(:disabled):active .spinner:after,input[type=submit].red:not(:disabled):active .spinner:after,input[type=reset].red:not(:disabled):active .spinner:after,a.button.red:not(:disabled):active .spinner:after{border-color:gray rgba(0,0,0,0) gray rgba(0,0,0,0)}button.yellow,input[type=button].yellow,input[type=submit].yellow,input[type=reset].yellow,a.button.yellow{background-color:#ffc107;color:#000}button.yellow .spinner:after,input[type=button].yellow .spinner:after,input[type=submit].yellow .spinner:after,input[type=reset].yellow .spinner:after,a.button.yellow .spinner:after{border-color:#000 rgba(0,0,0,0) #000 rgba(0,0,0,0)}button.yellow:not(:disabled):active,input[type=button].yellow:not(:disabled):active,input[type=submit].yellow:not(:disabled):active,input[type=reset].yellow:not(:disabled):active,a.button.yellow:not(:disabled):active{background-color:#806104;color:#000}button.yellow:not(:disabled):active .spinner:after,input[type=button].yellow:not(:disabled):active .spinner:after,input[type=submit].yellow:not(:disabled):active .spinner:after,input[type=reset].yellow:not(:disabled):active .spinner:after,a.button.yellow:not(:disabled):active .spinner:after{border-color:#000 rgba(0,0,0,0) #000 rgba(0,0,0,0)}button .spinner,input[type=button] .spinner,input[type=submit] .spinner,input[type=reset] .spinner,a.button .spinner{display:none}button.working .caption,input[type=button].working .caption,input[type=submit].working .caption,input[type=reset].working .caption,a.button.working .caption{display:none}button.working .spinner,input[type=button].working .spinner,input[type=submit].working .spinner,input[type=reset].working .spinner,a.button.working .spinner{display:revert}input[type=submit],button.default,a.button.default{background-color:#9c0fb0;color:#fff}input[type=submit] .spinner:after,button.default .spinner:after,a.button.default .spinner:after{border-color:#fff rgba(0,0,0,0) #fff rgba(0,0,0,0)}input[type=submit]:not(:disabled):active,button.default:not(:disabled):active,a.button.default:not(:disabled):active{background-color:#4e0858;color:gray}input[type=submit]:not(:disabled):active .spinner:after,button.default:not(:disabled):active .spinner:after,a.button.default:not(:disabled):active .spinner:after{border-color:gray rgba(0,0,0,0) gray rgba(0,0,0,0)}.text-field,input[type=text],input[type=password],input[type=email],input[type=url],input[type=tel],input[type=search],input[type=number],textarea{-webkit-appearance:none;appearance:none;border-radius:.25rem;border:1px solid #b3b3b3;box-sizing:border-box;font-family:inherit;font-size:1rem;margin:0px;padding:.375rem .75rem;-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s;-webkit-transition-property:background-color,color,border-color,box-shadow;-o-transition-property:background-color,color,border-color,box-shadow;transition-property:background-color,color,border-color,box-shadow;-webkit-transition-timing-function:ease-in-out;-o-transition-timing-function:ease-in-out;transition-timing-function:ease-in-out;width:100%;line-height:1rem;background-clip:padding-box;background-color:#fff;color:#414141}.text-field::placeholder,input[type=text]::placeholder,input[type=password]::placeholder,input[type=email]::placeholder,input[type=url]::placeholder,input[type=tel]::placeholder,input[type=search]::placeholder,input[type=number]::placeholder,textarea::placeholder{line-height:1rem;vertical-align:bottom;padding-top:.3rem;color:#414141;opacity:.6}.text-field:focus,input[type=text]:focus,input[type=password]:focus,input[type=email]:focus,input[type=url]:focus,input[type=tel]:focus,input[type=search]:focus,input[type=number]:focus,textarea:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.text-field.invalid,input[type=text].invalid,input[type=password].invalid,input[type=email].invalid,input[type=url].invalid,input[type=tel].invalid,input[type=search].invalid,input[type=number].invalid,textarea.invalid{border-color:#e30c00}textarea{line-height:1.5}textarea::placeholder{padding-top:0px;line-height:1.5}.floating-label{position:relative;margin-top:16px;margin-bottom:16px}.floating-label>.text-field{padding:1rem .75rem;height:calc(3.5rem + 2px);line-height:1.25rem;box-sizing:border-box}.floating-label>.text-field::placeholder{color:rgba(0,0,0,0) !important}.floating-label>.text-field:focus,.floating-label>.text-field:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.floating-label>.text-field:focus~label,.floating-label>.text-field:not(:placeholder-shown)~label{transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.floating-label>.text-field:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.floating-label>.text-field:-webkit-autofill~label{transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.floating-label>div.select>select{padding:1.625rem 2.5rem .625rem .75rem;height:calc(3.5rem + 2px);line-height:1.25rem;box-sizing:border-box}.floating-label>label{position:absolute;top:0;left:0;height:100%;padding:1rem .75rem;pointer-events:none;border:1px solid rgba(0,0,0,0);transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out;box-sizing:border-box;opacity:.6}@media(prefers-reduced-motion: reduce){.floating-label>label{transition:none}}.floating-label>div.select+label{transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem);color:#fff}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%;margin-top:16px;margin-bottom:16px}.input-group>.text-field{position:relative;flex:1 1 auto;width:1%;min-width:0}.input-group>.text-field:focus{z-index:3}.input-group button{border:1px solid #b3b3b3;background-color:#f9f9f9;color:#414141}.input-group button .spinner:after{border-color:#414141 rgba(0,0,0,0) #414141 rgba(0,0,0,0)}.input-group button:not(:disabled):active{background-color:#7d7d7d;color:#212121}.input-group button:not(:disabled):active .spinner:after{border-color:#212121 rgba(0,0,0,0) #212121 rgba(0,0,0,0)}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5rem;text-align:center;white-space:nowrap;border:1px solid #b3b3b3;box-sizing:border-box;border-radius:.25rem;background-color:#f9f9f9}.input-group-sm{margin-top:12px;margin-bottom:12px}.input-group-sm>.text-field,.input-group-sm>.input-group-text{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.input-group-lg{margin-top:20px;margin-bottom:20px}.input-group-lg>.text-field,.input-group-lg>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.input-group>:not(:first-child){margin-left:-1px;border-top-left-radius:0;border-bottom-left-radius:0}.input-group>:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.field-pair label{padding-left:6px;line-height:1.6rem;color:#7a7a7a}label.invalid,span.invalid,p.invalid,div.invalid{color:#e30c00}label.checkbox input[type=checkbox]{display:none}label.checkbox span{display:inline-block;vertical-align:middle;margin:6px;position:relative;border-radius:1rem;-webkit-transition-property:background-color,border-color;-o-transition-property:background-color,border-color;transition-property:background-color,border-color;-webkit-transition-timing-function:ease-out;-o-transition-timing-function:ease-out;transition-timing-function:ease-out;-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s;border-width:2px;border-style:solid;background-color:rgba(156,15,176,0);border-color:#9c0fb0}label.checkbox span:after{background-color:#9c0fb0}label.checkbox :checked:active+span{background-color:#490752;border-color:#490752}label.checkbox :checked:active+span:after{background-color:#ccc}label.checkbox :checked+span{background-color:#9c0fb0}label.checkbox :checked+span:after{background-color:#fff}label.checkbox :active+span{background-color:#ccc;border-color:#490752}label.checkbox :active:after{background-color:#490752}label.checkbox :disabled+span{opacity:.4}label.checkbox span{width:42px;height:20px}label.checkbox span:after{position:absolute;top:3px;left:3px;width:14px;height:14px;border-radius:14px;display:block;content:"";-webkit-transition-property:left,background-color;-o-transition-property:left,background-color;transition-property:left,background-color;-webkit-transition-timing-function:ease-out;-o-transition-timing-function:ease-out;transition-timing-function:ease-out;-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s}label.checkbox :checked+span:after{left:26px}.input-radio{display:block;min-height:1.5rem;padding-left:1.4em;box-sizing:border-box;margin:0px}.input-radio input{-webkit-appearance:none;appearance:none;border:1px solid #a0a0a0;background-color:#fff;width:1em;height:1em;border-radius:50%;vertical-align:top;transition-duration:.15s;transition-property:border-color,border-width;transition-timing-function:ease-in-out;margin-top:.25em;margin-left:-1.4em;float:left;box-sizing:border-box;color-adjust:exact}.input-radio input:checked{border-width:.3em;border-color:#9c0fb0}.input-radio label{display:inline-block;box-sizing:border-box}.input-radio+.input-radio{margin-left:16px}div.select{position:relative;display:inline-block}div.select select{-webkit-appearance:none;-moz-appearance:none;appearance:none;min-height:22px;border:none;border-radius:.25rem;display:inline-block;font-family:inherit;font-size:1rem;text-decoration:none;-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s;-webkit-transition-property:background-color;-o-transition-property:background-color;transition-property:background-color;-webkit-transition-timing-function:ease-out;-o-transition-timing-function:ease-out;transition-timing-function:ease-out;vertical-align:middle;white-space:nowrap;padding:.375rem 2.5rem .375rem .75rem;line-height:1.5em}div.select select:focus{font-size:1rem}div.select span:after{position:absolute;top:calc(50% - .5em);right:.75rem;content:"▾";font-size:1rem;pointer-events:none;line-height:.9em}div.select select{background-color:#9c0fb0;color:#fff}div.select select:not(:disabled):active{background-color:#4e0858;color:gray}div.select span:after{color:#fff}div.select.gray select{background-color:#5c636a;color:#fff}div.select.gray select:not(:disabled):active{background-color:#2e3235;color:gray}div.select.gray span:after{color:#fff}div.select.blue select{background-color:#0d6efd;color:#fff}div.select.blue select:not(:disabled):active{background-color:#07377f;color:gray}div.select.blue span:after{color:#fff}div.select.green select{background-color:#157347;color:#fff}div.select.green select:not(:disabled):active{background-color:#0b3a24;color:gray}div.select.green span:after{color:#fff}div.select.red select{background-color:#bb2d3b;color:#fff}div.select.red select:not(:disabled):active{background-color:#5e171e;color:gray}div.select.red span:after{color:#fff}div.select.yellow select{background-color:#ffc107;color:#000}div.select.yellow select:not(:disabled):active{background-color:#806104;color:#000}div.select.yellow span:after{color:#000}div.select.small select{border-radius:.2rem;font-size:.875rem;padding:.25rem 1.875rem .25rem .5rem}div.select.small span:after{top:.25rem;right:.5rem;font-size:.875rem}div.select.large select{border-radius:.3rem;font-size:1.25rem;padding:.5rem 3.25rem .5rem 1rem}div.select.large span:after{top:.25rem;right:1rem;font-size:1.25rem}#explore_container{display:none;z-index:1000;position:fixed;top:0px;bottom:0px;left:0px;right:0px;-webkit-tap-highlight-color:rgba(0,0,0,0)}#explore_popover{position:absolute;width:260px;top:20px;left:20px;box-shadow:0px 5px 5px rgba(0,0,0,.05);border-radius:4px;padding:9px;border:1px solid #8f8f8f;-webkit-tap-highlight-color:rgba(0,0,0,0);background-color:#fff;border-color:#e3e3e3}@media(min-width: 350px){#explore_popover{width:310px}}#explore_popover #explore_search_field{margin-bottom:9px}#explore_popover #explore_results{display:none}#explore_popover #explore_results ul{max-height:250px;overflow-y:auto}#explore_popover #explore_results_empty{display:none;text-align:center}#explore_popover #explore_results_buttons{display:-webkit-box;display:-ms-flexbox;display:flex;margin-top:9px;width:100%}#explore_popover #explore_results_buttons #explore_results_left_button{-webkit-box-flex:1;-ms-flex:1 0 0px;flex:1 0 0;text-align:left}#explore_popover #explore_results_buttons #explore_results_right_button{-webkit-box-flex:1;-ms-flex:1 0 0px;flex:1 0 0;text-align:right}#explore_popover ul{list-style-type:none;margin:0px;padding:0px;overflow:hidden}#explore_popover ul li a{text-decoration:none;border-radius:4px;padding:4px 6px;-webkit-transition-property:color,background-color;-o-transition-property:color,background-color;transition-property:color,background-color;-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s;-webkit-transition-timing-function:ease-out;-o-transition-timing-function:ease-out;transition-timing-function:ease-out;display:block;color:#414141}#explore_popover ul li a .result_preview{font-size:10pt;color:rgba(65,65,65,.5)}#explore_popover ul li a .result_type{font-size:9pt;margin-left:12px;float:right;border-width:1px;border-style:solid;padding:2px 4px;border-radius:4px;color:rgba(65,65,65,.4);border-color:rgba(65,65,65,.2)}#explore_popover ul li a:hover{color:#fff;background-color:#9c0fb0}#explore_popover ul li a:hover .result_preview{color:#fff}#explore_popover ul li a:hover .result_type{color:#fff;border-color:#fff}#explore_popover ul li+li{margin-top:4px}#menu_explore_link.expanded{border-radius:4px 4px 0px 0px;background-color:#e3e3e3;color:#414141}div.double_column,div.triple_column{display:table;width:100%;border-spacing:30px;border-collapse:separate}div.double_column div.column,div.triple_column div.column{display:table-row;width:100%;vertical-align:top}div.results span.result_type{font-weight:500;margin:.8em;color:rgba(65,65,65,.35)}div.results div.result{border:1px solid #e4e4e4;padding:.8em;border-radius:.5em;border-color:rgba(65,65,65,.15)}div.results div.result+div.result{margin-top:.8em}div.results div.result span.summary{font-size:.9em;color:rgba(65,65,65,.65)}#overlay{display:none;position:fixed;top:0px;bottom:0px;left:0px;right:0px;transition-duration:.25s;transition-timing-function:ease-out;transition-property:background-color,-webkit-backdrop-filter,backdrop-filter;z-index:2000;background-color:rgba(0,0,0,0);-webkit-tap-highlight-color:rgba(0,0,0,0);background-color:rgba(0,0,0,0)}@supports(-webkit-backdrop-filter: blur(0px)){#overlay{-webkit-backdrop-filter:blur(0px);background-color:rgba(0,0,0,0)}}@supports(backdrop-filter: blur(0px)){#overlay{backdrop-filter:blur(0px);background-color:rgba(0,0,0,0)}}#overlay.exist{display:block}#overlay.visible{background-color:rgba(0,0,0,.8)}@supports(-webkit-backdrop-filter: blur(15px)){#overlay.visible{-webkit-backdrop-filter:blur(15px);background-color:rgba(0,0,0,.3)}}@supports(backdrop-filter: blur(15px)){#overlay.visible{backdrop-filter:blur(15px);background-color:rgba(0,0,0,.3)}}#dialog{display:none;position:fixed;top:30%;left:0px;right:0px;z-index:2001;opacity:0;transition-duration:.25s;transition-timing-function:ease-out;transition-property:opacity,filter;filter:blur(30px);background-color:#fff}#dialog.exist{display:block}#dialog.visible{opacity:1;filter:none}#dialog p{margin:20px}#dialog_inner{width:300px;margin-left:auto;margin-right:auto}#dialog_message{font-weight:600;text-align:left}#dialog_explanation{text-align:left;font-size:smaller}#dialog_buttons{text-align:center}.modal{display:none;opacity:1;transition-duration:.25s;transition-timing-function:ease-out;transition-property:opacity,top;z-index:2001;position:fixed;top:100%;left:0px;width:100%;min-width:320px;height:100%;padding:0px;box-shadow:0px 4px 16px rgba(0,0,0,.45);box-sizing:border-box;line-height:1.5rem;background-color:#fff}.modal.exist{display:block}.modal.visible{top:0%}.modal .modal-content{display:flex;flex-direction:column;box-sizing:border-box;max-height:100vh}.modal .modal-content .title-bar{font-weight:600;flex:0 0 auto;box-sizing:border-box;margin-top:max(env(safe-area-inset-top, 20px),20px);margin-left:max(env(safe-area-inset-left, 20px),20px);margin-right:max(env(safe-area-inset-right, 20px),20px);margin-bottom:20px}.modal .modal-content .content{flex:1 1 auto;overflow:auto;box-sizing:border-box;padding-top:20px;padding-left:max(env(safe-area-inset-left, 20px),20px);padding-right:max(env(safe-area-inset-right, 20px),20px);padding-bottom:20px;border-top:1px solid #e3e3e3;border-bottom:1px solid #e3e3e3}.modal .modal-content .button-bar{display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:1rem;flex:0 0 auto;box-sizing:border-box;margin-top:20px;margin-left:max(env(safe-area-inset-left, 20px),20px);margin-right:max(env(safe-area-inset-right, 20px),20px);margin-bottom:max(env(safe-area-inset-bottom, 20px),20px)}.modal .modal-content .button-bar .left{text-align:left;flex:1 1 auto}.modal .modal-content .button-bar .middle{text-align:center;flex:1 1 auto}.modal .modal-content .button-bar .right{text-align:right;flex:1 1 auto}.spinner{display:inline-block;width:30px;height:30px}.spinner.large{width:65px;height:65px}.spinner.large:after{width:52px;height:52px;border-width:4.875px}.spinner:after{width:24px;height:24px;border-width:2.25px}.spinner.small{width:20px;height:20px}.spinner.small:after{width:16px;height:16px;border-width:1.5px}.spinner:after{content:" ";display:block;border-radius:50%;border-style:solid;animation:spinner-keyframes 1.2s linear infinite;border-color:#9c0fb0 rgba(0,0,0,0) #9c0fb0 rgba(0,0,0,0)}.spinner.hidden{display:none}@keyframes spinner-keyframes{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}#account_toolbar_menu div a{color:#9c0fb0;background-color:none}#account_toolbar_menu div.active{background-color:#9c0fb0}#account_toolbar_menu div.active a{color:#fff}#knowledge_wrapper{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:column-reverse;-ms-flex-direction:column-reverse;flex-direction:column-reverse;-ms-flex-wrap:nowrap;flex-wrap:nowrap}#knowledge_wrapper #knowledge_main>*{margin-top:.5rem;margin-bottom:.5rem}#knowledge_wrapper #knowledge_main>*:first-child{margin-top:0px}#knowledge_wrapper #knowledge_main>*:last-child{margin-bottom:0px}#knowledge_wrapper #knowledge_main h2,#knowledge_wrapper #knowledge_main h3{border-bottom-width:1px;border-bottom-style:solid;padding-bottom:.3rem;border-color:#f1f1f1}#knowledge_wrapper #knowledge_main code{padding:0px 5px;border-radius:4px;line-height:1.4rem}#knowledge_wrapper #knowledge_main pre code{padding:0px}#knowledge_wrapper #knowledge_main .affected_ini_keys{border-top-width:1px;border-top-style:solid;font-size:.8em;padding:15px;margin-top:20px;font-weight:300;background-color:#fbfbfb;text-shadow:0px 1px 0px #fff;border-top-color:#f3f3f3}#knowledge_wrapper #knowledge_contents{border-top-width:1px;border-top-style:solid;margin-top:30px;padding-top:30px;font-size:.9em;border-color:#e3e3e3}#knowledge_wrapper #knowledge_contents #knowledge_search_block{text-align:center;margin-bottom:30px}#knowledge_wrapper #knowledge_contents #knowledge_version{text-align:center;margin-bottom:20px;font-size:11pt;font-weight:600;color:#a0a0a0}#knowledge_wrapper #knowledge_contents p{margin-top:0px;margin-bottom:0px;padding-left:1em;text-indent:-1em;font-size:1.1em;font-weight:500}#knowledge_wrapper #knowledge_contents ul{margin-top:4px;list-style:none;padding-left:0px}#knowledge_wrapper #knowledge_contents ul+p{margin-top:12px}#knowledge_wrapper #knowledge_contents ul li{padding-left:10px;padding-top:4px;padding-bottom:4px;line-height:1.4rem}#knowledge_wrapper #knowledge_contents ul li.current{padding-left:7px;border-left-width:3px;border-left-style:solid;color:#9c0fb0;border-left-color:#9c0fb0}#knowledge_wrapper #knowledge_contents ul li.current::before{color:#c6c6c6}#knowledge_wrapper #knowledge_contents ul li+li{border-top-width:1px;border-top-style:solid;border-top-color:#f7f7f7}#knowledge_wrapper #knowledge_contents ul li:first-child{padding-top:0px}#knowledge_wrapper #knowledge_contents ul li:last-child{padding-bottom:0px}#knowledge_wrapper #knowledge_contents ul li a{text-decoration:none}#knowledge_wrapper #knowledge_contents ul li a:hover{text-decoration:underline}p.help-summary{border-bottom-width:1px;border-bottom-style:dotted;font-size:1.1em;padding:0px 0px 10px 0px;border-bottom-color:#e3e3e3}ul.object_list{list-style:none;padding-left:15px}ul.object_list li+li{margin-top:10px}ul.no-markings,ol.no-markings{list-style:none}.notice-block{border-width:1px;border-style:solid;text-align:center;padding:16px}.notice-block+.notice-block{margin-top:1rem}.notice-block.notice-warning{border-color:#f5aaa6;color:#e30c00;background-color:#fef8f7}.notice-block.notice-caution{border-color:#d3c9a6;color:#806600;background-color:#fbfaf7}.notice-block.notice-info{border-color:#a6ccf6;color:#006ee6;background-color:#f7fbfe}.subsection{border-width:1px;border-style:solid;padding:16px;border-color:rgba(65,65,65,.1)}.downloads-table .row{padding:10px;display:flex;align-items:center;border-width:1px;border-style:solid;border-top-width:0px;border-color:#e3e3e3}.downloads-table .row:first-child{border-top-left-radius:10px;border-top-right-radius:10px;border-top-width:1px;background-color:#9c0fb0;color:#fff;border-color:#9c0fb0}.downloads-table .row:last-child{border-bottom-left-radius:10px;border-bottom-right-radius:10px}.downloads-table .row .label{flex:1 1}.downloads-table .row .button{flex:0 0 80px;margin-left:10px}.downloads-table .row .header{line-height:1rem}.downloads-table .row .games{opacity:.7;font-size:small}.downloads-table+.downloads-table{margin-top:20px}.mini-only{display:revert}.mobile-only{display:none}.desktop-only{display:none}.small-only{display:revert}.not-mini{display:none}.large-only{display:none}.embedded_youtube_video{position:relative;padding-bottom:56.25%;padding-top:30px;height:0;overflow:hidden}.embedded_youtube_video iframe,.embedded_youtube_video object,.embedded_youtube_video embed{position:absolute;top:0;left:0;width:100%;height:100%}div.page-panel{display:flex;flex-direction:column;gap:20px}@supports not (selector(:first-child)){div.page-panel>*{margin:10px}}div.page-panel div.page-panel-nav ul{display:flex;flex-direction:row;margin:0px;padding:0px;gap:10px;justify-content:center;align-items:stretch;flex-wrap:wrap}@supports not (selector(:first-child)){div.page-panel div.page-panel-nav ul>*{margin:5px}}div.page-panel div.page-panel-nav ul li{list-style:none}div.page-panel div.page-panel-nav ul li a{text-decoration:none;display:inline-block;padding:.375rem .75rem;border-radius:.25rem;white-space:nowrap;background-color:none;color:#9c0fb0;line-height:1.5em;font-size:1rem}div.page-panel div.page-panel-nav ul li.page-panel-active a{background-color:#9c0fb0;color:#fff}div.page-panel div.page-panel-page{display:none}div.page-panel div.page-panel-visible{display:block}div.page-panel div.page-panel-footer{display:none}div.flex-grid{display:flex;flex-wrap:wrap;flex-direction:row;justify-content:flex-start;align-items:flex-start;align-content:flex-start}div.flex-grid div.flex-grid-item{flex:0 0 auto}img.avatar{border-radius:10px;box-shadow:0px 2px 4px 0px rgba(0,0,0,.25);display:block}.pagination-controls{display:flex;gap:10px;justify-content:center;margin-top:20px;flex-wrap:wrap}.pagination-controls .pagination-button{display:block;padding:3px 6px;border:1px solid #000;border-radius:6px;text-align:center;white-space:nowrap;border-color:#f6f6f6;background-color:#fbfbfb;text-decoration:none}.pagination-controls .pagination-current{background-color:#9c0fb0;color:#fff;border-color:#9c0fb0}.pagination-controls .pagination-placeholder{display:block}.pagination-controls .pagination-text,.pagination-controls .pagination-placeholder{min-width:50px}.pagination-controls a.pagination-button:hover{background-color:#2472cb;color:#fff;border-color:#2472cb}@media(min-width: 400px){#header_logo{height:80px}}@media(min-width: 635px){html{font-size:16px}h1{font-size:19.2px}h2{font-size:17.6px}h3{font-size:16px}#header_wrapper{padding-top:20px;padding-bottom:20px}#header{-ms-flex-wrap:nowrap;flex-wrap:nowrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center}#header_logo_cell{-ms-flex-preferred-size:80px;flex-basis:80px}#header_links_cell{height:auto}#header_links_cell ul{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;margin:0px}#header_links_cell li{-webkit-box-flex:0;-ms-flex:0 0 0px;flex:0 0 0}:target:before{height:140px;margin:-140px 0 0}.mini-only{display:none}.mobile-only{display:revert}.not-mini{display:revert}}div.comment-block{text-align:center;font-size:small;margin:20px auto;max-width:400px;border-width:1px;border-style:solid;border-radius:4pt;padding:10pt;display:flex;justify-content:center;align-items:center;background-color:rgba(65,65,65,.02);border-color:rgba(65,65,65,.1);color:rgba(65,65,65,.7)}div.comment-block div.icon{margin-right:10pt}div.comment-block div.icon img{margin:0px;display:block}#mode_tabs div.selected,#mode_view,#mode_customizations{background-color:#ececec}#mode_tabs_new,#mode_tabs_paste,#mode_tabs_upload{background-color:#f6f6f6}#browse_results div.properties-text{color:rgba(65,65,65,.5);border-color:rgba(65,65,65,.1)}input.no-stepper::-webkit-outer-spin-button,input.no-stepper::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}input.no-stepper[type=number]{-moz-appearance:textfield}#checkout-wizard-list>.checkout-wizard-list-game{display:flex;align-items:center}#checkout-wizard-list>.checkout-wizard-list-game.separated{margin-top:20px;padding-top:20px;border-top:1px solid #e3e3e3}#checkout-wizard-list>.checkout-wizard-list-game .checkout-wizard-checkbox-cell{flex:0 1 auto}#checkout-wizard-list>.checkout-wizard-list-game .checkout-wizard-description-cell{flex:1 1 auto;padding-left:10px;padding-right:10px}#checkout-wizard-list>.checkout-wizard-list-game .checkout-wizard-description-cell>div>label{font-weight:bold}#checkout-wizard-list>.checkout-wizard-list-game .checkout-wizard-description-cell .checkout-wizard-status{font-size:.8em;opacity:.8}#checkout-wizard-list>.checkout-wizard-list-game .checkout-wizard-description-cell .checkout-wizard-promo{font-weight:bold;color:#177826;font-size:.9em}#checkout-wizard-list>.checkout-wizard-list-game .checkout-wizard-description-cell div.field-with-label{display:inline-flex;align-items:center;gap:12px}#checkout-wizard-list>.checkout-wizard-list-game .checkout-wizard-description-cell div.field-with-label>div:nth-child(1){flex:0 0 auto;text-align:right}#checkout-wizard-list>.checkout-wizard-list-game .checkout-wizard-description-cell div.field-with-label>div:nth-child(2){flex:0 0 60px}#checkout-wizard-list>.checkout-wizard-list-game .checkout-wizard-description-cell div.field-with-label>div:nth-child(3){flex:0 0 auto}#checkout-wizard-list>.checkout-wizard-list-game .checkout-wizard-price-cell{flex:0 1 auto}#checkout-wizard-list>.checkout-wizard-list-game .checkout-wizard-price-cell .checkout-wizard-discounted{text-decoration:line-through}#storefront .storefront-cart-section{box-sizing:border-box;max-width:800px;margin-left:auto;margin-right:auto}#storefront #storefront-cart-asanotice{margin-bottom:1rem}#storefront #storefront-cart-header{display:flex;align-items:center;border-bottom:1px solid #e3e3e3;padding-bottom:1rem;margin-bottom:1rem}#storefront #storefront-cart-header div:nth-child(1){text-align:left;flex:0 1 auto}#storefront #storefront-cart-header div:nth-child(2){text-align:right;margin-left:1rem;margin-right:1rem;flex:1 1 auto;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}#storefront #storefront-cart-header div:nth-child(3){text-align:right;flex:0 1 auto}#storefront>div{webkit-transition-property:opacity;-o-transition-property:opacity;transition-property:opacity;-webkit-transition-timing-function:linear;-o-transition-timing-function:linear;transition-timing-function:linear;-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s}#storefront #storefront-cart{margin-top:1rem;margin-bottom:1rem}#storefront #storefront-cart.empty{display:flex;flex-direction:column;min-height:300px}#storefront #storefront-cart.empty div:nth-child(1){flex:1 1 30%}#storefront #storefront-cart.empty div:nth-child(2){flex:0 0 auto;text-align:center}#storefront #storefront-cart.empty div:nth-child(3){flex:1 1 70%}#storefront #storefront-cart .bundle{border:1px solid #e3e3e3;border-radius:.25rem;margin:1rem;padding:1rem}#storefront #storefront-cart .bundle.gift{border-color:skyblue;background-color:#f3fafd;color:#506c77}#storefront #storefront-cart .bundle .bundle-product{display:flex;align-items:center;gap:1rem}#storefront #storefront-cart .bundle .bundle-product div:nth-child(1){flex:0 0 5%;text-align:center}#storefront #storefront-cart .bundle .bundle-product div:nth-child(2){flex:1 1 auto}#storefront #storefront-cart .bundle .bundle-product div:nth-child(3){flex:0 0 20%;text-align:right}#storefront #storefront-cart .bundle>div:not(:last-child){margin-bottom:.25rem}#storefront #storefront-cart .bundle .gift{font-size:.75em;font-weight:600;color:#186c8e}#storefront #storefront-cart .bundle .actions{margin-top:1rem}#storefront #storefront-cart-footer{padding-top:1rem;border-top:1px solid #e3e3e3;margin-top:1rem}#storefront #storefront-cart-footer .storefront-cart-totals{margin-bottom:1rem}#storefront #storefront-cart-footer .storefront-cart-totals .storefront-cart-total-row{display:flex;flex-wrap:wrap;justify-content:flex-end}#storefront #storefront-cart-footer .storefront-cart-totals .storefront-cart-total-row div:nth-child(1){text-align:right;flex:0 0 auto}#storefront #storefront-cart-footer .storefront-cart-totals .storefront-cart-total-row div:nth-child(2){text-align:right;flex:0 0 120px}#storefront #storefront-cart-footer .storefront-cart-totals .storefront-cart-total-row+.storefront-cart-total-row{margin-top:.5rem}#storefront #storefront-cart-footer .storefront-cart-paymethods{display:flex;gap:.4rem;align-items:center;justify-content:center;margin-top:2rem;flex-wrap:wrap}#storefront #storefront-cart-footer .storefront-cart-paymethods img{width:2.5rem;margin:0px;display:block}#storefront #storefront-cart-footer .storefront-cart-notice{text-align:center;margin-top:2rem;font-size:.8rem}#storefront #storefront-cart-footer .storefront-button-row{margin-top:2rem}#storefront #storefront-cart-footer .storefront-refund-notice{max-width:600px;font-size:.8rem;margin-top:2rem;margin-left:auto;margin-right:auto}.shake{animation:shake-keyframes .4s linear 1}.omni-game-header{display:flex;align-items:center;margin-bottom:1.5rem}.omni-game-header .omni-game-header-title{font-weight:600;font-size:1.5rem;margin-right:1.5rem;flex:1 1 auto}.omni-game-header .omni-game-header-button{flex:0 0 auto;align-self:flex-start}@keyframes shake-keyframes{0%{transform:translate(10px)}20%{transform:translate(-10px)}40%{transform:translate(5px)}80%{transform:translate(-5px)}100%{transform:translate(0px)}}.header-with-subtitle{margin-bottom:1rem}.header-with-subtitle h1,.header-with-subtitle h2,.header-with-subtitle h3,.header-with-subtitle h4,.header-with-subtitle h5,.header-with-subtitle h6{margin-bottom:0px;margin-top:0px}.header-with-subtitle .subtitle{font-size:.8rem;line-height:1rem;display:block}.beacon-engram-mod-name{font-size:14px;color:rgba(65,65,65,.5)}body.no-navigation #header_wrapper,body.no-navigation #footer{display:none}body.no-navigation #content_wrapper{margin-top:max(20px,env(safe-area-inset-top, 20px));margin-bottom:max(20px,env(safe-area-inset-bottom, 20px))}.breadcrumbs{border-radius:.2rem;display:flex;gap:.5rem;background-color:#fbfbfb;font-size:14px;padding:.25rem .5rem;flex-wrap:wrap}.breadcrumbs .breadcrumb{display:inline-block;white-space:nowrap}.breadcrumbs .divider{display:inline-block;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23c3c3c3' class='bi bi-chevron-right' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3E%3C/svg%3E");background-position:center;background-repeat:no-repeat;background-size:16px 16px;width:8px}.license-name{color:#9c0fb0}.game-name{color:#006ee6}@media(min-width: 840px){div.double_column div.column{display:table-cell;width:50%}div.triple_column div.column{display:table-cell;width:33%}#knowledge_wrapper{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}#knowledge_wrapper #knowledge_main{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;padding-left:20px;width:980px}#knowledge_wrapper #knowledge_contents{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;width:220px;border-right-width:1px;border-right-style:solid;border-top:none;padding-right:20px;padding-top:0px;margin-top:0px}table.generic div.row-details{display:none}table.generic .low-priority{display:table-cell}.mini-only{display:none}.desktop-only{display:revert}.small-only{display:none}.large-only{display:revert}div.page-panel{flex-direction:row}div.page-panel div.page-panel-nav{flex:1 1 0}div.page-panel div.page-panel-nav ul{flex-direction:column}div.page-panel div.page-panel-nav ul li{text-align:right}div.page-panel div.page-panel-pages{flex:2 2 600px}div.page-panel div.page-panel-footer{flex:1 1 0;display:block}.modal{opacity:0;top:35%;left:50%;width:600px;margin-left:-300px;margin-right:auto;transform:translateY(-50%);border-radius:.25rem;margin-left:-300px;margin-right:auto;height:auto}.modal.visible{top:40%;opacity:1}.modal.centered{top:45%}.modal.centered.visible{top:50%}.modal .modal-content .title-bar{margin:0px;padding:22px 22px 0px 22px}.modal .modal-content .content{border:none;padding:22px}.modal .modal-content .button-bar{margin:0px;padding:0px 22px 22px 22px}.modal.scrolled{border-radius:0px}.modal.scrolled .modal-content .title-bar{padding:15px 22px 15px 22px;border-bottom:1px solid #e3e3e3}.modal.scrolled .modal-content .content{padding:14px 22px}.modal.scrolled .modal-content .button-bar{padding:15px 22px 15px 22px;border-top:1px solid #e3e3e3}}@media(min-width: 840px)and (max-width: 640px){.modal{left:20px;width:auto;right:20px;margin:0px;top:45%}.modal.visible{top:50%}}@media print{#header,#header_wrapper,#footer{display:none}#content_wrapper{margin-top:max(20px,env(safe-area-inset-top, 20px));margin-bottom:max(20px,env(safe-area-inset-bottom, 20px))}table.generic{border-color:#d9d9d9}table.generic td,table.generic th{border-color:inherit;background-color:#fff}table.generic thead,table.generic tr.header{background-color:#fff;color:#000}table.generic thead td,table.generic thead th,table.generic tr.header td,table.generic tr.header th{border-color:#000;color:inherit;font-weight:600}}.accented-foreground{filter:invert(22%) sepia(68%) saturate(2401%) hue-rotate(271deg) brightness(88%) contrast(105%)}.dark-only{display:none}.light-only{display:unset} diff --git a/Website/www/assets/css/theme-purple.css b/Website/www/assets/css/theme-purple.css index 911317ca5..8f69bf600 100644 --- a/Website/www/assets/css/theme-purple.css +++ b/Website/www/assets/css/theme-purple.css @@ -1 +1 @@ -/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}button,html [type=button],[type=reset],[type=submit]{-webkit-appearance:button}button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}template{display:none}[hidden]{display:none}@font-face{font-family:"Source Code Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcecodepro/4c416e66-c769-51a5-a337-ad832ad2e74a.woff) format("woff");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:"Source Code Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcecodepro/0842acdd-1516-53e8-a07d-ded3b64aba16.woff) format("woff");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:"Source Code Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcecodepro/7c7d8d1f-3387-5d0f-9110-4b3a57111e3a.woff) format("woff");unicode-range:U+1F00-1FFF}@font-face{font-family:"Source Code Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcecodepro/f13cc6a0-67e3-5260-b676-7f411573d880.woff) format("woff");unicode-range:U+0370-03FF}@font-face{font-family:"Source Code Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcecodepro/8a252637-447c-5992-bbab-0f6d8488e53e.woff) format("woff");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:"Source Code Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcecodepro/6e6237d0-47c2-532a-8c66-b8a1f411afbb.woff) format("woff");unicode-range:U+0100-02AF,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Source Code Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcecodepro/fb7509bc-2466-5fd4-a06a-4e9769262bed.woff) format("woff");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:"Source Sans Pro";font-style:italic;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/cd80694f-b33d-508d-907e-7544db677936.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:"Source Sans Pro";font-style:italic;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/78f748da-0e7b-5feb-aeae-a6b3b7a72fbb.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:"Source Sans Pro";font-style:italic;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/0f3938f3-e96b-588f-af3a-ba8b244f5c62.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:"Source Sans Pro";font-style:italic;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/b95003c5-6216-5e4c-a68b-40a22643520d.woff2) format("woff2");unicode-range:U+0370-03FF}@font-face{font-family:"Source Sans Pro";font-style:italic;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/83549da6-17a0-5f7e-afb7-bfd2913cebc8.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:"Source Sans Pro";font-style:italic;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/bae9ad58-d401-5d8d-8885-e653187e8945.woff2) format("woff2");unicode-range:U+0100-02AF,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Source Sans Pro";font-style:italic;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/56c68f48-8586-5f86-a831-5341d8f8893e.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:300;font-display:swap;src:url(/assets/fonts/sourcesanspro/049831a8-098c-5e17-b9b7-3d4d147220b8.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:300;font-display:swap;src:url(/assets/fonts/sourcesanspro/099262aa-4d57-59f2-95f5-89baa13aeb31.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:300;font-display:swap;src:url(/assets/fonts/sourcesanspro/b4c545b6-4511-5a9f-8754-22b7acf2d1cf.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:300;font-display:swap;src:url(/assets/fonts/sourcesanspro/90df22a0-015c-52a7-9cbf-ddd09ade4fbd.woff2) format("woff2");unicode-range:U+0370-03FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:300;font-display:swap;src:url(/assets/fonts/sourcesanspro/2134ded7-88ee-5959-bb8d-8be588893e60.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:300;font-display:swap;src:url(/assets/fonts/sourcesanspro/ba0a93ec-d1e4-5657-808d-e3c06cac07d3.woff2) format("woff2");unicode-range:U+0100-02AF,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:300;font-display:swap;src:url(/assets/fonts/sourcesanspro/611734bb-b7fa-5cd8-be7e-0185b98d7968.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/03db3c69-1325-596b-a3e5-1ab821511b04.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/36c309c3-ea5a-583d-808b-4001bca13238.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/b6fd8ba7-a279-5d7d-9bcb-f85971b2980f.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/7c2623bd-f50f-5fc6-b037-d2cf4948a4a4.woff2) format("woff2");unicode-range:U+0370-03FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/a8b3a121-1cae-5965-8e16-0aa443bda51d.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/16115b0a-c537-59b3-b8af-70ec055da4b2.woff2) format("woff2");unicode-range:U+0100-02AF,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/9a0511ad-4231-545f-8ef4-f35d35fbb60c.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/sourcesanspro/bdde82bb-f944-551f-836a-4c05e0f1573f.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/sourcesanspro/d26025f7-d2e5-54d4-aa4d-2e25b1c8c4cf.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/sourcesanspro/15f136cb-e12c-5694-887e-1dc9ed9259a4.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/sourcesanspro/87121b63-28d6-5847-8a68-1218b8ba3738.woff2) format("woff2");unicode-range:U+0370-03FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/sourcesanspro/ba62d068-2dab-5fb6-9e0e-b2ce6830c777.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/sourcesanspro/a1fa33d8-a2d4-5fdb-988a-69860d54fcef.woff2) format("woff2");unicode-range:U+0100-02AF,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/sourcesanspro/fdc908e0-7ba0-5e1b-be7c-7b60913a7e89.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sourcesanspro/6921b0ca-4eb6-5888-896b-f5e043beb61e.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sourcesanspro/9b3d0111-ea94-5ed8-85ee-c4f8c0838f54.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sourcesanspro/6bcf4d01-711e-5b49-a4ae-4b72c5a950c6.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sourcesanspro/1eafcd61-b347-5326-aa01-28314ba55db8.woff2) format("woff2");unicode-range:U+0370-03FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sourcesanspro/6c0dcb70-dff7-591a-a43d-3d94def35579.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sourcesanspro/eb85d155-2a94-58f6-b344-3c0a4747ee4d.woff2) format("woff2");unicode-range:U+0100-02AF,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sourcesanspro/b58ffa91-9940-5088-8866-d6f8bce9c45d.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}.w-0{width:0%}.w-10{width:10%}.w-20{width:20%}.w-25{width:25%}.w-30{width:30%}.w-40{width:40%}.w-50{width:50%}.w-60{width:60%}.w-70{width:70%}.w-75{width:75%}.w-80{width:80%}.w-90{width:90%}.w-100{width:100%}.m-0{margin:0px !important}.m-1{margin:.25rem !important}.m-2{margin:.5rem !important}.m-3{margin:1rem !important}.m-4{margin:1.5rem !important}.m-5{margin:3rem !important}.my-0{margin-top:0px !important;margin-bottom:0px !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-5{margin-top:3rem !important;margin-bottom:3rem !important}.mx-0{margin-left:0px !important;margin-right:0px !important}.mx-1{margin-left:.25rem !important;margin-right:.25rem !important}.mx-2{margin-left:.5rem !important;margin-right:.5rem !important}.mx-3{margin-left:1rem !important;margin-right:1rem !important}.mx-4{margin-left:1.5rem !important;margin-right:1.5rem !important}.mx-5{margin-left:3rem !important;margin-right:3rem !important}.mt-0{margin-top:0px !important}.mt-1{margin-top:.25rem !important}.mt-2{margin-top:.5rem !important}.mt-3{margin-top:1rem !important}.mt-4{margin-top:1.5rem !important}.mt-5{margin-top:3rem !important}.ml-0{margin-left:0px !important}.ml-1{margin-left:.25rem !important}.ml-2{margin-left:.5rem !important}.ml-3{margin-left:1rem !important}.ml-4{margin-left:1.5rem !important}.ml-5{margin-left:3rem !important}.mr-0{margin-right:0px !important}.mr-1{margin-right:.25rem !important}.mr-2{margin-right:.5rem !important}.mr-3{margin-right:1rem !important}.mr-4{margin-right:1.5rem !important}.mr-5{margin-right:3rem !important}.mb-0{margin-bottom:0px !important}.mb-1{margin-bottom:.25rem !important}.mb-2{margin-bottom:.5rem !important}.mb-3{margin-bottom:1rem !important}.mb-4{margin-bottom:1.5rem !important}.mb-5{margin-bottom:3rem !important}.p-0{padding:0px !important}.p-1{padding:.25rem !important}.p-2{padding:.5rem !important}.p-3{padding:1rem !important}.p-4{padding:1.5rem !important}.p-5{padding:3rem !important}.py-0{padding-top:0px !important;padding-bottom:0px !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-5{padding-top:3rem !important;padding-bottom:3rem !important}.px-0{padding-left:0px !important;padding-right:0px !important}.px-1{padding-left:.25rem !important;padding-right:.25rem !important}.px-2{padding-left:.5rem !important;padding-right:.5rem !important}.px-3{padding-left:1rem !important;padding-right:1rem !important}.px-4{padding-left:1.5rem !important;padding-right:1.5rem !important}.px-5{padding-left:3rem !important;padding-right:3rem !important}.pt-0{padding-top:0px !important}.pt-1{padding-top:.25rem !important}.pt-2{padding-top:.5rem !important}.pt-3{padding-top:1rem !important}.pt-4{padding-top:1.5rem !important}.pt-5{padding-top:3rem !important}.pl-0{padding-left:0px !important}.pl-1{padding-left:.25rem !important}.pl-2{padding-left:.5rem !important}.pl-3{padding-left:1rem !important}.pl-4{padding-left:1.5rem !important}.pl-5{padding-left:3rem !important}.pr-0{padding-right:0px !important}.pr-1{padding-right:.25rem !important}.pr-2{padding-right:.5rem !important}.pr-3{padding-right:1rem !important}.pr-4{padding-right:1.5rem !important}.pr-5{padding-right:3rem !important}.pb-0{padding-bottom:0px !important}.pb-1{padding-bottom:.25rem !important}.pb-2{padding-bottom:.5rem !important}.pb-3{padding-bottom:1rem !important}.pb-4{padding-bottom:1.5rem !important}.pb-5{padding-bottom:3rem !important}html{font-family:"Source Sans Pro",sans-serif;font-weight:400;font-style:normal;font-size:14px;color:#fff;background-color:#9c0fb0;font-kerning:normal}*:first-child{margin-top:0px}*:last-child{margin-bottom:0px}b,strong,.bold{font-weight:700}i,em,.italic{font-style:italic}.uppercase{text-transform:uppercase}.lowercase{text-transform:lowercase}p,h1,h2,h3{margin-bottom:15px;margin-top:15px}code,.code{font-family:"Source Code Pro",monospace;font-weight:400;font-style:normal;border-width:1px;border-style:solid;border-radius:.4rem;padding:.2rem .5rem;font-size:.8rem;line-height:2rem;background-color:rgba(255,255,255,.05);border-color:rgba(255,255,255,.1)}pre{border-width:1px;border-style:solid;padding:.6rem;border-radius:.4rem;overflow:auto;margin:10px;display:block;font-size:.8rem;background-color:rgba(255,255,255,.05);border-color:rgba(255,255,255,.1)}pre code,pre .code{background:none;padding:0px;border-radius:0px;border:none}a{word-break:break-word;color:#c6dcf5}a.username-suggestion{font-style:italic}.source-code-font{font-family:"Source Code Pro",monospace;font-weight:400;font-style:normal}.break-code{word-break:break-word}.break-code code{display:revert;word-break:break-all;border:none;padding:0px;background:none}.platform_tag,.tag{border-radius:.25em;line-height:1;font-weight:700;display:inline-block;padding:.35em .65em;font-size:.75em}.platform_tag+.platform_tag,.platform_tag+.tag,.tag+.platform_tag,.tag+.tag{margin-left:6px}.platform_tag.left-space,.tag.left-space{margin-left:1em}.platform_tag.right-space,.tag.right-space{margin-right:1em}.platform_tag.xbox,.tag.xbox{background-color:#83ef83;color:#595959}.platform_tag.playstation,.tag.playstation{background-color:#c4daff;color:#595959}.platform_tag.pc,.tag.pc{background-color:#f7cd8c;color:#595959}.platform_tag.nintendo,.tag.nintendo{background-color:#ffcdd0;color:#595959}.platform_tag.red,.tag.red{background-color:#f6cdd1;color:#595959}.platform_tag.grey,.tag.grey{background-color:#d8dbdd;color:#595959}.platform_tag.blue,.tag.blue{background-color:#bed8fe;color:#595959}.platform_tag.green,.tag.green{background-color:#84e8ba;color:#595959}.platform_tag.yellow,.tag.yellow{background-color:#ffce3a;color:#212529}.platform_tag.cyan,.tag.cyan{background-color:#84e5f8;color:#212529}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.text-red{color:#ffccc9}.text-green{color:#8ce99a}.text-blue{color:#b3d7ff}.text-purple{color:#ead2f7}.text-yellow{color:#ffd11a}.larger{font-size:larger}.smaller{font-size:smaller}.mini{font-size:small}.redacted{text-decoration:line-through}.nowrap{white-space:nowrap}.push{clear:both;height:0px;max-height:0px;overflow:hidden}.pagebody{min-width:320px;width:auto;max-width:1200px;margin-left:auto;margin-right:auto;padding-left:max(20px,env(safe-area-inset-left, 20px));padding-right:max(20px,env(safe-area-inset-right, 20px));box-sizing:border-box}.reduced-width{max-width:800px;margin-left:auto;margin-right:auto}.indent{margin-left:40px}.hidden{display:none !important}.invisible{opacity:0}div.small_section{max-width:400px;margin-left:auto;margin-right:auto}div.medium_section{width:100%;max-width:600px;margin-left:auto;margin-right:auto}.option_group{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-ms-flex-wrap:wrap;flex-wrap:wrap}.button-group{display:inline-flex;align-items:center;flex-wrap:wrap;gap:1rem}.button-group *{margin:0px}.double-group{display:flex;align-items:center;flex-wrap:wrap;gap:1rem}.double-group div{margin:0px}.double-group div:nth-child(1){flex:1 1 auto;text-align:left}.double-group div:nth-child(2){flex:1 1 auto;text-align:right}.tripe-group{display:flex;align-items:center;flex-wrap:wrap;gap:1rem}.tripe-group div{margin:0px}.tripe-group div:nth-child(1){flex:0 1 auto;text-align:left}.tripe-group div:nth-child(2){flex:1 1 auto;text-align:center}.tripe-group div:nth-child(3){flex:0 1 auto;text-align:right}h1{font-size:16.8px}h2{font-size:15.4px}h3{font-size:14px}h1,h2,h3{font-weight:600}h1 .subtitle,h2 .subtitle,h3 .subtitle{font-weight:300;font-size:.8rem;line-height:.8rem}.user-suffix{font-weight:300;color:#ce87d8}.text-lighter{color:#ce87d8}span.object_type{font-weight:300;font-size:smaller;float:right;color:rgba(255,255,255,.35)}.inset-note{border-width:1px;border-style:solid;margin-left:30px;margin-right:30px;padding:15px;border-color:#ab33bc}blockquote{border-left-width:3px;border-left-style:solid;margin-left:0px;margin-right:0px;padding-left:30px;padding-top:10px;padding-bottom:10px;padding-right:10px;border-left-color:#bc5cc9;color:#ecd0f0;background-color:#9f16b2}blockquote p:first-child{margin-top:0px !important}blockquote p:last-child{margin-bottom:0px !important}div.visual-group{padding:20px;border-radius:10px;box-shadow:0px 2px 4px rgba(0,0,0,.15);background-color:#a11bb4}div.visual-group h1,div.visual-group h2,div.visual-group h3{margin-bottom:20px}div.visual-group+div.visual-group{margin-top:20px}table.generic{border-collapse:collapse;width:100%;border-width:1px;border-style:solid}table.generic tbody>tr:nth-child(even){background-color:#a019b3}table.generic tbody>tr:nth-child(odd){background-color:#9c0fb0}table.generic.no-row-colors tbody tr{background-color:rgba(0,0,0,0)}table.generic td,table.generic th{border-width:1px;border-style:solid;padding:6pt;border-color:#ab33bc}table.generic thead,table.generic tr.header{background-color:#fff;color:#9c0fb0}table.generic thead td,table.generic thead th,table.generic tr.header td,table.generic tr.header th{text-align:left;font-weight:400;border-color:#fff;color:inherit}table.generic ul{list-style:none;padding-left:0px}table.generic ul span.crafting_quantity{display:inline-block;min-width:40px;text-align:right}table.generic ul ul{padding-left:40px}table.generic div.row-details{display:flex;flex-wrap:wrap;flex-direction:row;justify-content:start;font-size:10pt;border-top-style:solid;border-top-width:1px;margin-top:8pt;margin-bottom:-2pt;padding-top:2pt;word-break:break-word;border-color:#ab33bc;color:#ce87d8}table.generic div.row-details span.detail{margin:4pt}table.generic div.row-details a{word-break:normal}table.generic .low-priority{display:none}table.generic .min-width{width:1px;white-space:nowrap}table.generic.auto-width{width:auto}#header_wrapper{border-bottom-width:1px;border-bottom-style:solid;position:-webkit-sticky;position:-moz-sticky;position:-ms-sticky;position:-o-sticky;position:sticky;top:0px;left:0px;right:0px;z-index:99;padding-top:max(10px,env(safe-area-inset-top, 10px));padding-bottom:10px;background-color:#9c0fb0;border-color:rgba(255,255,255,.1)}#header{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start;-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch}#header_logo_cell{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1;-ms-flex-preferred-size:100%;flex-basis:100%;text-align:center}#header_logo{height:60px;vertical-align:top}#header_links_cell{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2;-ms-flex-preferred-size:100%;flex-basis:100%;overflow:hidden}#header_links_cell ul{list-style-type:none;margin:10px 0px 0px 0px;padding:0;overflow:hidden;display:-webkit-box;display:-ms-flexbox;display:flex;height:100%;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}#header_links_cell ul li{-webkit-box-flex:0;-ms-flex:0 0 0px;flex:0 0 0;text-align:center;white-space:nowrap;vertical-align:center}#header_links_cell ul li+li{margin-left:2px}#header_links_cell ul li a{display:block;text-align:center;text-decoration:none;padding:.2em .5em;border-radius:1em;border-width:1px;border-style:solid;border-color:rgba(0,0,0,0);-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s;-webkit-transition-timing-function:ease-out;-o-transition-timing-function:ease-out;transition-timing-function:ease-out;-webkit-transition-property:border-color,color,background-color,border-radius;-o-transition-property:border-color,color,background-color,border-radius;transition-property:border-color,color,background-color,border-radius;color:rgba(255,255,255,.6)}#header_links_cell ul li a:hover{border-color:rgba(255,255,255,.4)}#header_links_cell ul li a:active{background-color:rgba(255,255,255,.2)}@media(min-width: 340px){#header_links_cell ul li a{padding-left:.75em;padding-right:.75em}}.accent-color{color:#fff;fill:#fff}.separator-color{border-color:#ab33bc}:target:before{content:"";display:block;height:150px;margin:-150px 0 0}#content_wrapper{margin-top:20px;line-height:1.7rem}#footer{font-size:.8em;text-align:center;margin-top:40px;margin-bottom:max(20px,env(safe-area-inset-bottom, 20px))}#footer a.external_logo{margin-left:6px;margin-right:6px}#footer a.external_logo img{border:none}button,input[type=button],input[type=submit],input[type=reset],a.button{-webkit-appearance:none;appearance:none;border-radius:.25rem;border:1px solid rgba(0,0,0,0);box-sizing:border-box;cursor:pointer;display:inline-block;font-family:inherit;font-size:1rem;padding:.375rem .75rem;text-align:center;text-decoration:none;-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s;-webkit-transition-property:background-color,color,opacity;-o-transition-property:background-color,color,opacity;transition-property:background-color,color,opacity;-webkit-transition-timing-function:ease-out;-o-transition-timing-function:ease-out;transition-timing-function:ease-out;vertical-align:middle;white-space:nowrap;line-height:1.5em;font-weight:400;background-color:#ab33bc;color:#fff}button.small,input[type=button].small,input[type=submit].small,input[type=reset].small,a.button.small{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}button.large,input[type=button].large,input[type=submit].large,input[type=reset].large,a.button.large{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}button+button,button+input[type=button],button+input[type=submit],button+input[type=reset],button+a.button,input[type=button]+button,input[type=button]+input[type=button],input[type=button]+input[type=submit],input[type=button]+input[type=reset],input[type=button]+a.button,input[type=submit]+button,input[type=submit]+input[type=button],input[type=submit]+input[type=submit],input[type=submit]+input[type=reset],input[type=submit]+a.button,input[type=reset]+button,input[type=reset]+input[type=button],input[type=reset]+input[type=submit],input[type=reset]+input[type=reset],input[type=reset]+a.button,a.button+button,a.button+input[type=button],a.button+input[type=submit],a.button+input[type=reset],a.button+a.button{margin-left:12px}button:disabled,input[type=button]:disabled,input[type=submit]:disabled,input[type=reset]:disabled,a.button:disabled{cursor:default;opacity:.4}button .spinner:after,input[type=button] .spinner:after,input[type=submit] .spinner:after,input[type=reset] .spinner:after,a.button .spinner:after{border-color:#fff rgba(0,0,0,0) #fff rgba(0,0,0,0)}button:not(:disabled):active,input[type=button]:not(:disabled):active,input[type=submit]:not(:disabled):active,input[type=reset]:not(:disabled):active,a.button:not(:disabled):active{background-color:#561a5e;color:gray}button:not(:disabled):active .spinner:after,input[type=button]:not(:disabled):active .spinner:after,input[type=submit]:not(:disabled):active .spinner:after,input[type=reset]:not(:disabled):active .spinner:after,a.button:not(:disabled):active .spinner:after{border-color:gray rgba(0,0,0,0) gray rgba(0,0,0,0)}button.blue,input[type=button].blue,input[type=submit].blue,input[type=reset].blue,a.button.blue{background-color:#0d6efd;color:#fff}button.blue .spinner:after,input[type=button].blue .spinner:after,input[type=submit].blue .spinner:after,input[type=reset].blue .spinner:after,a.button.blue .spinner:after{border-color:#fff rgba(0,0,0,0) #fff rgba(0,0,0,0)}button.blue:not(:disabled):active,input[type=button].blue:not(:disabled):active,input[type=submit].blue:not(:disabled):active,input[type=reset].blue:not(:disabled):active,a.button.blue:not(:disabled):active{background-color:#07377f;color:gray}button.blue:not(:disabled):active .spinner:after,input[type=button].blue:not(:disabled):active .spinner:after,input[type=submit].blue:not(:disabled):active .spinner:after,input[type=reset].blue:not(:disabled):active .spinner:after,a.button.blue:not(:disabled):active .spinner:after{border-color:gray rgba(0,0,0,0) gray rgba(0,0,0,0)}button.green,input[type=button].green,input[type=submit].green,input[type=reset].green,a.button.green{background-color:#157347;color:#fff}button.green .spinner:after,input[type=button].green .spinner:after,input[type=submit].green .spinner:after,input[type=reset].green .spinner:after,a.button.green .spinner:after{border-color:#fff rgba(0,0,0,0) #fff rgba(0,0,0,0)}button.green:not(:disabled):active,input[type=button].green:not(:disabled):active,input[type=submit].green:not(:disabled):active,input[type=reset].green:not(:disabled):active,a.button.green:not(:disabled):active{background-color:#0b3a24;color:gray}button.green:not(:disabled):active .spinner:after,input[type=button].green:not(:disabled):active .spinner:after,input[type=submit].green:not(:disabled):active .spinner:after,input[type=reset].green:not(:disabled):active .spinner:after,a.button.green:not(:disabled):active .spinner:after{border-color:gray rgba(0,0,0,0) gray rgba(0,0,0,0)}button.red,input[type=button].red,input[type=submit].red,input[type=reset].red,a.button.red{background-color:#eb142a;color:#fff}button.red .spinner:after,input[type=button].red .spinner:after,input[type=submit].red .spinner:after,input[type=reset].red .spinner:after,a.button.red .spinner:after{border-color:#fff rgba(0,0,0,0) #fff rgba(0,0,0,0)}button.red:not(:disabled):active,input[type=button].red:not(:disabled):active,input[type=submit].red:not(:disabled):active,input[type=reset].red:not(:disabled):active,a.button.red:not(:disabled):active{background-color:#760a15;color:gray}button.red:not(:disabled):active .spinner:after,input[type=button].red:not(:disabled):active .spinner:after,input[type=submit].red:not(:disabled):active .spinner:after,input[type=reset].red:not(:disabled):active .spinner:after,a.button.red:not(:disabled):active .spinner:after{border-color:gray rgba(0,0,0,0) gray rgba(0,0,0,0)}button.yellow,input[type=button].yellow,input[type=submit].yellow,input[type=reset].yellow,a.button.yellow{background-color:#ffc107;color:#000}button.yellow .spinner:after,input[type=button].yellow .spinner:after,input[type=submit].yellow .spinner:after,input[type=reset].yellow .spinner:after,a.button.yellow .spinner:after{border-color:#000 rgba(0,0,0,0) #000 rgba(0,0,0,0)}button.yellow:not(:disabled):active,input[type=button].yellow:not(:disabled):active,input[type=submit].yellow:not(:disabled):active,input[type=reset].yellow:not(:disabled):active,a.button.yellow:not(:disabled):active{background-color:#806104;color:#000}button.yellow:not(:disabled):active .spinner:after,input[type=button].yellow:not(:disabled):active .spinner:after,input[type=submit].yellow:not(:disabled):active .spinner:after,input[type=reset].yellow:not(:disabled):active .spinner:after,a.button.yellow:not(:disabled):active .spinner:after{border-color:#000 rgba(0,0,0,0) #000 rgba(0,0,0,0)}button .spinner,input[type=button] .spinner,input[type=submit] .spinner,input[type=reset] .spinner,a.button .spinner{display:none}button.working .caption,input[type=button].working .caption,input[type=submit].working .caption,input[type=reset].working .caption,a.button.working .caption{display:none}button.working .spinner,input[type=button].working .spinner,input[type=submit].working .spinner,input[type=reset].working .spinner,a.button.working .spinner{display:revert}input[type=submit],button.default,a.button.default{background-color:#fff;color:#9c0fb0}input[type=submit] .spinner:after,button.default .spinner:after,a.button.default .spinner:after{border-color:#9c0fb0 rgba(0,0,0,0) #9c0fb0 rgba(0,0,0,0)}input[type=submit]:not(:disabled):active,button.default:not(:disabled):active,a.button.default:not(:disabled):active{background-color:gray;color:#4e0858}input[type=submit]:not(:disabled):active .spinner:after,button.default:not(:disabled):active .spinner:after,a.button.default:not(:disabled):active .spinner:after{border-color:#4e0858 rgba(0,0,0,0) #4e0858 rgba(0,0,0,0)}.text-field,input[type=text],input[type=password],input[type=email],input[type=url],input[type=tel],input[type=search],input[type=number],textarea{-webkit-appearance:none;appearance:none;border-radius:.25rem;border:1px solid #c46fd0;box-sizing:border-box;font-family:inherit;font-size:1rem;margin:0px;padding:.375rem .75rem;-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s;-webkit-transition-property:background-color,color,border-color,box-shadow;-o-transition-property:background-color,color,border-color,box-shadow;transition-property:background-color,color,border-color,box-shadow;-webkit-transition-timing-function:ease-in-out;-o-transition-timing-function:ease-in-out;transition-timing-function:ease-in-out;width:100%;line-height:1rem;background-clip:padding-box;background-color:#9c0fb0;color:#fff}.text-field::placeholder,input[type=text]::placeholder,input[type=password]::placeholder,input[type=email]::placeholder,input[type=url]::placeholder,input[type=tel]::placeholder,input[type=search]::placeholder,input[type=number]::placeholder,textarea::placeholder{line-height:1rem;vertical-align:bottom;padding-top:.3rem;color:#fff;opacity:.6}.text-field:focus,input[type=text]:focus,input[type=password]:focus,input[type=email]:focus,input[type=url]:focus,input[type=tel]:focus,input[type=search]:focus,input[type=number]:focus,textarea:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.text-field.invalid,input[type=text].invalid,input[type=password].invalid,input[type=email].invalid,input[type=url].invalid,input[type=tel].invalid,input[type=search].invalid,input[type=number].invalid,textarea.invalid{border-color:#ffccc9}textarea{line-height:1.5}textarea::placeholder{padding-top:0px;line-height:1.5}.floating-label{position:relative;margin-top:16px;margin-bottom:16px}.floating-label>.text-field{padding:1rem .75rem;height:calc(3.5rem + 2px);line-height:1.25rem;box-sizing:border-box}.floating-label>.text-field::placeholder{color:rgba(0,0,0,0) !important}.floating-label>.text-field:focus,.floating-label>.text-field:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.floating-label>.text-field:focus~label,.floating-label>.text-field:not(:placeholder-shown)~label{transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.floating-label>.text-field:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.floating-label>.text-field:-webkit-autofill~label{transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.floating-label>div.select>select{padding:1.625rem 2.5rem .625rem .75rem;height:calc(3.5rem + 2px);line-height:1.25rem;box-sizing:border-box}.floating-label>label{position:absolute;top:0;left:0;height:100%;padding:1rem .75rem;pointer-events:none;border:1px solid rgba(0,0,0,0);transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out;box-sizing:border-box;opacity:.6}@media(prefers-reduced-motion: reduce){.floating-label>label{transition:none}}.floating-label>div.select+label{transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem);color:#fff}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%;margin-top:16px;margin-bottom:16px}.input-group>.text-field{position:relative;flex:1 1 auto;width:1%;min-width:0}.input-group>.text-field:focus{z-index:3}.input-group button{border:1px solid #c46fd0;background-color:#9f16b2;color:#fff}.input-group button .spinner:after{border-color:#fff rgba(0,0,0,0) #fff rgba(0,0,0,0)}.input-group button:not(:disabled):active{background-color:#500b59;color:gray}.input-group button:not(:disabled):active .spinner:after{border-color:gray rgba(0,0,0,0) gray rgba(0,0,0,0)}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5rem;text-align:center;white-space:nowrap;border:1px solid #c46fd0;box-sizing:border-box;border-radius:.25rem;background-color:#9f16b2}.input-group-sm{margin-top:12px;margin-bottom:12px}.input-group-sm>.text-field,.input-group-sm>.input-group-text{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.input-group-lg{margin-top:20px;margin-bottom:20px}.input-group-lg>.text-field,.input-group-lg>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.input-group>:not(:first-child){margin-left:-1px;border-top-left-radius:0;border-bottom-left-radius:0}.input-group>:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.field-pair label{padding-left:6px;line-height:1.6rem;color:#e1b7e7}label.invalid,span.invalid,p.invalid,div.invalid{color:#ffccc9}label.checkbox input[type=checkbox]{display:none}label.checkbox span{display:inline-block;vertical-align:middle;margin:6px;position:relative;border-radius:1rem;-webkit-transition-property:background-color,border-color;-o-transition-property:background-color,border-color;transition-property:background-color,border-color;-webkit-transition-timing-function:ease-out;-o-transition-timing-function:ease-out;transition-timing-function:ease-out;-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s;border-width:2px;border-style:solid;background-color:rgba(255,255,255,0);border-color:#fff}label.checkbox span:after{background-color:#fff}label.checkbox :checked:active+span{background-color:#ccc;border-color:#ccc}label.checkbox :checked:active+span:after{background-color:#490752}label.checkbox :checked+span{background-color:#fff}label.checkbox :checked+span:after{background-color:#9c0fb0}label.checkbox :active+span{background-color:#490752;border-color:#ccc}label.checkbox :active:after{background-color:#ccc}label.checkbox :disabled+span{opacity:.4}label.checkbox span{width:42px;height:20px}label.checkbox span:after{position:absolute;top:3px;left:3px;width:14px;height:14px;border-radius:14px;display:block;content:"";-webkit-transition-property:left,background-color;-o-transition-property:left,background-color;transition-property:left,background-color;-webkit-transition-timing-function:ease-out;-o-transition-timing-function:ease-out;transition-timing-function:ease-out;-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s}label.checkbox :checked+span:after{left:26px}.input-radio{display:block;min-height:1.5rem;padding-left:1.4em;box-sizing:border-box;margin:0px}.input-radio input{-webkit-appearance:none;appearance:none;border:1px solid #ce87d8;background-color:#9c0fb0;width:1em;height:1em;border-radius:50%;vertical-align:top;transition-duration:.15s;transition-property:border-color,border-width;transition-timing-function:ease-in-out;margin-top:.25em;margin-left:-1.4em;float:left;box-sizing:border-box;color-adjust:exact}.input-radio input:checked{border-width:.3em;border-color:#fff}.input-radio label{display:inline-block;box-sizing:border-box}.input-radio+.input-radio{margin-left:16px}div.select{position:relative;display:inline-block}div.select select{-webkit-appearance:none;-moz-appearance:none;appearance:none;min-height:22px;border:none;border-radius:.25rem;display:inline-block;font-family:inherit;font-size:1rem;text-decoration:none;-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s;-webkit-transition-property:background-color;-o-transition-property:background-color;transition-property:background-color;-webkit-transition-timing-function:ease-out;-o-transition-timing-function:ease-out;transition-timing-function:ease-out;vertical-align:middle;white-space:nowrap;padding:.375rem 2.5rem .375rem .75rem;line-height:1.5em}div.select select:focus{font-size:1rem}div.select span:after{position:absolute;top:calc(50% - .5em);right:.75rem;content:"▾";font-size:1rem;pointer-events:none;line-height:.9em}div.select select{background-color:#fff;color:#9c0fb0}div.select select:not(:disabled):active{background-color:gray;color:#4e0858}div.select span:after{color:#9c0fb0}div.select.gray select{background-color:#ab33bc;color:#fff}div.select.gray select:not(:disabled):active{background-color:#561a5e;color:gray}div.select.gray span:after{color:#fff}div.select.blue select{background-color:#0d6efd;color:#fff}div.select.blue select:not(:disabled):active{background-color:#07377f;color:gray}div.select.blue span:after{color:#fff}div.select.green select{background-color:#157347;color:#fff}div.select.green select:not(:disabled):active{background-color:#0b3a24;color:gray}div.select.green span:after{color:#fff}div.select.red select{background-color:#eb142a;color:#fff}div.select.red select:not(:disabled):active{background-color:#760a15;color:gray}div.select.red span:after{color:#fff}div.select.yellow select{background-color:#ffc107;color:#000}div.select.yellow select:not(:disabled):active{background-color:#806104;color:#000}div.select.yellow span:after{color:#000}div.select.small select{border-radius:.2rem;font-size:.875rem;padding:.25rem 1.875rem .25rem .5rem}div.select.small span:after{top:.25rem;right:.5rem;font-size:.875rem}div.select.large select{border-radius:.3rem;font-size:1.25rem;padding:.5rem 3.25rem .5rem 1rem}div.select.large span:after{top:.25rem;right:1rem;font-size:1.25rem}#explore_container{display:none;z-index:1000;position:fixed;top:0px;bottom:0px;left:0px;right:0px;-webkit-tap-highlight-color:rgba(0,0,0,0)}#explore_popover{position:absolute;width:260px;top:20px;left:20px;box-shadow:0px 5px 5px rgba(0,0,0,.05);border-radius:4px;padding:9px;border:1px solid #8f8f8f;-webkit-tap-highlight-color:rgba(0,0,0,0);background-color:#9c0fb0;border-color:#ab33bc}@media(min-width: 350px){#explore_popover{width:310px}}#explore_popover #explore_search_field{margin-bottom:9px}#explore_popover #explore_results{display:none}#explore_popover #explore_results ul{max-height:250px;overflow-y:auto}#explore_popover #explore_results_empty{display:none;text-align:center}#explore_popover #explore_results_buttons{display:-webkit-box;display:-ms-flexbox;display:flex;margin-top:9px;width:100%}#explore_popover #explore_results_buttons #explore_results_left_button{-webkit-box-flex:1;-ms-flex:1 0 0px;flex:1 0 0;text-align:left}#explore_popover #explore_results_buttons #explore_results_right_button{-webkit-box-flex:1;-ms-flex:1 0 0px;flex:1 0 0;text-align:right}#explore_popover ul{list-style-type:none;margin:0px;padding:0px;overflow:hidden}#explore_popover ul li a{text-decoration:none;border-radius:4px;padding:4px 6px;-webkit-transition-property:color,background-color;-o-transition-property:color,background-color;transition-property:color,background-color;-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s;-webkit-transition-timing-function:ease-out;-o-transition-timing-function:ease-out;transition-timing-function:ease-out;display:block;color:#fff}#explore_popover ul li a .result_preview{font-size:10pt;color:rgba(255,255,255,.5)}#explore_popover ul li a .result_type{font-size:9pt;margin-left:12px;float:right;border-width:1px;border-style:solid;padding:2px 4px;border-radius:4px;color:rgba(255,255,255,.4);border-color:rgba(255,255,255,.2)}#explore_popover ul li a:hover{color:#9c0fb0;background-color:#fff}#explore_popover ul li a:hover .result_preview{color:#9c0fb0}#explore_popover ul li a:hover .result_type{color:#9c0fb0;border-color:#9c0fb0}#explore_popover ul li+li{margin-top:4px}#menu_explore_link.expanded{border-radius:4px 4px 0px 0px;background-color:#ab33bc;color:#fff}div.double_column,div.triple_column{display:table;width:100%;border-spacing:30px;border-collapse:separate}div.double_column div.column,div.triple_column div.column{display:table-row;width:100%;vertical-align:top}div.results span.result_type{font-weight:500;margin:.8em;color:rgba(255,255,255,.35)}div.results div.result{border:1px solid #e4e4e4;padding:.8em;border-radius:.5em;border-color:rgba(255,255,255,.15)}div.results div.result+div.result{margin-top:.8em}div.results div.result span.summary{font-size:.9em;color:rgba(255,255,255,.65)}#overlay{display:none;position:fixed;top:0px;bottom:0px;left:0px;right:0px;transition-duration:.25s;transition-timing-function:ease-out;transition-property:background-color,-webkit-backdrop-filter,backdrop-filter;z-index:2000;background-color:rgba(0,0,0,0);-webkit-tap-highlight-color:rgba(0,0,0,0);background-color:rgba(0,0,0,0)}@supports(-webkit-backdrop-filter: blur(0px)){#overlay{-webkit-backdrop-filter:blur(0px);background-color:rgba(0,0,0,0)}}@supports(backdrop-filter: blur(0px)){#overlay{backdrop-filter:blur(0px);background-color:rgba(0,0,0,0)}}#overlay.exist{display:block}#overlay.visible{background-color:rgba(0,0,0,.8)}@supports(-webkit-backdrop-filter: blur(15px)){#overlay.visible{-webkit-backdrop-filter:blur(15px);background-color:rgba(0,0,0,.3)}}@supports(backdrop-filter: blur(15px)){#overlay.visible{backdrop-filter:blur(15px);background-color:rgba(0,0,0,.3)}}#dialog{display:none;position:fixed;top:30%;left:0px;right:0px;z-index:2001;opacity:0;transition-duration:.25s;transition-timing-function:ease-out;transition-property:opacity,filter;filter:blur(30px);background-color:#9c0fb0}#dialog.exist{display:block}#dialog.visible{opacity:1;filter:none}#dialog p{margin:20px}#dialog_inner{width:300px;margin-left:auto;margin-right:auto}#dialog_message{font-weight:600;text-align:left}#dialog_explanation{text-align:left;font-size:smaller}#dialog_buttons{text-align:center}.modal{display:none;opacity:1;transition-duration:.25s;transition-timing-function:ease-out;transition-property:opacity,top;z-index:2001;position:fixed;top:100%;left:0px;width:100%;min-width:320px;height:100%;padding:0px;box-shadow:0px 4px 16px rgba(0,0,0,.45);box-sizing:border-box;line-height:1.5rem;background-color:#9c0fb0}.modal.exist{display:block}.modal.visible{top:0%}.modal .modal-content{display:flex;flex-direction:column;box-sizing:border-box;max-height:100vh}.modal .modal-content .title-bar{font-weight:600;flex:0 0 auto;box-sizing:border-box;margin-top:max(env(safe-area-inset-top, 20px),20px);margin-left:max(env(safe-area-inset-left, 20px),20px);margin-right:max(env(safe-area-inset-right, 20px),20px);margin-bottom:20px}.modal .modal-content .content{flex:1 1 auto;overflow:auto;box-sizing:border-box;padding-top:20px;padding-left:max(env(safe-area-inset-left, 20px),20px);padding-right:max(env(safe-area-inset-right, 20px),20px);padding-bottom:20px;border-top:1px solid #ab33bc;border-bottom:1px solid #ab33bc}.modal .modal-content .button-bar{display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:1rem;flex:0 0 auto;box-sizing:border-box;margin-top:20px;margin-left:max(env(safe-area-inset-left, 20px),20px);margin-right:max(env(safe-area-inset-right, 20px),20px);margin-bottom:max(env(safe-area-inset-bottom, 20px),20px)}.modal .modal-content .button-bar .left{text-align:left;flex:1 1 auto}.modal .modal-content .button-bar .middle{text-align:center;flex:1 1 auto}.modal .modal-content .button-bar .right{text-align:right;flex:1 1 auto}.spinner{display:inline-block;width:30px;height:30px}.spinner.large{width:65px;height:65px}.spinner.large:after{width:52px;height:52px;border-width:4.875px}.spinner:after{width:24px;height:24px;border-width:2.25px}.spinner.small{width:20px;height:20px}.spinner.small:after{width:16px;height:16px;border-width:1.5px}.spinner:after{content:" ";display:block;border-radius:50%;border-style:solid;animation:spinner-keyframes 1.2s linear infinite;border-color:#fff rgba(0,0,0,0) #fff rgba(0,0,0,0)}.spinner.hidden{display:none}@keyframes spinner-keyframes{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}#account_toolbar_menu div a{color:#fff;background-color:none}#account_toolbar_menu div.active{background-color:#fff}#account_toolbar_menu div.active a{color:#9c0fb0}#knowledge_wrapper{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:column-reverse;-ms-flex-direction:column-reverse;flex-direction:column-reverse;-ms-flex-wrap:nowrap;flex-wrap:nowrap}#knowledge_wrapper #knowledge_main>*{margin-top:.5rem;margin-bottom:.5rem}#knowledge_wrapper #knowledge_main>*:first-child{margin-top:0px}#knowledge_wrapper #knowledge_main>*:last-child{margin-bottom:0px}#knowledge_wrapper #knowledge_main h2,#knowledge_wrapper #knowledge_main h3{border-bottom-width:1px;border-bottom-style:solid;padding-bottom:.3rem;border-color:#a421b6}#knowledge_wrapper #knowledge_main code{padding:0px 5px;border-radius:4px;line-height:1.4rem}#knowledge_wrapper #knowledge_main pre code{padding:0px}#knowledge_wrapper #knowledge_main .affected_ini_keys{border-top-width:1px;border-top-style:solid;font-size:.8em;padding:15px;margin-top:20px;font-weight:300;background-color:#9e14b2;text-shadow:0px 1px 0px #9c0fb0;border-top-color:#a31fb6}#knowledge_wrapper #knowledge_contents{border-top-width:1px;border-top-style:solid;margin-top:30px;padding-top:30px;font-size:.9em;border-color:#ab33bc}#knowledge_wrapper #knowledge_contents #knowledge_search_block{text-align:center;margin-bottom:30px}#knowledge_wrapper #knowledge_contents #knowledge_version{text-align:center;margin-bottom:20px;font-size:11pt;font-weight:600;color:#ce87d8}#knowledge_wrapper #knowledge_contents p{margin-top:0px;margin-bottom:0px;padding-left:1em;text-indent:-1em;font-size:1.1em;font-weight:500}#knowledge_wrapper #knowledge_contents ul{margin-top:4px;list-style:none;padding-left:0px}#knowledge_wrapper #knowledge_contents ul+p{margin-top:12px}#knowledge_wrapper #knowledge_contents ul li{padding-left:10px;padding-top:4px;padding-bottom:4px;line-height:1.4rem}#knowledge_wrapper #knowledge_contents ul li.current{padding-left:7px;border-left-width:3px;border-left-style:solid;color:#fff;border-left-color:#fff}#knowledge_wrapper #knowledge_contents ul li.current::before{color:#ba57c8}#knowledge_wrapper #knowledge_contents ul li+li{border-top-width:1px;border-top-style:solid;border-top-color:#a11ab4}#knowledge_wrapper #knowledge_contents ul li:first-child{padding-top:0px}#knowledge_wrapper #knowledge_contents ul li:last-child{padding-bottom:0px}#knowledge_wrapper #knowledge_contents ul li a{text-decoration:none}#knowledge_wrapper #knowledge_contents ul li a:hover{text-decoration:underline}p.help-summary{border-bottom-width:1px;border-bottom-style:dotted;font-size:1.1em;padding:0px 0px 10px 0px;border-bottom-color:#ab33bc}ul.object_list{list-style:none;padding-left:15px}ul.object_list li+li{margin-top:10px}ul.no-markings,ol.no-markings{list-style:none}.notice-block{border-width:1px;border-style:solid;text-align:center;padding:16px}.notice-block.notice-warning{border-color:#bf51b9;color:#ffccc9;background-color:#9f15b1}.notice-block.notice-caution{border-color:#bf537c;color:#ffd11a;background-color:#9f15ac}.notice-block.notice-info{border-color:#a455cc;color:#b3d7ff;background-color:#9d15b2}.subsection{border-width:1px;border-style:solid;padding:16px;border-color:rgba(255,255,255,.1)}.downloads-table .row{padding:10px;display:flex;align-items:center;border-width:1px;border-style:solid;border-top-width:0px;border-color:#ab33bc}.downloads-table .row:first-child{border-top-left-radius:10px;border-top-right-radius:10px;border-top-width:1px;background-color:#fff;color:#9c0fb0;border-color:#fff}.downloads-table .row:last-child{border-bottom-left-radius:10px;border-bottom-right-radius:10px}.downloads-table .row .label{flex:1 1}.downloads-table .row .button{flex:0 0 80px;margin-left:10px}.downloads-table .row .header{line-height:1rem}.downloads-table .row .games{opacity:.7;font-size:small}.downloads-table+.downloads-table{margin-top:20px}.mini-only{display:revert}.mobile-only{display:none}.desktop-only{display:none}.small-only{display:revert}.not-mini{display:none}.large-only{display:none}.embedded_youtube_video{position:relative;padding-bottom:56.25%;padding-top:30px;height:0;overflow:hidden}.embedded_youtube_video iframe,.embedded_youtube_video object,.embedded_youtube_video embed{position:absolute;top:0;left:0;width:100%;height:100%}div.page-panel{display:flex;flex-direction:column;gap:20px}@supports not (selector(:first-child)){div.page-panel>*{margin:10px}}div.page-panel div.page-panel-nav ul{display:flex;flex-direction:row;margin:0px;padding:0px;gap:10px;justify-content:center;align-items:stretch;flex-wrap:wrap}@supports not (selector(:first-child)){div.page-panel div.page-panel-nav ul>*{margin:5px}}div.page-panel div.page-panel-nav ul li{list-style:none}div.page-panel div.page-panel-nav ul li a{text-decoration:none;display:inline-block;padding:.375rem .75rem;border-radius:.25rem;white-space:nowrap;background-color:none;color:#fff;line-height:1.5em;font-size:1rem}div.page-panel div.page-panel-nav ul li.page-panel-active a{background-color:#fff;color:#9c0fb0}div.page-panel div.page-panel-page{display:none}div.page-panel div.page-panel-visible{display:block}div.page-panel div.page-panel-footer{display:none}div.flex-grid{display:flex;flex-wrap:wrap;flex-direction:row;justify-content:flex-start;align-items:flex-start;align-content:flex-start}div.flex-grid div.flex-grid-item{flex:0 0 auto}img.avatar{border-radius:10px;box-shadow:0px 2px 4px 0px rgba(0,0,0,.25);display:block}.pagination-controls{display:flex;gap:10px;justify-content:center;margin-top:20px;flex-wrap:wrap}.pagination-controls .pagination-button{display:block;padding:3px 6px;border:1px solid #000;border-radius:6px;text-align:center;white-space:nowrap;border-color:#a11bb4;background-color:#9e14b2;text-decoration:none}.pagination-controls .pagination-current{background-color:#fff;color:#9c0fb0;border-color:#fff}.pagination-controls .pagination-placeholder{display:block}.pagination-controls .pagination-text,.pagination-controls .pagination-placeholder{min-width:50px}.pagination-controls a.pagination-button:hover{background-color:#c6dcf5;color:#9c0fb0;border-color:#c6dcf5}@media(min-width: 400px){#header_logo{height:80px}}@media(min-width: 635px){html{font-size:16px}h1{font-size:19.2px}h2{font-size:17.6px}h3{font-size:16px}#header_wrapper{padding-top:20px;padding-bottom:20px}#header{-ms-flex-wrap:nowrap;flex-wrap:nowrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center}#header_logo_cell{-ms-flex-preferred-size:80px;flex-basis:80px}#header_links_cell{height:auto}#header_links_cell ul{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;margin:0px}#header_links_cell li{-webkit-box-flex:0;-ms-flex:0 0 0px;flex:0 0 0}:target:before{height:140px;margin:-140px 0 0}.mini-only{display:none}.mobile-only{display:revert}.not-mini{display:revert}}div.comment-block{text-align:center;font-size:small;margin:20px auto;max-width:400px;border-width:1px;border-style:solid;border-radius:4pt;padding:10pt;display:flex;justify-content:center;align-items:center;background-color:rgba(255,255,255,.02);border-color:rgba(255,255,255,.1);color:rgba(255,255,255,.7)}div.comment-block div.icon{margin-right:10pt}div.comment-block div.icon img{margin:0px;display:block}#mode_tabs div.selected,#mode_view,#mode_customizations{background-color:#a627b8}#mode_tabs_new,#mode_tabs_paste,#mode_tabs_upload{background-color:#a11bb4}#browse_results div.properties-text{color:rgba(255,255,255,.5);border-color:rgba(255,255,255,.1)}input.no-stepper::-webkit-outer-spin-button,input.no-stepper::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}input.no-stepper[type=number]{-moz-appearance:textfield}#checkout-wizard-list>div{display:flex;align-items:center}#checkout-wizard-list>div+div{margin-top:20px}#checkout-wizard-list>div .checkout-wizard-checkbox-cell{flex:0 1 auto}#checkout-wizard-list>div .checkout-wizard-description-cell{flex:1 1 auto;padding-left:10px;padding-right:10px}#checkout-wizard-list>div .checkout-wizard-description-cell>div>label{font-weight:bold}#checkout-wizard-list>div .checkout-wizard-description-cell .checkout-wizard-status{font-size:.8em;opacity:.8}#checkout-wizard-list>div .checkout-wizard-description-cell .checkout-wizard-promo{font-weight:bold;color:#8ce99a;font-size:.9em}#checkout-wizard-list>div .checkout-wizard-description-cell div.field-with-label{display:inline-flex;align-items:center;gap:12px}#checkout-wizard-list>div .checkout-wizard-description-cell div.field-with-label>div:nth-child(1){flex:0 0 auto;text-align:right}#checkout-wizard-list>div .checkout-wizard-description-cell div.field-with-label>div:nth-child(2){flex:0 0 60px}#checkout-wizard-list>div .checkout-wizard-description-cell div.field-with-label>div:nth-child(3){flex:0 0 auto}#checkout-wizard-list>div .checkout-wizard-price-cell{flex:0 1 auto}#checkout-wizard-list>div .checkout-wizard-price-cell .checkout-wizard-discounted{text-decoration:line-through}#storefront .storefront-cart-section{box-sizing:border-box;max-width:800px;margin-left:auto;margin-right:auto}#storefront #storefront-cart-asanotice{margin-bottom:1rem}#storefront #storefront-cart-header{display:flex;align-items:center;border-bottom:1px solid #ab33bc;padding-bottom:1rem;margin-bottom:1rem}#storefront #storefront-cart-header div:nth-child(1){text-align:left;flex:0 1 auto}#storefront #storefront-cart-header div:nth-child(2){text-align:right;margin-left:1rem;margin-right:1rem;flex:1 1 auto;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}#storefront #storefront-cart-header div:nth-child(3){text-align:right;flex:0 1 auto}#storefront>div{webkit-transition-property:opacity;-o-transition-property:opacity;transition-property:opacity;-webkit-transition-timing-function:linear;-o-transition-timing-function:linear;transition-timing-function:linear;-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s}#storefront #storefront-cart{margin-top:1rem;margin-bottom:1rem}#storefront #storefront-cart.empty{display:flex;flex-direction:column;min-height:300px}#storefront #storefront-cart.empty div:nth-child(1){flex:1 1 30%}#storefront #storefront-cart.empty div:nth-child(2){flex:0 0 auto;text-align:center}#storefront #storefront-cart.empty div:nth-child(3){flex:1 1 70%}#storefront #storefront-cart .bundle{border:1px solid #ab33bc;border-radius:.25rem;margin:1rem;padding:1rem}#storefront #storefront-cart .bundle.gift{border-color:skyblue;background-color:#9a22b6;color:#c3e7f5}#storefront #storefront-cart .bundle .bundle-product{display:flex;align-items:center;gap:1rem}#storefront #storefront-cart .bundle .bundle-product div:nth-child(1){flex:0 0 5%;text-align:center}#storefront #storefront-cart .bundle .bundle-product div:nth-child(2){flex:1 1 auto}#storefront #storefront-cart .bundle .bundle-product div:nth-child(3){flex:0 0 20%;text-align:right}#storefront #storefront-cart .bundle>div:not(:last-child){margin-bottom:.25rem}#storefront #storefront-cart .bundle .gift{font-size:.75em;font-weight:600;color:#b3e0f2}#storefront #storefront-cart .bundle .actions{margin-top:1rem}#storefront #storefront-cart-footer{padding-top:1rem;border-top:1px solid #ab33bc;margin-top:1rem}#storefront #storefront-cart-footer .storefront-cart-totals{margin-bottom:1rem}#storefront #storefront-cart-footer .storefront-cart-totals .storefront-cart-total-row{display:flex;flex-wrap:wrap;justify-content:flex-end}#storefront #storefront-cart-footer .storefront-cart-totals .storefront-cart-total-row div:nth-child(1){text-align:right;flex:0 0 auto}#storefront #storefront-cart-footer .storefront-cart-totals .storefront-cart-total-row div:nth-child(2){text-align:right;flex:0 0 120px}#storefront #storefront-cart-footer .storefront-cart-totals .storefront-cart-total-row+.storefront-cart-total-row{margin-top:.5rem}#storefront #storefront-cart-footer .storefront-cart-paymethods{display:flex;gap:.4rem;align-items:center;justify-content:center;margin-top:2rem;flex-wrap:wrap}#storefront #storefront-cart-footer .storefront-cart-paymethods img{width:2.5rem;margin:0px;display:block}#storefront #storefront-cart-footer .storefront-cart-notice{text-align:center;margin-top:2rem;font-size:.8rem}#storefront #storefront-cart-footer .storefront-button-row{margin-top:2rem}#storefront #storefront-cart-footer .storefront-refund-notice{max-width:600px;font-size:.8rem;margin-top:2rem;margin-left:auto;margin-right:auto}.shake{animation:shake-keyframes .4s linear 1}@keyframes shake-keyframes{0%{transform:translate(10px)}20%{transform:translate(-10px)}40%{transform:translate(5px)}80%{transform:translate(-5px)}100%{transform:translate(0px)}}.header-with-subtitle{margin-bottom:1rem}.header-with-subtitle h1,.header-with-subtitle h2,.header-with-subtitle h3,.header-with-subtitle h4,.header-with-subtitle h5,.header-with-subtitle h6{margin-bottom:0px;margin-top:0px}.header-with-subtitle .subtitle{font-size:.8rem;line-height:1rem;display:block}.beacon-engram-mod-name{font-size:14px;color:rgba(255,255,255,.5)}body.no-navigation #header_wrapper,body.no-navigation #footer{display:none}body.no-navigation #content_wrapper{margin-top:max(20px,env(safe-area-inset-top, 20px));margin-bottom:max(20px,env(safe-area-inset-bottom, 20px))}.breadcrumbs{border-radius:.2rem;display:flex;gap:.5rem;background-color:#9e14b2;font-size:14px;padding:.25rem .5rem;flex-wrap:wrap}.breadcrumbs .breadcrumb{display:inline-block;white-space:nowrap}.breadcrumbs .divider{display:inline-block;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23bb5bc9' class='bi bi-chevron-right' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3E%3C/svg%3E");background-position:center;background-repeat:no-repeat;background-size:16px 16px;width:8px}@media(min-width: 840px){div.double_column div.column{display:table-cell;width:50%}div.triple_column div.column{display:table-cell;width:33%}#knowledge_wrapper{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}#knowledge_wrapper #knowledge_main{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;padding-left:20px;width:980px}#knowledge_wrapper #knowledge_contents{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;width:220px;border-right-width:1px;border-right-style:solid;border-top:none;padding-right:20px;padding-top:0px;margin-top:0px}table.generic div.row-details{display:none}table.generic .low-priority{display:table-cell}.mini-only{display:none}.desktop-only{display:revert}.small-only{display:none}.large-only{display:revert}div.page-panel{flex-direction:row}div.page-panel div.page-panel-nav{flex:1 1 0}div.page-panel div.page-panel-nav ul{flex-direction:column}div.page-panel div.page-panel-nav ul li{text-align:right}div.page-panel div.page-panel-pages{flex:2 2 600px}div.page-panel div.page-panel-footer{flex:1 1 0;display:block}.modal{opacity:0;top:35%;left:50%;width:600px;margin-left:-300px;margin-right:auto;transform:translateY(-50%);border-radius:.25rem;margin-left:-300px;margin-right:auto;height:auto}.modal.visible{top:40%;opacity:1}.modal.centered{top:45%}.modal.centered.visible{top:50%}.modal .modal-content .title-bar{margin:0px;padding:22px 22px 0px 22px}.modal .modal-content .content{border:none;padding:22px}.modal .modal-content .button-bar{margin:0px;padding:0px 22px 22px 22px}.modal.scrolled{border-radius:0px}.modal.scrolled .modal-content .title-bar{padding:15px 22px 15px 22px;border-bottom:1px solid #ab33bc}.modal.scrolled .modal-content .content{padding:14px 22px}.modal.scrolled .modal-content .button-bar{padding:15px 22px 15px 22px;border-top:1px solid #ab33bc}}@media(min-width: 840px)and (max-width: 640px){.modal{left:20px;width:auto;right:20px;margin:0px;top:45%}.modal.visible{top:50%}}@media print{#header,#header_wrapper,#footer{display:none}#content_wrapper{margin-top:max(20px,env(safe-area-inset-top, 20px));margin-bottom:max(20px,env(safe-area-inset-bottom, 20px))}table.generic{border-color:#d9d9d9}table.generic td,table.generic th{border-color:inherit;background-color:#fff}table.generic thead,table.generic tr.header{background-color:#fff;color:#000}table.generic thead td,table.generic thead th,table.generic tr.header td,table.generic tr.header th{border-color:#000;color:inherit;font-weight:600}}#header_logo,.white-on-dark,.accented-foreground{filter:brightness(0) invert(1)} +/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}button,html [type=button],[type=reset],[type=submit]{-webkit-appearance:button}button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}template{display:none}[hidden]{display:none}@font-face{font-family:"Source Code Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcecodepro/4c416e66-c769-51a5-a337-ad832ad2e74a.woff) format("woff");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:"Source Code Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcecodepro/0842acdd-1516-53e8-a07d-ded3b64aba16.woff) format("woff");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:"Source Code Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcecodepro/7c7d8d1f-3387-5d0f-9110-4b3a57111e3a.woff) format("woff");unicode-range:U+1F00-1FFF}@font-face{font-family:"Source Code Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcecodepro/f13cc6a0-67e3-5260-b676-7f411573d880.woff) format("woff");unicode-range:U+0370-03FF}@font-face{font-family:"Source Code Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcecodepro/8a252637-447c-5992-bbab-0f6d8488e53e.woff) format("woff");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:"Source Code Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcecodepro/6e6237d0-47c2-532a-8c66-b8a1f411afbb.woff) format("woff");unicode-range:U+0100-02AF,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Source Code Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcecodepro/fb7509bc-2466-5fd4-a06a-4e9769262bed.woff) format("woff");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:"Source Sans Pro";font-style:italic;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/cd80694f-b33d-508d-907e-7544db677936.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:"Source Sans Pro";font-style:italic;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/78f748da-0e7b-5feb-aeae-a6b3b7a72fbb.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:"Source Sans Pro";font-style:italic;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/0f3938f3-e96b-588f-af3a-ba8b244f5c62.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:"Source Sans Pro";font-style:italic;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/b95003c5-6216-5e4c-a68b-40a22643520d.woff2) format("woff2");unicode-range:U+0370-03FF}@font-face{font-family:"Source Sans Pro";font-style:italic;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/83549da6-17a0-5f7e-afb7-bfd2913cebc8.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:"Source Sans Pro";font-style:italic;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/bae9ad58-d401-5d8d-8885-e653187e8945.woff2) format("woff2");unicode-range:U+0100-02AF,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Source Sans Pro";font-style:italic;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/56c68f48-8586-5f86-a831-5341d8f8893e.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:300;font-display:swap;src:url(/assets/fonts/sourcesanspro/049831a8-098c-5e17-b9b7-3d4d147220b8.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:300;font-display:swap;src:url(/assets/fonts/sourcesanspro/099262aa-4d57-59f2-95f5-89baa13aeb31.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:300;font-display:swap;src:url(/assets/fonts/sourcesanspro/b4c545b6-4511-5a9f-8754-22b7acf2d1cf.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:300;font-display:swap;src:url(/assets/fonts/sourcesanspro/90df22a0-015c-52a7-9cbf-ddd09ade4fbd.woff2) format("woff2");unicode-range:U+0370-03FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:300;font-display:swap;src:url(/assets/fonts/sourcesanspro/2134ded7-88ee-5959-bb8d-8be588893e60.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:300;font-display:swap;src:url(/assets/fonts/sourcesanspro/ba0a93ec-d1e4-5657-808d-e3c06cac07d3.woff2) format("woff2");unicode-range:U+0100-02AF,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:300;font-display:swap;src:url(/assets/fonts/sourcesanspro/611734bb-b7fa-5cd8-be7e-0185b98d7968.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/03db3c69-1325-596b-a3e5-1ab821511b04.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/36c309c3-ea5a-583d-808b-4001bca13238.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/b6fd8ba7-a279-5d7d-9bcb-f85971b2980f.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/7c2623bd-f50f-5fc6-b037-d2cf4948a4a4.woff2) format("woff2");unicode-range:U+0370-03FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/a8b3a121-1cae-5965-8e16-0aa443bda51d.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/16115b0a-c537-59b3-b8af-70ec055da4b2.woff2) format("woff2");unicode-range:U+0100-02AF,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sourcesanspro/9a0511ad-4231-545f-8ef4-f35d35fbb60c.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/sourcesanspro/bdde82bb-f944-551f-836a-4c05e0f1573f.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/sourcesanspro/d26025f7-d2e5-54d4-aa4d-2e25b1c8c4cf.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/sourcesanspro/15f136cb-e12c-5694-887e-1dc9ed9259a4.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/sourcesanspro/87121b63-28d6-5847-8a68-1218b8ba3738.woff2) format("woff2");unicode-range:U+0370-03FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/sourcesanspro/ba62d068-2dab-5fb6-9e0e-b2ce6830c777.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/sourcesanspro/a1fa33d8-a2d4-5fdb-988a-69860d54fcef.woff2) format("woff2");unicode-range:U+0100-02AF,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/sourcesanspro/fdc908e0-7ba0-5e1b-be7c-7b60913a7e89.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sourcesanspro/6921b0ca-4eb6-5888-896b-f5e043beb61e.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sourcesanspro/9b3d0111-ea94-5ed8-85ee-c4f8c0838f54.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sourcesanspro/6bcf4d01-711e-5b49-a4ae-4b72c5a950c6.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sourcesanspro/1eafcd61-b347-5326-aa01-28314ba55db8.woff2) format("woff2");unicode-range:U+0370-03FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sourcesanspro/6c0dcb70-dff7-591a-a43d-3d94def35579.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sourcesanspro/eb85d155-2a94-58f6-b344-3c0a4747ee4d.woff2) format("woff2");unicode-range:U+0100-02AF,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Source Sans Pro";font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sourcesanspro/b58ffa91-9940-5088-8866-d6f8bce9c45d.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}.w-0{width:0%}.w-10{width:10%}.w-20{width:20%}.w-25{width:25%}.w-30{width:30%}.w-40{width:40%}.w-50{width:50%}.w-60{width:60%}.w-70{width:70%}.w-75{width:75%}.w-80{width:80%}.w-90{width:90%}.w-100{width:100%}.m-0{margin:0px !important}.m-1{margin:.25rem !important}.m-2{margin:.5rem !important}.m-3{margin:1rem !important}.m-4{margin:1.5rem !important}.m-5{margin:3rem !important}.my-0{margin-top:0px !important;margin-bottom:0px !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-5{margin-top:3rem !important;margin-bottom:3rem !important}.mx-0{margin-left:0px !important;margin-right:0px !important}.mx-1{margin-left:.25rem !important;margin-right:.25rem !important}.mx-2{margin-left:.5rem !important;margin-right:.5rem !important}.mx-3{margin-left:1rem !important;margin-right:1rem !important}.mx-4{margin-left:1.5rem !important;margin-right:1.5rem !important}.mx-5{margin-left:3rem !important;margin-right:3rem !important}.mt-0{margin-top:0px !important}.mt-1{margin-top:.25rem !important}.mt-2{margin-top:.5rem !important}.mt-3{margin-top:1rem !important}.mt-4{margin-top:1.5rem !important}.mt-5{margin-top:3rem !important}.ml-0{margin-left:0px !important}.ml-1{margin-left:.25rem !important}.ml-2{margin-left:.5rem !important}.ml-3{margin-left:1rem !important}.ml-4{margin-left:1.5rem !important}.ml-5{margin-left:3rem !important}.mr-0{margin-right:0px !important}.mr-1{margin-right:.25rem !important}.mr-2{margin-right:.5rem !important}.mr-3{margin-right:1rem !important}.mr-4{margin-right:1.5rem !important}.mr-5{margin-right:3rem !important}.mb-0{margin-bottom:0px !important}.mb-1{margin-bottom:.25rem !important}.mb-2{margin-bottom:.5rem !important}.mb-3{margin-bottom:1rem !important}.mb-4{margin-bottom:1.5rem !important}.mb-5{margin-bottom:3rem !important}.p-0{padding:0px !important}.p-1{padding:.25rem !important}.p-2{padding:.5rem !important}.p-3{padding:1rem !important}.p-4{padding:1.5rem !important}.p-5{padding:3rem !important}.py-0{padding-top:0px !important;padding-bottom:0px !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-5{padding-top:3rem !important;padding-bottom:3rem !important}.px-0{padding-left:0px !important;padding-right:0px !important}.px-1{padding-left:.25rem !important;padding-right:.25rem !important}.px-2{padding-left:.5rem !important;padding-right:.5rem !important}.px-3{padding-left:1rem !important;padding-right:1rem !important}.px-4{padding-left:1.5rem !important;padding-right:1.5rem !important}.px-5{padding-left:3rem !important;padding-right:3rem !important}.pt-0{padding-top:0px !important}.pt-1{padding-top:.25rem !important}.pt-2{padding-top:.5rem !important}.pt-3{padding-top:1rem !important}.pt-4{padding-top:1.5rem !important}.pt-5{padding-top:3rem !important}.pl-0{padding-left:0px !important}.pl-1{padding-left:.25rem !important}.pl-2{padding-left:.5rem !important}.pl-3{padding-left:1rem !important}.pl-4{padding-left:1.5rem !important}.pl-5{padding-left:3rem !important}.pr-0{padding-right:0px !important}.pr-1{padding-right:.25rem !important}.pr-2{padding-right:.5rem !important}.pr-3{padding-right:1rem !important}.pr-4{padding-right:1.5rem !important}.pr-5{padding-right:3rem !important}.pb-0{padding-bottom:0px !important}.pb-1{padding-bottom:.25rem !important}.pb-2{padding-bottom:.5rem !important}.pb-3{padding-bottom:1rem !important}.pb-4{padding-bottom:1.5rem !important}.pb-5{padding-bottom:3rem !important}html{font-family:"Source Sans Pro",sans-serif;font-weight:400;font-style:normal;font-size:14px;color:#fff;background-color:#9c0fb0;font-kerning:normal}*:first-child{margin-top:0px}*:last-child{margin-bottom:0px}b,strong,.bold{font-weight:700}i,em,.italic{font-style:italic}.uppercase{text-transform:uppercase}.lowercase{text-transform:lowercase}p,h1,h2,h3{margin-bottom:15px;margin-top:15px}code,.code{font-family:"Source Code Pro",monospace;font-weight:400;font-style:normal;border-width:1px;border-style:solid;border-radius:.4rem;padding:.2rem .5rem;font-size:.8rem;line-height:2rem;background-color:rgba(255,255,255,.05);border-color:rgba(255,255,255,.1)}pre{border-width:1px;border-style:solid;padding:.6rem;border-radius:.4rem;overflow:auto;margin:10px;display:block;font-size:.8rem;background-color:rgba(255,255,255,.05);border-color:rgba(255,255,255,.1)}pre code,pre .code{background:none;padding:0px;border-radius:0px;border:none}a{word-break:break-word;color:#c6dcf5}a.username-suggestion{font-style:italic}.source-code-font{font-family:"Source Code Pro",monospace;font-weight:400;font-style:normal}.break-code{word-break:break-word}.break-code code{display:revert;word-break:break-all;border:none;padding:0px;background:none}.platform_tag,.tag{border-radius:.25em;line-height:1;font-weight:700;display:inline-block;padding:.35em .65em;font-size:.75em}.platform_tag+.platform_tag,.platform_tag+.tag,.tag+.platform_tag,.tag+.tag{margin-left:6px}.platform_tag.left-space,.tag.left-space{margin-left:1em}.platform_tag.right-space,.tag.right-space{margin-right:1em}.platform_tag.xbox,.tag.xbox{background-color:#83ef83;color:#595959}.platform_tag.playstation,.tag.playstation{background-color:#c4daff;color:#595959}.platform_tag.pc,.tag.pc{background-color:#f7cd8c;color:#595959}.platform_tag.nintendo,.tag.nintendo{background-color:#ffcdd0;color:#595959}.platform_tag.red,.tag.red{background-color:#f6cdd1;color:#595959}.platform_tag.grey,.tag.grey{background-color:#d8dbdd;color:#595959}.platform_tag.blue,.tag.blue{background-color:#bed8fe;color:#595959}.platform_tag.green,.tag.green{background-color:#84e8ba;color:#595959}.platform_tag.yellow,.tag.yellow{background-color:#ffce3a;color:#212529}.platform_tag.cyan,.tag.cyan{background-color:#84e5f8;color:#212529}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.text-red{color:#ffccc9}.text-green{color:#8ce99a}.text-blue{color:#b3d7ff}.text-purple{color:#ead2f7}.text-yellow{color:#ffd11a}.larger{font-size:larger}.smaller{font-size:smaller}.mini{font-size:small}.redacted{text-decoration:line-through}.nowrap{white-space:nowrap}.push{clear:both;height:0px;max-height:0px;overflow:hidden}.pagebody{min-width:320px;width:auto;max-width:1200px;margin-left:auto;margin-right:auto;padding-left:max(20px,env(safe-area-inset-left, 20px));padding-right:max(20px,env(safe-area-inset-right, 20px));box-sizing:border-box}.reduced-width{max-width:800px;margin-left:auto;margin-right:auto}.indent{margin-left:40px}.hidden{display:none !important}.invisible{opacity:0}div.small_section{max-width:400px;margin-left:auto;margin-right:auto}div.medium_section{width:100%;max-width:600px;margin-left:auto;margin-right:auto}.option_group{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-ms-flex-wrap:wrap;flex-wrap:wrap}.button-group{display:inline-flex;align-items:center;flex-wrap:wrap;gap:1rem}.button-group *{margin:0px}.double-group{display:flex;align-items:center;flex-wrap:wrap;gap:1rem}.double-group div{margin:0px}.double-group div:nth-child(1){flex:1 1 auto;text-align:left}.double-group div:nth-child(2){flex:1 1 auto;text-align:right}.tripe-group{display:flex;align-items:center;flex-wrap:wrap;gap:1rem}.tripe-group div{margin:0px}.tripe-group div:nth-child(1){flex:0 1 auto;text-align:left}.tripe-group div:nth-child(2){flex:1 1 auto;text-align:center}.tripe-group div:nth-child(3){flex:0 1 auto;text-align:right}h1{font-size:16.8px}h2{font-size:15.4px}h3{font-size:14px}h1,h2,h3{font-weight:600}h1 .subtitle,h2 .subtitle,h3 .subtitle{font-weight:300;font-size:.8rem;line-height:.8rem}.user-suffix{font-weight:300;color:#ce87d8}.text-lighter{color:#ce87d8}span.object_type{font-weight:300;font-size:smaller;float:right;color:rgba(255,255,255,.35)}.inset-note{border-width:1px;border-style:solid;margin-left:30px;margin-right:30px;padding:15px;border-color:#ab33bc}blockquote{border-left-width:3px;border-left-style:solid;margin-left:0px;margin-right:0px;padding-left:30px;padding-top:10px;padding-bottom:10px;padding-right:10px;border-left-color:#bc5cc9;color:#ecd0f0;background-color:#9f16b2}blockquote p:first-child{margin-top:0px !important}blockquote p:last-child{margin-bottom:0px !important}div.visual-group{padding:20px;border-radius:10px;box-shadow:0px 2px 4px rgba(0,0,0,.15);background-color:#a11bb4}div.visual-group h1,div.visual-group h2,div.visual-group h3{margin-bottom:20px}div.visual-group+div.visual-group{margin-top:20px}table.generic{border-collapse:collapse;width:100%;border-width:1px;border-style:solid}table.generic tbody>tr:nth-child(even){background-color:#a019b3}table.generic tbody>tr:nth-child(odd){background-color:#9c0fb0}table.generic.no-row-colors tbody tr{background-color:rgba(0,0,0,0)}table.generic td,table.generic th{border-width:1px;border-style:solid;padding:6pt;border-color:#ab33bc}table.generic thead,table.generic tr.header{background-color:#fff;color:#9c0fb0}table.generic thead td,table.generic thead th,table.generic tr.header td,table.generic tr.header th{text-align:left;font-weight:400;border-color:#fff;color:inherit}table.generic ul{list-style:none;padding-left:0px}table.generic ul span.crafting_quantity{display:inline-block;min-width:40px;text-align:right}table.generic ul ul{padding-left:40px}table.generic div.row-details{display:flex;flex-wrap:wrap;flex-direction:row;justify-content:start;font-size:10pt;border-top-style:solid;border-top-width:1px;margin-top:8pt;margin-bottom:-2pt;padding-top:2pt;word-break:break-word;border-color:#ab33bc;color:#ce87d8}table.generic div.row-details span.detail{margin:4pt}table.generic div.row-details a{word-break:normal}table.generic .low-priority{display:none}table.generic .min-width{width:1px;white-space:nowrap}table.generic.auto-width{width:auto}#header_wrapper{border-bottom-width:1px;border-bottom-style:solid;position:-webkit-sticky;position:-moz-sticky;position:-ms-sticky;position:-o-sticky;position:sticky;top:0px;left:0px;right:0px;z-index:99;padding-top:max(10px,env(safe-area-inset-top, 10px));padding-bottom:10px;background-color:#9c0fb0;border-color:rgba(255,255,255,.1)}#header{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start;-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch}#header_logo_cell{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1;-ms-flex-preferred-size:100%;flex-basis:100%;text-align:center}#header_logo{height:60px;vertical-align:top}#header_links_cell{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2;-ms-flex-preferred-size:100%;flex-basis:100%;overflow:hidden}#header_links_cell ul{list-style-type:none;margin:10px 0px 0px 0px;padding:0;overflow:hidden;display:-webkit-box;display:-ms-flexbox;display:flex;height:100%;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}#header_links_cell ul li{-webkit-box-flex:0;-ms-flex:0 0 0px;flex:0 0 0;text-align:center;white-space:nowrap;vertical-align:center}#header_links_cell ul li+li{margin-left:2px}#header_links_cell ul li a{display:block;text-align:center;text-decoration:none;padding:.2em .5em;border-radius:1em;border-width:1px;border-style:solid;border-color:rgba(0,0,0,0);-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s;-webkit-transition-timing-function:ease-out;-o-transition-timing-function:ease-out;transition-timing-function:ease-out;-webkit-transition-property:border-color,color,background-color,border-radius;-o-transition-property:border-color,color,background-color,border-radius;transition-property:border-color,color,background-color,border-radius;color:rgba(255,255,255,.6)}#header_links_cell ul li a:hover{border-color:rgba(255,255,255,.4)}#header_links_cell ul li a:active{background-color:rgba(255,255,255,.2)}@media(min-width: 340px){#header_links_cell ul li a{padding-left:.75em;padding-right:.75em}}.accent-color{color:#fff;fill:#fff}.separator-color{border-color:#ab33bc}:target:before{content:"";display:block;height:150px;margin:-150px 0 0}#content_wrapper{margin-top:20px;line-height:1.7rem}#footer{font-size:.8em;text-align:center;margin-top:40px;margin-bottom:max(20px,env(safe-area-inset-bottom, 20px))}#footer a.external_logo{margin-left:6px;margin-right:6px}#footer a.external_logo img{border:none}button,input[type=button],input[type=submit],input[type=reset],a.button{-webkit-appearance:none;appearance:none;border-radius:.25rem;border:1px solid rgba(0,0,0,0);box-sizing:border-box;cursor:pointer;display:inline-block;font-family:inherit;font-size:1rem;padding:.375rem .75rem;text-align:center;text-decoration:none;-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s;-webkit-transition-property:background-color,color,opacity;-o-transition-property:background-color,color,opacity;transition-property:background-color,color,opacity;-webkit-transition-timing-function:ease-out;-o-transition-timing-function:ease-out;transition-timing-function:ease-out;vertical-align:middle;white-space:nowrap;line-height:1.5em;font-weight:400;background-color:#ab33bc;color:#fff}button.small,input[type=button].small,input[type=submit].small,input[type=reset].small,a.button.small{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}button.large,input[type=button].large,input[type=submit].large,input[type=reset].large,a.button.large{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}button+button,button+input[type=button],button+input[type=submit],button+input[type=reset],button+a.button,input[type=button]+button,input[type=button]+input[type=button],input[type=button]+input[type=submit],input[type=button]+input[type=reset],input[type=button]+a.button,input[type=submit]+button,input[type=submit]+input[type=button],input[type=submit]+input[type=submit],input[type=submit]+input[type=reset],input[type=submit]+a.button,input[type=reset]+button,input[type=reset]+input[type=button],input[type=reset]+input[type=submit],input[type=reset]+input[type=reset],input[type=reset]+a.button,a.button+button,a.button+input[type=button],a.button+input[type=submit],a.button+input[type=reset],a.button+a.button{margin-left:12px}button:disabled,input[type=button]:disabled,input[type=submit]:disabled,input[type=reset]:disabled,a.button:disabled{cursor:default;opacity:.4}button .spinner:after,input[type=button] .spinner:after,input[type=submit] .spinner:after,input[type=reset] .spinner:after,a.button .spinner:after{border-color:#fff rgba(0,0,0,0) #fff rgba(0,0,0,0)}button:not(:disabled):active,input[type=button]:not(:disabled):active,input[type=submit]:not(:disabled):active,input[type=reset]:not(:disabled):active,a.button:not(:disabled):active{background-color:#561a5e;color:gray}button:not(:disabled):active .spinner:after,input[type=button]:not(:disabled):active .spinner:after,input[type=submit]:not(:disabled):active .spinner:after,input[type=reset]:not(:disabled):active .spinner:after,a.button:not(:disabled):active .spinner:after{border-color:gray rgba(0,0,0,0) gray rgba(0,0,0,0)}button.blue,input[type=button].blue,input[type=submit].blue,input[type=reset].blue,a.button.blue{background-color:#0d6efd;color:#fff}button.blue .spinner:after,input[type=button].blue .spinner:after,input[type=submit].blue .spinner:after,input[type=reset].blue .spinner:after,a.button.blue .spinner:after{border-color:#fff rgba(0,0,0,0) #fff rgba(0,0,0,0)}button.blue:not(:disabled):active,input[type=button].blue:not(:disabled):active,input[type=submit].blue:not(:disabled):active,input[type=reset].blue:not(:disabled):active,a.button.blue:not(:disabled):active{background-color:#07377f;color:gray}button.blue:not(:disabled):active .spinner:after,input[type=button].blue:not(:disabled):active .spinner:after,input[type=submit].blue:not(:disabled):active .spinner:after,input[type=reset].blue:not(:disabled):active .spinner:after,a.button.blue:not(:disabled):active .spinner:after{border-color:gray rgba(0,0,0,0) gray rgba(0,0,0,0)}button.green,input[type=button].green,input[type=submit].green,input[type=reset].green,a.button.green{background-color:#157347;color:#fff}button.green .spinner:after,input[type=button].green .spinner:after,input[type=submit].green .spinner:after,input[type=reset].green .spinner:after,a.button.green .spinner:after{border-color:#fff rgba(0,0,0,0) #fff rgba(0,0,0,0)}button.green:not(:disabled):active,input[type=button].green:not(:disabled):active,input[type=submit].green:not(:disabled):active,input[type=reset].green:not(:disabled):active,a.button.green:not(:disabled):active{background-color:#0b3a24;color:gray}button.green:not(:disabled):active .spinner:after,input[type=button].green:not(:disabled):active .spinner:after,input[type=submit].green:not(:disabled):active .spinner:after,input[type=reset].green:not(:disabled):active .spinner:after,a.button.green:not(:disabled):active .spinner:after{border-color:gray rgba(0,0,0,0) gray rgba(0,0,0,0)}button.red,input[type=button].red,input[type=submit].red,input[type=reset].red,a.button.red{background-color:#eb142a;color:#fff}button.red .spinner:after,input[type=button].red .spinner:after,input[type=submit].red .spinner:after,input[type=reset].red .spinner:after,a.button.red .spinner:after{border-color:#fff rgba(0,0,0,0) #fff rgba(0,0,0,0)}button.red:not(:disabled):active,input[type=button].red:not(:disabled):active,input[type=submit].red:not(:disabled):active,input[type=reset].red:not(:disabled):active,a.button.red:not(:disabled):active{background-color:#760a15;color:gray}button.red:not(:disabled):active .spinner:after,input[type=button].red:not(:disabled):active .spinner:after,input[type=submit].red:not(:disabled):active .spinner:after,input[type=reset].red:not(:disabled):active .spinner:after,a.button.red:not(:disabled):active .spinner:after{border-color:gray rgba(0,0,0,0) gray rgba(0,0,0,0)}button.yellow,input[type=button].yellow,input[type=submit].yellow,input[type=reset].yellow,a.button.yellow{background-color:#ffc107;color:#000}button.yellow .spinner:after,input[type=button].yellow .spinner:after,input[type=submit].yellow .spinner:after,input[type=reset].yellow .spinner:after,a.button.yellow .spinner:after{border-color:#000 rgba(0,0,0,0) #000 rgba(0,0,0,0)}button.yellow:not(:disabled):active,input[type=button].yellow:not(:disabled):active,input[type=submit].yellow:not(:disabled):active,input[type=reset].yellow:not(:disabled):active,a.button.yellow:not(:disabled):active{background-color:#806104;color:#000}button.yellow:not(:disabled):active .spinner:after,input[type=button].yellow:not(:disabled):active .spinner:after,input[type=submit].yellow:not(:disabled):active .spinner:after,input[type=reset].yellow:not(:disabled):active .spinner:after,a.button.yellow:not(:disabled):active .spinner:after{border-color:#000 rgba(0,0,0,0) #000 rgba(0,0,0,0)}button .spinner,input[type=button] .spinner,input[type=submit] .spinner,input[type=reset] .spinner,a.button .spinner{display:none}button.working .caption,input[type=button].working .caption,input[type=submit].working .caption,input[type=reset].working .caption,a.button.working .caption{display:none}button.working .spinner,input[type=button].working .spinner,input[type=submit].working .spinner,input[type=reset].working .spinner,a.button.working .spinner{display:revert}input[type=submit],button.default,a.button.default{background-color:#fff;color:#9c0fb0}input[type=submit] .spinner:after,button.default .spinner:after,a.button.default .spinner:after{border-color:#9c0fb0 rgba(0,0,0,0) #9c0fb0 rgba(0,0,0,0)}input[type=submit]:not(:disabled):active,button.default:not(:disabled):active,a.button.default:not(:disabled):active{background-color:gray;color:#4e0858}input[type=submit]:not(:disabled):active .spinner:after,button.default:not(:disabled):active .spinner:after,a.button.default:not(:disabled):active .spinner:after{border-color:#4e0858 rgba(0,0,0,0) #4e0858 rgba(0,0,0,0)}.text-field,input[type=text],input[type=password],input[type=email],input[type=url],input[type=tel],input[type=search],input[type=number],textarea{-webkit-appearance:none;appearance:none;border-radius:.25rem;border:1px solid #c46fd0;box-sizing:border-box;font-family:inherit;font-size:1rem;margin:0px;padding:.375rem .75rem;-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s;-webkit-transition-property:background-color,color,border-color,box-shadow;-o-transition-property:background-color,color,border-color,box-shadow;transition-property:background-color,color,border-color,box-shadow;-webkit-transition-timing-function:ease-in-out;-o-transition-timing-function:ease-in-out;transition-timing-function:ease-in-out;width:100%;line-height:1rem;background-clip:padding-box;background-color:#9c0fb0;color:#fff}.text-field::placeholder,input[type=text]::placeholder,input[type=password]::placeholder,input[type=email]::placeholder,input[type=url]::placeholder,input[type=tel]::placeholder,input[type=search]::placeholder,input[type=number]::placeholder,textarea::placeholder{line-height:1rem;vertical-align:bottom;padding-top:.3rem;color:#fff;opacity:.6}.text-field:focus,input[type=text]:focus,input[type=password]:focus,input[type=email]:focus,input[type=url]:focus,input[type=tel]:focus,input[type=search]:focus,input[type=number]:focus,textarea:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.text-field.invalid,input[type=text].invalid,input[type=password].invalid,input[type=email].invalid,input[type=url].invalid,input[type=tel].invalid,input[type=search].invalid,input[type=number].invalid,textarea.invalid{border-color:#ffccc9}textarea{line-height:1.5}textarea::placeholder{padding-top:0px;line-height:1.5}.floating-label{position:relative;margin-top:16px;margin-bottom:16px}.floating-label>.text-field{padding:1rem .75rem;height:calc(3.5rem + 2px);line-height:1.25rem;box-sizing:border-box}.floating-label>.text-field::placeholder{color:rgba(0,0,0,0) !important}.floating-label>.text-field:focus,.floating-label>.text-field:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.floating-label>.text-field:focus~label,.floating-label>.text-field:not(:placeholder-shown)~label{transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.floating-label>.text-field:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.floating-label>.text-field:-webkit-autofill~label{transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.floating-label>div.select>select{padding:1.625rem 2.5rem .625rem .75rem;height:calc(3.5rem + 2px);line-height:1.25rem;box-sizing:border-box}.floating-label>label{position:absolute;top:0;left:0;height:100%;padding:1rem .75rem;pointer-events:none;border:1px solid rgba(0,0,0,0);transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out;box-sizing:border-box;opacity:.6}@media(prefers-reduced-motion: reduce){.floating-label>label{transition:none}}.floating-label>div.select+label{transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem);color:#fff}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%;margin-top:16px;margin-bottom:16px}.input-group>.text-field{position:relative;flex:1 1 auto;width:1%;min-width:0}.input-group>.text-field:focus{z-index:3}.input-group button{border:1px solid #c46fd0;background-color:#9f16b2;color:#fff}.input-group button .spinner:after{border-color:#fff rgba(0,0,0,0) #fff rgba(0,0,0,0)}.input-group button:not(:disabled):active{background-color:#500b59;color:gray}.input-group button:not(:disabled):active .spinner:after{border-color:gray rgba(0,0,0,0) gray rgba(0,0,0,0)}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5rem;text-align:center;white-space:nowrap;border:1px solid #c46fd0;box-sizing:border-box;border-radius:.25rem;background-color:#9f16b2}.input-group-sm{margin-top:12px;margin-bottom:12px}.input-group-sm>.text-field,.input-group-sm>.input-group-text{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.input-group-lg{margin-top:20px;margin-bottom:20px}.input-group-lg>.text-field,.input-group-lg>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.input-group>:not(:first-child){margin-left:-1px;border-top-left-radius:0;border-bottom-left-radius:0}.input-group>:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.field-pair label{padding-left:6px;line-height:1.6rem;color:#e1b7e7}label.invalid,span.invalid,p.invalid,div.invalid{color:#ffccc9}label.checkbox input[type=checkbox]{display:none}label.checkbox span{display:inline-block;vertical-align:middle;margin:6px;position:relative;border-radius:1rem;-webkit-transition-property:background-color,border-color;-o-transition-property:background-color,border-color;transition-property:background-color,border-color;-webkit-transition-timing-function:ease-out;-o-transition-timing-function:ease-out;transition-timing-function:ease-out;-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s;border-width:2px;border-style:solid;background-color:rgba(255,255,255,0);border-color:#fff}label.checkbox span:after{background-color:#fff}label.checkbox :checked:active+span{background-color:#ccc;border-color:#ccc}label.checkbox :checked:active+span:after{background-color:#490752}label.checkbox :checked+span{background-color:#fff}label.checkbox :checked+span:after{background-color:#9c0fb0}label.checkbox :active+span{background-color:#490752;border-color:#ccc}label.checkbox :active:after{background-color:#ccc}label.checkbox :disabled+span{opacity:.4}label.checkbox span{width:42px;height:20px}label.checkbox span:after{position:absolute;top:3px;left:3px;width:14px;height:14px;border-radius:14px;display:block;content:"";-webkit-transition-property:left,background-color;-o-transition-property:left,background-color;transition-property:left,background-color;-webkit-transition-timing-function:ease-out;-o-transition-timing-function:ease-out;transition-timing-function:ease-out;-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s}label.checkbox :checked+span:after{left:26px}.input-radio{display:block;min-height:1.5rem;padding-left:1.4em;box-sizing:border-box;margin:0px}.input-radio input{-webkit-appearance:none;appearance:none;border:1px solid #ce87d8;background-color:#9c0fb0;width:1em;height:1em;border-radius:50%;vertical-align:top;transition-duration:.15s;transition-property:border-color,border-width;transition-timing-function:ease-in-out;margin-top:.25em;margin-left:-1.4em;float:left;box-sizing:border-box;color-adjust:exact}.input-radio input:checked{border-width:.3em;border-color:#fff}.input-radio label{display:inline-block;box-sizing:border-box}.input-radio+.input-radio{margin-left:16px}div.select{position:relative;display:inline-block}div.select select{-webkit-appearance:none;-moz-appearance:none;appearance:none;min-height:22px;border:none;border-radius:.25rem;display:inline-block;font-family:inherit;font-size:1rem;text-decoration:none;-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s;-webkit-transition-property:background-color;-o-transition-property:background-color;transition-property:background-color;-webkit-transition-timing-function:ease-out;-o-transition-timing-function:ease-out;transition-timing-function:ease-out;vertical-align:middle;white-space:nowrap;padding:.375rem 2.5rem .375rem .75rem;line-height:1.5em}div.select select:focus{font-size:1rem}div.select span:after{position:absolute;top:calc(50% - .5em);right:.75rem;content:"▾";font-size:1rem;pointer-events:none;line-height:.9em}div.select select{background-color:#fff;color:#9c0fb0}div.select select:not(:disabled):active{background-color:gray;color:#4e0858}div.select span:after{color:#9c0fb0}div.select.gray select{background-color:#ab33bc;color:#fff}div.select.gray select:not(:disabled):active{background-color:#561a5e;color:gray}div.select.gray span:after{color:#fff}div.select.blue select{background-color:#0d6efd;color:#fff}div.select.blue select:not(:disabled):active{background-color:#07377f;color:gray}div.select.blue span:after{color:#fff}div.select.green select{background-color:#157347;color:#fff}div.select.green select:not(:disabled):active{background-color:#0b3a24;color:gray}div.select.green span:after{color:#fff}div.select.red select{background-color:#eb142a;color:#fff}div.select.red select:not(:disabled):active{background-color:#760a15;color:gray}div.select.red span:after{color:#fff}div.select.yellow select{background-color:#ffc107;color:#000}div.select.yellow select:not(:disabled):active{background-color:#806104;color:#000}div.select.yellow span:after{color:#000}div.select.small select{border-radius:.2rem;font-size:.875rem;padding:.25rem 1.875rem .25rem .5rem}div.select.small span:after{top:.25rem;right:.5rem;font-size:.875rem}div.select.large select{border-radius:.3rem;font-size:1.25rem;padding:.5rem 3.25rem .5rem 1rem}div.select.large span:after{top:.25rem;right:1rem;font-size:1.25rem}#explore_container{display:none;z-index:1000;position:fixed;top:0px;bottom:0px;left:0px;right:0px;-webkit-tap-highlight-color:rgba(0,0,0,0)}#explore_popover{position:absolute;width:260px;top:20px;left:20px;box-shadow:0px 5px 5px rgba(0,0,0,.05);border-radius:4px;padding:9px;border:1px solid #8f8f8f;-webkit-tap-highlight-color:rgba(0,0,0,0);background-color:#9c0fb0;border-color:#ab33bc}@media(min-width: 350px){#explore_popover{width:310px}}#explore_popover #explore_search_field{margin-bottom:9px}#explore_popover #explore_results{display:none}#explore_popover #explore_results ul{max-height:250px;overflow-y:auto}#explore_popover #explore_results_empty{display:none;text-align:center}#explore_popover #explore_results_buttons{display:-webkit-box;display:-ms-flexbox;display:flex;margin-top:9px;width:100%}#explore_popover #explore_results_buttons #explore_results_left_button{-webkit-box-flex:1;-ms-flex:1 0 0px;flex:1 0 0;text-align:left}#explore_popover #explore_results_buttons #explore_results_right_button{-webkit-box-flex:1;-ms-flex:1 0 0px;flex:1 0 0;text-align:right}#explore_popover ul{list-style-type:none;margin:0px;padding:0px;overflow:hidden}#explore_popover ul li a{text-decoration:none;border-radius:4px;padding:4px 6px;-webkit-transition-property:color,background-color;-o-transition-property:color,background-color;transition-property:color,background-color;-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s;-webkit-transition-timing-function:ease-out;-o-transition-timing-function:ease-out;transition-timing-function:ease-out;display:block;color:#fff}#explore_popover ul li a .result_preview{font-size:10pt;color:rgba(255,255,255,.5)}#explore_popover ul li a .result_type{font-size:9pt;margin-left:12px;float:right;border-width:1px;border-style:solid;padding:2px 4px;border-radius:4px;color:rgba(255,255,255,.4);border-color:rgba(255,255,255,.2)}#explore_popover ul li a:hover{color:#9c0fb0;background-color:#fff}#explore_popover ul li a:hover .result_preview{color:#9c0fb0}#explore_popover ul li a:hover .result_type{color:#9c0fb0;border-color:#9c0fb0}#explore_popover ul li+li{margin-top:4px}#menu_explore_link.expanded{border-radius:4px 4px 0px 0px;background-color:#ab33bc;color:#fff}div.double_column,div.triple_column{display:table;width:100%;border-spacing:30px;border-collapse:separate}div.double_column div.column,div.triple_column div.column{display:table-row;width:100%;vertical-align:top}div.results span.result_type{font-weight:500;margin:.8em;color:rgba(255,255,255,.35)}div.results div.result{border:1px solid #e4e4e4;padding:.8em;border-radius:.5em;border-color:rgba(255,255,255,.15)}div.results div.result+div.result{margin-top:.8em}div.results div.result span.summary{font-size:.9em;color:rgba(255,255,255,.65)}#overlay{display:none;position:fixed;top:0px;bottom:0px;left:0px;right:0px;transition-duration:.25s;transition-timing-function:ease-out;transition-property:background-color,-webkit-backdrop-filter,backdrop-filter;z-index:2000;background-color:rgba(0,0,0,0);-webkit-tap-highlight-color:rgba(0,0,0,0);background-color:rgba(0,0,0,0)}@supports(-webkit-backdrop-filter: blur(0px)){#overlay{-webkit-backdrop-filter:blur(0px);background-color:rgba(0,0,0,0)}}@supports(backdrop-filter: blur(0px)){#overlay{backdrop-filter:blur(0px);background-color:rgba(0,0,0,0)}}#overlay.exist{display:block}#overlay.visible{background-color:rgba(0,0,0,.8)}@supports(-webkit-backdrop-filter: blur(15px)){#overlay.visible{-webkit-backdrop-filter:blur(15px);background-color:rgba(0,0,0,.3)}}@supports(backdrop-filter: blur(15px)){#overlay.visible{backdrop-filter:blur(15px);background-color:rgba(0,0,0,.3)}}#dialog{display:none;position:fixed;top:30%;left:0px;right:0px;z-index:2001;opacity:0;transition-duration:.25s;transition-timing-function:ease-out;transition-property:opacity,filter;filter:blur(30px);background-color:#9c0fb0}#dialog.exist{display:block}#dialog.visible{opacity:1;filter:none}#dialog p{margin:20px}#dialog_inner{width:300px;margin-left:auto;margin-right:auto}#dialog_message{font-weight:600;text-align:left}#dialog_explanation{text-align:left;font-size:smaller}#dialog_buttons{text-align:center}.modal{display:none;opacity:1;transition-duration:.25s;transition-timing-function:ease-out;transition-property:opacity,top;z-index:2001;position:fixed;top:100%;left:0px;width:100%;min-width:320px;height:100%;padding:0px;box-shadow:0px 4px 16px rgba(0,0,0,.45);box-sizing:border-box;line-height:1.5rem;background-color:#9c0fb0}.modal.exist{display:block}.modal.visible{top:0%}.modal .modal-content{display:flex;flex-direction:column;box-sizing:border-box;max-height:100vh}.modal .modal-content .title-bar{font-weight:600;flex:0 0 auto;box-sizing:border-box;margin-top:max(env(safe-area-inset-top, 20px),20px);margin-left:max(env(safe-area-inset-left, 20px),20px);margin-right:max(env(safe-area-inset-right, 20px),20px);margin-bottom:20px}.modal .modal-content .content{flex:1 1 auto;overflow:auto;box-sizing:border-box;padding-top:20px;padding-left:max(env(safe-area-inset-left, 20px),20px);padding-right:max(env(safe-area-inset-right, 20px),20px);padding-bottom:20px;border-top:1px solid #ab33bc;border-bottom:1px solid #ab33bc}.modal .modal-content .button-bar{display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:1rem;flex:0 0 auto;box-sizing:border-box;margin-top:20px;margin-left:max(env(safe-area-inset-left, 20px),20px);margin-right:max(env(safe-area-inset-right, 20px),20px);margin-bottom:max(env(safe-area-inset-bottom, 20px),20px)}.modal .modal-content .button-bar .left{text-align:left;flex:1 1 auto}.modal .modal-content .button-bar .middle{text-align:center;flex:1 1 auto}.modal .modal-content .button-bar .right{text-align:right;flex:1 1 auto}.spinner{display:inline-block;width:30px;height:30px}.spinner.large{width:65px;height:65px}.spinner.large:after{width:52px;height:52px;border-width:4.875px}.spinner:after{width:24px;height:24px;border-width:2.25px}.spinner.small{width:20px;height:20px}.spinner.small:after{width:16px;height:16px;border-width:1.5px}.spinner:after{content:" ";display:block;border-radius:50%;border-style:solid;animation:spinner-keyframes 1.2s linear infinite;border-color:#fff rgba(0,0,0,0) #fff rgba(0,0,0,0)}.spinner.hidden{display:none}@keyframes spinner-keyframes{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}#account_toolbar_menu div a{color:#fff;background-color:none}#account_toolbar_menu div.active{background-color:#fff}#account_toolbar_menu div.active a{color:#9c0fb0}#knowledge_wrapper{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:column-reverse;-ms-flex-direction:column-reverse;flex-direction:column-reverse;-ms-flex-wrap:nowrap;flex-wrap:nowrap}#knowledge_wrapper #knowledge_main>*{margin-top:.5rem;margin-bottom:.5rem}#knowledge_wrapper #knowledge_main>*:first-child{margin-top:0px}#knowledge_wrapper #knowledge_main>*:last-child{margin-bottom:0px}#knowledge_wrapper #knowledge_main h2,#knowledge_wrapper #knowledge_main h3{border-bottom-width:1px;border-bottom-style:solid;padding-bottom:.3rem;border-color:#a421b6}#knowledge_wrapper #knowledge_main code{padding:0px 5px;border-radius:4px;line-height:1.4rem}#knowledge_wrapper #knowledge_main pre code{padding:0px}#knowledge_wrapper #knowledge_main .affected_ini_keys{border-top-width:1px;border-top-style:solid;font-size:.8em;padding:15px;margin-top:20px;font-weight:300;background-color:#9e14b2;text-shadow:0px 1px 0px #9c0fb0;border-top-color:#a31fb6}#knowledge_wrapper #knowledge_contents{border-top-width:1px;border-top-style:solid;margin-top:30px;padding-top:30px;font-size:.9em;border-color:#ab33bc}#knowledge_wrapper #knowledge_contents #knowledge_search_block{text-align:center;margin-bottom:30px}#knowledge_wrapper #knowledge_contents #knowledge_version{text-align:center;margin-bottom:20px;font-size:11pt;font-weight:600;color:#ce87d8}#knowledge_wrapper #knowledge_contents p{margin-top:0px;margin-bottom:0px;padding-left:1em;text-indent:-1em;font-size:1.1em;font-weight:500}#knowledge_wrapper #knowledge_contents ul{margin-top:4px;list-style:none;padding-left:0px}#knowledge_wrapper #knowledge_contents ul+p{margin-top:12px}#knowledge_wrapper #knowledge_contents ul li{padding-left:10px;padding-top:4px;padding-bottom:4px;line-height:1.4rem}#knowledge_wrapper #knowledge_contents ul li.current{padding-left:7px;border-left-width:3px;border-left-style:solid;color:#fff;border-left-color:#fff}#knowledge_wrapper #knowledge_contents ul li.current::before{color:#ba57c8}#knowledge_wrapper #knowledge_contents ul li+li{border-top-width:1px;border-top-style:solid;border-top-color:#a11ab4}#knowledge_wrapper #knowledge_contents ul li:first-child{padding-top:0px}#knowledge_wrapper #knowledge_contents ul li:last-child{padding-bottom:0px}#knowledge_wrapper #knowledge_contents ul li a{text-decoration:none}#knowledge_wrapper #knowledge_contents ul li a:hover{text-decoration:underline}p.help-summary{border-bottom-width:1px;border-bottom-style:dotted;font-size:1.1em;padding:0px 0px 10px 0px;border-bottom-color:#ab33bc}ul.object_list{list-style:none;padding-left:15px}ul.object_list li+li{margin-top:10px}ul.no-markings,ol.no-markings{list-style:none}.notice-block{border-width:1px;border-style:solid;text-align:center;padding:16px}.notice-block+.notice-block{margin-top:1rem}.notice-block.notice-warning{border-color:#bf51b9;color:#ffccc9;background-color:#9f15b1}.notice-block.notice-caution{border-color:#bf537c;color:#ffd11a;background-color:#9f15ac}.notice-block.notice-info{border-color:#a455cc;color:#b3d7ff;background-color:#9d15b2}.subsection{border-width:1px;border-style:solid;padding:16px;border-color:rgba(255,255,255,.1)}.downloads-table .row{padding:10px;display:flex;align-items:center;border-width:1px;border-style:solid;border-top-width:0px;border-color:#ab33bc}.downloads-table .row:first-child{border-top-left-radius:10px;border-top-right-radius:10px;border-top-width:1px;background-color:#fff;color:#9c0fb0;border-color:#fff}.downloads-table .row:last-child{border-bottom-left-radius:10px;border-bottom-right-radius:10px}.downloads-table .row .label{flex:1 1}.downloads-table .row .button{flex:0 0 80px;margin-left:10px}.downloads-table .row .header{line-height:1rem}.downloads-table .row .games{opacity:.7;font-size:small}.downloads-table+.downloads-table{margin-top:20px}.mini-only{display:revert}.mobile-only{display:none}.desktop-only{display:none}.small-only{display:revert}.not-mini{display:none}.large-only{display:none}.embedded_youtube_video{position:relative;padding-bottom:56.25%;padding-top:30px;height:0;overflow:hidden}.embedded_youtube_video iframe,.embedded_youtube_video object,.embedded_youtube_video embed{position:absolute;top:0;left:0;width:100%;height:100%}div.page-panel{display:flex;flex-direction:column;gap:20px}@supports not (selector(:first-child)){div.page-panel>*{margin:10px}}div.page-panel div.page-panel-nav ul{display:flex;flex-direction:row;margin:0px;padding:0px;gap:10px;justify-content:center;align-items:stretch;flex-wrap:wrap}@supports not (selector(:first-child)){div.page-panel div.page-panel-nav ul>*{margin:5px}}div.page-panel div.page-panel-nav ul li{list-style:none}div.page-panel div.page-panel-nav ul li a{text-decoration:none;display:inline-block;padding:.375rem .75rem;border-radius:.25rem;white-space:nowrap;background-color:none;color:#fff;line-height:1.5em;font-size:1rem}div.page-panel div.page-panel-nav ul li.page-panel-active a{background-color:#fff;color:#9c0fb0}div.page-panel div.page-panel-page{display:none}div.page-panel div.page-panel-visible{display:block}div.page-panel div.page-panel-footer{display:none}div.flex-grid{display:flex;flex-wrap:wrap;flex-direction:row;justify-content:flex-start;align-items:flex-start;align-content:flex-start}div.flex-grid div.flex-grid-item{flex:0 0 auto}img.avatar{border-radius:10px;box-shadow:0px 2px 4px 0px rgba(0,0,0,.25);display:block}.pagination-controls{display:flex;gap:10px;justify-content:center;margin-top:20px;flex-wrap:wrap}.pagination-controls .pagination-button{display:block;padding:3px 6px;border:1px solid #000;border-radius:6px;text-align:center;white-space:nowrap;border-color:#a11bb4;background-color:#9e14b2;text-decoration:none}.pagination-controls .pagination-current{background-color:#fff;color:#9c0fb0;border-color:#fff}.pagination-controls .pagination-placeholder{display:block}.pagination-controls .pagination-text,.pagination-controls .pagination-placeholder{min-width:50px}.pagination-controls a.pagination-button:hover{background-color:#c6dcf5;color:#9c0fb0;border-color:#c6dcf5}@media(min-width: 400px){#header_logo{height:80px}}@media(min-width: 635px){html{font-size:16px}h1{font-size:19.2px}h2{font-size:17.6px}h3{font-size:16px}#header_wrapper{padding-top:20px;padding-bottom:20px}#header{-ms-flex-wrap:nowrap;flex-wrap:nowrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center}#header_logo_cell{-ms-flex-preferred-size:80px;flex-basis:80px}#header_links_cell{height:auto}#header_links_cell ul{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;margin:0px}#header_links_cell li{-webkit-box-flex:0;-ms-flex:0 0 0px;flex:0 0 0}:target:before{height:140px;margin:-140px 0 0}.mini-only{display:none}.mobile-only{display:revert}.not-mini{display:revert}}div.comment-block{text-align:center;font-size:small;margin:20px auto;max-width:400px;border-width:1px;border-style:solid;border-radius:4pt;padding:10pt;display:flex;justify-content:center;align-items:center;background-color:rgba(255,255,255,.02);border-color:rgba(255,255,255,.1);color:rgba(255,255,255,.7)}div.comment-block div.icon{margin-right:10pt}div.comment-block div.icon img{margin:0px;display:block}#mode_tabs div.selected,#mode_view,#mode_customizations{background-color:#a627b8}#mode_tabs_new,#mode_tabs_paste,#mode_tabs_upload{background-color:#a11bb4}#browse_results div.properties-text{color:rgba(255,255,255,.5);border-color:rgba(255,255,255,.1)}input.no-stepper::-webkit-outer-spin-button,input.no-stepper::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}input.no-stepper[type=number]{-moz-appearance:textfield}#checkout-wizard-list>.checkout-wizard-list-game{display:flex;align-items:center}#checkout-wizard-list>.checkout-wizard-list-game.separated{margin-top:20px;padding-top:20px;border-top:1px solid #ab33bc}#checkout-wizard-list>.checkout-wizard-list-game .checkout-wizard-checkbox-cell{flex:0 1 auto}#checkout-wizard-list>.checkout-wizard-list-game .checkout-wizard-description-cell{flex:1 1 auto;padding-left:10px;padding-right:10px}#checkout-wizard-list>.checkout-wizard-list-game .checkout-wizard-description-cell>div>label{font-weight:bold}#checkout-wizard-list>.checkout-wizard-list-game .checkout-wizard-description-cell .checkout-wizard-status{font-size:.8em;opacity:.8}#checkout-wizard-list>.checkout-wizard-list-game .checkout-wizard-description-cell .checkout-wizard-promo{font-weight:bold;color:#8ce99a;font-size:.9em}#checkout-wizard-list>.checkout-wizard-list-game .checkout-wizard-description-cell div.field-with-label{display:inline-flex;align-items:center;gap:12px}#checkout-wizard-list>.checkout-wizard-list-game .checkout-wizard-description-cell div.field-with-label>div:nth-child(1){flex:0 0 auto;text-align:right}#checkout-wizard-list>.checkout-wizard-list-game .checkout-wizard-description-cell div.field-with-label>div:nth-child(2){flex:0 0 60px}#checkout-wizard-list>.checkout-wizard-list-game .checkout-wizard-description-cell div.field-with-label>div:nth-child(3){flex:0 0 auto}#checkout-wizard-list>.checkout-wizard-list-game .checkout-wizard-price-cell{flex:0 1 auto}#checkout-wizard-list>.checkout-wizard-list-game .checkout-wizard-price-cell .checkout-wizard-discounted{text-decoration:line-through}#storefront .storefront-cart-section{box-sizing:border-box;max-width:800px;margin-left:auto;margin-right:auto}#storefront #storefront-cart-asanotice{margin-bottom:1rem}#storefront #storefront-cart-header{display:flex;align-items:center;border-bottom:1px solid #ab33bc;padding-bottom:1rem;margin-bottom:1rem}#storefront #storefront-cart-header div:nth-child(1){text-align:left;flex:0 1 auto}#storefront #storefront-cart-header div:nth-child(2){text-align:right;margin-left:1rem;margin-right:1rem;flex:1 1 auto;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}#storefront #storefront-cart-header div:nth-child(3){text-align:right;flex:0 1 auto}#storefront>div{webkit-transition-property:opacity;-o-transition-property:opacity;transition-property:opacity;-webkit-transition-timing-function:linear;-o-transition-timing-function:linear;transition-timing-function:linear;-webkit-transition-duration:.15s;-o-transition-duration:.15s;transition-duration:.15s}#storefront #storefront-cart{margin-top:1rem;margin-bottom:1rem}#storefront #storefront-cart.empty{display:flex;flex-direction:column;min-height:300px}#storefront #storefront-cart.empty div:nth-child(1){flex:1 1 30%}#storefront #storefront-cart.empty div:nth-child(2){flex:0 0 auto;text-align:center}#storefront #storefront-cart.empty div:nth-child(3){flex:1 1 70%}#storefront #storefront-cart .bundle{border:1px solid #ab33bc;border-radius:.25rem;margin:1rem;padding:1rem}#storefront #storefront-cart .bundle.gift{border-color:skyblue;background-color:#9a22b6;color:#c3e7f5}#storefront #storefront-cart .bundle .bundle-product{display:flex;align-items:center;gap:1rem}#storefront #storefront-cart .bundle .bundle-product div:nth-child(1){flex:0 0 5%;text-align:center}#storefront #storefront-cart .bundle .bundle-product div:nth-child(2){flex:1 1 auto}#storefront #storefront-cart .bundle .bundle-product div:nth-child(3){flex:0 0 20%;text-align:right}#storefront #storefront-cart .bundle>div:not(:last-child){margin-bottom:.25rem}#storefront #storefront-cart .bundle .gift{font-size:.75em;font-weight:600;color:#b3e0f2}#storefront #storefront-cart .bundle .actions{margin-top:1rem}#storefront #storefront-cart-footer{padding-top:1rem;border-top:1px solid #ab33bc;margin-top:1rem}#storefront #storefront-cart-footer .storefront-cart-totals{margin-bottom:1rem}#storefront #storefront-cart-footer .storefront-cart-totals .storefront-cart-total-row{display:flex;flex-wrap:wrap;justify-content:flex-end}#storefront #storefront-cart-footer .storefront-cart-totals .storefront-cart-total-row div:nth-child(1){text-align:right;flex:0 0 auto}#storefront #storefront-cart-footer .storefront-cart-totals .storefront-cart-total-row div:nth-child(2){text-align:right;flex:0 0 120px}#storefront #storefront-cart-footer .storefront-cart-totals .storefront-cart-total-row+.storefront-cart-total-row{margin-top:.5rem}#storefront #storefront-cart-footer .storefront-cart-paymethods{display:flex;gap:.4rem;align-items:center;justify-content:center;margin-top:2rem;flex-wrap:wrap}#storefront #storefront-cart-footer .storefront-cart-paymethods img{width:2.5rem;margin:0px;display:block}#storefront #storefront-cart-footer .storefront-cart-notice{text-align:center;margin-top:2rem;font-size:.8rem}#storefront #storefront-cart-footer .storefront-button-row{margin-top:2rem}#storefront #storefront-cart-footer .storefront-refund-notice{max-width:600px;font-size:.8rem;margin-top:2rem;margin-left:auto;margin-right:auto}.shake{animation:shake-keyframes .4s linear 1}.omni-game-header{display:flex;align-items:center;margin-bottom:1.5rem}.omni-game-header .omni-game-header-title{font-weight:600;font-size:1.5rem;margin-right:1.5rem;flex:1 1 auto}.omni-game-header .omni-game-header-button{flex:0 0 auto;align-self:flex-start}@keyframes shake-keyframes{0%{transform:translate(10px)}20%{transform:translate(-10px)}40%{transform:translate(5px)}80%{transform:translate(-5px)}100%{transform:translate(0px)}}.header-with-subtitle{margin-bottom:1rem}.header-with-subtitle h1,.header-with-subtitle h2,.header-with-subtitle h3,.header-with-subtitle h4,.header-with-subtitle h5,.header-with-subtitle h6{margin-bottom:0px;margin-top:0px}.header-with-subtitle .subtitle{font-size:.8rem;line-height:1rem;display:block}.beacon-engram-mod-name{font-size:14px;color:rgba(255,255,255,.5)}body.no-navigation #header_wrapper,body.no-navigation #footer{display:none}body.no-navigation #content_wrapper{margin-top:max(20px,env(safe-area-inset-top, 20px));margin-bottom:max(20px,env(safe-area-inset-bottom, 20px))}.breadcrumbs{border-radius:.2rem;display:flex;gap:.5rem;background-color:#9e14b2;font-size:14px;padding:.25rem .5rem;flex-wrap:wrap}.breadcrumbs .breadcrumb{display:inline-block;white-space:nowrap}.breadcrumbs .divider{display:inline-block;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23bb5bc9' class='bi bi-chevron-right' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3E%3C/svg%3E");background-position:center;background-repeat:no-repeat;background-size:16px 16px;width:8px}.license-name{color:#fff}.game-name{color:#b3d7ff}@media(min-width: 840px){div.double_column div.column{display:table-cell;width:50%}div.triple_column div.column{display:table-cell;width:33%}#knowledge_wrapper{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}#knowledge_wrapper #knowledge_main{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;padding-left:20px;width:980px}#knowledge_wrapper #knowledge_contents{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;width:220px;border-right-width:1px;border-right-style:solid;border-top:none;padding-right:20px;padding-top:0px;margin-top:0px}table.generic div.row-details{display:none}table.generic .low-priority{display:table-cell}.mini-only{display:none}.desktop-only{display:revert}.small-only{display:none}.large-only{display:revert}div.page-panel{flex-direction:row}div.page-panel div.page-panel-nav{flex:1 1 0}div.page-panel div.page-panel-nav ul{flex-direction:column}div.page-panel div.page-panel-nav ul li{text-align:right}div.page-panel div.page-panel-pages{flex:2 2 600px}div.page-panel div.page-panel-footer{flex:1 1 0;display:block}.modal{opacity:0;top:35%;left:50%;width:600px;margin-left:-300px;margin-right:auto;transform:translateY(-50%);border-radius:.25rem;margin-left:-300px;margin-right:auto;height:auto}.modal.visible{top:40%;opacity:1}.modal.centered{top:45%}.modal.centered.visible{top:50%}.modal .modal-content .title-bar{margin:0px;padding:22px 22px 0px 22px}.modal .modal-content .content{border:none;padding:22px}.modal .modal-content .button-bar{margin:0px;padding:0px 22px 22px 22px}.modal.scrolled{border-radius:0px}.modal.scrolled .modal-content .title-bar{padding:15px 22px 15px 22px;border-bottom:1px solid #ab33bc}.modal.scrolled .modal-content .content{padding:14px 22px}.modal.scrolled .modal-content .button-bar{padding:15px 22px 15px 22px;border-top:1px solid #ab33bc}}@media(min-width: 840px)and (max-width: 640px){.modal{left:20px;width:auto;right:20px;margin:0px;top:45%}.modal.visible{top:50%}}@media print{#header,#header_wrapper,#footer{display:none}#content_wrapper{margin-top:max(20px,env(safe-area-inset-top, 20px));margin-bottom:max(20px,env(safe-area-inset-bottom, 20px))}table.generic{border-color:#d9d9d9}table.generic td,table.generic th{border-color:inherit;background-color:#fff}table.generic thead,table.generic tr.header{background-color:#fff;color:#000}table.generic thead td,table.generic thead th,table.generic tr.header td,table.generic tr.header th{border-color:#000;color:inherit;font-weight:600}}#header_logo,.white-on-dark,.accented-foreground{filter:brightness(0) invert(1)}.dark-only{display:unset}.light-only{display:none} diff --git a/Website/www/assets/scripts/account.js b/Website/www/assets/scripts/account.js index dbfbce010..0dea10026 100644 --- a/Website/www/assets/scripts/account.js +++ b/Website/www/assets/scripts/account.js @@ -1 +1 @@ -(()=>{var e={53:function(e){e.exports=function(){"use strict";var e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",t="ARRAYBUFFER not supported by this environment",n="UINT8ARRAY not supported by this environment";function r(e,t,n,r){var a,o,i,c=t||[0],u=(n=n||0)>>>3,s=-1===r?3:0;for(a=0;a>>2,c.length<=o&&c.push(0),c[o]|=e[a]<<8*(s+r*(i%4));return{value:c,binLen:8*e.length+n}}function a(a,o,i){switch(o){case"UTF8":case"UTF16BE":case"UTF16LE":break;default:throw new Error("encoding must be UTF8, UTF16BE, or UTF16LE")}switch(a){case"HEX":return function(e,t,n){return function(e,t,n,r){var a,o,i,c;if(0!=e.length%2)throw new Error("String of HEX type must be in byte increments");var u=t||[0],s=(n=n||0)>>>3,l=-1===r?3:0;for(a=0;a>>1)+s)>>>2;u.length<=i;)u.push(0);u[i]|=o<<8*(l+r*(c%4))}return{value:u,binLen:4*e.length+n}}(e,t,n,i)};case"TEXT":return function(e,t,n){return function(e,t,n,r,a){var o,i,c,u,s,l,d,h,f=0,p=n||[0],v=(r=r||0)>>>3;if("UTF8"===t)for(d=-1===a?3:0,c=0;c(o=e.charCodeAt(c))?i.push(o):2048>o?(i.push(192|o>>>6),i.push(128|63&o)):55296>o||57344<=o?i.push(224|o>>>12,128|o>>>6&63,128|63&o):(c+=1,o=65536+((1023&o)<<10|1023&e.charCodeAt(c)),i.push(240|o>>>18,128|o>>>12&63,128|o>>>6&63,128|63&o)),u=0;u>>2;p.length<=s;)p.push(0);p[s]|=i[u]<<8*(d+a*(l%4)),f+=1}else for(d=-1===a?2:0,h="UTF16LE"===t&&1!==a||"UTF16LE"!==t&&1===a,c=0;c>>8),s=(l=f+v)>>>2;p.length<=s;)p.push(0);p[s]|=o<<8*(d+a*(l%4)),f+=2}return{value:p,binLen:8*f+r}}(e,o,t,n,i)};case"B64":return function(t,n,r){return function(t,n,r,a){var o,i,c,u,s,l,d=0,h=n||[0],f=(r=r||0)>>>3,p=-1===a?3:0,v=t.indexOf("=");if(-1===t.search(/^[a-zA-Z0-9=+/]+$/))throw new Error("Invalid character in base-64 string");if(t=t.replace(/=/g,""),-1!==v&&v>e/4).toString(16)})),type:"TOTP",nickname:"Google Authenticator",metadata:{secret:null,setup:null}};N.addEventListener("input",(function(e){O.disabled=""===e.target.value.trim()})),P.addEventListener("click",(function(){q.metadata.secret=function(){var e="ABCDEFGHIJKLMNOPQRSTUVWXYZ234567",t=new Uint32Array(4);self.crypto.getRandomValues(t);var n,r="",a=g(t);try{for(a.s();!(n=a.n()).done;){var o=n.value;r+=e[o>>>27&31]+e[o>>>22&31]+e[o>>>17&31]+e[o>>>12&31]+e[o>>>7&31]+e[o>>>2&31]}}catch(e){a.e(e)}finally{a.f()}return r}(),q.metadata.setup="otpauth://totp/".concat(encodeURIComponent("Beacon:"+s+" ("+q.authenticatorId+")"),"?secret=").concat(q.metadata.secret,"&issuer=Beacon"),j&&(j.src="/account/assets/qr.php?content=".concat(btoa(q.metadata.setup)),j.setAttribute("alt",q.metadata.setup),j.setAttribute("title",q.metadata.setup)),N.value="",h.showModal("add-authenticator-modal")})),F.addEventListener("click",(function(){h.hideModal()})),O.addEventListener("click",(function(){var e=N.value.trim();if(q.nickname=K.value.trim(),q.verificationCode=e,e!==w()(q.metadata.secret)){N.classList.add("invalid");var t=document.querySelector('label[for="'.concat(N.id,'"]'));return t&&(t.classList.add("invalid"),t.innerText="Incorrect Code"),void setTimeout((function(){t&&(t.classList.remove("invalid"),t.innerText="Verification Code"),N&&N.classList.remove("invalid")}),3e3)}v.post("https://".concat(a,"/v4/authenticators"),q,{Authorization:"Bearer ".concat(r)}).then((function(){window.location.reload(!0)})).catch((function(e){console.log(JSON.stringify(e))}))}))}catch(e){P.addEventListener("click",(function(){h.show("Sorry, this browser is not supported","There was an error generating the authenticator, which means your browser does not support modern cryptography features. Try again with an updated browser.")}))}var X=document.querySelectorAll("#authenticators-table tbody tr").length;if(X>0){(function(){var e=arguments.length>0&&void 0!==arguments[0]&&arguments[0],t=arguments.length>1&&void 0!==arguments[1]&&arguments[1];document.querySelectorAll("time").forEach((function(n){var r=new Date(n.getAttribute("datetime"));n.innerText=function(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1],n=arguments.length>2&&void 0!==arguments[2]&&arguments[2],r=Intl.DateTimeFormat().resolvedOptions(),a={dateStyle:"medium"};t&&(a.timeStyle="short");var o=Intl.DateTimeFormat(r.locale,a).format(e);return n&&(o="".concat(o," ").concat(r.timeZone)),o}(r,e,t)}))})(!0,!1),Y&&(Y.innerText=Intl.DateTimeFormat().resolvedOptions().timeZone);var J,W=g(document.querySelectorAll("button.delete_authenticator_button"));try{for(W.s();!(J=W.n()).done;)J.value.addEventListener("click",(function(e){var t=e.target.getAttribute("beacon-authenticator-id"),n=e.target.getAttribute("beacon-authenticator-name"),o={message:"Are you sure you want to delete the authenticator ".concat(n,"?")};if(X>1){var i=X-1,c=1===i?"authenticator":"authenticators";o.explanation="You will have ".concat(i," ").concat(c," remaining. Your account will still be protected by two factor authentication.")}else o.explanation="This is your only authenticator. Deleting it will disable two factor authentication for your account. You will be able to add a new authenticator to enable two factor authentication again.";h.confirm(o.message,o.explanation).then((function(){v.delete("https://".concat(a,"/v4/authenticators/").concat(t),{Authorization:"Bearer ".concat(r)}).then((function(){var e=document.getElementById("authenticator-".concat(t));e&&X>1?(e.remove(),X--):window.location.reload(!0)})).catch((function(e){var t={message:"The authenticator was not deleted",explanation:"There was a ".concat(e.status," error.")};try{var n=JSON.parse(e.body);n.message&&(t.explanation=n.message)}catch(e){}h.show(t.message,t.explanation)}))})).catch((function(){}))}))}catch(e){W.e(e)}finally{W.f()}}D&&D.addEventListener("click",(function(){h.confirm("Replace backup codes?","This will replace all of your backup codes with new ones.").then((function(){v.post("/account/actions/replace_backup_codes",{},{Authorization:"Bearer ".concat(r)}).then((function(e){try{var t=document.getElementById("backup-codes"),n=JSON.parse(e.body).codes;t.innerHTML="";var r,a=g(n);try{for(a.s();!(r=a.n()).done;){var o=r.value,i=document.createElement("div");i.innerText=o,i.className="flex-grid-item",t.appendChild(i)}}catch(e){a.e(e)}finally{a.f()}}catch(e){window.location.reload(!0)}})).catch((function(e){console.log(JSON.stringify(e));var t={message:"Backup codes not replaced",explanation:"There was a ".concat(e.status," error.")};try{var n=JSON.parse(e.body);n.message&&(t.explanation=n.message)}catch(e){}h.show(t.message,t.explanation)}))})).catch((function(){}))}));var V,G=function(e){return e.preventDefault(),v.delete("https://".concat(a,"/v4/sessions/").concat(encodeURIComponent(e.target.getAttribute("sessionHash"))),{Authorization:"Bearer ".concat(r)}).then((function(){h.show("Session revoked","Be aware that any enabled user with a copy of your account's private key can start a new session.").then((function(){window.location.reload(!0)}))})).catch((function(e){401===e.status?h.show("Session not revoked","There was an authentication error"):h.show("Session not revoked","Sorry, there was a "+e.status+" error.")})),!1},Z=g(document.querySelectorAll('#panel-account div[page="sessions"] a.revokeLink'));try{for(Z.s();!(V=Z.n()).done;)V.value.addEventListener("click",G)}catch(e){Z.e(e)}finally{Z.f()}var $,Q=function(e){e.preventDefault();var t=e.currentTarget.getAttribute("beacon-app-id");v.get("https://".concat(a,"/v4/applications/").concat(encodeURIComponent(t)),{Authorization:"Bearer ".concat(r)}).then((function(e){JSON.parse(e.body)})).catch((function(){h.show("Could not retrieve application info")}))},ee=g(document.querySelectorAll('#panel-account div[page="apps"] button.apps-edit-button'));try{for(ee.s();!($=ee.n()).done;)$.value.addEventListener("click",Q)}catch(e){ee.e(e)}finally{ee.f()}var te,ne=document.getElementById("static-token-modal"),re=document.getElementById("static-token-name-field"),ae=document.getElementById("static-token-token-field"),oe=document.getElementById("static-token-cancel-button"),ie=document.getElementById("static-token-action-button"),ce=document.getElementById("static-token-provider-field"),ue=document.getElementById("static-token-generate-link"),se=document.getElementById("static-token-help-field"),le=document.getElementById("static-token-error-field"),de=function(e){e.preventDefault();var t=e.currentTarget.getAttribute("beacon-provider"),n=e.currentTarget.getAttribute("beacon-provider-type"),o=e.currentTarget.getAttribute("beacon-token-id"),i=e.currentTarget.getAttribute("beacon-token-name");if(""===o)switch(n){case"oauth":window.location="/account/oauth/v4/begin/".concat(t);break;case"static":if(ne&&re&&ae&&ce&&ue&&se){switch(re.value="",ae.value="",ce.value=t,le.classList.add("hidden"),t){case"nitrado":ue.href="https://server.nitrado.net/usa/developer/tokens",se.innerText='Beacon requires long life tokens from Nitrado to have the "service" scope enabled.',se.classList.remove("hidden");break;case"gameserverapp.com":ue.href="https://dash.gameserverapp.com/configure/api",se.innerText='On your GameServerApp.com dashboard, you will find an "API / Integrate" option where you can issue a token for Beacon. Copy the token into the field below to continue. Remember to keep your token in a safe place in case you need it again.',se.classList.remove("hidden")}h.showModal("static-token-modal")}}else h.confirm("Are you sure you want to remove the service ".concat(i,"?"),"You will be able to connect the service again if you choose to.","Delete","Cancel").then((function(){v.delete("https://".concat(a,"/v4/tokens/").concat(o),{Authorization:"Bearer ".concat(r)}).then((function(){window.location.reload(!0)})).catch((function(){h.show("The service was not deleted.")}))}))},he=g(document.querySelectorAll('#panel-account div[page="services"] .service-action button'));try{for(he.s();!(te=he.n()).done;)te.value.addEventListener("click",de)}catch(e){he.e(e)}finally{he.f()}if(ne&&re&&ae&&oe&&ie&&ce&&le){var fe=function(){ie.disabled=""===re.value.trim()||""===ae.value.trim()};re.addEventListener("input",(function(){fe()})),ae.addEventListener("input",(function(){fe()})),oe.addEventListener("click",(function(e){e.preventDefault(),h.hideModal()})),ie.addEventListener("click",(function(e){e.preventDefault(),ie.disabled=!0,le.classList.add("hidden");var t={provider:ce.value,type:"Static",accessToken:ae.value.trim(),providerSpecific:{tokenName:re.value.trim()}},n=function(){v.post("https://".concat(a,"/v4/user/tokens"),t,{Authorization:"Bearer ".concat(r)}).then((function(){window.location.reload(!0)})).catch((function(){le.innerText="Could not save token.",le.classList.remove("hidden"),ie.disabled=!1}))};"nitrado"===t.provider?v.get("https://api.nitrado.net/token",{Authorization:"Bearer ".concat(t.accessToken)}).then((function(e){var r=JSON.parse(e.body);if(!1===r.data.token.scopes.includes("service"))return le.innerText='The long life token is valid, but is missing the "service" scope that Beacon requires.',le.classList.remove("hidden"),void(ie.disabled=!1);t.providerSpecific.user=r.data.token.user,n()})).catch((function(){le.innerText="The long life token is not valid. Double check the Nitrado website, as the beginning of the token can wrap to another line.",le.classList.remove("hidden"),ie.disabled=!1})):n()}))}var pe=new URLSearchParams(window.location.search);if(pe.get("message")&&pe.get("explanation")){h.show(pe.get("message"),pe.get("explanation"));var ve=new(window.URL||window.webkitURL||window.mozURL||window.msURL||window.oURL)(window.location);ve.search="",window.history.replaceState(null,document.title,ve.toString())}}))})()})(); \ No newline at end of file +(()=>{var e={53:function(e){e.exports=function(){"use strict";var e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",t="ARRAYBUFFER not supported by this environment",n="UINT8ARRAY not supported by this environment";function r(e,t,n,r){var a,o,i,c=t||[0],u=(n=n||0)>>>3,s=-1===r?3:0;for(a=0;a>>2,c.length<=o&&c.push(0),c[o]|=e[a]<<8*(s+r*(i%4));return{value:c,binLen:8*e.length+n}}function a(a,o,i){switch(o){case"UTF8":case"UTF16BE":case"UTF16LE":break;default:throw new Error("encoding must be UTF8, UTF16BE, or UTF16LE")}switch(a){case"HEX":return function(e,t,n){return function(e,t,n,r){var a,o,i,c;if(0!=e.length%2)throw new Error("String of HEX type must be in byte increments");var u=t||[0],s=(n=n||0)>>>3,l=-1===r?3:0;for(a=0;a>>1)+s)>>>2;u.length<=i;)u.push(0);u[i]|=o<<8*(l+r*(c%4))}return{value:u,binLen:4*e.length+n}}(e,t,n,i)};case"TEXT":return function(e,t,n){return function(e,t,n,r,a){var o,i,c,u,s,l,d,h,f=0,p=n||[0],v=(r=r||0)>>>3;if("UTF8"===t)for(d=-1===a?3:0,c=0;c(o=e.charCodeAt(c))?i.push(o):2048>o?(i.push(192|o>>>6),i.push(128|63&o)):55296>o||57344<=o?i.push(224|o>>>12,128|o>>>6&63,128|63&o):(c+=1,o=65536+((1023&o)<<10|1023&e.charCodeAt(c)),i.push(240|o>>>18,128|o>>>12&63,128|o>>>6&63,128|63&o)),u=0;u>>2;p.length<=s;)p.push(0);p[s]|=i[u]<<8*(d+a*(l%4)),f+=1}else for(d=-1===a?2:0,h="UTF16LE"===t&&1!==a||"UTF16LE"!==t&&1===a,c=0;c>>8),s=(l=f+v)>>>2;p.length<=s;)p.push(0);p[s]|=o<<8*(d+a*(l%4)),f+=2}return{value:p,binLen:8*f+r}}(e,o,t,n,i)};case"B64":return function(t,n,r){return function(t,n,r,a){var o,i,c,u,s,l,d=0,h=n||[0],f=(r=r||0)>>>3,p=-1===a?3:0,v=t.indexOf("=");if(-1===t.search(/^[a-zA-Z0-9=+/]+$/))throw new Error("Invalid character in base-64 string");if(t=t.replace(/=/g,""),-1!==v&&v>e/4).toString(16)})),type:"TOTP",nickname:"Google Authenticator",metadata:{secret:null,setup:null}};N.addEventListener("input",(function(e){K.disabled=""===e.target.value.trim()})),x.addEventListener("click",(function(){q.metadata.secret=function(){var e="ABCDEFGHIJKLMNOPQRSTUVWXYZ234567",t=new Uint32Array(4);self.crypto.getRandomValues(t);var n,r="",a=g(t);try{for(a.s();!(n=a.n()).done;){var o=n.value;r+=e[o>>>27&31]+e[o>>>22&31]+e[o>>>17&31]+e[o>>>12&31]+e[o>>>7&31]+e[o>>>2&31]}}catch(e){a.e(e)}finally{a.f()}return r}(),q.metadata.setup="otpauth://totp/".concat(encodeURIComponent("Beacon:"+s+" ("+q.authenticatorId+")"),"?secret=").concat(q.metadata.secret,"&issuer=Beacon"),F&&(F.src="/account/assets/qr.php?content=".concat(btoa(q.metadata.setup)),F.setAttribute("alt",q.metadata.setup),F.setAttribute("title",q.metadata.setup)),N.value="",h.showModal("add-authenticator-modal")})),j.addEventListener("click",(function(){h.hideModal()})),K.addEventListener("click",(function(){var e=N.value.trim();if(q.nickname=O.value.trim(),q.verificationCode=e,e!==w()(q.metadata.secret)){N.classList.add("invalid");var t=document.querySelector('label[for="'.concat(N.id,'"]'));return t&&(t.classList.add("invalid"),t.innerText="Incorrect Code"),void setTimeout((function(){t&&(t.classList.remove("invalid"),t.innerText="Verification Code"),N&&N.classList.remove("invalid")}),3e3)}v.post("https://".concat(a,"/v4/authenticators"),q,{Authorization:"Bearer ".concat(r)}).then((function(){window.location.reload(!0)})).catch((function(e){console.log(JSON.stringify(e))}))}))}catch(e){x.addEventListener("click",(function(){h.show("Sorry, this browser is not supported","There was an error generating the authenticator, which means your browser does not support modern cryptography features. Try again with an updated browser.")}))}var X=document.querySelectorAll("#authenticators-table tbody tr").length;if(X>0){(function(){var e=arguments.length>0&&void 0!==arguments[0]&&arguments[0],t=arguments.length>1&&void 0!==arguments[1]&&arguments[1];document.querySelectorAll("time").forEach((function(n){var r=new Date(n.getAttribute("datetime"));n.innerText=function(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1],n=arguments.length>2&&void 0!==arguments[2]&&arguments[2],r=Intl.DateTimeFormat().resolvedOptions(),a={dateStyle:"medium"};t&&(a.timeStyle="short");var o=Intl.DateTimeFormat(r.locale,a).format(e);return n&&(o="".concat(o," ").concat(r.timeZone)),o}(r,e,t)}))})(!0,!1),Y&&(Y.innerText=Intl.DateTimeFormat().resolvedOptions().timeZone);var J,W=g(document.querySelectorAll("button.delete_authenticator_button"));try{for(W.s();!(J=W.n()).done;)J.value.addEventListener("click",(function(e){var t=e.target.getAttribute("beacon-authenticator-id"),n=e.target.getAttribute("beacon-authenticator-name"),o={message:"Are you sure you want to delete the authenticator ".concat(n,"?")};if(X>1){var i=X-1,c=1===i?"authenticator":"authenticators";o.explanation="You will have ".concat(i," ").concat(c," remaining. Your account will still be protected by two factor authentication.")}else o.explanation="This is your only authenticator. Deleting it will disable two factor authentication for your account. You will be able to add a new authenticator to enable two factor authentication again.";h.confirm(o.message,o.explanation).then((function(){v.delete("https://".concat(a,"/v4/authenticators/").concat(t),{Authorization:"Bearer ".concat(r)}).then((function(){var e=document.getElementById("authenticator-".concat(t));e&&X>1?(e.remove(),X--):window.location.reload(!0)})).catch((function(e){var t={message:"The authenticator was not deleted",explanation:"There was a ".concat(e.status," error.")};try{var n=JSON.parse(e.body);n.message&&(t.explanation=n.message)}catch(e){}h.show(t.message,t.explanation)}))})).catch((function(){}))}))}catch(e){W.e(e)}finally{W.f()}}D&&D.addEventListener("click",(function(){h.confirm("Replace backup codes?","This will replace all of your backup codes with new ones.").then((function(){v.post("/account/actions/replace_backup_codes",{},{Authorization:"Bearer ".concat(r)}).then((function(e){try{var t=document.getElementById("backup-codes"),n=JSON.parse(e.body).codes;t.innerHTML="";var r,a=g(n);try{for(a.s();!(r=a.n()).done;){var o=r.value,i=document.createElement("div");i.innerText=o,i.className="flex-grid-item",t.appendChild(i)}}catch(e){a.e(e)}finally{a.f()}}catch(e){window.location.reload(!0)}})).catch((function(e){console.log(JSON.stringify(e));var t={message:"Backup codes not replaced",explanation:"There was a ".concat(e.status," error.")};try{var n=JSON.parse(e.body);n.message&&(t.explanation=n.message)}catch(e){}h.show(t.message,t.explanation)}))})).catch((function(){}))}));var V,G=function(e){return e.preventDefault(),v.delete("https://".concat(a,"/v4/sessions/").concat(encodeURIComponent(e.target.getAttribute("sessionHash"))),{Authorization:"Bearer ".concat(r)}).then((function(){h.show("Session revoked","Be aware that any enabled user with a copy of your account's private key can start a new session.").then((function(){window.location.reload(!0)}))})).catch((function(e){401===e.status?h.show("Session not revoked","There was an authentication error"):h.show("Session not revoked","Sorry, there was a "+e.status+" error.")})),!1},Z=g(document.querySelectorAll('#panel-account div[page="sessions"] a.revokeLink'));try{for(Z.s();!(V=Z.n()).done;)V.value.addEventListener("click",G)}catch(e){Z.e(e)}finally{Z.f()}var $,Q=function(e){e.preventDefault();var t=e.currentTarget.getAttribute("beacon-app-id");v.get("https://".concat(a,"/v4/applications/").concat(encodeURIComponent(t)),{Authorization:"Bearer ".concat(r)}).then((function(e){JSON.parse(e.body)})).catch((function(){h.show("Could not retrieve application info")}))},ee=g(document.querySelectorAll('#panel-account div[page="apps"] button.apps-edit-button'));try{for(ee.s();!($=ee.n()).done;)$.value.addEventListener("click",Q)}catch(e){ee.e(e)}finally{ee.f()}var te,ne=document.getElementById("static-token-modal"),re=document.getElementById("static-token-name-field"),ae=document.getElementById("static-token-token-field"),oe=document.getElementById("static-token-cancel-button"),ie=document.getElementById("static-token-action-button"),ce=document.getElementById("static-token-provider-field"),ue=document.getElementById("static-token-generate-link"),se=document.getElementById("static-token-help-field"),le=document.getElementById("static-token-error-field"),de=function(e){e.preventDefault();var t=e.currentTarget.getAttribute("beacon-provider"),n=e.currentTarget.getAttribute("beacon-provider-type"),o=e.currentTarget.getAttribute("beacon-token-id"),i=e.currentTarget.getAttribute("beacon-token-name");if(""===o)switch(n){case"oauth":window.location="/account/oauth/v4/begin/".concat(t);break;case"static":if(ne&&re&&ae&&ce&&ue&&se){switch(re.value="",ae.value="",ce.value=t,le.classList.add("hidden"),t){case"nitrado":ue.href="https://server.nitrado.net/usa/developer/tokens",se.innerText='Beacon requires long life tokens from Nitrado to have the "service" scope enabled.',se.classList.remove("hidden");break;case"gameserverapp.com":ue.href="https://dash.gameserverapp.com/configure/api",se.innerText='On your GameServerApp.com dashboard, you will find an "API / Integrate" option where you can issue a token for Beacon. Copy the token into the field below to continue. Remember to keep your token in a safe place in case you need it again.',se.classList.remove("hidden")}h.showModal("static-token-modal")}}else h.confirm("Are you sure you want to remove the service ".concat(i,"?"),"You will be able to connect the service again if you choose to.","Delete","Cancel").then((function(){v.delete("https://".concat(a,"/v4/tokens/").concat(o),{Authorization:"Bearer ".concat(r)}).then((function(){window.location.reload(!0)})).catch((function(){h.show("The service was not deleted.")}))}))},he=g(document.querySelectorAll('#panel-account div[page="services"] .service-action button'));try{for(he.s();!(te=he.n()).done;)te.value.addEventListener("click",de)}catch(e){he.e(e)}finally{he.f()}if(ne&&re&&ae&&oe&&ie&&ce&&le){var fe=function(){ie.disabled=""===re.value.trim()||""===ae.value.trim()};re.addEventListener("input",(function(){fe()})),ae.addEventListener("input",(function(){fe()})),oe.addEventListener("click",(function(e){e.preventDefault(),h.hideModal()})),ie.addEventListener("click",(function(e){e.preventDefault(),ie.disabled=!0,le.classList.add("hidden");var t={provider:ce.value,type:"Static",accessToken:ae.value.trim(),providerSpecific:{tokenName:re.value.trim()}},n=function(){v.post("https://".concat(a,"/v4/user/tokens"),t,{Authorization:"Bearer ".concat(r)}).then((function(){window.location.reload(!0)})).catch((function(){le.innerText="Could not save token.",le.classList.remove("hidden"),ie.disabled=!1}))};"nitrado"===t.provider?v.get("https://api.nitrado.net/token",{Authorization:"Bearer ".concat(t.accessToken)}).then((function(e){var r=JSON.parse(e.body);if(!1===r.data.token.scopes.includes("service"))return le.innerText='The long life token is valid, but is missing the "service" scope that Beacon requires.',le.classList.remove("hidden"),void(ie.disabled=!1);t.providerSpecific.user=r.data.token.user,n()})).catch((function(){le.innerText="The long life token is not valid. Double check the Nitrado website, as the beginning of the token can wrap to another line.",le.classList.remove("hidden"),ie.disabled=!1})):n()}))}var pe=new URLSearchParams(window.location.search);if(pe.get("message")&&pe.get("explanation")){h.show(pe.get("message"),pe.get("explanation"));var ve=new(window.URL||window.webkitURL||window.mozURL||window.msURL||window.oURL)(window.location);ve.search="",window.history.replaceState(null,document.title,ve.toString())}}))})()})(); \ No newline at end of file diff --git a/Website/www/assets/scripts/checkout.js b/Website/www/assets/scripts/checkout.js index 553469e55..ca8c1f5e0 100644 --- a/Website/www/assets/scripts/checkout.js +++ b/Website/www/assets/scripts/checkout.js @@ -1 +1 @@ -(()=>{"use strict";function e(t){return e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},e(t)}function t(e,t){for(var n=0;n1&&void 0!==arguments[1]?arguments[1]:null,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"Ok";return this.confirm(e,t,n,null)}},{key:"confirm",value:function(e){var t=this,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"Ok",r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:"Cancel";return new Promise((function(a,o){var c=document.getElementById("overlay"),s=document.getElementById("dialog"),l=document.getElementById("dialog_message"),u=document.getElementById("dialog_explanation"),d=document.getElementById("dialog_action_button"),h=document.getElementById("dialog_cancel_button");c&&s&&l&&u&&d&&h?(c.className="exist",s.className="exist",setTimeout((function(){c.className="exist visible",s.className="exist visible"}),10),l.innerText=e,u.innerText=n||"",d.clickHandler&&d.removeEventListener("click",d.clickHandler),h.clickHandler&&h.removeEventListener("click",h.clickHandler),d.clickHandler=function(){t.hide(),setTimeout((function(){a()}),300)},d.addEventListener("click",d.clickHandler),d.innerText=i,r?(h.clickHandler=function(){t.hide(),setTimeout((function(){o()}),300)},h.addEventListener("click",h.clickHandler),h.innerText=r):h.className="hidden"):o()}))}},{key:"hide",value:function(){var e=document.getElementById("overlay"),t=document.getElementById("dialog");e&&t&&(e.className="exist",t.className="exist",setTimeout((function(){e.className="",t.className=""}),300))}},{key:"showModal",value:function(e){var t=this;if(!this.activeModal){var n=document.getElementById("overlay"),i=document.getElementById(e);n&&i&&(n.classList.add("exist"),i.classList.add("exist"),this.activeModal=e,setTimeout((function(){n.classList.add("visible"),i.classList.add("visible")}),10),this.viewportWatcher=setInterval((function(){if(t.activeModal){document.querySelectorAll("#".concat(t.activeModal," .modal-content .content")).forEach((function(e){i.classList.toggle("scrolled",e.scrollHeight>e.clientHeight)}));var e=Math.max(document.documentElement.clientHeight||0,window.innerHeight||0);i.classList.toggle("centered",i.clientHeight>.75*e)}}),100))}}},{key:"hideModal",value:function(){var e=this;return new Promise((function(t,n){if(e.activeModal){var i=document.getElementById("overlay"),r=document.getElementById(e.activeModal);i&&r?(e.viewportWatcher&&(clearInterval(e.viewportWatcher),e.viewportWatcher=null),i.classList.remove("visible"),r.classList.remove("visible"),setTimeout((function(){i.classList.remove("exist"),r.classList.remove("exist"),e.activeModal=null,t()}),300)):n()}else n()}))}}],null&&t(n.prototype,null),i&&t(n,i),Object.defineProperty(n,"prototype",{writable:!1}),e}();function a(e){return a="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},a(e)}function o(e,t){for(var n=0;n=200&&e.status<300||304===e.status}}},{key:"start",value:function(e,t){var n=this,i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null,r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{};return new Promise((function(o,c){var s=new XMLHttpRequest;if(s.open(e,t,!0),"object"===a(r)&&null!==r&&!1===Array.isArray(r))for(var l=0,u=Object.keys(r);l1&&void 0!==arguments[1]?arguments[1]:{};return e.start("GET",t,null,n)}},{key:"post",value:function(t,n){var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return n instanceof URLSearchParams?(i["Content-Type"]="application/x-www-form-urlencoded",e.start("POST",t,n.toString(),i)):"object"===a(n)&&null!==n||Array.isArray(n)?(i["Content-Type"]="application/json",e.start("POST",t,JSON.stringify(n),i)):e.start("POST",t,n,i)}},{key:"delete",value:function(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return e.start("DELETE",t,null,n)}}],null&&o(t.prototype,null),n&&o(t,n),Object.defineProperty(t,"prototype",{writable:!1}),e}(),s=function(e){return Intl.NumberFormat("en-US",{style:"currency",currency:e}).format},l=function(e){return new Date(1e3*e)},u=function(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1],n=arguments.length>2&&void 0!==arguments[2]&&arguments[2],i=Intl.DateTimeFormat().resolvedOptions(),r={dateStyle:"medium"};t&&(r.timeStyle="short");var a=Intl.DateTimeFormat(i.locale,r).format(e);return n&&(a="".concat(a," ").concat(i.timeZone)),a};function d(e){return d="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},d(e)}function h(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);t&&(i=i.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,i)}return n}function f(e){for(var t=1;t=e.length?{done:!0}:{done:!1,value:e[i++]}},e:function(e){throw e},f:r}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var a,o=!0,c=!1;return{s:function(){n=n.call(e)},n:function(){var e=n.next();return o=e.done,e},e:function(e){c=!0,a=e},f:function(){try{o||null==n.return||n.return()}finally{if(c)throw a}}}}function v(e,t){if(e){if("string"==typeof e)return y(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);return"Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?y(e,t):void 0}}function y(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,i=new Array(t);n>e/4).toString(16)})))}return b(e,[{key:"id",get:function(){return S(this,o)}},{key:"isGift",get:function(){return S(this,h)},set:function(e){E(this,h,e)}},{key:"productIds",get:function(){return Object.keys(S(this,d))}},{key:"count",get:function(){return Object.keys(S(this,d)).length}},{key:"reset",value:function(){E(this,d,{})}},{key:"add",value:function(e,t){this.setQuantity(e,this.getQuantity(e)+t)}},{key:"remove",value:function(e,t){this.setQuantity(e,this.getQuantity(e)-t)}},{key:"getQuantity",value:function(e){var t;return null!==(t=S(this,d)[e])&&void 0!==t?t:0}},{key:"setQuantity",value:function(e,t){t<=0?Object.prototype.hasOwnProperty.call(S(this,d),e)&&delete S(this,d)[e]:S(this,d)[e]=t}},{key:"toJSON",value:function(){return{id:S(this,o),products:S(this,d),isGift:S(this,h)}}},{key:"fingerprint",get:function(){var e=this,t=Object.keys(S(this,d)).sort().reduce((function(t,n){return t[n]=S(e,d)[n],t}),{});return btoa(JSON.stringify({id:S(this,o),products:t,isGift:S(this,h)}))}},{key:"hasArk",get:function(){var e;return this.getQuantity(null==n||null===(e=n.Ark)||void 0===e||null===(e=e.Base)||void 0===e?void 0:e.ProductId)>0}},{key:"hasArkSA",get:function(){var e,t,i;return this.getQuantity(null==n||null===(e=n.ArkSA)||void 0===e||null===(e=e.Base)||void 0===e?void 0:e.ProductId)>0||this.getQuantity(null==n||null===(t=n.ArkSA)||void 0===t||null===(t=t.Upgrade)||void 0===t?void 0:t.ProductId)>0||this.getQuantity(null==n||null===(i=n.ArkSA)||void 0===i||null===(i=i.Renewal)||void 0===i?void 0:i.ProductId)>0}},{key:"arkSAYears",get:function(){var e,t,i;return Math.min(this.getQuantity(null==n||null===(e=n.ArkSA)||void 0===e||null===(e=e.Base)||void 0===e?void 0:e.ProductId)+this.getQuantity(null==n||null===(t=n.ArkSA)||void 0===t||null===(t=t.Upgrade)||void 0===t?void 0:t.ProductId)+this.getQuantity(null==n||null===(i=n.ArkSA)||void 0===i||null===(i=i.Renewal)||void 0===i?void 0:i.ProductId),10)}},{key:"build",value:function(e,t,i,r){this.reset(),this.isGift=t,i&&(t||null===e.arkLicense)&&this.setQuantity(n.Ark.Base.ProductId,1),r>0&&(r=Math.min(r,5),t?(i?this.setQuantity(n.ArkSA.Upgrade.ProductId,1):this.setQuantity(n.ArkSA.Base.ProductId,1),r>1&&this.setQuantity(n.ArkSA.Renewal.ProductId,r-1)):null!==e.arkSALicense?this.setQuantity(n.ArkSA.Renewal.ProductId,r):(i||null!==e.arkLicense?this.setQuantity(n.ArkSA.Upgrade.ProductId,1):this.setQuantity(n.ArkSA.Base.ProductId,1),r>1&&this.setQuantity(n.ArkSA.Renewal.ProductId,r-1)))}},{key:"rebuild",value:function(e){var t=this.fingerprint;return this.build(e,this.isGift,this.hasArk,this.arkSAYears),this.fingerprint!==t}},{key:"consume",value:function(e,t){if(t){if(this.isGift!==t.isGift)throw new Error("Cannot merge a gift item with a non-gift item.");var n=this.hasArk||t.hasArk,i=this.arkSAYears+t.arkSAYears;this.build(e,this.isGift,n,i)}}},{key:"total",get:function(){var e,t=0,n=m(this.productIds);try{for(n.s();!(e=n.n()).done;){var r=e.value;t+=i[r].Price*this.getQuantity(r)}}catch(e){n.e(e)}finally{n.f()}return t}}]),e}(),g=new WeakMap,w=new WeakMap,C=new WeakMap,T=new WeakMap,x=function(){function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null;if(p(this,e),A(this,g,{writable:!0,value:[]}),A(this,w,{writable:!0,value:null}),A(this,C,{writable:!0,value:!1}),A(this,T,{writable:!0,value:[]}),t)try{var n=JSON.parse(t);E(this,w,n.email),E(this,g,n.items.reduce((function(e,t){var n=new k(t);return n.count>0&&e.push(n),e}),[])),E(this,T,n.licenses)}catch(e){console.log("Failed to load saved cart")}}return b(e,[{key:"reset",value:function(){E(this,g,[]),E(this,w,null),E(this,C,!1),E(this,T,[]),this.save()}},{key:"toJSON",value:function(){return{email:S(this,w),items:S(this,g).reduce((function(e,t){return t.count>0&&e.push(t),e}),[]),licenses:S(this,T)}}},{key:"save",value:function(){localStorage.setItem("beaconCart",JSON.stringify(this))}},{key:"add",value:function(e){S(this,g).push(e),this.save()}},{key:"remove",value:function(e){E(this,g,S(this,g).filter((function(t){return t.id!==e.id}))),this.save()}},{key:"hasProduct",value:function(e){var t,n=arguments.length>1&&void 0!==arguments[1]&&arguments[1],i=m(S(this,g));try{for(i.s();!(t=i.n()).done;){var r=t.value;if(r.getQuantity(e)>0&&r.isGift===n)return!0}}catch(e){i.e(e)}finally{i.f()}return!1}},{key:"email",get:function(){return S(this,w)}},{key:"setEmail",value:function(e){var t=this;return new Promise((function(i,r){var a=function(){var e,i=t.personalCartItem;return!!i&&(i.hasArk&&t.arkLicense?(i.setQuantity(null===(e=n.Ark)||void 0===e||null===(e=e.Base)||void 0===e?void 0:e.ProductId,0),0===i.count&&t.remove(i),!0):i.rebuild(t))};if(null===e){E(t,w,null),E(t,C,!1),E(t,T,[]);var o=a();return t.save(),void i({newEmail:null,cartChanged:o})}if(/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test(String(e).trim().toLowerCase())){var s=new URLSearchParams;s.append("email",e),c.get("/omni/lookup?".concat(s.toString())).then((function(n){var r=JSON.parse(n.body);E(t,w,r.email),E(t,C,r.verified),E(t,T,r.purchases);var o=a();t.save(),i({newEmail:e,cartChanged:o})})).catch((function(e){E(t,w,null),E(t,C,!1),E(t,T,[]),a(),t.save(),r(e.statusText)}))}else r("Address is not valid")}))}},{key:"emailVerified",get:function(){return S(this,C)}},{key:"checkingEmail",get:function(){return!1}},{key:"items",get:function(){return function(e){if(Array.isArray(e))return y(e)}(e=S(this,g))||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||v(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}();var e}},{key:"count",get:function(){return S(this,g).reduce((function(e,t){return t.count>0&&e++,e}),0)}},{key:"findLicense",value:function(e){var t,n=m(S(this,T));try{for(n.s();!(t=n.n()).done;){var i=t.value;if(i.productId===e)return i}}catch(e){n.e(e)}finally{n.f()}return null}},{key:"arkLicense",get:function(){var e;return this.findLicense(null==n||null===(e=n.Ark)||void 0===e||null===(e=e.Base)||void 0===e?void 0:e.ProductId)}},{key:"arkSALicense",get:function(){var e;return this.findLicense(null==n||null===(e=n.ArkSA)||void 0===e||null===(e=e.Base)||void 0===e?void 0:e.ProductId)}},{key:"ark2License",get:function(){return null}},{key:"personalCartItem",get:function(){var e,t=m(S(this,g));try{for(t.s();!(e=t.n()).done;){var n=e.value;if(!1===n.isGift)return n}}catch(e){t.e(e)}finally{t.f()}return null}},{key:"total",get:function(){var e,t=0,n=m(S(this,g));try{for(n.s();!(e=n.n()).done;)t+=e.value.total}catch(e){n.e(e)}finally{n.f()}return t}}],[{key:"load",value:function(){return new this(localStorage.getItem("beaconCart"))}}]),e}(),O=x.load(),F=new WeakMap,D=new WeakMap,M=function(){function e(t){p(this,e),A(this,F,{writable:!0,value:[]}),A(this,D,{writable:!0,value:null}),S(this,F).push(t)}return b(e,[{key:"currentView",get:function(){return S(this,F).slice(-1)}},{key:"back",value:function(){var e=!(arguments.length>0&&void 0!==arguments[0])||arguments[0];return!(S(this,F).length<=1||(this.switchView(S(this,F)[S(this,F).length-2],e),E(this,F,S(this,F).slice(0,S(this,F).length-2)),0))}},{key:"clearHistory",value:function(){S(this,F).length<=1||E(this,F,S(this,F).slice(-1))}},{key:"switchView",value:function(e){var t=this,n=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];if(this.currentView!==e){S(this,D)&&(clearTimeout(S(this,D)),E(this,D,null));var i=document.getElementById(this.currentView),r=document.getElementById(e);S(this,F).push(e),n?(i.classList.add("invisible"),E(this,D,setTimeout((function(){i.classList.add("hidden"),r.classList.remove("hidden"),E(t,D,setTimeout((function(){r.classList.remove("invisible"),E(t,D,null)}),150))}),150))):(i.classList.add("hidden"),i.classList.add("invisible"),r.classList.remove("invisible"),r.classList.remove("hidden"))}}}]),e}();new M("checkout-wizard-start");var G=new M("page-landing"),N=document.getElementById("buy-button"),j=document.getElementById("cart-back-button"),z=1===Object.keys(i).length,R=function(){history.pushState({},"","/omni#checkout"),dispatchEvent(new PopStateEvent("popstate",{}))},Q={cancelButton:document.getElementById("checkout-email-cancel"),actionButton:document.getElementById("checkout-email-action"),emailField:document.getElementById("checkout-email-field"),errorField:document.getElementById("checkout-email-error"),allowsSkipping:!1,successFunction:function(e){},init:function(){var e=this,t=function(t){e.actionButton.disabled=!0,e.cancelButton.disabled=!0,e.errorField.classList.add("hidden"),O.setEmail(t).then((function(t){t.newEmail;var n=t.cartChanged;r.hideModal(),setTimeout((function(){e.successFunction(n)}),310)})).catch((function(t){e.errorField.innerText=t,e.errorField.classList.remove("hidden")})).finally((function(){e.actionButton.disabled=!1,e.cancelButton.disabled=!1}))};this.actionButton.addEventListener("click",(function(n){n.preventDefault(),t(e.emailField.value)})),this.cancelButton.addEventListener("click",(function(n){n.preventDefault(),e.allowsSkipping?t(null):r.hideModal()}))},present:function(e,n){t.forceEmail?O.setEmail(t.forceEmail).then((function(e){e.newEmail;var t=e.cartChanged;n(t)})):(this.allowsSkipping=e,this.successFunction=n,O.email?this.emailField.value=O.email:this.emailField.value=sessionStorage.getItem("email")||localStorage.getItem("email")||"",this.cancelButton.innerText=e?"Skip For Now":"Cancel",r.showModal("checkout-email"))}};Q.init();var U={cartView:null,editCartItem:null,cancelButton:document.getElementById("checkout-wizard-cancel"),actionButton:document.getElementById("checkout-wizard-action"),giftCheck:document.getElementById("checkout-wizard-gift-check"),arkSACheck:document.getElementById("checkout-wizard-arksa-check"),arkCheck:document.getElementById("checkout-wizard-ark-check"),arkPriceField:document.getElementById("checkout-wizard-ark-price"),arkSAPriceField:document.getElementById("checkout-wizard-arksa-full-price"),arkSAUpgradePriceField:document.getElementById("checkout-wizard-arksa-discount-price"),arkSAStatusField:document.getElementById("checkout-wizard-status-arksa"),arkStatusField:document.getElementById("checkout-wizard-status-ark"),arkSADurationGroup:document.getElementById("checkout-wizard-arksa-duration-group"),arkSADurationField:document.getElementById("checkout-wizard-arksa-duration-field"),arkSADurationUpButton:document.getElementById("checkout-wizard-arksa-yearup-button"),arkSADurationDownButton:document.getElementById("checkout-wizard-arksa-yeardown-button"),arkSAPromoField:document.getElementById("checkout-wizard-promo-arksa"),arkOnlyCheck:document.getElementById("storefront-ark-check"),arkOnlyGiftField:document.getElementById("storefront-ark-gift-field"),arkOnlyOwnedField:document.getElementById("storefront-ark-owned"),arkOnlyGiftQuantityGroup:document.getElementById("storefront-ark-gift-group"),arkOnlyGiftUpButton:document.getElementById("storefront-ark-gift-increase"),arkOnlyGiftDownButton:document.getElementById("storefront-ark-gift-decrease"),init:function(e){var t=this;if(this.cartView=e,this.cancelButton&&this.cancelButton.addEventListener("click",(function(e){e.preventDefault(),r.hideModal()})),this.actionButton&&this.actionButton.addEventListener("click",(function(e){e.preventDefault();var n=t.giftCheck.checked,i=t.arkCheck&&!1===t.arkCheck.disabled&&t.arkCheck.checked,a=t.arkSACheck&&!1===t.arkSACheck.disabled&&t.arkSACheck.checked;if(!1!==(i||a)){var o,c=a?parseInt(t.arkSADurationField.value)||1:0;if(t.editCartItem?o=t.editCartItem:(o=new k).isGift=n,o.build(O,n,i,c),!1===Boolean(t.editCartItem)){var s=O.personalCartItem;!1===n&&!0===Boolean(s)?s.consume(O,o):O.add(o)}O.save(),t.cartView.update(),R(),r.hideModal()}})),this.giftCheck&&this.giftCheck.addEventListener("change",(function(){t.update()})),this.arkCheck&&this.arkCheck.addEventListener("change",(function(){t.update()})),this.arkSACheck&&this.arkSADurationField&&this.arkSADurationUpButton&&this.arkSADurationDownButton&&this.arkSADurationGroup){this.arkSACheck.addEventListener("change",(function(){t.update()})),this.arkSADurationField.addEventListener("input",(function(){t.update()}));var n=function(e){var n=parseInt(t.arkSADurationField.value),i=n+e;(i>5||i<1)&&(t.arkSADurationGroup.classList.add("shake"),setTimeout((function(){t.arkSADurationGroup.classList.remove("shake")}),400),i=Math.max(Math.min(i,5),1)),n!==i&&(t.arkSADurationField.value=i,t.arkSACheck.checked=!0,t.update())};this.arkSADurationUpButton.addEventListener("click",(function(e){e.preventDefault(),n(1)})),this.arkSADurationDownButton.addEventListener("click",(function(e){e.preventDefault(),n(-1)}))}if(this.arkOnlyCheck&&this.arkOnlyCheck.addEventListener("change",(function(e){if(e.preventDefault(),e.currentTarget.checked){var n=O.personalCartItem;n||(n=new k,O.add(n)),n.build(O,!1,!0,0),O.save()}else{var i=O.personalCartItem;i&&O.remove(i)}t.cartView.update()})),this.arkOnlyGiftField){var i=function(e){var n=O.items.filter((function(e){return!0===e.isGift&&e.hasArk}));if(n.length!==e){if(n.length>e)for(var i=e;i<=n.length-1;i++)O.remove(n[i]);else if(n.length5||r<0)&&(t.arkOnlyGiftQuantityGroup.classList.add("shake"),setTimeout((function(){t.arkOnlyGiftQuantityGroup.classList.remove("shake")}),400),r=Math.max(Math.min(r,5),0)),n!==r&&(t.arkOnlyGiftField.value=r,i(r))};this.arkOnlyGiftUpButton.addEventListener("click",(function(e){e.preventDefault(),a(1)})),this.arkOnlyGiftDownButton.addEventListener("click",(function(e){e.preventDefault(),a(-1)}))}},present:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null;this.editCartItem=e,this.arkCheck.disabled=!1,this.arkSACheck&&(this.arkSACheck.disabled=!1),e?(this.giftCheck.checked=e.isGift,this.giftCheck.disabled=!0,this.arkCheck.checked=e.hasArk,this.arkSACheck&&(this.arkSACheck.checked=e.hasArkSA)):(this.giftCheck.checked=!1,this.giftCheck.disabled=!1,this.arkCheck.checked=!1,this.arkSACheck&&(this.arkSACheck.checked=!1)),this.arkSADurationField&&(this.arkSADurationField.value=e?e.arkSAYears:"1"),this.cancelButton.innerText="Cancel",this.update(),r.showModal("checkout-wizard")},getGameStatus:function(){var e={Ark:P,ArkSA:P},t=O.personalCartItem,n=Boolean(this.editCartItem);return this.giftCheck&&this.giftCheck.checked?(e.Ark=this.arkCheck&&!1===this.arkCheck.disabled&&this.arkCheck.checked?I:P,e.ArkSA=this.arkSACheck&&!1===this.arkSACheck.disabled&&this.arkSACheck.checked?I:P):(O.arkLicense?e.Ark=B:t&&(!1===n||t.id!==this.editCartItem.id)&&t.hasArk?e.Ark=L:this.arkCheck&&!1===this.arkCheck.disabled&&this.arkCheck.checked?e.Ark=I:e.Ark=P,O.arkSALicense?e.ArkSA=B:t&&(!1===n||t.id!==this.editCartItem.id)&&t.hasArkSA?e.ArkSA=L:this.arkSACheck&&this.arkSACheck&&!1===this.arkSACheck.disabled&&this.arkSACheck.checked?e.ArkSA=I:e.ArkSA=P),e},update:function(){var e=this.getGameStatus(),t=0;if(this.arkCheck&&!1===this.arkCheck.disabled&&!0===this.arkCheck.checked&&(t+=n.Ark.Base.Price),this.arkSACheck){var i=n.ArkSA.Base.Price,r=n.ArkSA.Base.Price,o=Math.min(Math.max(parseInt(this.arkSADurationField.value)||1,1),5);parseInt(this.arkSADurationField.value)!==o&&document.activeElement!==this.arkSADurationField&&(this.arkSADurationField.value=o);var c=Math.max(o-1,0),s=Math.round((n.ArkSA.Base.Price-n.ArkSA.Upgrade.Price)/n.ArkSA.Base.Price*100);if(this.arkSAPromoField.innerText="".concat(s,"% off first year when bundled with ").concat(n.Ark.Base.Name),e.ArkSA===B){var d,h=O.arkSALicense,f=Math.floor(Date.now()/1e3),k=h.expiresEpoch,m=u(l(k),!1),v=(f>k?86400*Math.floor(f/86400)+86400:k)+n.ArkSA.Renewal.PlanLengthSeconds*o,y=u(l(v),!1);d=f>k?'Renew your update plan
Expired on '.concat(m,'
New expiration: ').concat(y,""):'Extend your update plan
Expires on '.concat(m,'
New expiration: ').concat(y,""),this.arkSAStatusField.innerHTML=d,r=i=n.ArkSA.Renewal.Price*o,this.arkSAPromoField.innerText="",this.arkSAPromoField.classList.add("hidden")}else if(e.ArkSA===L)r=i=n.ArkSA.Renewal.Price*o,this.arkSAStatusField.innerText="Additional renewal years for ".concat(n.ArkSA.Base.Name," in your cart."),this.arkSAPromoField.innerText="",this.arkSAPromoField.classList.add("hidden");else if(e.Ark!==P){i=n.ArkSA.Base.Price+n.ArkSA.Renewal.Price*c,r=n.ArkSA.Upgrade.Price+n.ArkSA.Renewal.Price*c;var p=e.Ark===B?"because you own":"when bundled with";this.arkSAStatusField.innerText="Includes first year of app updates. Additional years cost ".concat(a(n.ArkSA.Renewal.Price)," each."),this.arkSAPromoField.innerText="".concat(s,"% off first year ").concat(p," ").concat(n.Ark.Base.Name),this.arkSAPromoField.classList.remove("hidden")}else this.arkSAStatusField.innerText="Includes first year of app updates. Additional years cost ".concat(a(n.ArkSA.Renewal.Price)," each."),this.arkSAPromoField.innerText="".concat(s,"% off first year when bundled with ").concat(n.Ark.Base.Name),this.arkSAPromoField.classList.remove("hidden"),r=i=n.ArkSA.Base.Price+n.ArkSA.Renewal.Price*c;this.arkSAPriceField.classList.toggle("checkout-wizard-discounted",i!==r),this.arkSAPriceField.innerText=a(i),this.arkSAUpgradePriceField.classList.toggle("hidden",i===r),this.arkSAUpgradePriceField.innerText=a(r),!1===this.arkSACheck.disabled&&!0===this.arkSACheck.checked&&(t+=r)}e.Ark===B?(this.arkStatusField.innerText="You already own ".concat(n.Ark.Base.Name,"."),this.arkCheck.disabled=!0,this.arkCheck.checked=!1):e.Ark===L?(this.arkStatusField.innerText="".concat(n.Ark.Base.Name," is already in your cart."),this.arkCheck.disabled=!0,this.arkCheck.checked=!1):(this.arkStatusField.innerText="Includes lifetime app updates.",this.arkCheck.disabled=!1);var g=this.editCartItem?"Edit":"Add to Cart";t>0?(this.actionButton.disabled=!1,this.actionButton.innerText="".concat(g,": ").concat(a(t))):(this.actionButton.disabled=!0,this.actionButton.innerText=g)}},H={wizard:null,emailField:document.getElementById("storefront-cart-header-email-field"),changeEmailButton:document.getElementById("storefront-cart-header-email-button"),currencyMenu:document.getElementById("storefront-cart-currency-menu"),addMoreButton:document.getElementById("storefront-cart-more-button"),checkoutButton:document.getElementById("storefront-cart-checkout-button"),refundCheckbox:document.getElementById("storefront-refund-checkbox"),footer:document.getElementById("storefront-cart-footer"),body:document.getElementById("storefront-cart"),totalField:document.getElementById("storefront-cart-total"),init:function(e){var t=this;this.wizard=e,this.currencyMenu.addEventListener("change",(function(e){e.preventDefault(),c.get("/omni/currency?currency=".concat(e.target.value)).then((function(){window.location.reload()})).catch((function(e){console.log(JSON.stringify(e)),r.show("Currency was not changed",e.statusText)}))})),this.addMoreButton.addEventListener("click",(function(e){e.preventDefault(),t.wizard.present()})),this.checkoutButton.addEventListener("click",(function(e){e.preventDefault();var n=t.refundCheckbox.checked,i=function(e){t.update(),e?O.count>0?r.show("Your cart contents have changed.","The items in your cart have changed based on your e-mail address. Please review before continuing checkout."):r.show("Your cart is now empty.","You already own everything in your cart. There is no need to purchase again."):(t.checkoutButton.disabled=!0,c.post("/omni/begin",f(f({},O.toJSON()),{},{refundPolicyAgreed:n})).then((function(e){try{var t=JSON.parse(e.body),n=t.url;sessionStorage.setItem("clientReferenceId",t.client_reference_id),window.location=n}catch(t){console.log(e.body),r.show("Unknown Error","There was an unknown error while starting the checkout process.")}})).catch((function(e){try{var t=JSON.parse(e.body).message;r.show("Checkout Error",t)}catch(t){console.log(e.body),r.show("Unknown Error","There was an unknown error while starting the checkout process.")}})).finally((function(){t.checkoutButton.disabled=!1})))};O.email?n?i(!1):r.show("Hang on","Please agree to the refund policy."):Q.present(!1,i)})),this.changeEmailButton.addEventListener("click",(function(e){e.preventDefault(),Q.present(!1,(function(){t.update()}))})),this.update()},update:function(){var e,n=this;if(z){this.emailField.innerText=O.email,this.changeEmailButton.disabled=Boolean(t.forceEmail),this.changeEmailButton.innerText=O.email?"Change Email":"Set Email",this.changeEmailButton.classList.remove("hidden"),this.addMoreButton.classList.add("hidden"),O.arkLicense?(this.wizard.arkOnlyOwnedField.classList.remove("hidden"),this.wizard.arkOnlyCheck.parentElement.classList.add("hidden")):(this.wizard.arkOnlyOwnedField.classList.add("hidden"),this.wizard.arkOnlyCheck.parentElement.classList.remove("hidden"));var i=O.personalCartItem;if(this.wizard.arkOnlyCheck.checked=i&&i.hasArk,document.activeElement!==this.wizard.arkOnlyGiftField){var r=O.items.filter((function(e){return!0===e.isGift&&e.hasArk}));this.wizard.arkOnlyGiftField.value=r.length}}else if(O.count>0)this.emailField.innerText=O.email,this.changeEmailButton.disabled=Boolean(t.forceEmail),this.changeEmailButton.innerText=O.email?"Change Email":"Set Email",this.changeEmailButton.classList.remove("hidden"),this.body.innerText="",this.body.classList.remove("empty"),this.footer.classList.remove("hidden"),O.items.forEach((function(e){var t=n.createCartItemRow(e);t&&n.body.appendChild(t)})),N.innerText="Go to Cart";else{this.emailField.innerText="",this.changeEmailButton.classList.add("hidden"),this.body.innerText="",this.body.classList.add("empty"),this.footer.classList.add("hidden"),this.body.appendChild(document.createElement("div"));var a=document.createElement("div"),o=document.createElement("p");o.appendChild(document.createTextNode("Your cart is empty.")),a.appendChild(o),this.buyMoreButton=document.createElement("button"),this.buyMoreButton.addEventListener("click",(function(){Q.present(!0,(function(){n.wizard.present()}))})),this.buyMoreButton.classList.add("default"),this.buyMoreButton.appendChild(document.createTextNode("Buy Omni"));var c=document.createElement("p");c.appendChild(this.buyMoreButton),a.appendChild(c),this.body.appendChild(a),this.body.appendChild(document.createElement("div")),N.innerText="Buy Omni"}this.totalField.setAttribute("beacon-price",O.total),e=t.currencyCode,document.querySelectorAll(".formatted-price").forEach((function(t){var n,i=parseFloat(null!==(n=t.getAttribute("beacon-price"))&&void 0!==n?n:t.innerText),r=t.getAttribute("beacon-currency"),a=s(null!=r?r:e);a&&(t.innerText=a(i))})),this.checkoutButton.disabled=0===O.total},createProductRow:function(e,t){var n=e.getQuantity(t);if(n<=0)return null;var r=i[t].Name,a=i[t].Price,o=document.createElement("div");o.appendChild(document.createTextNode(n));var c=document.createElement("div");c.appendChild(document.createTextNode(r));var s=document.createElement("div");s.classList.add("formatted-price"),s.appendChild(document.createTextNode(a*n));var l=document.createElement("div");return l.classList.add("bundle-product"),l.appendChild(o),l.appendChild(c),l.appendChild(s),l},createCartItemRow:function(e){var t=this,n=e.productIds;if(n.length<=0)return null;var i=document.createElement("div");i.classList.add("bundle"),n.forEach((function(n){var r=t.createProductRow(e,n);r&&i.appendChild(r)}));var r=document.createElement("div");e.isGift&&(i.classList.add("gift"),r.classList.add("gift"),n.length>1?r.appendChild(document.createTextNode("These products are a gift. You will receive a gift code for them.")):r.appendChild(document.createTextNode("This product is a gift. You will receive a gift code for it.")));var a=document.createElement("button");a.appendChild(document.createTextNode("Edit")),a.classList.add("small"),a.addEventListener("click",(function(n){n.preventDefault(),t.wizard.present(e)}));var o=document.createElement("button");o.appendChild(document.createTextNode("Remove")),o.classList.add("red"),o.classList.add("small"),o.addEventListener("click",(function(n){n.preventDefault(),O.remove(e),t.update()}));var c=document.createElement("div");c.classList.add("button-group"),c.appendChild(a),c.appendChild(o);var s=document.createElement("div");s.appendChild(c);var l=document.createElement("div");return l.classList.add("actions"),l.classList.add("double-group"),l.appendChild(r),l.appendChild(s),i.appendChild(l),i}};U.init(H),H.init(U);var V=function(){var e=!(arguments.length>0&&void 0!==arguments[0])||arguments[0];window.scrollTo(window.scrollX,0),"#checkout"===window.location.hash?G.switchView("page-cart",e):G.back(e)};N.addEventListener("click",(function(e){e.preventDefault(),z||O.count>0?R():Q.present(!0,(function(){U.present()}))})),j.addEventListener("click",(function(){history.pushState({},"","/omni"),dispatchEvent(new PopStateEvent("popstate",{}))})),window.addEventListener("popstate",(function(){V(!0)})),V(!1),t.forceEmail&&(O.email!==t.forceEmail&&O.reset(),0===O.count?Q.present(!0,(function(){U.present()})):R())}))})(); \ No newline at end of file +(()=>{"use strict";function e(t){return e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},e(t)}function t(e,t){for(var n=0;n1&&void 0!==arguments[1]?arguments[1]:null,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"Ok";return this.confirm(e,t,n,null)}},{key:"confirm",value:function(e){var t=this,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"Ok",a=arguments.length>3&&void 0!==arguments[3]?arguments[3]:"Cancel";return new Promise((function(r,o){var c=document.getElementById("overlay"),l=document.getElementById("dialog"),s=document.getElementById("dialog_message"),u=document.getElementById("dialog_explanation"),d=document.getElementById("dialog_action_button"),h=document.getElementById("dialog_cancel_button");c&&l&&s&&u&&d&&h?(c.className="exist",l.className="exist",setTimeout((function(){c.className="exist visible",l.className="exist visible"}),10),s.innerText=e,u.innerText=n||"",d.clickHandler&&d.removeEventListener("click",d.clickHandler),h.clickHandler&&h.removeEventListener("click",h.clickHandler),d.clickHandler=function(){t.hide(),setTimeout((function(){r()}),300)},d.addEventListener("click",d.clickHandler),d.innerText=i,a?(h.clickHandler=function(){t.hide(),setTimeout((function(){o()}),300)},h.addEventListener("click",h.clickHandler),h.innerText=a):h.className="hidden"):o()}))}},{key:"hide",value:function(){var e=document.getElementById("overlay"),t=document.getElementById("dialog");e&&t&&(e.className="exist",t.className="exist",setTimeout((function(){e.className="",t.className=""}),300))}},{key:"showModal",value:function(e){var t=this;if(!this.activeModal){var n=document.getElementById("overlay"),i=document.getElementById(e);n&&i&&(n.classList.add("exist"),i.classList.add("exist"),this.activeModal=e,setTimeout((function(){n.classList.add("visible"),i.classList.add("visible")}),10),this.viewportWatcher=setInterval((function(){if(t.activeModal){document.querySelectorAll("#".concat(t.activeModal," .modal-content .content")).forEach((function(e){i.classList.toggle("scrolled",e.scrollHeight>e.clientHeight)}));var e=Math.max(document.documentElement.clientHeight||0,window.innerHeight||0);i.classList.toggle("centered",i.clientHeight>.75*e)}}),100))}}},{key:"hideModal",value:function(){var e=this;return new Promise((function(t,n){if(e.activeModal){var i=document.getElementById("overlay"),a=document.getElementById(e.activeModal);i&&a?(e.viewportWatcher&&(clearInterval(e.viewportWatcher),e.viewportWatcher=null),i.classList.remove("visible"),a.classList.remove("visible"),setTimeout((function(){i.classList.remove("exist"),a.classList.remove("exist"),e.activeModal=null,t()}),300)):n()}else n()}))}}],null&&t(n.prototype,null),i&&t(n,i),Object.defineProperty(n,"prototype",{writable:!1}),e}();function r(e){return r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},r(e)}function o(e,t){for(var n=0;n=200&&e.status<300||304===e.status}}},{key:"start",value:function(e,t){var n=this,i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null,a=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{};return new Promise((function(o,c){var l=new XMLHttpRequest;if(l.open(e,t,!0),"object"===r(a)&&null!==a&&!1===Array.isArray(a))for(var s=0,u=Object.keys(a);s1&&void 0!==arguments[1]?arguments[1]:{};return e.start("GET",t,null,n)}},{key:"post",value:function(t,n){var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return n instanceof URLSearchParams?(i["Content-Type"]="application/x-www-form-urlencoded",e.start("POST",t,n.toString(),i)):"object"===r(n)&&null!==n||Array.isArray(n)?(i["Content-Type"]="application/json",e.start("POST",t,JSON.stringify(n),i)):e.start("POST",t,n,i)}},{key:"delete",value:function(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return e.start("DELETE",t,null,n)}}],null&&o(t.prototype,null),n&&o(t,n),Object.defineProperty(t,"prototype",{writable:!1}),e}();function l(e){return l="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},l(e)}function s(e,t){var n="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(!n){if(Array.isArray(e)||(n=function(e,t){if(e){if("string"==typeof e)return u(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);return"Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?u(e,t):void 0}}(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var i=0,a=function(){};return{s:a,n:function(){return i>=e.length?{done:!0}:{done:!1,value:e[i++]}},e:function(e){throw e},f:a}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var r,o=!0,c=!1;return{s:function(){n=n.call(e)},n:function(){var e=n.next();return o=e.done,e},e:function(e){c=!0,r=e},f:function(){try{o||null==n.return||n.return()}finally{if(c)throw r}}}}function u(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,i=new Array(t);n1&&void 0!==arguments[1]&&arguments[1],n=arguments.length>2&&void 0!==arguments[2]&&arguments[2],i=Intl.DateTimeFormat().resolvedOptions(),a={dateStyle:"medium"};t&&(a.timeStyle="short");var r=Intl.DateTimeFormat(i.locale,a).format(e);return n&&(r="".concat(r," ").concat(i.timeZone)),r};function y(e){return y="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},y(e)}function g(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);t&&(i=i.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,i)}return n}function b(e){for(var t=1;t=e.length?{done:!0}:{done:!1,value:e[i++]}},e:function(e){throw e},f:a}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var r,o=!0,c=!1;return{s:function(){n=n.call(e)},n:function(){var e=n.next();return o=e.done,e},e:function(e){c=!0,r=e},f:function(){try{o||null==n.return||n.return()}finally{if(c)throw r}}}}function S(e,t){if(e){if("string"==typeof e)return E(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);return"Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?E(e,t):void 0}}function E(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,i=new Array(t);n>e/4).toString(16)})))}return B(e,[{key:"id",get:function(){return T(this,u)}},{key:"isGift",get:function(){return T(this,h)},set:function(e){x(this,h,e)}},{key:"productIds",get:function(){return Object.keys(T(this,d))}},{key:"count",get:function(){return Object.keys(T(this,d)).length}},{key:"reset",value:function(){x(this,d,{})}},{key:"add",value:function(e,t){this.setQuantity(e,this.getQuantity(e)+t)}},{key:"remove",value:function(e,t){this.setQuantity(e,this.getQuantity(e)-t)}},{key:"getQuantity",value:function(e){var t;return null!==(t=T(this,d)[e])&&void 0!==t?t:0}},{key:"setQuantity",value:function(e,t){t<=0?Object.prototype.hasOwnProperty.call(T(this,d),e)&&delete T(this,d)[e]:T(this,d)[e]=t}},{key:"toJSON",value:function(){return{id:T(this,u),products:T(this,d),isGift:T(this,h)}}},{key:"fingerprint",get:function(){var e=this,t=Object.keys(T(this,d)).sort().reduce((function(t,n){return t[n]=T(e,d)[n],t}),{});return btoa(JSON.stringify({id:T(this,u),products:t,isGift:T(this,h)}))}},{key:"hasArk",get:function(){var e;return this.getQuantity(null==n||null===(e=n.Ark)||void 0===e||null===(e=e.Base)||void 0===e?void 0:e.ProductId)>0}},{key:"hasArkSA",get:function(){var e,t,i;return this.getQuantity(null==n||null===(e=n.ArkSA)||void 0===e||null===(e=e.Base)||void 0===e?void 0:e.ProductId)>0||this.getQuantity(null==n||null===(t=n.ArkSA)||void 0===t||null===(t=t.Upgrade)||void 0===t?void 0:t.ProductId)>0||this.getQuantity(null==n||null===(i=n.ArkSA)||void 0===i||null===(i=i.Renewal)||void 0===i?void 0:i.ProductId)>0}},{key:"arkSAYears",get:function(){var e,t,i;return Math.min(this.getQuantity(null==n||null===(e=n.ArkSA)||void 0===e||null===(e=e.Base)||void 0===e?void 0:e.ProductId)+this.getQuantity(null==n||null===(t=n.ArkSA)||void 0===t||null===(t=t.Upgrade)||void 0===t?void 0:t.ProductId)+this.getQuantity(null==n||null===(i=n.ArkSA)||void 0===i||null===(i=i.Renewal)||void 0===i?void 0:i.ProductId),5)}},{key:"hasGame",value:function(e){var t,i;return"ArkSA"===e?this.hasArkSA:this.getQuantity(null==n||null===(t=n[e])||void 0===t||null===(t=t.Base)||void 0===t?void 0:t.ProductId)>0||this.getQuantity(null==n||null===(i=n[e])||void 0===i||null===(i=i.Renewal)||void 0===i?void 0:i.ProductId)>0}},{key:"yearsForGame",value:function(e){var t,i;return"ArkSA"===e?this.arkSAYears:Math.min(this.getQuantity(null==n||null===(t=n[e])||void 0===t||null===(t=t.Base)||void 0===t?void 0:t.ProductId)+this.getQuantity(null==n||null===(i=n[e])||void 0===i||null===(i=i.Renewal)||void 0===i?void 0:i.ProductId),5)}},{key:"yearsForOtherGames",value:function(){var e,t={},n=A(l);try{for(n.s();!(e=n.n()).done;){var i=e.value;this.yearsForGame(i.gameId)>0&&(t[i.gameId]=this.yearsForGame(i.gameId))}}catch(e){n.e(e)}finally{n.f()}return t}},{key:"build",value:function(e,t,i,a,r){if(this.reset(),this.isGift=t,i&&(t||null===e.arkLicense)&&this.setQuantity(n.Ark.Base.ProductId,1),a>0&&(a=Math.min(a,5),t?(i?this.setQuantity(n.ArkSA.Upgrade.ProductId,1):this.setQuantity(n.ArkSA.Base.ProductId,1),a>1&&this.setQuantity(n.ArkSA.Renewal.ProductId,a-1)):null!==e.arkSALicense?this.setQuantity(n.ArkSA.Renewal.ProductId,a):(i||null!==e.arkLicense?this.setQuantity(n.ArkSA.Upgrade.ProductId,1):this.setQuantity(n.ArkSA.Base.ProductId,1),a>1&&this.setQuantity(n.ArkSA.Renewal.ProductId,a-1))),r){var o,c=A(l);try{for(c.s();!(o=c.n()).done;){var s,u=o.value,d=Math.min(null!==(s=r[u.gameId])&&void 0!==s?s:0,5);0!==d&&(!1===t&&null!==e.findLicense(n[u.gameId].Base.ProductId)?this.setQuantity(n[u.gameId].Renewal.ProductId,d):(this.setQuantity(n[u.gameId].Base.ProductId,1),d>1&&this.setQuantity(n[u.gameId].Renewal.ProductId,d-1)))}}catch(e){c.e(e)}finally{c.f()}}}},{key:"rebuild",value:function(e){var t=this.fingerprint;return this.build(e,this.isGift,this.hasArk,this.arkSAYears,this.yearsForOtherGames()),this.fingerprint!==t}},{key:"consume",value:function(e,t){if(t){if(this.isGift!==t.isGift)throw new Error("Cannot merge a gift item with a non-gift item.");for(var n=this.hasArk||t.hasArk,i=this.arkSAYears+t.arkSAYears,a=this.yearsForOtherGames(),r=t.yearsForOtherGames(),o=0,c=Object.entries(r);o0&&void 0!==arguments[0]?arguments[0]:null;if(I(this,e),L(this,y,{writable:!0,value:[]}),L(this,g,{writable:!0,value:null}),L(this,w,{writable:!0,value:!1}),L(this,C,{writable:!0,value:[]}),t)try{var n=JSON.parse(t);x(this,g,n.email),x(this,y,n.items.reduce((function(e,t){var n=new f(t);return n.count>0&&e.push(n),e}),[])),x(this,C,n.licenses)}catch(e){console.log("Failed to load saved cart")}}return B(e,[{key:"reset",value:function(){x(this,y,[]),x(this,g,null),x(this,w,!1),x(this,C,[]),this.save()}},{key:"toJSON",value:function(){return{email:T(this,g),items:T(this,y).reduce((function(e,t){return t.count>0&&e.push(t),e}),[]),licenses:T(this,C)}}},{key:"save",value:function(){localStorage.setItem("beaconCart",JSON.stringify(this))}},{key:"add",value:function(e){T(this,y).push(e),this.save()}},{key:"remove",value:function(e){x(this,y,T(this,y).filter((function(t){return t.id!==e.id}))),this.save()}},{key:"hasProduct",value:function(e){var t,n=arguments.length>1&&void 0!==arguments[1]&&arguments[1],i=A(T(this,y));try{for(i.s();!(t=i.n()).done;){var a=t.value;if(a.getQuantity(e)>0&&a.isGift===n)return!0}}catch(e){i.e(e)}finally{i.f()}return!1}},{key:"email",get:function(){return T(this,g)}},{key:"setEmail",value:function(e){var t=this;return new Promise((function(i,a){var r=function(){var e,i=t.personalCartItem;return!!i&&(i.hasArk&&t.arkLicense?(i.setQuantity(null===(e=n.Ark)||void 0===e||null===(e=e.Base)||void 0===e?void 0:e.ProductId,0),0===i.count&&t.remove(i),!0):i.rebuild(t))};if(null===e){x(t,g,null),x(t,w,!1),x(t,C,[]);var o=r();return t.save(),void i({newEmail:null,cartChanged:o})}if(/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test(String(e).trim().toLowerCase())){var l=new URLSearchParams;l.append("email",e),c.get("/omni/lookup?".concat(l.toString())).then((function(n){var a=JSON.parse(n.body);x(t,g,a.email),x(t,w,a.verified),x(t,C,a.purchases);var o=r();t.save(),i({newEmail:e,cartChanged:o})})).catch((function(e){x(t,g,null),x(t,w,!1),x(t,C,[]),r(),t.save(),a(e.statusText)}))}else a("Address is not valid")}))}},{key:"emailVerified",get:function(){return T(this,w)}},{key:"checkingEmail",get:function(){return!1}},{key:"items",get:function(){return function(e){if(Array.isArray(e))return E(e)}(e=T(this,y))||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||S(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}();var e}},{key:"count",get:function(){return T(this,y).reduce((function(e,t){return t.count>0&&e++,e}),0)}},{key:"findLicense",value:function(e){var t,n=A(T(this,C));try{for(n.s();!(t=n.n()).done;){var i=t.value;if(i.productId===e)return i}}catch(e){n.e(e)}finally{n.f()}return null}},{key:"arkLicense",get:function(){var e;return this.findLicense(null==n||null===(e=n.Ark)||void 0===e||null===(e=e.Base)||void 0===e?void 0:e.ProductId)}},{key:"arkSALicense",get:function(){var e;return this.findLicense(null==n||null===(e=n.ArkSA)||void 0===e||null===(e=e.Base)||void 0===e?void 0:e.ProductId)}},{key:"ark2License",get:function(){return null}},{key:"personalCartItem",get:function(){var e,t=A(T(this,y));try{for(t.s();!(e=t.n()).done;){var n=e.value;if(!1===n.isGift)return n}}catch(e){t.e(e)}finally{t.f()}return null}},{key:"total",get:function(){var e,t=0,n=A(T(this,y));try{for(n.s();!(e=n.n()).done;)t+=e.value.total}catch(e){n.e(e)}finally{n.f()}return t}}],[{key:"load",value:function(){return new this(localStorage.getItem("beaconCart"))}}]),e}(),O=P.load(),G=new WeakMap,N=new WeakMap,R=function(){function e(t){I(this,e),L(this,G,{writable:!0,value:[]}),L(this,N,{writable:!0,value:null}),T(this,G).push(t)}return B(e,[{key:"currentView",get:function(){return T(this,G).slice(-1)}},{key:"back",value:function(){var e=!(arguments.length>0&&void 0!==arguments[0])||arguments[0];return!(T(this,G).length<=1||(this.switchView(T(this,G)[T(this,G).length-2],e),x(this,G,T(this,G).slice(0,T(this,G).length-2)),0))}},{key:"clearHistory",value:function(){T(this,G).length<=1||x(this,G,T(this,G).slice(-1))}},{key:"switchView",value:function(e){var t=this,n=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];if(this.currentView!==e){T(this,N)&&(clearTimeout(T(this,N)),x(this,N,null));var i=document.getElementById(this.currentView),a=document.getElementById(e);T(this,G).push(e),n?(i.classList.add("invisible"),x(this,N,setTimeout((function(){i.classList.add("hidden"),a.classList.remove("hidden"),x(t,N,setTimeout((function(){a.classList.remove("invisible"),x(t,N,null)}),150))}),150))):(i.classList.add("hidden"),i.classList.add("invisible"),a.classList.remove("invisible"),a.classList.remove("hidden"))}}}]),e}();new R("checkout-wizard-start");var Q=new R("page-landing"),z=document.getElementById("buy-button"),U=document.getElementById("cart-back-button"),H=function(){document.title="Buy Beacon Omni",history.pushState({},"","/omni#checkout"),dispatchEvent(new PopStateEvent("popstate",{}))},V={cancelButton:document.getElementById("checkout-email-cancel"),actionButton:document.getElementById("checkout-email-action"),emailField:document.getElementById("checkout-email-field"),errorField:document.getElementById("checkout-email-error"),allowsSkipping:!1,successFunction:function(e){},init:function(){var e=this,t=function(t){e.actionButton.disabled=!0,e.cancelButton.disabled=!0,e.errorField.classList.add("hidden"),O.setEmail(t).then((function(t){t.newEmail;var n=t.cartChanged;a.hideModal(),setTimeout((function(){e.successFunction(n)}),310)})).catch((function(t){e.errorField.innerText=t,e.errorField.classList.remove("hidden")})).finally((function(){e.actionButton.disabled=!1,e.cancelButton.disabled=!1}))};this.actionButton.addEventListener("click",(function(n){n.preventDefault(),t(e.emailField.value)})),this.cancelButton.addEventListener("click",(function(n){n.preventDefault(),e.allowsSkipping?t(null):a.hideModal()}))},present:function(e,n){t.forceEmail?O.setEmail(t.forceEmail).then((function(e){e.newEmail;var t=e.cartChanged;n(t)})):(this.allowsSkipping=e,this.successFunction=n,O.email?this.emailField.value=O.email:this.emailField.value=sessionStorage.getItem("email")||localStorage.getItem("email")||"",this.cancelButton.innerText=e?"Skip For Now":"Cancel",a.showModal("checkout-email"))}};V.init();var W={cartView:null,editCartItem:null,actionButton:document.getElementById("checkout-wizard-action"),arkCheck:document.getElementById("checkout-wizard-ark-check"),arkOnlyCheck:document.getElementById("storefront-ark-check"),arkOnlyGiftDownButton:document.getElementById("storefront-ark-gift-decrease"),arkOnlyGiftField:document.getElementById("storefront-ark-gift-field"),arkOnlyGiftQuantityGroup:document.getElementById("storefront-ark-gift-group"),arkOnlyGiftUpButton:document.getElementById("storefront-ark-gift-increase"),arkOnlyOwnedField:document.getElementById("storefront-ark-owned"),arkPriceField:document.getElementById("checkout-wizard-ark-price"),arkSACheck:document.getElementById("checkout-wizard-arksa-check"),arkSADurationDownButton:document.getElementById("checkout-wizard-arksa-yeardown-button"),arkSADurationField:document.getElementById("checkout-wizard-arksa-duration-field"),arkSADurationGroup:document.getElementById("checkout-wizard-arksa-duration-group"),arkSADurationUpButton:document.getElementById("checkout-wizard-arksa-yearup-button"),arkSAPriceField:document.getElementById("checkout-wizard-arksa-full-price"),arkSAPromoField:document.getElementById("checkout-wizard-promo-arksa"),arkSAStatusField:document.getElementById("checkout-wizard-status-arksa"),arkSAUpgradePriceField:document.getElementById("checkout-wizard-arksa-discount-price"),arkStatusField:document.getElementById("checkout-wizard-status-ark"),giftCheck:document.getElementById("checkout-wizard-gift-check"),cancelButton:document.getElementById("checkout-wizard-cancel"),init:function(e){var t=this;this.cartView=e;var n,i=A(l);try{for(i.s();!(n=i.n()).done;){var r=n.value.gameId.toLowerCase();this["".concat(r,"Check")]=document.getElementById("checkout-wizard-".concat(r,"-check")),this["".concat(r,"DurationDownButton")]=document.getElementById("checkout-wizard-".concat(r,"-yeardown-button")),this["".concat(r,"DurationField")]=document.getElementById("checkout-wizard-".concat(r,"-duration-field")),this["".concat(r,"DurationGroup")]=document.getElementById("checkout-wizard-".concat(r,"-duration-group")),this["".concat(r,"DurationUpButton")]=document.getElementById("checkout-wizard-".concat(r,"-yearup-button")),this["".concat(r,"PriceField")]=document.getElementById("checkout-wizard-".concat(r,"-price")),this["".concat(r,"StatusField")]=document.getElementById("checkout-wizard-status-".concat(r))}}catch(e){i.e(e)}finally{i.f()}if(this.cancelButton&&this.cancelButton.addEventListener("click",(function(e){e.preventDefault(),a.hideModal()})),this.actionButton&&this.actionButton.addEventListener("click",(function(e){e.preventDefault();var n,i=t.giftCheck.checked,r=t.arkCheck&&!1===t.arkCheck.disabled&&t.arkCheck.checked,o=t.arkSACheck&&!1===t.arkSACheck.disabled&&t.arkSACheck.checked,c=!1,s={},u=A(l);try{for(u.s();!(n=u.n()).done;){var d=n.value.gameId,h=d.toLowerCase();t["".concat(h,"Check")]&&!1===t["".concat(h,"Check")].disabled&&t["".concat(h,"Check")].checked&&(c=!0,s[d]=parseInt(t["".concat(h,"DurationField")].value)||1)}}catch(e){u.e(e)}finally{u.f()}if(!1!==(r||o||c)){var v,m=o?parseInt(t.arkSADurationField.value)||1:0;if(t.editCartItem?v=t.editCartItem:(v=new f).isGift=i,v.build(O,i,r,m,s),!1===Boolean(t.editCartItem)){var k=O.personalCartItem;!1===i&&!0===Boolean(k)?k.consume(O,v):O.add(v)}O.save(),t.cartView.update(),H(),a.hideModal()}})),this.giftCheck&&this.giftCheck.addEventListener("change",(function(){t.update()})),this.arkCheck&&this.arkCheck.addEventListener("change",(function(){t.update()})),this.arkSACheck&&this.arkSADurationField&&this.arkSADurationUpButton&&this.arkSADurationDownButton&&this.arkSADurationGroup){this.arkSACheck.addEventListener("change",(function(){t.update()})),this.arkSADurationField.addEventListener("input",(function(){t.update()}));var o=function(e){var n=parseInt(t.arkSADurationField.value),i=n+e;(i>5||i<1)&&(t.arkSADurationGroup.classList.add("shake"),setTimeout((function(){t.arkSADurationGroup.classList.remove("shake")}),400),i=Math.max(Math.min(i,5),1)),n!==i&&(t.arkSADurationField.value=i,t.arkSACheck.checked=!0,t.update())};this.arkSADurationUpButton.addEventListener("click",(function(e){e.preventDefault(),o(1)})),this.arkSADurationDownButton.addEventListener("click",(function(e){e.preventDefault(),o(-1)}))}var c,s=A(l);try{var u=function(){var e=c.value.gameId.toLowerCase(),n=t["".concat(e,"Check")],i=t["".concat(e,"DurationField")],a=t["".concat(e,"DurationUpButton")],r=t["".concat(e,"DurationDownButton")],o=t["".concat(e,"DurationGroup")];if(n&&i&&a&&r&&o){n.addEventListener("change",(function(){t.update()})),i.addEventListener("input",(function(){t.update()}));var l=function(e){var a=parseInt(i.value),r=a+e;(r>5||r<1)&&(o.classList.add("shake"),setTimeout((function(){o.classList.remove("shake")}),400),r=Math.max(Math.min(r,5),1)),a!==r&&(i.value=r,n.checked=!0,t.update())};a.addEventListener("click",(function(e){e.preventDefault(),l(1)})),r.addEventListener("click",(function(e){e.preventDefault(),l(-1)}))}};for(s.s();!(c=s.n()).done;)u()}catch(e){s.e(e)}finally{s.f()}if(this.arkOnlyCheck&&this.arkOnlyCheck.addEventListener("change",(function(e){if(e.preventDefault(),e.currentTarget.checked){var n=O.personalCartItem;n||(n=new f,O.add(n)),n.build(O,!1,!0,0),O.save()}else{var i=O.personalCartItem;i&&O.remove(i)}t.cartView.update()})),this.arkOnlyGiftField){var d=function(e){var n=O.items.filter((function(e){return!0===e.isGift&&e.hasArk}));if(n.length!==e){if(n.length>e)for(var i=e;i<=n.length-1;i++)O.remove(n[i]);else if(n.length5||i<0)&&(t.arkOnlyGiftQuantityGroup.classList.add("shake"),setTimeout((function(){t.arkOnlyGiftQuantityGroup.classList.remove("shake")}),400),i=Math.max(Math.min(i,5),0)),n!==i&&(t.arkOnlyGiftField.value=i,d(i))};this.arkOnlyGiftUpButton.addEventListener("click",(function(e){e.preventDefault(),h(1)})),this.arkOnlyGiftDownButton.addEventListener("click",(function(e){e.preventDefault(),h(-1)}))}},filter:function(e){var t,n=!0,i=A(document.querySelectorAll(".checkout-wizard-list-game"));try{for(i.s();!(t=i.n()).done;){var a=t.value,r=a.getAttribute("beacon-game-id");0===e.length||e.includes(r)||("Ark"===r||"ArkSA"===r)&&(e.includes("Ark")||e.includes("ArkSA"))?(a.classList.remove("hidden"),n?(n=!1,a.classList.remove("separated")):a.classList.add("separated")):a.classList.add("hidden")}}catch(e){i.e(e)}finally{i.f()}},present:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[],n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;this.filter(t),t.length>0&&!1===t.includes(n)&&(n=null),this.editCartItem=e,this.arkCheck.disabled=!1,this.arkSACheck&&(this.arkSACheck.disabled=!1);var i,r=A(l);try{for(r.s();!(i=r.n()).done;){var o=i.value.gameId.toLowerCase();this["".concat(o,"Check")]&&(this["".concat(o,"Check")].disabled=!1)}}catch(e){r.e(e)}finally{r.f()}if(e){this.giftCheck.checked=e.isGift,this.giftCheck.disabled=!0,this.arkCheck.checked=e.hasArk,this.arkSACheck&&(this.arkSACheck.checked=e.hasArkSA);var c,s=A(l);try{for(s.s();!(c=s.n()).done;){var u=c.value.gameId,d=u.toLowerCase();this["".concat(d,"Check")]&&(this["".concat(d,"Check")].checked=e.hasGame(u))}}catch(e){s.e(e)}finally{s.f()}}else{this.giftCheck.checked=!1,this.giftCheck.disabled=!1,this.arkCheck.checked="Ark"===n,this.arkSACheck&&(this.arkSACheck.checked="ArkSA"===n);var h,f=A(l);try{for(f.s();!(h=f.n()).done;){var v=h.value.gameId,m=v.toLowerCase();this["".concat(m,"Check")]&&(this["".concat(m,"Check")].checked=n===v)}}catch(e){f.e(e)}finally{f.f()}}this.arkSADurationField&&(this.arkSADurationField.value=e?e.arkSAYears:"1");var k,p=A(l);try{for(p.s();!(k=p.n()).done;){var y=k.value.gameId,g=y.toLowerCase();this["".concat(g,"DurationField")].value=e?e.yearsForGame(y):"1"}}catch(e){p.e(e)}finally{p.f()}this.cancelButton.innerText="Cancel",this.update(),a.showModal("checkout-wizard")},getGameStatus:function(){var e,t={Ark:j,ArkSA:j},i=A(l);try{for(i.s();!(e=i.n()).done;)t[e.value.gameId]=j}catch(e){i.e(e)}finally{i.f()}var a=O.personalCartItem,r=Boolean(this.editCartItem);if(this.giftCheck&&this.giftCheck.checked){t.Ark=this.arkCheck&&!1===this.arkCheck.disabled&&this.arkCheck.checked?M:j,t.ArkSA=this.arkSACheck&&!1===this.arkSACheck.disabled&&this.arkSACheck.checked?M:j;var o,c=A(l);try{for(c.s();!(o=c.n()).done;){var s=o.value.gameId.toLowerCase();t[gameId]=this["".concat(s,"Check")]&&!1===this["".concat(s,"Check")].disabled&&this["".concat(s,"Check")].checked?M:j}}catch(e){c.e(e)}finally{c.f()}}else{O.arkLicense?t.Ark=F:a&&(!1===r||a.id!==this.editCartItem.id)&&a.hasArk?t.Ark=D:this.arkCheck&&!1===this.arkCheck.disabled&&this.arkCheck.checked?t.Ark=M:t.Ark=j,O.arkSALicense?t.ArkSA=F:a&&(!1===r||a.id!==this.editCartItem.id)&&a.hasArkSA?t.ArkSA=D:this.arkSACheck&&this.arkSACheck&&!1===this.arkSACheck.disabled&&this.arkSACheck.checked?t.ArkSA=M:t.ArkSA=j;var u,d=A(l);try{for(d.s();!(u=d.n()).done;){var h,f=u.value.gameId,v=f.toLowerCase();O.findLicense(null===(h=n[f])||void 0===h||null===(h=h.Base)||void 0===h?void 0:h.ProductId)?t[f]=F:a&&(!1===r||a.id!==this.editCartItem.id)&&a.hasGame(f)?t[f]=D:this["".concat(v,"Check")]&&!1===this["".concat(v,"Check")].disabled&&this["".concat(v,"Check")].checked?t[f]=M:t[f]=j}}catch(e){d.e(e)}finally{d.f()}}return t},update:function(){var e=this.getGameStatus(),t=0;if(this.arkCheck&&!1===this.arkCheck.disabled&&!0===this.arkCheck.checked&&(t+=n.Ark.Base.Price),this.arkSACheck){var i=n.ArkSA.Base.Price,a=n.ArkSA.Base.Price,o=Math.min(Math.max(parseInt(this.arkSADurationField.value)||1,1),5);parseInt(this.arkSADurationField.value)!==o&&document.activeElement!==this.arkSADurationField&&(this.arkSADurationField.value=o);var c=Math.max(o-1,0),s=Math.round((n.ArkSA.Base.Price-n.ArkSA.Upgrade.Price)/n.ArkSA.Base.Price*100);if(this.arkSAPromoField.innerText="".concat(s,"% off first year when bundled with ").concat(n.Ark.Base.Name),e.ArkSA===F){var u,d=O.arkSALicense,h=Math.floor(Date.now()/1e3),f=d.expiresEpoch,v=p(k(f),!1),m=(h>f?86400*Math.floor(h/86400)+86400:f)+n.ArkSA.Renewal.PlanLengthSeconds*o,y=p(k(m),!1);u=h>f?'Renew your update plan
Expired on '.concat(v,'
New expiration: ').concat(y,""):'Extend your update plan
Expires on '.concat(v,'
New expiration: ').concat(y,""),this.arkSAStatusField.innerHTML=u,a=i=n.ArkSA.Renewal.Price*o,this.arkSAPromoField.innerText="",this.arkSAPromoField.classList.add("hidden")}else if(e.ArkSA===D)a=i=n.ArkSA.Renewal.Price*o,this.arkSAStatusField.innerText="Additional renewal years for ".concat(n.ArkSA.Base.Name," in your cart."),this.arkSAPromoField.innerText="",this.arkSAPromoField.classList.add("hidden");else if(e.Ark!==j){i=n.ArkSA.Base.Price+n.ArkSA.Renewal.Price*c,a=n.ArkSA.Upgrade.Price+n.ArkSA.Renewal.Price*c;var g=e.Ark===F?"because you own":"when bundled with";this.arkSAStatusField.innerText="Includes 1 year of app updates. Additional years cost ".concat(r(n.ArkSA.Renewal.Price)," each."),this.arkSAPromoField.innerText="".concat(s,"% off first year ").concat(g," ").concat(n.Ark.Base.Name),this.arkSAPromoField.classList.remove("hidden")}else this.arkSAStatusField.innerText="Includes 1 year of app updates. Additional years cost ".concat(r(n.ArkSA.Renewal.Price)," each."),this.arkSAPromoField.innerText="".concat(s,"% off first year when bundled with ").concat(n.Ark.Base.Name),this.arkSAPromoField.classList.remove("hidden"),a=i=n.ArkSA.Base.Price+n.ArkSA.Renewal.Price*c;this.arkSAPriceField.classList.toggle("checkout-wizard-discounted",i!==a),this.arkSAPriceField.innerText=r(i),this.arkSAUpgradePriceField.classList.toggle("hidden",i===a),this.arkSAUpgradePriceField.innerText=r(a),!1===this.arkSACheck.disabled&&!0===this.arkSACheck.checked&&(t+=a)}e.Ark===F?(this.arkStatusField.innerText="You already own ".concat(n.Ark.Base.Name,"."),this.arkCheck.disabled=!0,this.arkCheck.checked=!1):e.Ark===D?(this.arkStatusField.innerText="".concat(n.Ark.Base.Name," is already in your cart."),this.arkCheck.disabled=!0,this.arkCheck.checked=!1):(this.arkStatusField.innerText="Includes lifetime app updates.",this.arkCheck.disabled=!1);var b,w=A(l);try{for(w.s();!(b=w.n()).done;){var S,E,I=b.value.gameId,C=I.toLowerCase(),B=this["".concat(C,"Check")],P=this["".concat(C,"DurationField")],L=this["".concat(C,"StatusField")],T=this["".concat(C,"PriceField")];if(B){var x=null===(S=n[I])||void 0===S||null===(S=S.Base)||void 0===S?void 0:S.Price,M=null===(null===(E=n[I])||void 0===E||null===(E=E.Base)||void 0===E?void 0:E.PlanLengthSeconds),G=Math.min(Math.max(parseInt(P.value)||1,1),5);parseInt(P.value)!==G&&document.activeElement!==P&&(P.value=G);var N,R=Math.max(G-1,0);if(M)if(e[I]===F)L.innerHTML="You already own ".concat(null===(N=n[I])||void 0===N||null===(N=N.Base)||void 0===N?void 0:N.Name,"."),B.disabled=!0,B.checked=!1;else if(e[I]===D){var Q;L.innerText="".concat(null===(Q=n[I])||void 0===Q||null===(Q=Q.Base)||void 0===Q?void 0:Q.Name," is already in your cart."),B.disabled=!0,B.checked=!1}else L.innerText="Includes lifetime app updates.",B.disabled=!1;else if(e[I]===F){var z,U,H,V=O.findLicense(null===(z=n[I])||void 0===z||null===(z=z.Base)||void 0===z?void 0:z.ProductId),W=Math.floor(Date.now()/1e3),Y=V.expiresEpoch,J=p(k(Y),!1),q=(W>Y?86400*Math.floor(W/86400)+86400:Y)+(null===(U=n[I])||void 0===U||null===(U=U.Renewal)||void 0===U?void 0:U.PlanLengthSeconds)*G,_=p(k(q),!1);H=W>Y?'Renew your update plan
Expired on '.concat(J,'
New expiration: ').concat(_,""):'Extend your update plan
Expires on '.concat(J,'
New expiration: ').concat(_,""),L.innerHTML=H}else if(e[I]===D){var Z,$;x=(null===(Z=n[I])||void 0===Z||null===(Z=Z.Renewal)||void 0===Z?void 0:Z.Price)*G,L.innerText="Additional renewal years for ".concat(null===($=n[I])||void 0===$||null===($=$.Base)||void 0===$?void 0:$.Name," in your cart.")}else{var X,K,ee,te;L.innerText="Includes ".concat(null===(X=n[I])||void 0===X||null===(X=X.Renewal)||void 0===X?void 0:X.PlanLength," of app updates. Additional years cost ").concat(r(null===(K=n[I])||void 0===K||null===(K=K.Renewal)||void 0===K?void 0:K.Price)," each."),x=(null===(ee=n[I])||void 0===ee||null===(ee=ee.Base)||void 0===ee?void 0:ee.Price)+(null===(te=n[I])||void 0===te||null===(te=te.Renewal)||void 0===te?void 0:te.Price)*R}T.innerText=r(x),!1===B.disabled&&!0===B.checked&&(t+=x)}}}catch(e){w.e(e)}finally{w.f()}var ne=this.editCartItem?"Edit":"Add to Cart";t>0?(this.actionButton.disabled=!1,this.actionButton.innerText="".concat(ne,": ").concat(r(t))):(this.actionButton.disabled=!0,this.actionButton.innerText=ne)}},Y={wizard:null,emailField:document.getElementById("storefront-cart-header-email-field"),changeEmailButton:document.getElementById("storefront-cart-header-email-button"),currencyMenu:document.getElementById("storefront-cart-currency-menu"),addMoreButton:document.getElementById("storefront-cart-more-button"),checkoutButton:document.getElementById("storefront-cart-checkout-button"),refundCheckbox:document.getElementById("storefront-refund-checkbox"),footer:document.getElementById("storefront-cart-footer"),body:document.getElementById("storefront-cart"),totalField:document.getElementById("storefront-cart-total"),init:function(e){var t=this;this.wizard=e,this.currencyMenu.addEventListener("change",(function(e){e.preventDefault(),c.get("/omni/currency?currency=".concat(e.target.value)).then((function(){window.location.reload()})).catch((function(e){console.log(JSON.stringify(e)),a.show("Currency was not changed",e.statusText)}))})),this.addMoreButton.addEventListener("click",(function(e){e.preventDefault(),t.wizard.present()})),this.checkoutButton.addEventListener("click",(function(e){e.preventDefault();var n=t.refundCheckbox.checked,i=function(e){t.update(),e?O.count>0?a.show("Your cart contents have changed.","The items in your cart have changed based on your e-mail address. Please review before continuing checkout."):a.show("Your cart is now empty.","You already own everything in your cart. There is no need to purchase again."):(t.checkoutButton.disabled=!0,c.post("/omni/begin",b(b({},O.toJSON()),{},{refundPolicyAgreed:n})).then((function(e){try{var t=JSON.parse(e.body),n=t.url;sessionStorage.setItem("clientReferenceId",t.client_reference_id),window.location=n}catch(t){console.log(e.body),a.show("Unknown Error","There was an unknown error while starting the checkout process.")}})).catch((function(e){try{var t=JSON.parse(e.body).message;a.show("Checkout Error",t)}catch(t){console.log(e.body),a.show("Unknown Error","There was an unknown error while starting the checkout process.")}})).finally((function(){t.checkoutButton.disabled=!1})))};O.email?n?i(!1):a.show("Hang on","Please agree to the refund policy."):V.present(!1,i)})),this.changeEmailButton.addEventListener("click",(function(e){e.preventDefault(),V.present(!1,(function(){t.update()}))})),this.update()},update:function(){var e,n=this;if(O.count>0)this.emailField.innerText=O.email,this.changeEmailButton.disabled=Boolean(t.forceEmail),this.changeEmailButton.innerText=O.email?"Change Email":"Set Email",this.changeEmailButton.classList.remove("hidden"),this.body.innerText="",this.body.classList.remove("empty"),this.footer.classList.remove("hidden"),O.items.forEach((function(e){var t=n.createCartItemRow(e);t&&n.body.appendChild(t)})),z.innerText="Go to Cart";else{this.emailField.innerText="",this.changeEmailButton.classList.add("hidden"),this.body.innerText="",this.body.classList.add("empty"),this.footer.classList.add("hidden"),this.body.appendChild(document.createElement("div"));var i=document.createElement("div"),a=document.createElement("p");a.appendChild(document.createTextNode("Your cart is empty.")),i.appendChild(a),this.buyMoreButton=document.createElement("button"),this.buyMoreButton.addEventListener("click",(function(){V.present(!0,(function(){n.wizard.present()}))})),this.buyMoreButton.classList.add("default"),this.buyMoreButton.appendChild(document.createTextNode("Buy Omni"));var r=document.createElement("p");r.appendChild(this.buyMoreButton),i.appendChild(r),this.body.appendChild(i),this.body.appendChild(document.createElement("div")),z.innerText="Buy Omni"}this.totalField.setAttribute("beacon-price",O.total),e=t.currencyCode,document.querySelectorAll(".formatted-price").forEach((function(t){var n,i=parseFloat(null!==(n=t.getAttribute("beacon-price"))&&void 0!==n?n:t.innerText),a=t.getAttribute("beacon-currency"),r=m(null!=a?a:e);r&&(t.innerText=r(i))})),this.checkoutButton.disabled=0===O.total},createProductRow:function(e,t){var n=e.getQuantity(t);if(n<=0)return null;var a=i[t].Name,r=i[t].Price,o=document.createElement("div");o.appendChild(document.createTextNode(n));var c=document.createElement("div");c.appendChild(document.createTextNode(a));var l=document.createElement("div");l.classList.add("formatted-price"),l.appendChild(document.createTextNode(r*n));var s=document.createElement("div");return s.classList.add("bundle-product"),s.appendChild(o),s.appendChild(c),s.appendChild(l),s},createCartItemRow:function(e){var t=this,n=e.productIds;if(n.length<=0)return null;var i=document.createElement("div");i.classList.add("bundle"),n.forEach((function(n){var a=t.createProductRow(e,n);a&&i.appendChild(a)}));var a=document.createElement("div");e.isGift&&(i.classList.add("gift"),a.classList.add("gift"),n.length>1?a.appendChild(document.createTextNode("These products are a gift. You will receive a gift code for them.")):a.appendChild(document.createTextNode("This product is a gift. You will receive a gift code for it.")));var r=document.createElement("button");r.appendChild(document.createTextNode("Edit")),r.classList.add("small"),r.addEventListener("click",(function(n){n.preventDefault(),t.wizard.present(e)}));var o=document.createElement("button");o.appendChild(document.createTextNode("Remove")),o.classList.add("red"),o.classList.add("small"),o.addEventListener("click",(function(n){n.preventDefault(),O.remove(e),t.update()}));var c=document.createElement("div");c.classList.add("button-group"),c.appendChild(r),c.appendChild(o);var l=document.createElement("div");l.appendChild(c);var s=document.createElement("div");return s.classList.add("actions"),s.classList.add("double-group"),s.appendChild(a),s.appendChild(l),i.appendChild(s),i}};W.init(Y),Y.init(W);var J=function(){var e=!(arguments.length>0&&void 0!==arguments[0])||arguments[0],t=window.location.hash.substring(1);if(s&&s.hasPage(t))return Q.switchView("page-landing",e),void s.switchPage(t);window.scrollTo(window.scrollX,0),"checkout"===t?Q.switchView("page-cart",e):Q.back(e)};z.addEventListener("click",(function(e){e.preventDefault(),O.count>0?H():V.present(!0,(function(){W.present()}))}));var q,_=A(o);try{var Z=function(){var e=q.value.gameId,t=document.getElementById("checkout-buy-".concat(e.toLowerCase(),"-button"));if(!t)return"continue";t.addEventListener("click",(function(t){t.preventDefault();var n=function(){W.present(null,[e],e)};O.count>0?n():V.present(!0,n)}))};for(_.s();!(q=_.n()).done;)Z()}catch(e){_.e(e)}finally{_.f()}U.addEventListener("click",(function(){s?(s.currentPageTitle?document.title="Beacon Omni for ".concat(s.currentPageTitle):document.title="Beacon Omni",history.pushState({},"","/omni#".concat(s.currentPageName))):(document.title="Beacon Omni",history.pushState({},"","/omni")),dispatchEvent(new PopStateEvent("popstate",{}))})),window.addEventListener("popstate",(function(){J(!0)})),J(!1),t.forceEmail&&(O.email!==t.forceEmail&&O.reset(),0===O.count?V.present(!0,(function(){W.present()})):H())}))})(); \ No newline at end of file diff --git a/Website/www/browse/view.php b/Website/www/browse/view.php index c7aeb06e2..0945bd575 100644 --- a/Website/www/browse/view.php +++ b/Website/www/browse/view.php @@ -103,6 +103,7 @@ case 'Ark.CustomConfig': case 'ArkSA.CustomConfig': case 'SDTD.CustomConfig': + case 'Palworld.CustomConfig': case 'CustomContent': $editorNames[] = 'Custom Config'; break; @@ -139,6 +140,7 @@ case 'Ark.GeneralSettings': case 'ArkSA.GeneralSettings': case 'SDTD.GeneralSettings': + Case 'Palworld.GeneralSettings': case 'LootScale': case 'OtherSettings': $editorNames[] = 'General Settings'; diff --git a/Website/www/download/index.php b/Website/www/download/index.php index c5184b2b9..154d83e62 100644 --- a/Website/www/download/index.php +++ b/Website/www/download/index.php @@ -53,6 +53,12 @@ } $database = BeaconCommon::Database(); +$gameRows = $database->Query('SELECT game_id, game_name FROM public.games WHERE public = TRUE;'); +$gameNames = []; +while (!$gameRows->EOF()) { + $gameNames[$gameRows->Field('game_id')] = $gameRows->Field('game_name'); + $gameRows->MoveNext(); +} $download_links = [ 'current' => BuildLinks($stable) ]; @@ -75,22 +81,16 @@ BeaconTemplate::FinishScript(); function BuildLinks(array $update): array { + global $gameNames; + $build = $update['build_number']; $delta_version = $update['delta_version']; $stage = $update['stage']; $games = []; foreach ($update['supported_games'] as $gameId) { - switch ($gameId) { - case 'Ark': - $games[] = 'Ark: Survival Evolved'; - break; - case 'ArkSA': - $games[] = 'Ark: Survival Ascended'; - break; - case 'SDTD': - $games[] = '7 Days to Die'; - break; + if (isset($gameNames[$gameId])) { + $games[] = $gameNames[$gameId]; } } sort($games); diff --git a/Website/www/games/index.php b/Website/www/games/index.php index 6dda47d1c..b0a2e2f1a 100644 --- a/Website/www/games/index.php +++ b/Website/www/games/index.php @@ -11,4 +11,5 @@ diff --git a/Website/www/omni/begin.php b/Website/www/omni/begin.php index a47eedc63..9898fb181 100644 --- a/Website/www/omni/begin.php +++ b/Website/www/omni/begin.php @@ -161,6 +161,7 @@ $includeArk = isset($products['Ark']['Base']); $includeArkSA = isset($products['ArkSA']['Base']); +$includeMinimalGames = isset($products['BeaconMinimal']['Base']); $lines = []; foreach ($bundles as $bundle) { @@ -168,6 +169,7 @@ $wantsArk = $includeArk ? $bundle->getQuantity($products['Ark']['Base']['ProductId']) > 0 : false; $wantsArkSAYears = $includeArkSA ? $bundle->getQuantity($products['ArkSA']['Base']['ProductId']) + $bundle->getQuantity($products['ArkSA']['Upgrade']['ProductId']) + $bundle->getQuantity($products['ArkSA']['Renewal']['ProductId']) : 0; + $wantsMinimalGamesYears = $includeMinimalGames ? $bundle->getQuantity($products['BeaconMinimal']['Base']['ProductId']) + $bundle->getQuantity($products['BeaconMinimal']['Renewal']['ProductId']) : 0; if ($bundle->isGift()) { if ($wantsArk) { @@ -184,9 +186,17 @@ $lines[$products['ArkSA']['Renewal']['PriceId']] = ($lines[$products['ArkSA']['Renewal']['PriceId']] ?? 0) + ($wantsArkSAYears - 1); } } + + if ($wantsMinimalGamesYears > 0) { + $lines[$products['BeaconMinimal']['Base']['PriceId']] = ($lines[$products['BeaconMinimal']['Base']['PriceId']] ?? 0) + 1; + if ($wantsMinimalGamesYears > 1) { + $lines[$products['BeaconMinimal']['Renewal']['PriceId']] = ($lines[$products['BeaconMinimal']['Renewal']['PriceId']] ?? 0) + ($wantsMinimalGamesYears - 1); + } + } } else { $ownsArk = $includeArk && findLicense($licenses, $products['Ark']['Base']['ProductId']) !== null; $ownsArkSA = $includeArkSA && findLicense($licenses, $products['ArkSA']['Base']['ProductId']) !== null; + $ownsMinimalGames = $includeMinimalGames && findLicense($licenses, $products['BeaconMinimal']['Base']['ProductId']) !== null; if ($wantsArk && !$ownsArk) { $lines[$products['Ark']['Base']['PriceId']] = ($lines[$products['Ark']['Base']['PriceId']] ?? 0) + 1; @@ -206,6 +216,17 @@ } } } + + if ($wantsMinimalGamesYears > 0) { + if ($ownsMinimalGames) { + $lines[$products['BeaconMinimal']['Renewal']['PriceId']] = ($lines[$products['BeaconMinimal']['Renewal']['PriceId']] ?? 0) + $wantsMinimalGamesYears; + } else { + $lines[$products['BeaconMinimal']['Base']['PriceId']] = ($lines[$products['BeaconMinimal']['Base']['PriceId']] ?? 0) + 1; + if ($wantsMinimalGamesYears > 1) { + $lines[$products['BeaconMinimal']['Renewal']['PriceId']] = ($lines[$products['BeaconMinimal']['Renewal']['PriceId']] ?? 0) + ($wantsMinimalGamesYears - 1); + } + } + } } } foreach ($lines as $priceId => $quantity) { diff --git a/Website/www/omni/index.php b/Website/www/omni/index.php index 2f1a8f20e..3b9ce5824 100644 --- a/Website/www/omni/index.php +++ b/Website/www/omni/index.php @@ -6,49 +6,67 @@ $database = BeaconCommon::Database(); -$stable_version = BeaconCommon::NewestVersionForStage(3); +$stableVersion = BeaconCommon::NewestVersionForStage(3); $currency = BeaconShop::GetCurrency(); -$supported_currencies = BeaconShop::GetCurrencyOptions(); +$supportedCurrencies = BeaconShop::GetCurrencyOptions(); BeaconTemplate::SetTitle('Buy Beacon Omni'); BeaconTemplate::SetCanonicalPath('/omni', str_starts_with($_SERVER['REQUEST_URI'], '/omni/license/') === false); -$results = $database->Query('SELECT products.game_id, products.tag, products.product_id, products.product_name, product_prices.price, EXTRACT(epoch FROM products.updates_length) AS plan_length_seconds FROM public.products INNER JOIN public.product_prices ON (product_prices.product_id = products.product_id) WHERE product_prices.currency = $1 AND products.active = TRUE;', $currency); -$product_details = []; -$product_ids = []; +$gameRows = $database->Query('SELECT game_id, game_name, early_access, beacon_major_version, beacon_minor_version FROM public.games WHERE public = TRUE ORDER BY game_name;'); +$gamesList = []; +$gameInfo = []; +while (!$gameRows->EOF()) { + $game = [ + 'gameId' => $gameRows->Field('game_id'), + 'name' => $gameRows->Field('game_name'), + 'earlyAccess' => $gameRows->Field('early_access'), + 'majorVersion' => $gameRows->Field('beacon_major_version'), + 'minorVersion' => $gameRows->Field('beacon_minor_version') + ]; + $gamesList[] = $game; + $gameInfo[$game['gameId']] = $game; + $gameRows->MoveNext(); +} + +$results = $database->Query('SELECT products.game_id, products.tag, products.product_id, products.product_name, product_prices.price, EXTRACT(epoch FROM products.updates_length) AS plan_length_seconds, products.updates_length::TEXT AS plan_length FROM public.products INNER JOIN public.product_prices ON (product_prices.product_id = products.product_id) WHERE product_prices.currency = $1 AND products.active = TRUE AND products.hidden = FALSE;', $currency); +$productDetails = []; +$productIds = []; while (!$results->EOF()) { - $product_id = $results->Field('product_id'); - $product_name = $results->Field('product_name'); - $product_price = $results->Field('price'); - $game_id = $results->Field('game_id'); + $productId = $results->Field('product_id'); + $productName = $results->Field('product_name'); + $productPrice = $results->Field('price'); + $gameId = $results->Field('game_id'); $tag = $results->Field('tag'); - $plan_length_seconds = $results->Field('plan_length_seconds'); + $planLengthSeconds = $results->Field('plan_length_seconds'); + $planLength = $results->Field('plan_length'); $product = [ - 'ProductId' => $product_id, - 'Name' => $product_name, - 'Price' => floatval($product_price), - 'GameId' => $game_id, + 'ProductId' => $productId, + 'Name' => $productName, + 'Price' => floatval($productPrice), + 'GameId' => $gameId, 'Tag' => $tag, - 'PlanLengthSeconds' => $plan_length_seconds + 'PlanLengthSeconds' => $planLengthSeconds, + 'PlanLength' => $planLength, ]; - $product_details[$game_id][$tag] = $product; - $product_ids[$product_id] = $product; + $productDetails[$gameId][$tag] = $product; + $productIds[$productId] = $product; $results->MoveNext(); } -$ark2Enabled = isset($product_details['Ark2']); -$arkSAEnabled = isset($product_details['ArkSA']); -$arkOnlyMode = ($ark2Enabled || $arkSAEnabled) === false; +$ark2Enabled = isset($productDetails['Ark2']); +$arkSAEnabled = isset($productDetails['ArkSA']); +$palworldEnabled = isset($productDetails['Palworld']); -$payment_methods = [ +$paymentMethods = [ 'Universal' => ['apple', 'google', 'mastercard', 'visa', 'amex', 'discover', 'dinersclub', 'jcb'], 'USD' => ['cashapp'], 'EUR' => ['bancontact', 'eps', 'giropay', 'ideal', 'p24'], 'PLN' => ['p24'] ]; -$payment_labels = [ +$paymentLabels = [ 'apple' => 'Apple Pay', 'google' => 'Google Pay', 'mastercard' => 'Mastercard', @@ -65,15 +83,15 @@ 'cashapp' => 'Cash App Pay' ]; -$supported_payment_methods = $payment_methods['Universal']; -if (array_key_exists($currency, $payment_methods)) { - $supported_payment_methods = array_merge($supported_payment_methods, $payment_methods[$currency]); +$supportedPaymentMethods = $paymentMethods['Universal']; +if (array_key_exists($currency, $paymentMethods)) { + $supportedPaymentMethods = array_merge($supportedPaymentMethods, $paymentMethods[$currency]); } -$payment_method_info = []; -foreach ($supported_payment_methods as $payment_method) { - $payment_method_info[] = [ +$paymentMethodInfo = []; +foreach ($supportedPaymentMethods as $payment_method) { + $paymentMethodInfo[] = [ 'key' => $payment_method, - 'label' => $payment_labels[$payment_method], + 'label' => $paymentLabels[$payment_method], 'iconUrl' => BeaconCommon::AssetURI('paymethod_' . $payment_method . '.svg') ]; } @@ -115,10 +133,11 @@ event.checkoutProperties = $currency, 'currencies' => $_SESSION['store_currency_options'], - 'paymentMethods' => $payment_method_info, - 'products' => $product_details, - 'productIds' => $product_ids, + 'paymentMethods' => $paymentMethodInfo, + 'products' => $productDetails, + 'productIds' => $productIds, 'forceEmail' => $forceEmail, + 'games' => $gamesList, ]); ?>; document.dispatchEvent(event); }); @@ -131,122 +150,41 @@ ?>
-

Do more with Beacon Omni

-

Beacon does a lot for free. Loot drops, server control, file sharing, and more. But when it's time to get into more advanced configuration like crafting costs and player experience, then it's time to upgrade to Beacon Omni.

-

All users of Beacon can use all features, however Omni-only config types will not be included in generated Game.ini and GameUserSettings.ini files.

+
+
+
+

Already purchased? See your account control panel for more details.
-
-
-
2% of your purchase will be contributed to the removal of carbon dioxide from the atmosphere.
+
+
+ +
+
+ '; + include('modules/' . strtolower($gameId) . '.php'); + echo '
'; + $firstGame = false; + } + ?> +
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - = 10502300) { ?> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FeatureBeaconOmni
Nitrado Server Control
Nitrado server owners can allow Beacon to directly control their server, including proper restart timing, config editing, and server settings changes.
GameServerApp.com Support
Import and update GameServerApp.com config templates with only a few clicks.
FTP Upload and Download
Beacon can use FTP edit your Game.ini and GameUserSettings.ini files right on the server.
Download Community Beacon Files
Download Beacon files created by other users to make getting started with custom loot easier.
Create Community Beacon Files
Share your creation with the world to serve as a starting point for others.
Breeding Multipliers
Adjust any of the breeding-related multipliers with realtime display of their effects on Ark's creatures and their imprint times.
Day and Night Cycle
Change the length of Ark's days and nights using minutes instead of multipliers.
Decay and Spoil
Change and preview item decay, decomposition, and spoil times.
General SettingsNew in Beacon 1.5.2
Beacon has support for nearly every setting available to Ark servers.
Item Stat Limits
Globally limit item stats to precise admin-defined amounts, just like official servers.
Loot Drops
Beacon's original purpose, editing loot drops, is what it does best.
Stat Multipliers
Change the stats of players, wild creatures, and tamed creatures.
Crafting Costs
Change the cost of any craftable item in Ark.
 
Creature Adjustments
Adjust creature-specific damage and vulnerability multipliers, replace creatures with others, or disable specific creatures entirely.
 
Creature Spawns
Add, remove, or change the creatures available on any map. Want to add lots of Featherlights to The Island, or put one really high level Pteranodon on Aberration? It's possible.
 
Engram Control
Change when engrams are unlockable, if they auto-unlock, and the number of engram points awarded each level. Beacon's powerful wizard allows users to instantly build full engram designs, such as auto-unlocking everything except tek items at spawn.
 
Harvest Rates
Change the harvest multiplier for any harvestable item in the game. Tip: this is a great way to improve server performance.
 
Levels and XP
Control max level and the experience curve for both players and tamed dinos.
 
Stack Sizes
Ark finally allows admins to customize stack sizes, so Beacon Omni has an editor ready to go.
 
@@ -385,8 +344,7 @@
- +
' . $titleHtml . '
' . "\n"; +} + +?> diff --git a/Website/www/omni/modules/ark.php b/Website/www/omni/modules/ark.php new file mode 100644 index 000000000..5b827b852 --- /dev/null +++ b/Website/www/omni/modules/ark.php @@ -0,0 +1,109 @@ +Ark: Survival Evolved features, with a \'Beacon Omni for Ark: Survival Evolved\' license available to unlock all features.', 'Ark'); ?> +

Beacon does a lot for free. Loot drops, server control, file sharing, and more. But when it's time to get into more advanced configuration like crafting costs and player experience, then it's time to upgrade to Beacon Omni.

+

All users of Beacon can use all features, however Omni-only config types will not be included in generated Game.ini and GameUserSettings.ini files.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FeatureBeacon FreeBeacon Omni
Nitrado Server Control
Nitrado server owners can allow Beacon to directly control their server, including proper restart timing, config editing, and server settings changes.
GameServerApp.com Support
Import and update GameServerApp.com config templates with only a few clicks.
FTP Upload and Download
Beacon can use FTP edit your Game.ini and GameUserSettings.ini files right on the server.
Download Community Beacon Files
Download Beacon files created by other users to make getting started with custom loot easier.
Create Community Beacon Files
Share your creation with the world to serve as a starting point for others.
Breeding Multipliers
Adjust any of the breeding-related multipliers with realtime display of their effects on Ark's creatures and their imprint times.
Day and Night Cycle
Change the length of Ark's days and nights using minutes instead of multipliers.
Decay and Spoil
Change and preview item decay, decomposition, and spoil times.
General Settings
Beacon has support for nearly every setting available to Ark servers.
Item Stat Limits
Globally limit item stats to precise admin-defined amounts, just like official servers.
Loot Drops
Beacon's original purpose, editing loot drops, is what it does best.
Stat Multipliers
Change the stats of players, wild creatures, and tamed creatures.
Crafting Costs
Change the cost of any craftable item in Ark.
 
Creature Adjustments
Adjust creature-specific damage and vulnerability multipliers, replace creatures with others, or disable specific creatures entirely.
 
Creature Spawns
Add, remove, or change the creatures available on any map. Want to add lots of Featherlights to The Island, or put one really high level Pteranodon on Aberration? It's possible.
 
Engram Control
Change when engrams are unlockable, if they auto-unlock, and the number of engram points awarded each level. Beacon's powerful wizard allows users to instantly build full engram designs, such as auto-unlocking everything except tek items at spawn.
 
Harvest Rates
Change the harvest multiplier for any harvestable item in the game. Tip: this is a great way to improve server performance.
 
Levels and XP
Control max level and the experience curve for both players and tamed dinos.
 
Stack Sizes
Ark finally allows admins to customize stack sizes, so Beacon Omni has an editor ready to go.
 
diff --git a/Website/www/omni/modules/arksa.php b/Website/www/omni/modules/arksa.php new file mode 100644 index 000000000..9b8194cea --- /dev/null +++ b/Website/www/omni/modules/arksa.php @@ -0,0 +1,109 @@ +Ark: Survival Ascended features, with a \'Beacon Omni for Ark: Survival Ascended\' license available to unlock all features.', 'ArkSA'); ?> +

Beacon does a lot for free. Loot drops, server control, file sharing, and more. But when it's time to get into more advanced configuration like crafting costs and player experience, then it's time to upgrade to Beacon Omni.

+

All users of Beacon can use all features, however Omni-only config types will not be included in generated Game.ini and GameUserSettings.ini files.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FeatureBeacon FreeBeacon Omni
Nitrado Server Control
Nitrado server owners can allow Beacon to directly control their server, including proper restart timing, config editing, and server settings changes.
GameServerApp.com Support
Import and update GameServerApp.com config templates with only a few clicks.
FTP Upload and Download
Beacon can use FTP edit your Game.ini and GameUserSettings.ini files right on the server.
Download Community Beacon Files
Download Beacon files created by other users to make getting started with custom loot easier.
Create Community Beacon Files
Share your creation with the world to serve as a starting point for others.
Breeding Multipliers
Adjust any of the breeding-related multipliers with realtime display of their effects on Ark's creatures and their imprint times.
Day and Night Cycle
Change the length of Ark's days and nights using minutes instead of multipliers.
Decay and Spoil
Change and preview item decay, decomposition, and spoil times.
General Settings
Beacon has support for nearly every setting available to Ark servers.
Item Stat Limits
Globally limit item stats to precise admin-defined amounts, just like official servers.
Loot Drops
Beacon's original purpose, editing loot drops, is what it does best.
Stat Multipliers
Change the stats of players, wild creatures, and tamed creatures.
Stack Sizes
Ark finally allows admins to customize stack sizes, so Beacon Omni has an editor ready to go.
Crafting Costs
Change the cost of any craftable item in Ark.
 
Creature Adjustments
Adjust creature-specific damage and vulnerability multipliers, replace creatures with others, or disable specific creatures entirely.
 
Creature Spawns
Add, remove, or change the creatures available on any map. Want to add lots of Featherlights to The Island, or put one really high level Pteranodon on Aberration? It's possible.
 
Engram Control
Change when engrams are unlockable, if they auto-unlock, and the number of engram points awarded each level. Beacon's powerful wizard allows users to instantly build full engram designs, such as auto-unlocking everything except tek items at spawn.
 
Harvest Rates
Change the harvest multiplier for any harvestable item in the game. Tip: this is a great way to improve server performance.
 
Levels and XP
Control max level and the experience curve for both players and tamed dinos.
 
diff --git a/Website/www/omni/modules/palworld.php b/Website/www/omni/modules/palworld.php new file mode 100644 index 000000000..a091a03f6 --- /dev/null +++ b/Website/www/omni/modules/palworld.php @@ -0,0 +1,70 @@ +Palworld requires a \'Beacon Omni for Palworld\' license.', 'Palworld'); + +if ($palworld['earlyAccess']) { + echo '
Palworld is currently Early Access. This game is very new and its future is uncertain. It could grow to support hundreds of config options, die from legal issues, or somewhere in between. Features and pricing subject to change. Beacon\'s refund policy remains in effect for Palworld.
'; +} + +$palworldBuild = ($palworld['majorVersion'] * 10000000) + ($palworld['minorVersion'] * 100000); +$palworldStableBuild = $palworldBuild + 300; +if ($stableVersion < $palworldStableBuild) { + $buildRows = $database->Query('SELECT stage FROM public.updates WHERE build_number >= $1 ORDER BY build_number DESC LIMIT 1;', $palworldBuild); + if ($buildRows->RecordCount() === 1) { + switch ($buildRows->Field('stage')) { + case 1: + $previewLabel = 'Alpha Preview'; + break; + case 2: + $previewLabel = 'Beta Preview'; + break; + } + echo '
Beacon\'s Palworld support requires Beacon version ' . $palworld['majorVersion'] . '.' . $palworld['minorVersion'] . ', which can be downloaded from the ' . $previewLabel . ' section of the downloads page.
'; + } else { + echo '
Beacon\'s Palworld support requires Beacon version ' . $palworld['majorVersion'] . '.' . $palworld['minorVersion'] . ', which is not ready yet. Sit tight, a new version is coming soon.
'; + } +} + +?> +

Palworld has a small number of options that are not yet complex, so Beacon has just one Palworld editor: General Settings. This is an Omni-exclusive editor in Palworld.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FeatureBeacon FreeBeacon Omni
Nitrado Server Control
Nitrado server owners can allow Beacon to directly control their server, including proper restart timing, config editing, and server settings changes.
GameServerApp.com Support
Import and update GameServerApp.com config templates with only a few clicks.
FTP Upload and Download
Beacon can use FTP edit your PalWorldSettings.ini file right on the server.
Download Community Beacon Files
Download Beacon files created by other users to make getting started easier.
Create Community Beacon Files
Share your creation with the world to serve as a starting point for others.
General Settings
Beacon has support for nearly every setting available to Palworld servers.